Support slicing by chain.

BUG: b/73975181

Test: statsd test
Change-Id: I913ae0f68ff21ed0703bb5da9c60d3eaa3bf5981
This commit is contained in:
Yangster-mac
2018-03-10 23:22:59 -08:00
parent d357342a4a
commit e06cfd777a
18 changed files with 365 additions and 355 deletions

View File

@@ -54,29 +54,13 @@ static void BM_FilterValue(benchmark::State& state) {
translateFieldMatcher(field_matcher, &matchers);
while (state.KeepRunning()) {
vector<HashableDimensionKey> output;
HashableDimensionKey output;
filterValues(matchers, event.getValues(), &output);
}
}
BENCHMARK(BM_FilterValue);
static void BM_FilterValue2(benchmark::State& state) {
LogEvent event(1, 100000);
FieldMatcher field_matcher;
createLogEventAndMatcher(&event, &field_matcher);
std::vector<Matcher> matchers;
translateFieldMatcher(field_matcher, &matchers);
while (state.KeepRunning()) {
HashableDimensionKey output;
filterValues(matchers, event.getValues(), &output);
}
}
BENCHMARK(BM_FilterValue2);
} // namespace statsd
} // namespace os
} // namespace android

View File

@@ -48,6 +48,11 @@ bool Field::matches(const Matcher& matcher) const {
return true;
}
if (matcher.hasAllPositionMatcher() &&
(mField & (matcher.mMask & kClearAllPositionMatcherMask)) == matcher.mMatcher.getField()) {
return true;
}
return false;
}
@@ -67,6 +72,10 @@ void translateFieldMatcher(int tag, const FieldMatcher& matcher, int depth, int*
return;
}
switch (matcher.position()) {
case Position::ALL:
pos[depth] = 0x00;
mask[depth] = 0x7f;
break;
case Position::ANY:
pos[depth] = 0;
mask[depth] = 0;

View File

@@ -30,6 +30,7 @@ const int32_t kAttributionField = 1;
const int32_t kMaxLogDepth = 2;
const int32_t kLastBitMask = 0x80;
const int32_t kClearLastBitDeco = 0x7f;
const int32_t kClearAllPositionMatcherMask = 0xffff00ff;
enum Type { UNKNOWN, INT, LONG, FLOAT, STRING };
@@ -205,6 +206,7 @@ public:
* First: [Matcher Field] 0x02010101 [Mask]0xff7f7f7f
* Last: [Matcher Field] 0x02018001 [Mask]0xff7f807f
* Any: [Matcher Field] 0x02010001 [Mask]0xff7f007f
* All: [Matcher Field] 0x02010001 [Mask]0xff7f7f7f
*
* [To match a log Field with a Matcher] we apply the bit mask to the log Field and check if
* the result is equal to the Matcher Field. That's a bit wise AND operation + check if 2 ints are
@@ -226,9 +228,21 @@ struct Matcher {
return mMask;
}
inline int32_t getRawMaskAtDepth(int32_t depth) const {
int32_t field = (mMask & 0x00ffffff);
int32_t shift = 8 * (kMaxLogDepth - depth);
int32_t mask = 0xff << shift;
return (field & mask) >> shift;
}
bool hasAllPositionMatcher() const {
return mMatcher.getDepth() == 2 && getRawMaskAtDepth(1) == 0x7f;
}
bool hasAnyPositionMatcher(int* prefix) const {
if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(2) == 0) {
(*prefix) = mMatcher.getPrefix(2);
if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(1) == 0) {
(*prefix) = mMatcher.getPrefix(1);
return true;
}
return false;

View File

@@ -61,125 +61,22 @@ android::hash_t hashDimension(const HashableDimensionKey& value) {
bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
HashableDimensionKey* output) {
for (size_t i = 0; i < matcherFields.size(); ++i) {
const auto& matcher = matcherFields[i];
bool found = false;
for (const auto& value : values) {
size_t num_matches = 0;
for (const auto& value : values) {
for (size_t i = 0; i < matcherFields.size(); ++i) {
const auto& matcher = matcherFields[i];
// TODO: potential optimization here to break early because all fields are naturally
// sorted.
if (value.mField.matches(matcher)) {
output->addValue(value);
output->mutableValue(i)->mField.setTag(value.mField.getTag());
output->mutableValue(i)->mField.setField(value.mField.getField() & matcher.mMask);
found = true;
break;
}
}
if (!found) {
VLOG("We can't find a dimension value for matcher (%d)%#x.", matcher.mMatcher.getTag(),
matcher.mMatcher.getField());
return false;
}
}
return true;
}
// Filter fields using the matchers and output the results as a HashableDimensionKey.
// Note: HashableDimensionKey is just a wrapper for vector<FieldValue>
bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
vector<HashableDimensionKey>* output) {
output->push_back(HashableDimensionKey());
// Top level is only tag id. Now take the real child matchers
int prevAnyMatcherPrefix = 0;
size_t prevPrevFanout = 0;
size_t prevFanout = 0;
// For each matcher get matched results.
vector<FieldValue> matchedResults(2);
for (const auto& matcher : matcherFields) {
size_t num_matches = 0;
for (const auto& value : values) {
// TODO: potential optimization here to break early because all fields are naturally
// sorted.
if (value.mField.matches(matcher)) {
if (num_matches >= matchedResults.size()) {
matchedResults.resize(num_matches * 2);
}
matchedResults[num_matches].mField.setTag(value.mField.getTag());
matchedResults[num_matches].mField.setField(value.mField.getField() & matcher.mMask);
matchedResults[num_matches].mValue = value.mValue;
output->mutableValue(num_matches)->mField.setTag(value.mField.getTag());
output->mutableValue(num_matches)->mField.setField(
value.mField.getField() & matcher.mMask);
num_matches++;
}
}
if (num_matches == 0) {
VLOG("We can't find a dimension value for matcher (%d)%#x.", matcher.mMatcher.getTag(),
matcher.mMatcher.getField());
continue;
}
if (num_matches == 1) {
for (auto& dimension : *output) {
dimension.addValue(matchedResults[0]);
}
prevAnyMatcherPrefix = 0;
prevFanout = 0;
continue;
}
// All the complexity below is because we support ANY in dimension.
bool createFanout = true;
// createFanout is true when the matcher doesn't need to follow the prev matcher's
// order.
// e.g., get (uid, tag) from any position in attribution. because we have translated
// it as 2 matchers, they need to follow the same ordering, we can't create a cross
// product of all uid and tags.
// However, if the 2 matchers have different prefix, they will create a cross product
// e.g., [any uid] [any some other repeated field], we will create a cross product for them
if (prevAnyMatcherPrefix != 0) {
int anyMatcherPrefix = 0;
bool isAnyMatcher = matcher.hasAnyPositionMatcher(&anyMatcherPrefix);
if (isAnyMatcher && anyMatcherPrefix == prevAnyMatcherPrefix) {
createFanout = false;
} else {
prevAnyMatcherPrefix = anyMatcherPrefix;
}
}
// Each matcher should match exact one field, unless position is ANY
// When x number of fields matches a matcher, the returned dimension
// size is multiplied by x.
int oldSize;
if (createFanout) {
// First create fanout (fanout size is matchedResults.Size which could be one,
// which means we do nothing here)
oldSize = output->size();
for (size_t i = 1; i < num_matches; i++) {
output->insert(output->end(), output->begin(), output->begin() + oldSize);
}
prevPrevFanout = oldSize;
prevFanout = num_matches;
} else {
// If we should not create fanout, e.g., uid tag from same position should be remain
// together.
oldSize = prevPrevFanout;
if (prevFanout != num_matches) {
// sanity check.
ALOGE("2 Any matcher result in different output");
return false;
}
}
// now add the matched field value to output
for (size_t i = 0; i < num_matches; i++) {
for (int j = 0; j < oldSize; j++) {
(*output)[i * oldSize + j].addValue(matchedResults[i]);
}
}
}
return output->size() > 0 && (*output)[0].getValues().size() > 0;
return num_matches > 0;
}
void filterGaugeValues(const std::vector<Matcher>& matcherFields,

View File

@@ -122,16 +122,13 @@ android::hash_t hashDimension(const HashableDimensionKey& key);
/**
* Creating HashableDimensionKeys from FieldValues using matcher.
*
* This function may make modifications to the Field if the matcher has Position=LAST or ANY in
* it. This is because: for example, when we create dimension from last uid in attribution chain,
* This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
* in it. This is because: for example, when we create dimension from last uid in attribution chain,
* In one event, uid 1000 is at position 5 and it's the last
* In another event, uid 1000 is at position 6, and it's the last
* these 2 events should be mapped to the same dimension. So we will remove the original position
* from the dimension key for the uid field (by applying 0x80 bit mask).
*/
bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
std::vector<HashableDimensionKey>* output);
// This function is used when there is at most one output dimension key. (no ANY matcher)
bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
HashableDimensionKey* output);

View File

@@ -121,7 +121,8 @@ private:
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
@@ -138,6 +139,7 @@ private:
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition);
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition);
};
} // namespace statsd

