Merge "Slice by state in ValueMetricProducer"

This commit is contained in:
Christine Tsai
2020-01-02 19:21:33 +00:00
committed by Android (Google) Code Review
26 changed files with 1422 additions and 259 deletions

View File

@@ -152,6 +152,10 @@ bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
return false;
}
bool HashableDimensionKey::operator!=(const HashableDimensionKey& that) const {
return !((*this) == that);
}
bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
if (mValues.size() != that.getValues().size()) {
return false;

View File

@@ -71,6 +71,8 @@ public:
std::string toString() const;
bool operator!=(const HashableDimensionKey& that) const;
bool operator==(const HashableDimensionKey& that) const;
bool operator<(const HashableDimensionKey& that) const;

View File

@@ -284,6 +284,10 @@ private:
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
};
} // namespace statsd

View File

@@ -277,8 +277,8 @@ bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
void CountMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) {
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const map<int, HashableDimensionKey>& statePrimaryKeys) {
int64_t eventTimeNs = event.GetElapsedTimestampNs();
flushIfNeededLocked(eventTimeNs);

View File

@@ -59,8 +59,8 @@ public:
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
private:

View File

@@ -541,8 +541,8 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey
void DurationMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKeys, bool condition,
const LogEvent& event) {
const ConditionKey& conditionKeys, bool condition, const LogEvent& event,
const map<int, HashableDimensionKey>& statePrimaryKeys) {
ALOGW("Not used in duration tracker.");
}

View File

@@ -59,8 +59,8 @@ protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKeys, bool condition,
const LogEvent& event) override;
const ConditionKey& conditionKeys, bool condition, const LogEvent& event,
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
private:
void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys,

View File

@@ -143,8 +143,8 @@ void EventMetricProducer::onConditionChangedLocked(const bool conditionMet,
void EventMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) {
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const map<int, HashableDimensionKey>& statePrimaryKeys) {
if (!condition) {
return;
}

View File

@@ -47,8 +47,8 @@ public:
private:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
void onDumpReportLocked(const int64_t dumpTimeNs,
const bool include_current_partial_bucket,

View File

@@ -449,8 +449,8 @@ bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
void GaugeMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) {
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const map<int, HashableDimensionKey>& statePrimaryKeys) {
if (condition == false) {
return;
}

View File

@@ -95,8 +95,8 @@ public:
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
private:
void onDumpReportLocked(const int64_t dumpTimeNs,

View File

@@ -133,8 +133,8 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo
HashableDimensionKey dimensionInWhat;
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
MetricDimensionKey metricKey(dimensionInWhat, stateValuesKey);
onMatchedLogEventInternalLocked(
matcherIndex, metricKey, conditionKey, condition, event);
onMatchedLogEventInternalLocked(matcherIndex, metricKey, conditionKey, condition, event,
statePrimaryKeys);
}
bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) {
@@ -269,6 +269,7 @@ void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDim
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;
}

View File

@@ -330,8 +330,8 @@ protected:
*/
virtual void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) = 0;
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const map<int, HashableDimensionKey>& statePrimaryKeys) = 0;
// Consume the parsed stats log entry that already matched the "what" of the metric.
virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
@@ -475,6 +475,10 @@ protected:
FRIEND_TEST(StatsLogProcessorTest,
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
};
} // namespace statsd

View File

@@ -289,6 +289,10 @@ private:
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
};
} // namespace statsd

View File