View File

@@ -327,25 +327,7 @@ void SimpleConditionTracker::evaluateCondition(
// have both sliced and unsliced version of a predicate.
handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged);
} else {
std::vector<HashableDimensionKey> outputValues;
filterValues(mOutputDimensions, event.getValues(), &outputValues);
// If this event has multiple nodes in the attribution chain, this log event probably will
// generate multiple dimensions. If so, we will find if the condition changes for any
// dimension and ask the corresponding metric producer to verify whether the actual sliced
// condition has changed or not.
// A high level assumption is that a predicate is either sliced or unsliced. We will never
// have both sliced and unsliced version of a predicate.
for (const HashableDimensionKey& outputValue : outputValues) {
ConditionState tempState;
bool tempChanged = false;
handleConditionEvent(outputValue, matchedState == 1, &tempState, &tempChanged);
if (tempChanged) {
overallChanged = true;
}
// ConditionState's | operator is overridden
overallState = overallState | tempState;
}
ALOGE("The condition tracker should not be sliced by ANY position matcher.");
}
conditionCache[mIndex] = overallState;
conditionChangedCache[mIndex] = overallChanged;

View File

@@ -137,21 +137,18 @@ void StateTracker::evaluateCondition(const LogEvent& event,
VLOG("StateTracker evaluate event %s", event.ToString().c_str());
vector<HashableDimensionKey> keys;
vector<HashableDimensionKey> outputs;
filterValues(mPrimaryKeys, event.getValues(), &keys);
filterValues(mOutputDimensions, event.getValues(), &outputs);
if (keys.size() != 1 || outputs.size() != 1) {
ALOGE("More than 1 states in the event?? panic now!");
// Primary key can exclusive fields must be simple fields. so there won't be more than
// one keys matched.
HashableDimensionKey primaryKey;
HashableDimensionKey state;
if (!filterValues(mPrimaryKeys, event.getValues(), &primaryKey) ||
!filterValues(mOutputDimensions, event.getValues(), &state)) {
ALOGE("Failed to filter fields in the event?? panic now!");
conditionCache[mIndex] =
mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
conditionChangedCache[mIndex] = false;
return;
}
// Primary key can exclusive fields must be simple fields. so there won't be more than
// one keys matched.
const auto& primaryKey = keys[0];
const auto& state = outputs[0];
hitGuardRail(primaryKey);
VLOG("StateTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());

View File

@@ -185,6 +185,9 @@ bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher,
ranges.push_back(std::make_pair(newStart, end));
break;
}
case Position::ALL:
ALOGE("Not supported: field matcher with ALL position.");
break;
case Position::POSITION_UNKNOWN:
break;
}

View File

@@ -88,6 +88,12 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat
translateFieldMatcher(internalDimensions, &mInternalDimensions);
mContainANYPositionInInternalDimensions = HasPositionANY(internalDimensions);
}
if (mContainANYPositionInInternalDimensions) {
ALOGE("Position ANY in internal dimension not supported.");
}
if (mContainANYPositionInDimensionsInWhat) {
ALOGE("Position ANY in dimension_in_what not supported.");
}
if (metric.has_dimensions_in_condition()) {
translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition);
@@ -589,18 +595,10 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey
it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
event.GetElapsedTimestampNs(), conditionKeys);
} else {
if (mContainANYPositionInInternalDimensions) {
std::vector<HashableDimensionKey> dimensionKeys;
filterValues(mInternalDimensions, event.getValues(), &dimensionKeys);
for (const auto& key : dimensionKeys) {
it->second->noteStart(key, condition, event.GetElapsedTimestampNs(), conditionKeys);
}
} else {
HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
it->second->noteStart(
dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
}
HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
it->second->noteStart(
dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
}
}
@@ -612,8 +610,8 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked(
ALOGW("Not used in duration tracker.");
}
void DurationMetricProducer::onMatchedLogEventLocked_simple(const size_t matcherIndex,
const LogEvent& event) {
void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
const LogEvent& event) {
uint64_t eventTimeNs = event.GetElapsedTimestampNs();
if (eventTimeNs < mStartTimeNs) {
return;
@@ -712,117 +710,6 @@ void DurationMetricProducer::onMatchedLogEventLocked_simple(const size_t matcher
}
}
void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
const LogEvent& event) {
if (!mContainANYPositionInDimensionsInWhat) {
onMatchedLogEventLocked_simple(matcherIndex, event);
return;
}
uint64_t eventTimeNs = event.GetElapsedTimestampNs();
if (eventTimeNs < mStartTimeNs) {
return;
}
flushIfNeededLocked(event.GetElapsedTimestampNs());
// Handles Stopall events.
if (matcherIndex == mStopAllIndex) {
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
for (auto& pair : whatIt.second) {
pair.second->noteStopAll(event.GetElapsedTimestampNs());
}
}
return;
}
vector<HashableDimensionKey> dimensionInWhatValues;
if (!mDimensionsInWhat.empty()) {
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
} else {
dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
}
// Handles Stop events.
if (matcherIndex == mStopIndex) {
if (mUseWhatDimensionAsInternalDimension) {
for (const HashableDimensionKey& whatKey : dimensionInWhatValues) {
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
for (const auto& condIt : whatIt->second) {
condIt.second->noteStop(whatKey, event.GetElapsedTimestampNs(), false);
}
}
}
return;
}
HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY;
if (!mInternalDimensions.empty()) {
filterValues(mInternalDimensions, event.getValues(), &internalDimensionKey);
}
for (const HashableDimensionKey& whatDimension : dimensionInWhatValues) {
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
for (const auto& condIt : whatIt->second) {
condIt.second->noteStop(
internalDimensionKey, event.GetElapsedTimestampNs(), false);
}
}
}
return;
}
bool condition;
ConditionKey conditionKey;
std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
if (mConditionSliced) {
for (const auto& link : mMetric2ConditionLinks) {
getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
}
auto conditionState =
mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
!mSameConditionDimensionsInTracker,
!mHasLinksToAllConditionDimensionsInTracker,
&dimensionKeysInCondition);
condition = (conditionState == ConditionState::kTrue);
if (mDimensionsInCondition.empty() && condition) {
dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
}
} else {
condition = mCondition;
if (condition) {
dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
}
}
for (const auto& whatDimension : dimensionInWhatValues) {
if (dimensionKeysInCondition.empty()) {
handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
conditionKey, condition, event);
} else {
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
// If the what dimension is already there, we should update all the trackers even
// the condition is false.
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
for (const auto& condIt : whatIt->second) {
const bool cond = dimensionKeysInCondition.find(condIt.first) !=
dimensionKeysInCondition.end();
handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
conditionKey, cond, event);
dimensionKeysInCondition.erase(condIt.first);
}
}
for (const auto& conditionDimension : dimensionKeysInCondition) {
handleStartEvent(MetricDimensionKey(whatDimension, conditionDimension),
conditionKey, condition, event);
}
}
}
}
size_t DurationMetricProducer::byteSizeLocked() const {
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {

View File

@@ -52,8 +52,6 @@ public:
protected:
void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
void onMatchedLogEventLocked_simple(const size_t matcherIndex, const LogEvent& event);
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKeys, bool condition,

View File

@@ -53,39 +53,17 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo
dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
}
if (mContainANYPositionInDimensionsInWhat) {
vector<HashableDimensionKey> dimensionInWhatValues;
if (!mDimensionsInWhat.empty()) {
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
} else {
dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
}
for (const auto& whatDimension : dimensionInWhatValues) {
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
onMatchedLogEventInternalLocked(
matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
conditionKey, condition, event);
}
if (dimensionKeysInCondition.empty()) {
onMatchedLogEventInternalLocked(
matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
conditionKey, condition, event);
}
}
} else {
HashableDimensionKey dimensionInWhat;
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY);
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
metricKey.setDimensionKeyInCondition(conditionDimensionKey);
onMatchedLogEventInternalLocked(
matcherIndex, metricKey, conditionKey, condition, event);
}
if (dimensionKeysInCondition.empty()) {
onMatchedLogEventInternalLocked(
matcherIndex, metricKey, conditionKey, condition, event);
}
HashableDimensionKey dimensionInWhat;
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY);
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
metricKey.setDimensionKeyInCondition(conditionDimensionKey);
onMatchedLogEventInternalLocked(
matcherIndex, metricKey, conditionKey, condition, event);
}
if (dimensionKeysInCondition.empty()) {
onMatchedLogEventInternalLocked(
matcherIndex, metricKey, conditionKey, condition, event);
}
}