@@ -66,6 +66,7 @@ const int FIELD_ID_DROP_TIME = 2;
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 ValueBucketInfo
const int FIELD_ID_VALUE_INDEX = 1;
const int FIELD_ID_VALUE_LONG = 2;
@@ -146,6 +147,14 @@ ValueMetricProducer::ValueMetricProducer(
mConditionSliced = true;
}
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);
}
int64_t numBucketsForward = calcBucketsForwardCount(startTimeNs);
mCurrentBucketNum += numBucketsForward;
@@ -181,6 +190,33 @@ ValueMetricProducer::~ValueMetricProducer() {
}
}
void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId,
const HashableDimensionKey& primaryKey, int oldState,
int newState) {
VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d",
(long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
oldState, newState);
// If condition is not true, we do not need to pull for this state change.
if (mCondition != ConditionState::kTrue) {
return;
}
bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs;
if (isEventLate) {
VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
(long long)mCurrentBucketStartTimeNs);
invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
return;
}
mStateChangePrimaryKey.first = atomId;
mStateChangePrimaryKey.second = primaryKey;
if (mIsPulled) {
pullAndMatchEventsLocked(eventTimeNs);
}
mStateChangePrimaryKey.first = 0;
mStateChangePrimaryKey.second = DEFAULT_DIMENSION_KEY;
flushIfNeededLocked(eventTimeNs);
}
void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
const int64_t eventTime) {
VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
@@ -281,6 +317,14 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
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 (ValueBucketInfo).
for (const auto& bucket : pair.second) {
uint64_t bucketInfoToken = protoOutput->start(
@@ -300,7 +344,7 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS,
(long long)bucket.mConditionTrueNs);
}
for (int i = 0; i < (int)bucket.valueIndex.size(); i ++) {
for (int i = 0; i < (int)bucket.valueIndex.size(); i++) {
int index = bucket.valueIndex[i];
const Value& value = bucket.values[i];
uint64_t valueToken = protoOutput->start(
@@ -358,9 +402,10 @@ void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs,
}
void ValueMetricProducer::resetBase() {
for (auto& slice : mCurrentSlicedBucket) {
for (auto& interval : slice.second) {
interval.hasBase = false;
for (auto& slice : mCurrentBaseInfo) {
for (auto& baseInfo : slice.second) {
baseInfo.hasBase = false;
baseInfo.hasCurrentState = false;
}
}
mHasGlobalBase = false;
@@ -558,14 +603,20 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log
onMatchedLogEventLocked(mWhatMatcherIndex, localCopy);
}
}
// If the new pulled data does not contains some keys we track in our intervals, we need to
// reset the base.
// If a key that is:
// 1. Tracked in mCurrentSlicedBucket and
// 2. A superset of the current mStateChangePrimaryKey
// was not found in the new pulled data (i.e. not in mMatchedDimensionInWhatKeys)
// then we need to reset the base.
for (auto& slice : mCurrentSlicedBucket) {
bool presentInPulledData = mMatchedMetricDimensionKeys.find(slice.first)
!= mMatchedMetricDimensionKeys.end();
if (!presentInPulledData) {
for (auto& interval : slice.second) {
interval.hasBase = false;
const auto& whatKey = slice.first.getDimensionKeyInWhat();
bool presentInPulledData =
mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end();
if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) {
auto it = mCurrentBaseInfo.find(whatKey);
for (auto& baseInfo : it->second) {
baseInfo.hasBase = false;
baseInfo.hasCurrentState = false;
}
}
}
@@ -674,17 +725,30 @@ bool getDoubleOrLong(const LogEvent& event, const Matcher& matcher, Value& ret)
return false;
}
void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIndex,
const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey,
bool condition, const LogEvent& event) {
void ValueMetricProducer::onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const map<int, HashableDimensionKey>& statePrimaryKeys) {
auto whatKey = eventKey.getDimensionKeyInWhat();
auto stateKey = eventKey.getStateValuesKey();
// Skip this event if a state changed occurred for a different primary key.
auto it = statePrimaryKeys.find(mStateChangePrimaryKey.first);
// Check that both the atom id and the primary key are equal.
if (it != statePrimaryKeys.end() && it->second != mStateChangePrimaryKey.second) {
VLOG("ValueMetric skip event with primary key %s because state change primary key "
"is %s",
it->second.toString().c_str(), mStateChangePrimaryKey.second.toString().c_str());
return;
}
int64_t eventTimeNs = event.GetElapsedTimestampNs();
if (eventTimeNs < mCurrentBucketStartTimeNs) {
VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
(long long)mCurrentBucketStartTimeNs);
return;
}
mMatchedMetricDimensionKeys.insert(eventKey);
mMatchedMetricDimensionKeys.insert(whatKey);
if (!mIsPulled) {
// We cannot flush without doing a pull first.
@@ -709,10 +773,26 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn
if (hitGuardRailLocked(eventKey)) {
return;
}
vector<Interval>& multiIntervals = mCurrentSlicedBucket[eventKey];
if (multiIntervals.size() < mFieldMatchers.size()) {
vector<BaseInfo>& baseInfos = mCurrentBaseInfo[whatKey];
if (baseInfos.size() < mFieldMatchers.size()) {
VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
multiIntervals.resize(mFieldMatchers.size());
baseInfos.resize(mFieldMatchers.size());
}
for (auto baseInfo : baseInfos) {
if (!baseInfo.hasCurrentState) {
baseInfo.currentState = DEFAULT_DIMENSION_KEY;
baseInfo.hasCurrentState = true;
}
}
// We need to get the intervals stored with the previous state key so we can
// close these value intervals.
const auto oldStateKey = baseInfos[0].currentState;
vector<Interval>& intervals = mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)];
if (intervals.size() < mFieldMatchers.size()) {
VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
intervals.resize(mFieldMatchers.size());
}
// We only use anomaly detection under certain cases.
@@ -725,7 +805,8 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn
for (int i = 0; i < (int)mFieldMatchers.size(); i++) {
const Matcher& matcher = mFieldMatchers[i];
Interval& interval = multiIntervals[i];
BaseInfo& baseInfo = baseInfos[i];
Interval& interval = intervals[i];
interval.valueIndex = i;
Value value;
if (!getDoubleOrLong(event, matcher, value)) {
@@ -736,60 +817,61 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn
interval.seenNewData = true;
if (mUseDiff) {
if (!interval.hasBase) {
if (!baseInfo.hasBase) {
if (mHasGlobalBase && mUseZeroDefaultBase) {
// The bucket has global base. This key does not.
// Optionally use zero as base.
interval.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE);
interval.hasBase = true;
baseInfo.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE);
baseInfo.hasBase = true;
} else {
// no base. just update base and return.
interval.base = value;
interval.hasBase = true;
baseInfo.base = value;
baseInfo.hasBase = true;
// If we're missing a base, do not use anomaly detection on incomplete data
useAnomalyDetection = false;
// Continue (instead of return) here in order to set interval.base and
// interval.hasBase for other intervals
// Continue (instead of return) here in order to set baseInfo.base and
// baseInfo.hasBase for other baseInfos
continue;
}
}
Value diff;
switch (mValueDirection) {
case ValueMetric::INCREASING:
if (value >= interval.base) {
diff = value - interval.base;
if (value >= baseInfo.base) {
diff = value - baseInfo.base;
} else if (mUseAbsoluteValueOnReset) {
diff = value;
} else {
VLOG("Unexpected decreasing value");
StatsdStats::getInstance().notePullDataError(mPullTagId);
interval.base = value;
baseInfo.base = value;
// If we've got bad data, do not use anomaly detection
useAnomalyDetection = false;
continue;
}
break;
case ValueMetric::DECREASING:
if (interval.base >= value) {
diff = interval.base - value;
if (baseInfo.base >= value) {
diff = baseInfo.base - value;
} else if (mUseAbsoluteValueOnReset) {
diff = value;
} else {
VLOG("Unexpected increasing value");
StatsdStats::getInstance().notePullDataError(mPullTagId);
interval.base = value;
baseInfo.base = value;
// If we've got bad data, do not use anomaly detection
useAnomalyDetection = false;
continue;
}
break;
case ValueMetric::ANY:
diff = value - interval.base;
diff = value - baseInfo.base;
break;
default:
break;
}
interval.base = value;
baseInfo.base = value;
value = diff;
}
@@ -814,12 +896,13 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn
interval.hasValue = true;
}
interval.sampleSize += 1;
baseInfo.currentState = stateKey;
}
// Only trigger the tracker if all intervals are correct
if (useAnomalyDetection) {
// TODO: propgate proper values down stream when anomaly support doubles
long wholeBucketVal = multiIntervals[0].value.long_value;
long wholeBucketVal = intervals[0].value.long_value;
auto prev = mCurrentFullBucket.find(eventKey);
if (prev != mCurrentFullBucket.end()) {
wholeBucketVal += prev->second;
@@ -953,6 +1036,7 @@ void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs)
} else {
it++;
}
// TODO: remove mCurrentBaseInfo entries when obsolete
}
mCurrentBucketIsInvalid = false;

View File

@@ -83,11 +83,14 @@ public:
flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
};
void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
int oldState, int newState) override;
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKey, bool condition,
const LogEvent& event) override;
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
private:
void onDumpReportLocked(const int64_t dumpTimeNs,
@@ -144,7 +147,10 @@ private:
std::vector<Matcher> mFieldMatchers;
// Value fields for matching.
std::set<MetricDimensionKey> mMatchedMetricDimensionKeys;
std::set<HashableDimensionKey> mMatchedMetricDimensionKeys;
// Holds the atom id, primary key pair from a state change.
pair<int32_t, HashableDimensionKey> mStateChangePrimaryKey;
// tagId for pulled data. -1 if this is not pulled
const int mPullTagId;
@@ -156,10 +162,6 @@ private:
typedef struct {
// Index in multi value aggregation.
int valueIndex;
// Holds current base value of the dimension. Take diff and update if necessary.
Value base;
// Whether there is a base to diff to.
bool hasBase;
// Current value, depending on the aggregation type.
Value value;
// Number of samples collected.
@@ -171,8 +173,21 @@ private:
bool seenNewData = false;
} Interval;
typedef struct {
// Holds current base value of the dimension. Take diff and update if necessary.
Value base;
// Whether there is a base to diff to.
bool hasBase;
// Last seen state value(s).
HashableDimensionKey currentState;
// Whether this dimensions in what key has a current state key.
bool hasCurrentState;
} BaseInfo;
std::unordered_map<MetricDimensionKey, std::vector<Interval>> mCurrentSlicedBucket;
std::unordered_map<HashableDimensionKey, std::vector<BaseInfo>> mCurrentBaseInfo;
std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket;
// Save the past buckets and we can clear when the StatsLogReport is dumped.
@@ -285,6 +300,9 @@ private:
FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate);
FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput);
FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue);
FRIEND_TEST(ValueMetricProducerTest, TestSlicedState);
FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMap);
FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions);
FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey);
FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase);
FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures);

View File

@@ -18,11 +18,12 @@
#include "Log.h"
#include "metrics_manager_util.h"
#include "MetricProducer.h"
#include <inttypes.h>
#include "atoms_info.h"
#include "FieldValue.h"
#include "MetricProducer.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
#include "condition/StateConditionTracker.h"
@@ -173,6 +174,14 @@ bool handleMetricWithStates(
return true;
}
bool handleMetricWithStateLink(const FieldMatcher& stateMatcher,
const vector<Matcher>& dimensionsInWhat) {
vector<Matcher> stateMatchers;
translateFieldMatcher(stateMatcher, &stateMatchers);
return subsetDimensions(stateMatchers, dimensionsInWhat);
}
// Validates a metricActivation and populates state.
// EventActivationMap and EventDeactivationMap are supplied to a MetricProducer
// to provide the producer with state about its activators and deactivators.
@@ -669,18 +678,41 @@ 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 (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
return false;
}
} else {
if (metric.state_link_size() > 0) {
ALOGW("ValueMetric 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,
metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
eventDeactivationMap);
bool success = handleMetricActivation(
config, metric.id(), metricIndex, metricToActivationMap, logTrackerMap,
activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
metricsWithActivation, eventActivationMap, eventDeactivationMap);
if (!success) return false;
sp<MetricProducer> valueProducer = new ValueMetricProducer(
key, metric, conditionIndex, wizard, trackerIndex, matcherWizard, pullTagId,
timeBaseTimeNs, currentTimeNs, pullerManager, eventActivationMap,
eventDeactivationMap);
eventDeactivationMap, slicedStateAtoms, stateGroupMap);
allMetricProducers.push_back(valueProducer);
}