View File

@@ -158,7 +158,8 @@ private:
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);

View File

@@ -31,6 +31,8 @@ enum Position {
LAST = 2;
ANY = 3;
ALL = 4;
}
enum TimeUnit {

View File

@@ -45,20 +45,41 @@ TEST(AtomMatcherTest, TestFieldTranslation) {
const auto& matcher12 = output[0];
EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag());
EXPECT_EQ((int32_t)0x2010001, matcher12.mMatcher.getField());
EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField());
EXPECT_EQ((int32_t)0xff7f007f, matcher12.mMask);
}
TEST(AtomMatcherTest, TestFilter) {
TEST(AtomMatcherTest, TestFieldTranslation_ALL) {
FieldMatcher matcher1;
matcher1.set_field(10);
FieldMatcher* child = matcher1.add_child();
child->set_field(1);
child->set_position(Position::ANY);
child->set_position(Position::ALL);
child = child->add_child();
child->set_field(1);
vector<Matcher> output;
translateFieldMatcher(matcher1, &output);
EXPECT_EQ((size_t)1, output.size());
const auto& matcher12 = output[0];
EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag());
EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField());
EXPECT_EQ((int32_t)0xff7f7f7f, matcher12.mMask);
}
TEST(AtomMatcherTest, TestFilter_ALL) {
FieldMatcher matcher1;
matcher1.set_field(10);
FieldMatcher* child = matcher1.add_child();
child->set_field(1);
child->set_position(Position::ALL);
child->add_child()->set_field(1);
child->add_child()->set_field(2);
child = matcher1.add_child();
child->set_field(2);
@@ -85,32 +106,28 @@ TEST(AtomMatcherTest, TestFilter) {
event.write("some value");
// Convert to a LogEvent
event.init();
vector<HashableDimensionKey> output;
HashableDimensionKey output;
filterValues(matchers, event.getValues(), &output);
EXPECT_EQ((size_t)(3), output.size());
EXPECT_EQ((size_t)7, output.getValues().size());
EXPECT_EQ((int32_t)0x02010101, output.getValues()[0].mField.getField());
EXPECT_EQ((int32_t)1111, output.getValues()[0].mValue.int_value);
EXPECT_EQ((int32_t)0x02010102, output.getValues()[1].mField.getField());
EXPECT_EQ("location1", output.getValues()[1].mValue.str_value);
const auto& key1 = output[0];
EXPECT_EQ((size_t)2, key1.getValues().size());
EXPECT_EQ((int32_t)0x02010001, key1.getValues()[0].mField.getField());
EXPECT_EQ((int32_t)1111, key1.getValues()[0].mValue.int_value);
EXPECT_EQ((int32_t)0x00020000, key1.getValues()[1].mField.getField());
EXPECT_EQ("some value", key1.getValues()[1].mValue.str_value);
EXPECT_EQ((int32_t)0x02010201, output.getValues()[2].mField.getField());
EXPECT_EQ((int32_t)2222, output.getValues()[2].mValue.int_value);
EXPECT_EQ((int32_t)0x02010202, output.getValues()[3].mField.getField());
EXPECT_EQ("location2", output.getValues()[3].mValue.str_value);
const auto& key2 = output[1];
EXPECT_EQ((size_t)2, key2.getValues().size());
EXPECT_EQ((int32_t)0x02010001, key2.getValues()[0].mField.getField());
EXPECT_EQ((int32_t)2222, key2.getValues()[0].mValue.int_value);
EXPECT_EQ((int32_t)0x00020000, key2.getValues()[1].mField.getField());
EXPECT_EQ("some value", key2.getValues()[1].mValue.str_value);
EXPECT_EQ((int32_t)0x02010301, output.getValues()[4].mField.getField());
EXPECT_EQ((int32_t)3333, output.getValues()[4].mValue.int_value);
EXPECT_EQ((int32_t)0x02010302, output.getValues()[5].mField.getField());
EXPECT_EQ("location3", output.getValues()[5].mValue.str_value);
const auto& key3 = output[2];
EXPECT_EQ((size_t)2, key3.getValues().size());
EXPECT_EQ((int32_t)0x02010001, key3.getValues()[0].mField.getField());
EXPECT_EQ((int32_t)3333, key3.getValues()[0].mValue.int_value);
EXPECT_EQ((int32_t)0x00020000, key3.getValues()[1].mField.getField());
EXPECT_EQ("some value", key3.getValues()[1].mValue.str_value);
EXPECT_EQ((int32_t)0x00020000, output.getValues()[6].mField.getField());
EXPECT_EQ("some value", output.getValues()[6].mValue.str_value);
}
TEST(AtomMatcherTest, TestSubDimension) {

View File

@@ -28,7 +28,7 @@ namespace statsd {
namespace {
StatsdConfig CreateStatsdConfig() {
StatsdConfig CreateStatsdConfig(const Position position) {
StatsdConfig config;
auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
auto attributionNodeMatcher =
@@ -46,15 +46,15 @@ StatsdConfig CreateStatsdConfig() {
countMetric->set_what(wakelockAcquireMatcher.id());
*countMetric->mutable_dimensions_in_what() =
CreateAttributionUidAndTagDimensions(
android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
android::util::WAKELOCK_STATE_CHANGED, {position});
countMetric->set_bucket(FIVE_MINUTES);
return config;
}
} // namespace
TEST(AttributionE2eTest, TestAttributionMatchAndSlice) {
auto config = CreateStatsdConfig();
TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) {
auto config = CreateStatsdConfig(Position::FIRST);
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
@@ -199,6 +199,215 @@ TEST(AttributionE2eTest, TestAttributionMatchAndSlice) {
EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
}
TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) {
auto config = CreateStatsdConfig(Position::ALL);
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
// Here it assumes that GMS core has two uids.
processor->getUidMap()->updateApp(
android::String16("com.android.gmscore"), 222 /* uid */, 1 /* version code*/);
processor->getUidMap()->updateApp(
android::String16("com.android.gmscore"), 444 /* uid */, 1 /* version code*/);
processor->getUidMap()->updateApp(
android::String16("app1"), 111 /* uid */, 2 /* version code*/);
processor->getUidMap()->updateApp(
android::String16("APP3"), 333 /* uid */, 2 /* version code*/);
// GMS core node is in the middle.
std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
CreateAttribution(222, "GMSCoreModule1"),
CreateAttribution(333, "App3")};
// GMS core node is the last one.
std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App1"),
CreateAttribution(333, "App3"),
CreateAttribution(222, "GMSCoreModule1")};
// GMS core node is the first one.
std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1"),
CreateAttribution(333, "App3")};
// Single GMS core node.
std::vector<AttributionNodeInternal> attributions4 = {CreateAttribution(222, "GMSCoreModule1")};
// GMS core has another uid.
std::vector<AttributionNodeInternal> attributions5 = {CreateAttribution(111, "App1"),
CreateAttribution(444, "GMSCoreModule2"),
CreateAttribution(333, "App3")};
// Multiple GMS core nodes.
std::vector<AttributionNodeInternal> attributions6 = {CreateAttribution(444, "GMSCoreModule2"),
CreateAttribution(222, "GMSCoreModule1")};
// No GMS core nodes.
std::vector<AttributionNodeInternal> attributions7 = {CreateAttribution(111, "App1"),
CreateAttribution(333, "App3")};
std::vector<AttributionNodeInternal> attributions8 = {CreateAttribution(111, "App1")};
// GMS core node with isolated uid.
const int isolatedUid = 666;
std::vector<AttributionNodeInternal> attributions9 = {
CreateAttribution(isolatedUid, "GMSCoreModule1")};
std::vector<std::unique_ptr<LogEvent>> events;
// Events 1~4 are in the 1st bucket.
events.push_back(CreateAcquireWakelockEvent(
attributions1, "wl1", bucketStartTimeNs + 2));
events.push_back(CreateAcquireWakelockEvent(
attributions2, "wl1", bucketStartTimeNs + 200));
events.push_back(CreateAcquireWakelockEvent(
attributions3, "wl1", bucketStartTimeNs + bucketSizeNs - 1));
events.push_back(CreateAcquireWakelockEvent(
attributions4, "wl1", bucketStartTimeNs + bucketSizeNs));
// Events 5~8 are in the 3rd bucket.
events.push_back(CreateAcquireWakelockEvent(
attributions5, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1));
events.push_back(CreateAcquireWakelockEvent(
attributions6, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 100));
events.push_back(CreateAcquireWakelockEvent(
attributions7, "wl2", bucketStartTimeNs + 3 * bucketSizeNs - 2));
events.push_back(CreateAcquireWakelockEvent(
attributions8, "wl2", bucketStartTimeNs + 3 * bucketSizeNs));
events.push_back(CreateAcquireWakelockEvent(
attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 1));
events.push_back(CreateAcquireWakelockEvent(
attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 100));
events.push_back(CreateIsolatedUidChangedEvent(
isolatedUid, 222, true/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs - 1));
events.push_back(CreateIsolatedUidChangedEvent(
isolatedUid, 222, false/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs + 10));
sortLogEventsByTimestamp(&events);
for (const auto& event : events) {
processor->OnLogEvent(event.get());
}
ConfigMetricsReportList reports;
vector<uint8_t> buffer;
processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, &buffer);
EXPECT_TRUE(buffer.size() > 0);
EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
EXPECT_EQ(reports.reports_size(), 1);
EXPECT_EQ(reports.reports(0).metrics_size(), 1);
StatsLogReport::CountMetricDataWrapper countMetrics;
sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
EXPECT_EQ(countMetrics.data_size(), 6);
auto data = countMetrics.data(0);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
EXPECT_EQ(2, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
data.bucket_info(0).end_bucket_elapsed_nanos());
EXPECT_EQ(1, data.bucket_info(1).count());
EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs,
data.bucket_info(1).end_bucket_elapsed_nanos());
data = countMetrics.data(1);
ValidateUidDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
ValidateUidDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
EXPECT_EQ(data.bucket_info_size(), 1);
EXPECT_EQ(data.bucket_info(0).count(), 1);
EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
data = countMetrics.data(2);
ValidateUidDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2");
ValidateUidDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
EXPECT_EQ(data.bucket_info_size(), 1);
EXPECT_EQ(data.bucket_info(0).count(), 1);
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
data.bucket_info(0).end_bucket_elapsed_nanos());
data = countMetrics.data(3);
ValidateUidDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
ValidateUidDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
ValidateUidDimension(
data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
EXPECT_EQ(data.bucket_info_size(), 1);
EXPECT_EQ(data.bucket_info(0).count(), 1);
EXPECT_EQ(bucketStartTimeNs,
data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
data.bucket_info(0).end_bucket_elapsed_nanos());
data = countMetrics.data(4);
ValidateUidDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
ValidateUidDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
ValidateUidDimension(
data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
EXPECT_EQ(data.bucket_info_size(), 1);
EXPECT_EQ(data.bucket_info(0).count(), 1);
EXPECT_EQ(bucketStartTimeNs,
data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
data.bucket_info(0).end_bucket_elapsed_nanos());
data = countMetrics.data(5);
ValidateUidDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
ValidateUidDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2");
ValidateUidDimension(
data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333);
ValidateAttributionUidAndTagDimension(
data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
EXPECT_EQ(data.bucket_info_size(), 1);
EXPECT_EQ(data.bucket_info(0).count(), 1);
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
data.bucket_info(0).end_bucket_elapsed_nanos());
}
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif

View File

@@ -500,12 +500,42 @@ void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
.value_tuple().dimensions_value(0).value_int(), uid);
}
void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid) {
EXPECT_EQ(value.field(), atomId);
EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx);
// Attribution field.
EXPECT_EQ(value.value_tuple().dimensions_value(node_idx).field(), 1);
EXPECT_EQ(value.value_tuple().dimensions_value(node_idx)
.value_tuple().dimensions_value(0).field(), 1);
EXPECT_EQ(value.value_tuple().dimensions_value(node_idx)
.value_tuple().dimensions_value(0).value_int(), uid);
}
void ValidateAttributionUidAndTagDimension(
const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag) {
EXPECT_EQ(value.field(), atomId);
EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx);
// Attribution field.
EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx).field());
// Uid only.
EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx)
.value_tuple().dimensions_value_size());
EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx)
.value_tuple().dimensions_value(0).field());
EXPECT_EQ(uid, value.value_tuple().dimensions_value(node_idx)
.value_tuple().dimensions_value(0).value_int());
EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx)
.value_tuple().dimensions_value(1).field());
EXPECT_EQ(tag, value.value_tuple().dimensions_value(node_idx)
.value_tuple().dimensions_value(1).value_str());
}
void ValidateAttributionUidAndTagDimension(
const DimensionsValue& value, int atomId, int uid, const std::string& tag) {
EXPECT_EQ(value.field(), atomId);
EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
EXPECT_EQ(1, value.value_tuple().dimensions_value_size());
// Attribution field.
EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
EXPECT_EQ(1, value.value_tuple().dimensions_value(0).field());
// Uid only.
EXPECT_EQ(value.value_tuple().dimensions_value(0)
.value_tuple().dimensions_value_size(), 2);

View File

@@ -180,9 +180,12 @@ void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
int64_t StringToId(const string& str);
void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
void ValidateAttributionUidAndTagDimension(
const DimensionsValue& value, int atomId, int uid, const std::string& tag);
void ValidateAttributionUidAndTagDimension(
const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag);
struct DimensionsPair {
DimensionsPair(DimensionsValue m1, DimensionsValue m2) : dimInWhat(m1), dimInCondition(m2){};