View File

@@ -28,13 +28,17 @@ StateManager& StateManager::getInstance() {
return sStateManager;
}
void StateManager::clear() {
mStateTrackers.clear();
}
void StateManager::onLogEvent(const LogEvent& event) {
if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
mStateTrackers[event.GetTagId()]->onLogEvent(event);
}
}
bool StateManager::registerListener(int32_t atomId, wp<StateListener> listener) {
bool StateManager::registerListener(const int32_t atomId, wp<StateListener> listener) {
// Check if state tracker already exists.
if (mStateTrackers.find(atomId) == mStateTrackers.end()) {
// Create a new state tracker iff atom is a state atom.
@@ -50,7 +54,7 @@ bool StateManager::registerListener(int32_t atomId, wp<StateListener> listener)
return true;
}
void StateManager::unregisterListener(int32_t atomId, wp<StateListener> listener) {
void StateManager::unregisterListener(const int32_t atomId, wp<StateListener> listener) {
std::unique_lock<std::mutex> lock(mMutex);
// Hold the sp<> until the lock is released so that ~StateTracker() is
@@ -74,7 +78,7 @@ void StateManager::unregisterListener(int32_t atomId, wp<StateListener> listener
lock.unlock();
}
bool StateManager::getStateValue(int32_t atomId, const HashableDimensionKey& key,
bool StateManager::getStateValue(const int32_t atomId, const HashableDimensionKey& key,
FieldValue* output) const {
auto it = mStateTrackers.find(atomId);
if (it != mStateTrackers.end()) {

View File

@@ -40,30 +40,33 @@ public:
// Returns a pointer to the single, shared StateManager object.
static StateManager& getInstance();
// Unregisters all listeners and removes all trackers from StateManager.
void clear();
// Notifies the correct StateTracker of an event.
void onLogEvent(const LogEvent& event);
// Returns true if atomId is being tracked and is associated with a state
// atom. StateManager notifies the correct StateTracker to register listener.
// If the correct StateTracker does not exist, a new StateTracker is created.
bool registerListener(int32_t atomId, wp<StateListener> listener);
bool registerListener(const int32_t atomId, wp<StateListener> listener);
// Notifies the correct StateTracker to unregister a listener
// and removes the tracker if it no longer has any listeners.
void unregisterListener(int32_t atomId, wp<StateListener> listener);
void unregisterListener(const int32_t atomId, wp<StateListener> listener);
// Returns true if the StateTracker exists and queries for the
// original state value mapped to the given query key. The state value is
// stored and output in a FieldValue class.
// Returns false if the StateTracker doesn't exist.
bool getStateValue(int32_t atomId, const HashableDimensionKey& queryKey,
bool getStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
FieldValue* output) const;
inline int getStateTrackersCount() const {
return mStateTrackers.size();
}
inline int getListenersCount(int32_t atomId) const {
inline int getListenersCount(const int32_t atomId) const {
auto it = mStateTrackers.find(atomId);
if (it != mStateTrackers.end()) {
return it->second->getListenersCount();

View File

@@ -147,12 +147,14 @@ message ValueBucketInfo {
message ValueMetricData {
optional DimensionsValue dimensions_in_what = 1;
optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
repeated StateValue slice_by_state = 6;
repeated ValueBucketInfo 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];
}

View File

@@ -290,12 +290,14 @@ message ValueMetric {
optional FieldMatcher dimensions_in_what = 5;
optional FieldMatcher dimensions_in_condition = 9 [deprecated = true];
repeated int64 slice_by_state = 18;
optional TimeUnit bucket = 6;
repeated MetricConditionLink links = 7;
repeated MetricStateLink state_link = 19;
enum AggregationType {
SUM = 1;
MIN = 2;
@@ -325,6 +327,8 @@ message ValueMetric {
optional int32 max_pull_delay_sec = 16 [default = 10];
optional bool split_bucket_for_app_upgrade = 17 [default = true];
optional FieldMatcher dimensions_in_condition = 9 [deprecated = true];
}
message Alert {

View File

@@ -27,9 +27,6 @@ namespace statsd {
#ifdef __ANDROID__
const int SCREEN_STATE_ATOM_ID = android::util::SCREEN_STATE_CHANGED;
const int UID_PROCESS_STATE_ATOM_ID = android::util::UID_PROCESS_STATE_CHANGED;
/**
* Test a count metric that has one slice_by_state with no primary fields.
*

View File

@@ -369,6 +369,168 @@ TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) {
EXPECT_EQ(1, bucketInfo.values_size());
}
/**
* Test initialization of a simple value metric that is sliced by a state.
*
* ValueCpuUserTimePerScreenState
*/
TEST(ValueMetricE2eTest, TestInitWithSlicedState) {
// Create config.
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
auto pulledAtomMatcher =
CreateSimpleAtomMatcher("TestMatcher", android::util::SUBSYSTEM_SLEEP_STATE);
*config.add_atom_matcher() = pulledAtomMatcher;
auto screenState = CreateScreenState();
*config.add_state() = screenState;
// Create value metric that slices by screen state without a map.
int64_t metricId = 123456;
auto valueMetric = config.add_value_metric();
valueMetric->set_id(metricId);
valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
valueMetric->set_what(pulledAtomMatcher.id());
*valueMetric->mutable_value_field() =
CreateDimensions(android::util::CPU_TIME_PER_UID, {2 /* user_time_micros */});
valueMetric->add_slice_by_state(screenState.id());
valueMetric->set_max_pull_delay_sec(INT_MAX);
// Initialize StatsLogProcessor.
const uint64_t bucketStartTimeNs = 10000000000; // 0:10
const uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000LL;
int uid = 12345;
int64_t cfgId = 98765;
ConfigKey cfgKey(uid, cfgId);
auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
// Check that StateTrackers were initialized correctly.
EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
// Check that ValueMetricProducer was initialized correctly.
EXPECT_EQ(1U, processor->mMetricsManagers.size());
sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
EXPECT_TRUE(metricsManager->isConfigValid());
EXPECT_EQ(1, metricsManager->mAllMetricProducers.size());
sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
EXPECT_EQ(1, metricProducer->mSlicedStateAtoms.size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0));
EXPECT_EQ(0, metricProducer->mStateGroupMap.size());
}
/**
* Test initialization of a value metric that is sliced by state and has
* dimensions_in_what.
*
* ValueCpuUserTimePerUidPerUidProcessState
*/
TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions) {
// Create config.
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
auto cpuTimePerUidMatcher =
CreateSimpleAtomMatcher("CpuTimePerUidMatcher", android::util::CPU_TIME_PER_UID);
*config.add_atom_matcher() = cpuTimePerUidMatcher;
auto uidProcessState = CreateUidProcessState();
*config.add_state() = uidProcessState;
// Create value metric that slices by screen state with a complete map.
int64_t metricId = 123456;
auto valueMetric = config.add_value_metric();
valueMetric->set_id(metricId);
valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
valueMetric->set_what(cpuTimePerUidMatcher.id());
*valueMetric->mutable_value_field() =
CreateDimensions(android::util::CPU_TIME_PER_UID, {2 /* user_time_micros */});
*valueMetric->mutable_dimensions_in_what() =
CreateDimensions(android::util::CPU_TIME_PER_UID, {1 /* uid */});
valueMetric->add_slice_by_state(uidProcessState.id());
MetricStateLink* stateLink = valueMetric->add_state_link();
stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
auto fieldsInWhat = stateLink->mutable_fields_in_what();
*fieldsInWhat = CreateDimensions(android::util::CPU_TIME_PER_UID, {1 /* uid */});
auto fieldsInState = stateLink->mutable_fields_in_state();
*fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
valueMetric->set_max_pull_delay_sec(INT_MAX);
// Initialize StatsLogProcessor.
const uint64_t bucketStartTimeNs = 10000000000; // 0:10
int uid = 12345;
int64_t cfgId = 98765;
ConfigKey cfgKey(uid, cfgId);
auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
// Check that StateTrackers were initialized correctly.
EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
// Check that ValueMetricProducer was initialized correctly.
EXPECT_EQ(1U, processor->mMetricsManagers.size());
sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
EXPECT_TRUE(metricsManager->isConfigValid());
EXPECT_EQ(1, metricsManager->mAllMetricProducers.size());
sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
EXPECT_EQ(1, metricProducer->mSlicedStateAtoms.size());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, metricProducer->mSlicedStateAtoms.at(0));
EXPECT_EQ(0, metricProducer->mStateGroupMap.size());
}
/**
* Test initialization of a value metric that is sliced by state and has
* dimensions_in_what.
*
* ValueCpuUserTimePerUidPerUidProcessState
*/
TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions) {
// Create config.
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
auto cpuTimePerUidMatcher =
CreateSimpleAtomMatcher("CpuTimePerUidMatcher", android::util::CPU_TIME_PER_UID);
*config.add_atom_matcher() = cpuTimePerUidMatcher;
auto uidProcessState = CreateUidProcessState();
*config.add_state() = uidProcessState;
// Create value metric that slices by screen state with a complete map.
int64_t metricId = 123456;
auto valueMetric = config.add_value_metric();
valueMetric->set_id(metricId);
valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
valueMetric->set_what(cpuTimePerUidMatcher.id());
*valueMetric->mutable_value_field() =
CreateDimensions(android::util::CPU_TIME_PER_UID, {2 /* user_time_micros */});
valueMetric->add_slice_by_state(uidProcessState.id());
MetricStateLink* stateLink = valueMetric->add_state_link();
stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
auto fieldsInWhat = stateLink->mutable_fields_in_what();
*fieldsInWhat = CreateDimensions(android::util::CPU_TIME_PER_UID, {1 /* uid */});
auto fieldsInState = stateLink->mutable_fields_in_state();
*fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
valueMetric->set_max_pull_delay_sec(INT_MAX);
// Initialize StatsLogProcessor.
const uint64_t bucketStartTimeNs = 10000000000; // 0:10
int uid = 12345;
int64_t cfgId = 98765;
ConfigKey cfgKey(uid, cfgId);
auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
// No StateTrackers are initialized.
EXPECT_EQ(0, StateManager::getInstance().getStateTrackersCount());
// Config initialization fails.
EXPECT_EQ(0, processor->mMetricsManagers.size());
}
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -146,6 +146,7 @@ TEST(StateListenerTest, TestStateListenerWeakPointer) {
TEST(StateManagerTest, TestStateManagerGetInstance) {
sp<TestStateListener> listener1 = new TestStateListener();
StateManager& mgr = StateManager::getInstance();
mgr.clear();
mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
EXPECT_EQ(1, mgr.getStateTrackersCount());

View File

@@ -30,6 +30,9 @@ namespace statsd {
using android::util::ProtoReader;
using google::protobuf::RepeatedPtrField;
const int SCREEN_STATE_ATOM_ID = android::util::SCREEN_STATE_CHANGED;
const int UID_PROCESS_STATE_ATOM_ID = android::util::UID_PROCESS_STATE_CHANGED;
// Converts a ProtoOutputStream to a StatsLogReport proto.
StatsLogReport outputStreamToProto(ProtoOutputStream* proto);