Merge "Support dimension in condition in metric producers."
This commit is contained in:
committed by
Android (Google) Code Review
commit
0d3813e3da
@@ -165,6 +165,7 @@ LOCAL_CFLAGS += \
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(statsd_common_src) \
|
||||
tests/dimension_test.cpp \
|
||||
tests/AnomalyMonitor_test.cpp \
|
||||
tests/anomaly/AnomalyTracker_test.cpp \
|
||||
tests/ConfigManager_test.cpp \
|
||||
@@ -190,7 +191,8 @@ LOCAL_SRC_FILES := \
|
||||
tests/e2e/WakelockDuration_e2e_test.cpp \
|
||||
tests/e2e/MetricConditionLink_e2e_test.cpp \
|
||||
tests/e2e/Attribution_e2e_test.cpp \
|
||||
tests/e2e/GaugeMetric_e2e_test.cpp
|
||||
tests/e2e/GaugeMetric_e2e_test.cpp \
|
||||
tests/e2e/DimensionInCondition_e2e_test.cpp
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
$(statsd_common_static_libraries) \
|
||||
|
||||
@@ -67,12 +67,16 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value) {
|
||||
return hashDimensionsValue(0, value);
|
||||
}
|
||||
|
||||
android::hash_t hashMetricDimensionKey(int64_t seed, const MetricDimensionKey& dimensionKey) {
|
||||
android::hash_t hash = seed;
|
||||
hash = android::JenkinsHashMix(hash, std::hash<MetricDimensionKey>{}(dimensionKey));
|
||||
return JenkinsHashWhiten(hash);
|
||||
}
|
||||
|
||||
using std::string;
|
||||
|
||||
string HashableDimensionKey::toString() const {
|
||||
string flattened;
|
||||
DimensionsValueToString(getDimensionsValue(), &flattened);
|
||||
return flattened;
|
||||
return DimensionsValueToString(getDimensionsValue());
|
||||
}
|
||||
|
||||
bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) {
|
||||
@@ -162,6 +166,22 @@ bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
|
||||
return LessThan(getDimensionsValue(), that.getDimensionsValue());
|
||||
};
|
||||
|
||||
string MetricDimensionKey::toString() const {
|
||||
string flattened = mDimensionKeyInWhat.toString();
|
||||
flattened += mDimensionKeyInCondition.toString();
|
||||
return flattened;
|
||||
}
|
||||
|
||||
bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const {
|
||||
return mDimensionKeyInWhat == that.getDimensionKeyInWhat() &&
|
||||
mDimensionKeyInCondition == that.getDimensionKeyInCondition();
|
||||
};
|
||||
|
||||
bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const {
|
||||
return toString().compare(that.toString()) < 0;
|
||||
};
|
||||
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -41,6 +41,10 @@ public:
|
||||
return mDimensionsValue;
|
||||
}
|
||||
|
||||
inline DimensionsValue* getMutableDimensionsValue() {
|
||||
return &mDimensionsValue;
|
||||
}
|
||||
|
||||
bool operator==(const HashableDimensionKey& that) const;
|
||||
|
||||
bool operator<(const HashableDimensionKey& that) const;
|
||||
@@ -53,8 +57,52 @@ private:
|
||||
DimensionsValue mDimensionsValue;
|
||||
};
|
||||
|
||||
class MetricDimensionKey {
|
||||
public:
|
||||
explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat,
|
||||
const HashableDimensionKey& dimensionKeyInCondition)
|
||||
: mDimensionKeyInWhat(dimensionKeyInWhat),
|
||||
mDimensionKeyInCondition(dimensionKeyInCondition) {};
|
||||
|
||||
MetricDimensionKey(){};
|
||||
|
||||
MetricDimensionKey(const MetricDimensionKey& that)
|
||||
: mDimensionKeyInWhat(that.getDimensionKeyInWhat()),
|
||||
mDimensionKeyInCondition(that.getDimensionKeyInCondition()) {};
|
||||
|
||||
MetricDimensionKey& operator=(const MetricDimensionKey& from) = default;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
inline const HashableDimensionKey& getDimensionKeyInWhat() const {
|
||||
return mDimensionKeyInWhat;
|
||||
}
|
||||
|
||||
inline const HashableDimensionKey& getDimensionKeyInCondition() const {
|
||||
return mDimensionKeyInCondition;
|
||||
}
|
||||
|
||||
bool hasDimensionKeyInCondition() const {
|
||||
return mDimensionKeyInCondition.getDimensionsValue().has_field();
|
||||
}
|
||||
|
||||
bool operator==(const MetricDimensionKey& that) const;
|
||||
|
||||
bool operator<(const MetricDimensionKey& that) const;
|
||||
|
||||
inline const char* c_str() const {
|
||||
return toString().c_str();
|
||||
}
|
||||
private:
|
||||
HashableDimensionKey mDimensionKeyInWhat;
|
||||
HashableDimensionKey mDimensionKeyInCondition;
|
||||
};
|
||||
|
||||
bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2);
|
||||
|
||||
android::hash_t hashDimensionsValue(int64_t seed, const DimensionsValue& value);
|
||||
android::hash_t hashDimensionsValue(const DimensionsValue& value);
|
||||
android::hash_t hashMetricDimensionKey(int64_t see, const MetricDimensionKey& dimensionKey);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
@@ -63,6 +111,7 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value);
|
||||
namespace std {
|
||||
|
||||
using android::os::statsd::HashableDimensionKey;
|
||||
using android::os::statsd::MetricDimensionKey;
|
||||
|
||||
template <>
|
||||
struct hash<HashableDimensionKey> {
|
||||
@@ -71,4 +120,14 @@ struct hash<HashableDimensionKey> {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
template <>
|
||||
struct hash<MetricDimensionKey> {
|
||||
std::size_t operator()(const MetricDimensionKey& key) const {
|
||||
android::hash_t hash = hashDimensionsValue(
|
||||
key.getDimensionKeyInWhat().getDimensionsValue());
|
||||
hash = android::JenkinsHashMix(hash,
|
||||
hashDimensionsValue(key.getDimensionKeyInCondition().getDimensionsValue()));
|
||||
return android::JenkinsHashWhiten(hash);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
@@ -104,7 +104,10 @@ private:
|
||||
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
|
||||
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
|
||||
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -96,7 +96,7 @@ void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
|
||||
void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
|
||||
const int64_t& bucketNum) {
|
||||
flushPastBuckets(bucketNum);
|
||||
|
||||
@@ -147,7 +147,7 @@ void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
|
||||
}
|
||||
}
|
||||
|
||||
int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
|
||||
int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
|
||||
const int64_t& bucketNum) const {
|
||||
const auto& bucket = mPastBuckets[index(bucketNum)];
|
||||
if (bucket == nullptr) {
|
||||
@@ -157,7 +157,7 @@ int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
|
||||
return itr == bucket->end() ? 0 : itr->second;
|
||||
}
|
||||
|
||||
int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
|
||||
int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const {
|
||||
const auto& itr = mSumOverPastBuckets.find(key);
|
||||
if (itr != mSumOverPastBuckets.end()) {
|
||||
return itr->second;
|
||||
@@ -165,7 +165,7 @@ int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) c
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
|
||||
bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue) {
|
||||
if (currentBucketNum > mMostRecentBucketNum + 1) {
|
||||
// TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
|
||||
@@ -175,7 +175,7 @@ bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const Hashab
|
||||
&& getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
|
||||
}
|
||||
|
||||
void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key) {
|
||||
void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) {
|
||||
// TODO: Why receive timestamp? RefractoryPeriod should always be based on real time right now.
|
||||
if (isInRefractoryPeriod(timestampNs, key)) {
|
||||
VLOG("Skipping anomaly declaration since within refractory period");
|
||||
@@ -199,14 +199,14 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const HashableD
|
||||
|
||||
StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
|
||||
|
||||
// TODO: This should also take in the const HashableDimensionKey& key?
|
||||
// TODO: This should also take in the const MetricDimensionKey& key?
|
||||
android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(),
|
||||
mConfigKey.GetId(), mAlert.id());
|
||||
}
|
||||
|
||||
void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
|
||||
const int64_t& currBucketNum,
|
||||
const HashableDimensionKey& key,
|
||||
const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue) {
|
||||
if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
|
||||
declareAnomaly(timestampNs, key);
|
||||
@@ -214,7 +214,7 @@ void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestampNs,
|
||||
}
|
||||
|
||||
bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
|
||||
const HashableDimensionKey& key) {
|
||||
const MetricDimensionKey& key) {
|
||||
const auto& it = mRefractoryPeriodEndsSec.find(key);
|
||||
if (it != mRefractoryPeriodEndsSec.end()) {
|
||||
if ((timestampNs / NS_PER_SEC) <= it->second) {
|
||||
@@ -226,7 +226,7 @@ bool AnomalyTracker::isInRefractoryPeriod(const uint64_t& timestampNs,
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnomalyTracker::informSubscribers(const HashableDimensionKey& key) {
|
||||
void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) {
|
||||
VLOG("informSubscribers called.");
|
||||
if (mSubscriptions.empty()) {
|
||||
ALOGE("Attempt to call with no subscribers.");
|
||||
|
||||
@@ -48,19 +48,19 @@ public:
|
||||
// Adds a bucket.
|
||||
// Bucket index starts from 0.
|
||||
void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
|
||||
void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
|
||||
void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
|
||||
const int64_t& bucketNum);
|
||||
|
||||
// Returns true if detected anomaly for the existing buckets on one or more dimension keys.
|
||||
bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
|
||||
bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue);
|
||||
|
||||
// Informs incidentd about the detected alert.
|
||||
void declareAnomaly(const uint64_t& timestampNs, const HashableDimensionKey& key);
|
||||
void declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key);
|
||||
|
||||
// Detects the alert and informs the incidentd when applicable.
|
||||
void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
|
||||
const HashableDimensionKey& key,
|
||||
const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue);
|
||||
|
||||
// Init the AnomalyMonitor which is shared across anomaly trackers.
|
||||
@@ -69,10 +69,10 @@ public:
|
||||
}
|
||||
|
||||
// Helper function to return the sum value of past buckets at given dimension.
|
||||
int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
|
||||
int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const;
|
||||
|
||||
// Helper function to return the value for a past bucket.
|
||||
int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const;
|
||||
int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const;
|
||||
|
||||
// Returns the anomaly threshold.
|
||||
inline int64_t getAnomalyThreshold() const {
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
|
||||
// Returns the refractory period timestamp (in seconds) for the given key.
|
||||
// If there is no stored refractory period ending timestamp, returns 0.
|
||||
uint32_t getRefractoryPeriodEndsSec(const HashableDimensionKey& key) const {
|
||||
uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const {
|
||||
const auto& it = mRefractoryPeriodEndsSec.find(key);
|
||||
return it != mRefractoryPeriodEndsSec.end() ? it->second : 0;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ protected:
|
||||
// declared for that dimension) ends, in seconds. Only anomalies that occur after this period
|
||||
// ends will be declared.
|
||||
// Entries may be, but are not guaranteed to be, removed after the period is finished.
|
||||
unordered_map<HashableDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
|
||||
unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
|
||||
|
||||
void flushPastBuckets(const int64_t& currBucketNum);
|
||||
|
||||
@@ -135,7 +135,7 @@ protected:
|
||||
// and remove any items with value 0.
|
||||
void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
|
||||
|
||||
bool isInRefractoryPeriod(const uint64_t& timestampNs, const HashableDimensionKey& key);
|
||||
bool isInRefractoryPeriod(const uint64_t& timestampNs, const MetricDimensionKey& key);
|
||||
|
||||
// Calculates the corresponding bucket index within the circular array.
|
||||
size_t index(int64_t bucketNum) const;
|
||||
@@ -144,7 +144,7 @@ protected:
|
||||
virtual void resetStorage();
|
||||
|
||||
// Informs the subscribers that an anomaly has occurred.
|
||||
void informSubscribers(const HashableDimensionKey& key);
|
||||
void informSubscribers(const MetricDimensionKey& key);
|
||||
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
|
||||
|
||||
@@ -37,8 +37,8 @@ void DurationAnomalyTracker::resetStorage() {
|
||||
if (!mAlarms.empty()) VLOG("AnomalyTracker.resetStorage() called but mAlarms is NOT empty!");
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
|
||||
const uint64_t& timestampNs) {
|
||||
void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey,
|
||||
const uint64_t& timestampNs) {
|
||||
auto itr = mAlarms.find(dimensionKey);
|
||||
if (itr == mAlarms.end()) {
|
||||
return;
|
||||
@@ -51,7 +51,7 @@ void DurationAnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensio
|
||||
}
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
|
||||
void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey,
|
||||
const uint64_t& timestampNs) {
|
||||
|
||||
uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
|
||||
@@ -66,7 +66,7 @@ void DurationAnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey
|
||||
}
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
|
||||
void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey) {
|
||||
auto itr = mAlarms.find(dimensionKey);
|
||||
if (itr != mAlarms.end()) {
|
||||
mAlarms.erase(dimensionKey);
|
||||
@@ -77,7 +77,7 @@ void DurationAnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey)
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::stopAllAlarms() {
|
||||
std::set<HashableDimensionKey> keys;
|
||||
std::set<MetricDimensionKey> keys;
|
||||
for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
|
||||
keys.insert(itr->first);
|
||||
}
|
||||
@@ -95,7 +95,7 @@ void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
|
||||
// seldomly called. The alternative would be having AnomalyAlarms store information about the
|
||||
// DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is
|
||||
// rarely ever called.
|
||||
unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
|
||||
unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
|
||||
for (const auto& kv : mAlarms) {
|
||||
if (firedAlarms.count(kv.second) > 0) {
|
||||
matchedAlarms.insert({kv.first, kv.second});
|
||||
|
||||
@@ -32,10 +32,10 @@ public:
|
||||
virtual ~DurationAnomalyTracker();
|
||||
|
||||
// Starts the alarm at the given timestamp.
|
||||
void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
|
||||
void startAlarm(const MetricDimensionKey& dimensionKey, const uint64_t& eventTime);
|
||||
|
||||
// Stops the alarm.
|
||||
void stopAlarm(const HashableDimensionKey& dimensionKey);
|
||||
void stopAlarm(const MetricDimensionKey& dimensionKey);
|
||||
|
||||
// Stop all the alarms owned by this tracker.
|
||||
void stopAllAlarms();
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
}
|
||||
|
||||
// Declares the anomaly when the alarm expired given the current timestamp.
|
||||
void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
|
||||
void declareAnomalyIfAlarmExpired(const MetricDimensionKey& dimensionKey,
|
||||
const uint64_t& timestampNs);
|
||||
|
||||
// Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
protected:
|
||||
// The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
|
||||
// are still active.
|
||||
std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
|
||||
std::unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> mAlarms;
|
||||
|
||||
// Anomaly alarm monitor.
|
||||
sp<AnomalyMonitor> mAnomalyMonitor;
|
||||
|
||||
@@ -78,6 +78,7 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers,
|
||||
conditionIdIndexMap, stack);
|
||||
|
||||
@@ -88,8 +89,10 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf
|
||||
ALOGW("Child initialization success %lld ", (long long)child);
|
||||
}
|
||||
|
||||
if (allConditionTrackers[childIndex]->isSliced()) {
|
||||
setSliced(true);
|
||||
}
|
||||
mChildren.push_back(childIndex);
|
||||
|
||||
mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
|
||||
childTracker->getLogTrackerIndex().end());
|
||||
}
|
||||
@@ -105,11 +108,15 @@ bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConf
|
||||
void CombinationConditionTracker::isConditionMet(
|
||||
const ConditionKey& conditionParameters,
|
||||
const vector<sp<ConditionTracker>>& allConditions,
|
||||
vector<ConditionState>& conditionCache) const {
|
||||
const FieldMatcher& dimensionFields,
|
||||
vector<ConditionState>& conditionCache,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
|
||||
// So far, this is fine as there is at most one child having sliced output.
|
||||
for (const int childIndex : mChildren) {
|
||||
if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
|
||||
allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
|
||||
conditionCache);
|
||||
dimensionFields, conditionCache,
|
||||
dimensionsKeySet);
|
||||
}
|
||||
}
|
||||
conditionCache[mIndex] =
|
||||
@@ -127,6 +134,7 @@ void CombinationConditionTracker::evaluateCondition(
|
||||
}
|
||||
|
||||
for (const int childIndex : mChildren) {
|
||||
// So far, this is fine as there is at most one child having sliced output.
|
||||
if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
|
||||
const sp<ConditionTracker>& child = mAllConditions[childIndex];
|
||||
child->evaluateCondition(event, eventMatcherValues, mAllConditions,
|
||||
@@ -159,6 +167,24 @@ void CombinationConditionTracker::evaluateCondition(
|
||||
}
|
||||
}
|
||||
|
||||
ConditionState CombinationConditionTracker::getMetConditionDimension(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
|
||||
vector<ConditionState> conditionCache(allConditions.size(), ConditionState::kNotEvaluated);
|
||||
// So far, this is fine as there is at most one child having sliced output.
|
||||
for (const int childIndex : mChildren) {
|
||||
conditionCache[childIndex] = conditionCache[childIndex] |
|
||||
allConditions[childIndex]->getMetConditionDimension(
|
||||
allConditions, dimensionFields, dimensionsKeySet);
|
||||
}
|
||||
evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
|
||||
if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) {
|
||||
dimensionsKeySet.insert(DEFAULT_DIMENSION_KEY);
|
||||
}
|
||||
return conditionCache[mIndex];
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
@@ -41,12 +41,20 @@ public:
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& changedCache) override;
|
||||
|
||||
void isConditionMet(const ConditionKey& conditionParameters,
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
std::vector<ConditionState>& conditionCache) const override;
|
||||
void isConditionMet(
|
||||
const ConditionKey& conditionParameters,
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
|
||||
|
||||
ConditionState getMetConditionDimension(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
|
||||
private:
|
||||
LogicalOperation mLogicalOperation;
|
||||
|
||||
// Store index of the children Predicates.
|
||||
// We don't store string name of the Children, because we want to get rid of the hash map to
|
||||
// map the name to object. We don't want to store smart pointers to children, because it
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <log/logprint.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace android {
|
||||
@@ -84,10 +85,19 @@ public:
|
||||
// [allConditions]: all condition trackers. This is needed because the condition evaluation is
|
||||
// done recursively
|
||||
// [conditionCache]: the cache holding the condition evaluation values.
|
||||
// [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination
|
||||
// condition, it assumes that only one child predicate is sliced.
|
||||
virtual void isConditionMet(
|
||||
const ConditionKey& conditionParameters,
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
std::vector<ConditionState>& conditionCache) const = 0;
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const = 0;
|
||||
|
||||
virtual ConditionState getMetConditionDimension(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const = 0;
|
||||
|
||||
// return the list of LogMatchingTracker index that this ConditionTracker uses.
|
||||
virtual const std::set<int>& getLogTrackerIndex() const {
|
||||
@@ -98,6 +108,10 @@ public:
|
||||
mSliced = mSliced | sliced;
|
||||
}
|
||||
|
||||
bool isSliced() const {
|
||||
return mSliced;
|
||||
}
|
||||
|
||||
protected:
|
||||
const int64_t mConditionId;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "ConditionWizard.h"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
@@ -23,14 +24,26 @@ using std::map;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
ConditionState ConditionWizard::query(const int index,
|
||||
const ConditionKey& parameters) {
|
||||
ConditionState ConditionWizard::query(
|
||||
const int index, const ConditionKey& parameters,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> *dimensionKeySet) {
|
||||
|
||||
vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
|
||||
|
||||
mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
|
||||
mAllConditions[index]->isConditionMet(
|
||||
parameters, mAllConditions, dimensionFields, cache, *dimensionKeySet);
|
||||
return cache[index];
|
||||
}
|
||||
|
||||
ConditionState ConditionWizard::getMetConditionDimension(
|
||||
const int index, const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> *dimensionsKeySet) const {
|
||||
|
||||
return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields,
|
||||
*dimensionsKeySet);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -41,7 +41,14 @@ public:
|
||||
// the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
|
||||
virtual ConditionState query(
|
||||
const int conditionIndex,
|
||||
const ConditionKey& conditionParameters);
|
||||
const ConditionKey& conditionParameters,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> *dimensionKeySet);
|
||||
|
||||
virtual ConditionState getMetConditionDimension(
|
||||
const int index,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> *dimensionsKeySet) const;
|
||||
|
||||
private:
|
||||
std::vector<sp<ConditionTracker>> mAllConditions;
|
||||
|
||||
@@ -104,6 +104,9 @@ bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig,
|
||||
vector<bool>& stack) {
|
||||
// SimpleConditionTracker does not have dependency on other conditions, thus we just return
|
||||
// if the initialization was successful.
|
||||
if (mOutputDimensions.has_field() || mOutputDimensions.child_size() > 0) {
|
||||
setSliced(true);
|
||||
}
|
||||
return mInitialized;
|
||||
}
|
||||
|
||||
@@ -234,11 +237,12 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou
|
||||
conditionChangedCache[mIndex] == true);
|
||||
}
|
||||
|
||||
void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
|
||||
const vector<MatchingState>& eventMatcherValues,
|
||||
const vector<sp<ConditionTracker>>& mAllConditions,
|
||||
vector<ConditionState>& conditionCache,
|
||||
vector<bool>& conditionChangedCache) {
|
||||
void SimpleConditionTracker::evaluateCondition(
|
||||
const LogEvent& event,
|
||||
const vector<MatchingState>& eventMatcherValues,
|
||||
const vector<sp<ConditionTracker>>& mAllConditions,
|
||||
vector<ConditionState>& conditionCache,
|
||||
vector<bool>& conditionChangedCache) {
|
||||
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
|
||||
// it has been evaluated.
|
||||
VLOG("Yes, already evaluated, %lld %d",
|
||||
@@ -271,7 +275,7 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
|
||||
if (mSliced) {
|
||||
// if the condition result is sliced. metrics won't directly get value from the
|
||||
// cache, so just set any value other than kNotEvaluated.
|
||||
conditionCache[mIndex] = ConditionState::kUnknown;
|
||||
conditionCache[mIndex] = mInitialValue;
|
||||
} else {
|
||||
const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
|
||||
if (itr == mSlicedConditionState.end()) {
|
||||
@@ -310,10 +314,8 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
|
||||
vector<ConditionState> dimensionalConditionCache(conditionCache.size(),
|
||||
ConditionState::kNotEvaluated);
|
||||
vector<bool> dimensionalConditionChangedCache(conditionChangedCache.size(), false);
|
||||
|
||||
handleConditionEvent(HashableDimensionKey(outputValue), matchedState == 1,
|
||||
dimensionalConditionCache, dimensionalConditionChangedCache);
|
||||
|
||||
OrConditionState(dimensionalConditionCache, &conditionCache);
|
||||
OrBooleanVector(dimensionalConditionChangedCache, &conditionChangedCache);
|
||||
}
|
||||
@@ -323,42 +325,73 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
|
||||
void SimpleConditionTracker::isConditionMet(
|
||||
const ConditionKey& conditionParameters,
|
||||
const vector<sp<ConditionTracker>>& allConditions,
|
||||
vector<ConditionState>& conditionCache) const {
|
||||
const auto pair = conditionParameters.find(mConditionId);
|
||||
|
||||
if (pair == conditionParameters.end() && mOutputDimensions.child_size() > 0) {
|
||||
ALOGE("Predicate %lld output has dimension, but it's not specified in the query!",
|
||||
(long long)mConditionId);
|
||||
conditionCache[mIndex] = mInitialValue;
|
||||
const FieldMatcher& dimensionFields,
|
||||
vector<ConditionState>& conditionCache,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
|
||||
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
|
||||
// it has been evaluated.
|
||||
VLOG("Yes, already evaluated, %lld %d",
|
||||
(long long)mConditionId, conditionCache[mIndex]);
|
||||
return;
|
||||
}
|
||||
std::vector<HashableDimensionKey> defaultKeys = {DEFAULT_DIMENSION_KEY};
|
||||
const auto pair = conditionParameters.find(mConditionId);
|
||||
|
||||
if (pair == conditionParameters.end()) {
|
||||
ConditionState conditionState = ConditionState::kNotEvaluated;
|
||||
if (dimensionFields.has_field() && dimensionFields.child_size() > 0 &&
|
||||
dimensionFields.field() == mOutputDimensions.field()) {
|
||||
conditionState = conditionState | getMetConditionDimension(
|
||||
allConditions, dimensionFields, dimensionsKeySet);
|
||||
} else {
|
||||
conditionState = conditionState | mInitialValue;
|
||||
if (!mSliced) {
|
||||
const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
|
||||
if (itr != mSlicedConditionState.end()) {
|
||||
ConditionState sliceState =
|
||||
itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
conditionState = conditionState | sliceState;
|
||||
}
|
||||
}
|
||||
}
|
||||
conditionCache[mIndex] = conditionState;
|
||||
return;
|
||||
}
|
||||
std::vector<HashableDimensionKey> defaultKeys = { DEFAULT_DIMENSION_KEY };
|
||||
const std::vector<HashableDimensionKey> &keys =
|
||||
(pair == conditionParameters.end()) ? defaultKeys : pair->second;
|
||||
|
||||
ConditionState conditionState = ConditionState::kNotEvaluated;
|
||||
for (const auto& key : keys) {
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
const HashableDimensionKey& key = keys[i];
|
||||
auto startedCountIt = mSlicedConditionState.find(key);
|
||||
if (startedCountIt != mSlicedConditionState.end()) {
|
||||
conditionState = conditionState |
|
||||
(startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
|
||||
ConditionState sliceState =
|
||||
startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
conditionState = conditionState | sliceState;
|
||||
if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) {
|
||||
HashableDimensionKey dimensionKey;
|
||||
if (getSubDimension(startedCountIt->first.getDimensionsValue(), dimensionFields,
|
||||
dimensionKey.getMutableDimensionsValue())) {
|
||||
dimensionsKeySet.insert(dimensionKey);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For unseen key, check whether the require dimensions are subset of sliced condition
|
||||
// output.
|
||||
bool seenDimension = false;
|
||||
conditionState = conditionState | mInitialValue;
|
||||
for (const auto& slice : mSlicedConditionState) {
|
||||
if (IsSubDimension(slice.first.getDimensionsValue(),
|
||||
key.getDimensionsValue())) {
|
||||
seenDimension = true;
|
||||
conditionState = conditionState |
|
||||
(slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse);
|
||||
ConditionState sliceState =
|
||||
slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
if (IsSubDimension(slice.first.getDimensionsValue(), key.getDimensionsValue())) {
|
||||
conditionState = conditionState | sliceState;
|
||||
if (sliceState == ConditionState::kTrue && dimensionFields.has_field()) {
|
||||
HashableDimensionKey dimensionKey;
|
||||
if (getSubDimension(slice.first.getDimensionsValue(),
|
||||
dimensionFields, dimensionKey.getMutableDimensionsValue())) {
|
||||
dimensionsKeySet.insert(dimensionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (conditionState == ConditionState::kTrue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!seenDimension) {
|
||||
conditionState = conditionState | mInitialValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,6 +399,38 @@ void SimpleConditionTracker::isConditionMet(
|
||||
VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
|
||||
}
|
||||
|
||||
ConditionState SimpleConditionTracker::getMetConditionDimension(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const {
|
||||
ConditionState conditionState = mInitialValue;
|
||||
if (!dimensionFields.has_field() ||
|
||||
!mOutputDimensions.has_field() ||
|
||||
dimensionFields.field() != mOutputDimensions.field()) {
|
||||
const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
|
||||
if (itr != mSlicedConditionState.end()) {
|
||||
ConditionState sliceState =
|
||||
itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
conditionState = conditionState | sliceState;
|
||||
}
|
||||
return conditionState;
|
||||
}
|
||||
|
||||
for (const auto& slice : mSlicedConditionState) {
|
||||
ConditionState sliceState =
|
||||
slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
DimensionsValue dimensionsValue;
|
||||
conditionState = conditionState | sliceState;
|
||||
HashableDimensionKey dimensionKey;
|
||||
if (sliceState == ConditionState::kTrue &&
|
||||
getSubDimension(slice.first.getDimensionsValue(), dimensionFields,
|
||||
dimensionKey.getMutableDimensionsValue())) {
|
||||
dimensionsKeySet.insert(dimensionKey);
|
||||
}
|
||||
}
|
||||
return conditionState;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
@@ -48,7 +48,14 @@ public:
|
||||
|
||||
void isConditionMet(const ConditionKey& conditionParameters,
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
std::vector<ConditionState>& conditionCache) const override;
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
|
||||
|
||||
ConditionState getMetConditionDimension(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> &dimensionsKeySet) const override;
|
||||
|
||||
private:
|
||||
const ConfigKey mConfigKey;
|
||||
@@ -73,7 +80,8 @@ private:
|
||||
void handleStopAll(std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& changedCache);
|
||||
|
||||
void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart,
|
||||
void handleConditionEvent(const HashableDimensionKey& outputKey,
|
||||
bool matchStart,
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& changedCache);
|
||||
|
||||
|
||||
@@ -118,6 +118,9 @@ void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored) {
|
||||
|
||||
void getFieldsFromFieldMatcher(const FieldMatcher& matcher, Field* rootField, Field* leafField,
|
||||
std::vector<Field> *allFields) {
|
||||
if (matcher.has_position()) {
|
||||
leafField->set_position_index(0);
|
||||
}
|
||||
if (matcher.child_size() == 0) {
|
||||
allFields->push_back(*rootField);
|
||||
return;
|
||||
|
||||
@@ -253,6 +253,9 @@ void buildAttributionFieldMatcher(const int tagId, const Position position, Fiel
|
||||
}
|
||||
|
||||
void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) {
|
||||
if (!value.has_field()) {
|
||||
return;
|
||||
}
|
||||
*flattened += std::to_string(value.field());
|
||||
*flattened += ":";
|
||||
switch (value.value_case()) {
|
||||
@@ -352,6 +355,46 @@ long getLongFromDimenValue(const DimensionsValue& dimensionValue) {
|
||||
}
|
||||
}
|
||||
|
||||
bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher,
|
||||
DimensionsValue* subDimension) {
|
||||
if (!matcher.has_field()) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.field() != dimension.field()) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.child_size() <= 0) {
|
||||
if (dimension.value_case() == DimensionsValue::ValueCase::kValueTuple ||
|
||||
dimension.value_case() == DimensionsValue::ValueCase::VALUE_NOT_SET) {
|
||||
return false;
|
||||
}
|
||||
*subDimension = dimension;
|
||||
return true;
|
||||
} else {
|
||||
if (dimension.value_case() != DimensionsValue::ValueCase::kValueTuple) {
|
||||
return false;
|
||||
}
|
||||
bool found_value = true;
|
||||
auto value_tuple = dimension.value_tuple();
|
||||
subDimension->set_field(dimension.field());
|
||||
for (int i = 0; found_value && i < matcher.child_size(); ++i) {
|
||||
int j = 0;
|
||||
for (; j < value_tuple.dimensions_value_size(); ++j) {
|
||||
if (value_tuple.dimensions_value(j).field() == matcher.child(i).field()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < value_tuple.dimensions_value_size()) {
|
||||
found_value &= getSubDimension(value_tuple.dimensions_value(j), matcher.child(i),
|
||||
subDimension->mutable_value_tuple()->add_dimensions_value());
|
||||
} else {
|
||||
found_value = false;
|
||||
}
|
||||
}
|
||||
return found_value;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
@@ -63,6 +63,9 @@ bool IsSubDimension(const DimensionsValue& dimension, const DimensionsValue& sub
|
||||
|
||||
// Helper function to get long value from the DimensionsValue proto.
|
||||
long getLongFromDimenValue(const DimensionsValue& dimensionValue);
|
||||
|
||||
bool getSubDimension(const DimensionsValue& dimension, const FieldMatcher& matcher,
|
||||
DimensionsValue* subDimension);
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "stats_util.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "dimension.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
@@ -71,13 +72,15 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric
|
||||
}
|
||||
|
||||
// TODO: use UidMap if uid->pkg_name is required
|
||||
mDimensions = metric.dimensions_in_what();
|
||||
mDimensionsInWhat = metric.dimensions_in_what();
|
||||
mDimensionsInCondition = metric.dimensions_in_condition();
|
||||
|
||||
if (metric.links().size() > 0) {
|
||||
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
|
||||
metric.links().end());
|
||||
mConditionSliced = true;
|
||||
}
|
||||
mConditionSliced = (metric.links().size() > 0)||
|
||||
(mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
|
||||
|
||||
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
|
||||
(long long)mBucketSizeNs, (long long)mStartTimeNs);
|
||||
@@ -99,7 +102,10 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog
|
||||
auto count_metrics = report->mutable_count_metrics();
|
||||
for (const auto& counter : mPastBuckets) {
|
||||
CountMetricData* metricData = count_metrics->add_data();
|
||||
*metricData->mutable_dimensions_in_what() = counter.first.getDimensionsValue();
|
||||
*metricData->mutable_dimensions_in_what() =
|
||||
counter.first.getDimensionKeyInWhat().getDimensionsValue();
|
||||
*metricData->mutable_dimensions_in_condition() =
|
||||
counter.first.getDimensionKeyInCondition().getDimensionsValue();
|
||||
for (const auto& bucket : counter.second) {
|
||||
CountBucketInfo* bucketInfo = metricData->add_bucket_info();
|
||||
bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
|
||||
@@ -123,17 +129,26 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
|
||||
VLOG("metric %lld dump report now...",(long long)mMetricId);
|
||||
|
||||
for (const auto& counter : mPastBuckets) {
|
||||
const HashableDimensionKey& hashableKey = counter.first;
|
||||
VLOG(" dimension key %s", hashableKey.c_str());
|
||||
const MetricDimensionKey& dimensionKey = counter.first;
|
||||
VLOG(" dimension key %s", dimensionKey.c_str());
|
||||
|
||||
long long wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
|
||||
// First fill dimension.
|
||||
long long dimensionToken = protoOutput->start(
|
||||
long long dimensionInWhatToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
|
||||
writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionToken);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionInWhatToken);
|
||||
|
||||
if (dimensionKey.hasDimensionKeyInCondition()) {
|
||||
long long dimensionInConditionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionInConditionToken);
|
||||
}
|
||||
|
||||
// Then fill bucket_info (CountBucketInfo).
|
||||
for (const auto& bucket : counter.second) {
|
||||
@@ -166,7 +181,7 @@ void CountMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
mCondition = conditionMet;
|
||||
}
|
||||
|
||||
bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
|
||||
bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
|
||||
if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) {
|
||||
return false;
|
||||
}
|
||||
@@ -187,7 +202,7 @@ bool CountMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey)
|
||||
}
|
||||
|
||||
void CountMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) {
|
||||
uint64_t eventTimeNs = event.GetTimestampNs();
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
|
||||
protected:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) override;
|
||||
|
||||
@@ -74,14 +74,14 @@ private:
|
||||
void flushIfNeededLocked(const uint64_t& newEventTime);
|
||||
|
||||
// TODO: Add a lock to mPastBuckets.
|
||||
std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
|
||||
std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets;
|
||||
|
||||
// The current bucket.
|
||||
std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
|
||||
|
||||
static const size_t kBucketSize = sizeof(CountBucket{});
|
||||
|
||||
bool hitGuardRailLocked(const HashableDimensionKey& newKey);
|
||||
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
|
||||
|
||||
FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "stats_util.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "dimension.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
@@ -81,13 +82,15 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat
|
||||
}
|
||||
|
||||
// TODO: use UidMap if uid->pkg_name is required
|
||||
mDimensions = metric.dimensions_in_what();
|
||||
mDimensionsInWhat = metric.dimensions_in_what();
|
||||
mDimensionsInCondition = metric.dimensions_in_condition();
|
||||
|
||||
if (metric.links().size() > 0) {
|
||||
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
|
||||
metric.links().end());
|
||||
mConditionSliced = true;
|
||||
}
|
||||
mConditionSliced = (metric.links().size() > 0)||
|
||||
(mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
|
||||
|
||||
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
|
||||
(long long)mBucketSizeNs, (long long)mStartTimeNs);
|
||||
@@ -113,15 +116,17 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(const Alert &alert)
|
||||
}
|
||||
|
||||
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
|
||||
const HashableDimensionKey& eventKey) const {
|
||||
const MetricDimensionKey& eventKey) const {
|
||||
switch (mAggregationType) {
|
||||
case DurationMetric_AggregationType_SUM:
|
||||
return make_unique<OringDurationTracker>(
|
||||
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
|
||||
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
|
||||
mDimensionsInCondition, mNested,
|
||||
mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
|
||||
case DurationMetric_AggregationType_MAX_SPARSE:
|
||||
return make_unique<MaxDurationTracker>(
|
||||
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
|
||||
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
|
||||
mDimensionsInCondition, mNested,
|
||||
mCurrentBucketStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
|
||||
}
|
||||
}
|
||||
@@ -129,10 +134,34 @@ unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
|
||||
void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eventTime) {
|
||||
VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
|
||||
flushIfNeededLocked(eventTime);
|
||||
|
||||
// Now for each of the on-going event, check if the condition has changed for them.
|
||||
for (auto& pair : mCurrentSlicedDuration) {
|
||||
for (auto& pair : mCurrentSlicedDurationTrackerMap) {
|
||||
pair.second->onSlicedConditionMayChange(eventTime);
|
||||
}
|
||||
|
||||
|
||||
std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
|
||||
ConditionState conditionState = mWizard->getMetConditionDimension(
|
||||
mConditionTrackerIndex, mDimensionsInCondition, &conditionDimensionsKeySet);
|
||||
|
||||
bool condition = (conditionState == ConditionState::kTrue);
|
||||
for (auto& pair : mCurrentSlicedDurationTrackerMap) {
|
||||
conditionDimensionsKeySet.erase(pair.first.getDimensionKeyInCondition());
|
||||
}
|
||||
std::unordered_set<MetricDimensionKey> newKeys;
|
||||
for (const auto& conditionDimensionsKey : conditionDimensionsKeySet) {
|
||||
for (auto& pair : mCurrentSlicedDurationTrackerMap) {
|
||||
auto newKey =
|
||||
MetricDimensionKey(pair.first.getDimensionKeyInWhat(), conditionDimensionsKey);
|
||||
if (newKeys.find(newKey) == newKeys.end()) {
|
||||
mCurrentSlicedDurationTrackerMap[newKey] = pair.second->clone(eventTime);
|
||||
mCurrentSlicedDurationTrackerMap[newKey]->setEventKey(newKey);
|
||||
mCurrentSlicedDurationTrackerMap[newKey]->onSlicedConditionMayChange(eventTime);
|
||||
}
|
||||
newKeys.insert(newKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
@@ -142,7 +171,7 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
flushIfNeededLocked(eventTime);
|
||||
// TODO: need to populate the condition change time from the event which triggers the condition
|
||||
// change, instead of using current time.
|
||||
for (auto& pair : mCurrentSlicedDuration) {
|
||||
for (auto& pair : mCurrentSlicedDurationTrackerMap) {
|
||||
pair.second->onConditionChanged(conditionMet, eventTime);
|
||||
}
|
||||
}
|
||||
@@ -155,7 +184,10 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, Stats
|
||||
auto duration_metrics = report->mutable_duration_metrics();
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
DurationMetricData* metricData = duration_metrics->add_data();
|
||||
*metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue();
|
||||
*metricData->mutable_dimensions_in_what() =
|
||||
pair.first.getDimensionKeyInWhat().getDimensionsValue();
|
||||
*metricData->mutable_dimensions_in_condition() =
|
||||
pair.first.getDimensionKeyInCondition().getDimensionsValue();
|
||||
for (const auto& bucket : pair.second) {
|
||||
auto bucketInfo = metricData->add_bucket_info();
|
||||
bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
|
||||
@@ -179,8 +211,8 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
|
||||
VLOG("metric %lld dump report now...", (long long)mMetricId);
|
||||
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
const HashableDimensionKey& hashableKey = pair.first;
|
||||
VLOG(" dimension key %s", hashableKey.c_str());
|
||||
const MetricDimensionKey& dimensionKey = pair.first;
|
||||
VLOG(" dimension key %s", dimensionKey.c_str());
|
||||
|
||||
long long wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
@@ -188,9 +220,18 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
|
||||
// First fill dimension.
|
||||
long long dimensionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
|
||||
writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionToken);
|
||||
|
||||
if (dimensionKey.hasDimensionKeyInCondition()) {
|
||||
long long dimensionInConditionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionInConditionToken);
|
||||
}
|
||||
|
||||
// Then fill bucket_info (DurationBucketInfo).
|
||||
for (const auto& bucket : pair.second) {
|
||||
long long bucketInfoToken = protoOutput->start(
|
||||
@@ -219,10 +260,11 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) {
|
||||
return;
|
||||
}
|
||||
VLOG("flushing...........");
|
||||
for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) {
|
||||
for (auto it = mCurrentSlicedDurationTrackerMap.begin();
|
||||
it != mCurrentSlicedDurationTrackerMap.end();) {
|
||||
if (it->second->flushIfNeeded(eventTime, &mPastBuckets)) {
|
||||
VLOG("erase bucket for key %s", it->first.c_str());
|
||||
it = mCurrentSlicedDuration.erase(it);
|
||||
it = mCurrentSlicedDurationTrackerMap.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
@@ -234,28 +276,28 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) {
|
||||
}
|
||||
|
||||
void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
|
||||
if (mCurrentSlicedDuration.size() == 0) {
|
||||
if (mCurrentSlicedDurationTrackerMap.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId,
|
||||
(unsigned long)mCurrentSlicedDuration.size());
|
||||
(unsigned long)mCurrentSlicedDurationTrackerMap.size());
|
||||
if (verbose) {
|
||||
for (const auto& slice : mCurrentSlicedDuration) {
|
||||
for (const auto& slice : mCurrentSlicedDurationTrackerMap) {
|
||||
fprintf(out, "\t%s\n", slice.first.c_str());
|
||||
slice.second->dumpStates(out, verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
|
||||
bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
|
||||
// the key is not new, we are good.
|
||||
if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) {
|
||||
if (mCurrentSlicedDurationTrackerMap.find(newKey) != mCurrentSlicedDurationTrackerMap.end()) {
|
||||
return false;
|
||||
}
|
||||
// 1. Report the tuple count if the tuple count > soft limit
|
||||
if (mCurrentSlicedDuration.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
|
||||
size_t newTupleCount = mCurrentSlicedDuration.size() + 1;
|
||||
if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
|
||||
size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
|
||||
StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
|
||||
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
|
||||
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
|
||||
@@ -268,27 +310,26 @@ bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newK
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKeys, bool condition,
|
||||
const LogEvent& event) {
|
||||
flushIfNeededLocked(event.GetTimestampNs());
|
||||
|
||||
if (matcherIndex == mStopAllIndex) {
|
||||
for (auto& pair : mCurrentSlicedDuration) {
|
||||
for (auto& pair : mCurrentSlicedDurationTrackerMap) {
|
||||
pair.second->noteStopAll(event.GetTimestampNs());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
|
||||
if (mCurrentSlicedDurationTrackerMap.find(eventKey) == mCurrentSlicedDurationTrackerMap.end()) {
|
||||
if (hitGuardRailLocked(eventKey)) {
|
||||
return;
|
||||
}
|
||||
mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey);
|
||||
mCurrentSlicedDurationTrackerMap[eventKey] = createDurationTracker(eventKey);
|
||||
}
|
||||
|
||||
auto it = mCurrentSlicedDuration.find(eventKey);
|
||||
auto it = mCurrentSlicedDurationTrackerMap.find(eventKey);
|
||||
|
||||
std::vector<DimensionsValue> values;
|
||||
getDimensionKeys(event, mInternalDimensions, &values);
|
||||
@@ -302,10 +343,11 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked(
|
||||
} else {
|
||||
for (const DimensionsValue& value : values) {
|
||||
if (matcherIndex == mStartIndex) {
|
||||
it->second->noteStart(HashableDimensionKey(value), condition,
|
||||
event.GetTimestampNs(), conditionKeys);
|
||||
it->second->noteStart(
|
||||
HashableDimensionKey(value), condition, event.GetTimestampNs(), conditionKeys);
|
||||
} else if (matcherIndex == mStopIndex) {
|
||||
it->second->noteStop(HashableDimensionKey(value), event.GetTimestampNs(), false);
|
||||
it->second->noteStop(
|
||||
HashableDimensionKey(value), event.GetTimestampNs(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
|
||||
protected:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKeys, bool condition,
|
||||
const LogEvent& event) override;
|
||||
|
||||
@@ -92,21 +92,21 @@ private:
|
||||
|
||||
// Save the past buckets and we can clear when the StatsLogReport is dumped.
|
||||
// TODO: Add a lock to mPastBuckets.
|
||||
std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>> mPastBuckets;
|
||||
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
|
||||
|
||||
// The current bucket.
|
||||
std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
|
||||
mCurrentSlicedDuration;
|
||||
std::unordered_map<MetricDimensionKey, std::unique_ptr<DurationTracker>>
|
||||
mCurrentSlicedDurationTrackerMap;
|
||||
|
||||
// Helper function to create a duration tracker given the metric aggregation type.
|
||||
std::unique_ptr<DurationTracker> createDurationTracker(
|
||||
const HashableDimensionKey& eventKey) const;
|
||||
const MetricDimensionKey& eventKey) const;
|
||||
|
||||
// This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
|
||||
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
|
||||
|
||||
// Util function to check whether the specified dimension hits the guardrail.
|
||||
bool hitGuardRailLocked(const HashableDimensionKey& newKey);
|
||||
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
|
||||
|
||||
static const size_t kBucketSize = sizeof(DurationBucket{});
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ void EventMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
}
|
||||
|
||||
void EventMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) {
|
||||
if (!condition) {
|
||||
|
||||
@@ -45,7 +45,7 @@ protected:
|
||||
|
||||
private:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) override;
|
||||
|
||||
|
||||
@@ -82,13 +82,15 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric
|
||||
mFieldFilter = metric.gauge_fields_filter();
|
||||
|
||||
// TODO: use UidMap if uid->pkg_name is required
|
||||
mDimensions = metric.dimensions_in_what();
|
||||
mDimensionsInWhat = metric.dimensions_in_what();
|
||||
mDimensionsInCondition = metric.dimensions_in_condition();
|
||||
|
||||
if (metric.links().size() > 0) {
|
||||
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
|
||||
metric.links().end());
|
||||
mConditionSliced = true;
|
||||
}
|
||||
mConditionSliced = (metric.links().size() > 0)||
|
||||
(mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
|
||||
|
||||
// Kicks off the puller immediately.
|
||||
if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
|
||||
@@ -136,18 +138,27 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
|
||||
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
|
||||
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
const HashableDimensionKey& hashableKey = pair.first;
|
||||
const MetricDimensionKey& dimensionKey = pair.first;
|
||||
|
||||
VLOG(" dimension key %s", hashableKey.c_str());
|
||||
VLOG(" dimension key %s", dimensionKey.c_str());
|
||||
long long wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
|
||||
// First fill dimension.
|
||||
long long dimensionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
|
||||
writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionToken);
|
||||
|
||||
if (dimensionKey.hasDimensionKeyInCondition()) {
|
||||
long long dimensionInConditionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionInConditionToken);
|
||||
}
|
||||
|
||||
// Then fill bucket_info (GaugeBucketInfo).
|
||||
for (const auto& bucket : pair.second) {
|
||||
long long bucketInfoToken = protoOutput->start(
|
||||
@@ -248,7 +259,7 @@ void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven
|
||||
}
|
||||
}
|
||||
|
||||
bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
|
||||
bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
|
||||
if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) {
|
||||
return false;
|
||||
}
|
||||
@@ -268,7 +279,7 @@ bool GaugeMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey)
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) {
|
||||
if (condition == false) {
|
||||
|
||||
@@ -44,7 +44,7 @@ struct GaugeBucket {
|
||||
uint64_t mBucketNum;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<HashableDimensionKey, std::vector<GaugeAtom>>
|
||||
typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>>
|
||||
DimToGaugeAtomsMap;
|
||||
|
||||
// This gauge metric producer first register the puller to automatically pull the gauge at the
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
|
||||
protected:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) override;
|
||||
|
||||
@@ -99,7 +99,7 @@ private:
|
||||
|
||||
// Save the past buckets and we can clear when the StatsLogReport is dumped.
|
||||
// TODO: Add a lock to mPastBuckets.
|
||||
std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
|
||||
std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
|
||||
|
||||
// The current bucket.
|
||||
std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket;
|
||||
@@ -119,7 +119,7 @@ private:
|
||||
std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event);
|
||||
|
||||
// Util function to check whether the specified dimension hits the guardrail.
|
||||
bool hitGuardRailLocked(const HashableDimensionKey& newKey);
|
||||
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
|
||||
|
||||
static const size_t kBucketSize = sizeof(GaugeBucket{});
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
#include "MetricProducer.h"
|
||||
|
||||
#include "dimension.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
@@ -30,29 +32,51 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo
|
||||
|
||||
bool condition;
|
||||
ConditionKey conditionKey;
|
||||
|
||||
std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
|
||||
if (mConditionSliced) {
|
||||
for (const auto& link : mConditionLinks) {
|
||||
getDimensionKeysForCondition(event, link, &conditionKey[link.condition()]);
|
||||
}
|
||||
if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) {
|
||||
condition = false;
|
||||
} else {
|
||||
condition = true;
|
||||
}
|
||||
auto conditionState =
|
||||
mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
|
||||
&dimensionKeysInCondition);
|
||||
condition = (conditionState == ConditionState::kTrue);
|
||||
} else {
|
||||
condition = mCondition;
|
||||
}
|
||||
|
||||
if (mDimensions.has_field() && mDimensions.child_size() > 0) {
|
||||
vector<DimensionsValue> dimensionValues;
|
||||
getDimensionKeys(event, mDimensions, &dimensionValues);
|
||||
for (const DimensionsValue& dimensionValue : dimensionValues) {
|
||||
vector<DimensionsValue> dimensionInWhatValues;
|
||||
if (mDimensionsInWhat.has_field() && mDimensionsInWhat.child_size() > 0) {
|
||||
getDimensionKeys(event, mDimensionsInWhat, &dimensionInWhatValues);
|
||||
}
|
||||
|
||||
if (dimensionInWhatValues.empty() && dimensionKeysInCondition.empty()) {
|
||||
onMatchedLogEventInternalLocked(
|
||||
matcherIndex, DEFAULT_METRIC_DIMENSION_KEY, conditionKey, condition, event);
|
||||
} else if (dimensionKeysInCondition.empty()) {
|
||||
for (const DimensionsValue& whatValue : dimensionInWhatValues) {
|
||||
onMatchedLogEventInternalLocked(
|
||||
matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event);
|
||||
matcherIndex,
|
||||
MetricDimensionKey(HashableDimensionKey(whatValue), DEFAULT_DIMENSION_KEY),
|
||||
conditionKey, condition, event);
|
||||
}
|
||||
} else if (dimensionInWhatValues.empty()) {
|
||||
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
|
||||
onMatchedLogEventInternalLocked(
|
||||
matcherIndex,
|
||||
MetricDimensionKey(DEFAULT_DIMENSION_KEY, conditionDimensionKey),
|
||||
conditionKey, condition, event);
|
||||
}
|
||||
} else {
|
||||
onMatchedLogEventInternalLocked(
|
||||
matcherIndex, DEFAULT_DIMENSION_KEY, conditionKey, condition, event);
|
||||
for (const DimensionsValue& whatValue : dimensionInWhatValues) {
|
||||
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
|
||||
onMatchedLogEventInternalLocked(
|
||||
matcherIndex,
|
||||
MetricDimensionKey(HashableDimensionKey(whatValue), conditionDimensionKey),
|
||||
conditionKey, condition, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ public:
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return onDumpReportLocked(dumpTimeNs, protoOutput);
|
||||
}
|
||||
|
||||
void onDumpReport(const uint64_t dumpTimeNs, StatsLogReport* report) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return onDumpReportLocked(dumpTimeNs, report);
|
||||
@@ -156,7 +157,8 @@ protected:
|
||||
|
||||
int mConditionTrackerIndex;
|
||||
|
||||
FieldMatcher mDimensions; // The dimension defined in statsd_config
|
||||
FieldMatcher mDimensionsInWhat; // The dimensions_in_what defined in statsd_config
|
||||
FieldMatcher mDimensionsInCondition; // The dimensions_in_condition defined in statsd_config
|
||||
|
||||
std::vector<MetricConditionLink> mConditionLinks;
|
||||
|
||||
@@ -178,7 +180,7 @@ protected:
|
||||
* [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
|
||||
*/
|
||||
virtual void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) = 0;
|
||||
|
||||
|
||||
@@ -143,6 +143,10 @@ private:
|
||||
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
|
||||
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
|
||||
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -81,13 +81,15 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric
|
||||
}
|
||||
|
||||
mBucketSizeNs = bucketSizeMills * 1000000;
|
||||
mDimensions = metric.dimensions_in_what();
|
||||
mDimensionsInWhat = metric.dimensions_in_what();
|
||||
mDimensionsInCondition = metric.dimensions_in_condition();
|
||||
|
||||
if (metric.links().size() > 0) {
|
||||
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
|
||||
metric.links().end());
|
||||
mConditionSliced = true;
|
||||
}
|
||||
mConditionSliced = (metric.links().size() > 0)||
|
||||
(mDimensionsInCondition.has_field() && mDimensionsInCondition.child_size() > 0);
|
||||
|
||||
if (!metric.has_condition() && mPullTagId != -1) {
|
||||
VLOG("Setting up periodic pulling for %d", mPullTagId);
|
||||
@@ -124,7 +126,10 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLog
|
||||
auto value_metrics = report->mutable_value_metrics();
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
ValueMetricData* metricData = value_metrics->add_data();
|
||||
*metricData->mutable_dimensions_in_what() = pair.first.getDimensionsValue();
|
||||
*metricData->mutable_dimensions_in_what() =
|
||||
pair.first.getDimensionKeyInWhat().getDimensionsValue();
|
||||
*metricData->mutable_dimensions_in_condition() =
|
||||
pair.first.getDimensionKeyInCondition().getDimensionsValue();
|
||||
for (const auto& bucket : pair.second) {
|
||||
ValueBucketInfo* bucketInfo = metricData->add_bucket_info();
|
||||
bucketInfo->set_start_bucket_nanos(bucket.mBucketStartNs);
|
||||
@@ -146,16 +151,24 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
|
||||
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
|
||||
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
const HashableDimensionKey& hashableKey = pair.first;
|
||||
VLOG(" dimension key %s", hashableKey.c_str());
|
||||
const MetricDimensionKey& dimensionKey = pair.first;
|
||||
VLOG(" dimension key %s", dimensionKey.c_str());
|
||||
long long wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
|
||||
// First fill dimension.
|
||||
long long dimensionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
|
||||
writeDimensionsValueProtoToStream(hashableKey.getDimensionsValue(), protoOutput);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInWhat().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionToken);
|
||||
if (dimensionKey.hasDimensionKeyInCondition()) {
|
||||
long long dimensionInConditionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_CONDITION);
|
||||
writeDimensionsValueProtoToStream(
|
||||
dimensionKey.getDimensionKeyInCondition().getDimensionsValue(), protoOutput);
|
||||
protoOutput->end(dimensionInConditionToken);
|
||||
}
|
||||
|
||||
// Then fill bucket_info (ValueBucketInfo).
|
||||
for (const auto& bucket : pair.second) {
|
||||
@@ -239,7 +252,7 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven
|
||||
}
|
||||
}
|
||||
|
||||
bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) {
|
||||
bool ValueMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
|
||||
// ===========GuardRail==============
|
||||
// 1. Report the tuple count if the tuple count > soft limit
|
||||
if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) {
|
||||
@@ -260,7 +273,7 @@ bool ValueMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey)
|
||||
}
|
||||
|
||||
void ValueMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) {
|
||||
uint64_t eventTimeNs = event.GetTimestampNs();
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
|
||||
protected:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const HashableDimensionKey& eventKey,
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition,
|
||||
const LogEvent& event) override;
|
||||
|
||||
@@ -99,16 +99,16 @@ private:
|
||||
long sum;
|
||||
} Interval;
|
||||
|
||||
std::unordered_map<HashableDimensionKey, Interval> mCurrentSlicedBucket;
|
||||
std::unordered_map<MetricDimensionKey, Interval> mCurrentSlicedBucket;
|
||||
|
||||
// Save the past buckets and we can clear when the StatsLogReport is dumped.
|
||||
// TODO: Add a lock to mPastBuckets.
|
||||
std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets;
|
||||
std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
|
||||
|
||||
std::shared_ptr<FieldValueMap> getValueFields(const LogEvent& event);
|
||||
|
||||
// Util function to check whether the specified dimension hits the guardrail.
|
||||
bool hitGuardRailLocked(const HashableDimensionKey& newKey);
|
||||
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
|
||||
|
||||
static const size_t kBucketSize = sizeof(ValueBucket{});
|
||||
|
||||
|
||||
@@ -60,8 +60,9 @@ struct DurationBucket {
|
||||
|
||||
class DurationTracker {
|
||||
public:
|
||||
DurationTracker(const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
|
||||
DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex,
|
||||
const FieldMatcher& dimensionInCondition, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
|
||||
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
|
||||
: mConfigKey(key),
|
||||
@@ -70,6 +71,7 @@ public:
|
||||
mWizard(wizard),
|
||||
mConditionTrackerIndex(conditionIndex),
|
||||
mBucketSizeNs(bucketSizeNs),
|
||||
mDimensionInCondition(dimensionInCondition),
|
||||
mNested(nesting),
|
||||
mCurrentBucketStartTimeNs(currentBucketStartNs),
|
||||
mDuration(0),
|
||||
@@ -79,6 +81,8 @@ public:
|
||||
|
||||
virtual ~DurationTracker(){};
|
||||
|
||||
virtual unique_ptr<DurationTracker> clone(const uint64_t eventTime) = 0;
|
||||
|
||||
virtual void noteStart(const HashableDimensionKey& key, bool condition,
|
||||
const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
|
||||
virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
|
||||
@@ -92,7 +96,7 @@ public:
|
||||
// events, so that the owner can safely remove the tracker.
|
||||
virtual bool flushIfNeeded(
|
||||
uint64_t timestampNs,
|
||||
std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) = 0;
|
||||
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
|
||||
|
||||
// Predict the anomaly timestamp given the current status.
|
||||
virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
|
||||
@@ -100,6 +104,10 @@ public:
|
||||
// Dump internal states for debugging
|
||||
virtual void dumpStates(FILE* out, bool verbose) const = 0;
|
||||
|
||||
void setEventKey(const MetricDimensionKey& eventKey) {
|
||||
mEventKey = eventKey;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Starts the anomaly alarm.
|
||||
void startAnomalyAlarm(const uint64_t eventTime) {
|
||||
@@ -150,7 +158,7 @@ protected:
|
||||
|
||||
const int64_t mTrackerId;
|
||||
|
||||
HashableDimensionKey mEventKey;
|
||||
MetricDimensionKey mEventKey;
|
||||
|
||||
sp<ConditionWizard> mWizard;
|
||||
|
||||
@@ -158,6 +166,8 @@ protected:
|
||||
|
||||
const int64_t mBucketSizeNs;
|
||||
|
||||
const FieldMatcher mDimensionInCondition;
|
||||
|
||||
const bool mNested;
|
||||
|
||||
uint64_t mCurrentBucketStartTimeNs;
|
||||
|
||||
@@ -25,13 +25,23 @@ namespace os {
|
||||
namespace statsd {
|
||||
|
||||
MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
|
||||
const HashableDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
|
||||
const MetricDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex,
|
||||
const FieldMatcher& dimensionInCondition, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
|
||||
bool conditionSliced,
|
||||
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
|
||||
: DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
|
||||
bucketSizeNs, conditionSliced, anomalyTrackers) {
|
||||
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
|
||||
currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers) {
|
||||
}
|
||||
|
||||
unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) {
|
||||
auto clonedTracker = make_unique<MaxDurationTracker>(*this);
|
||||
for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end(); ++it) {
|
||||
it->second.lastStartTime = eventTime;
|
||||
it->second.lastDuration = 0;
|
||||
}
|
||||
return clonedTracker;
|
||||
}
|
||||
|
||||
bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
|
||||
@@ -44,7 +54,7 @@ bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
|
||||
if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
|
||||
size_t newTupleCount = mInfos.size() + 1;
|
||||
StatsdStats::getInstance().noteMetricDimensionSize(
|
||||
mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
|
||||
mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey),
|
||||
newTupleCount);
|
||||
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
|
||||
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
|
||||
@@ -149,7 +159,7 @@ void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
|
||||
}
|
||||
|
||||
bool MaxDurationTracker::flushIfNeeded(
|
||||
uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
|
||||
uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
|
||||
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
|
||||
return false;
|
||||
}
|
||||
@@ -236,8 +246,14 @@ void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
|
||||
if (pair.second.state == kStopped) {
|
||||
continue;
|
||||
}
|
||||
bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
|
||||
ConditionState::kTrue;
|
||||
std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
|
||||
ConditionState conditionState = mWizard->query(
|
||||
mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition,
|
||||
&conditionDimensionKeySet);
|
||||
bool conditionMet = (conditionState == ConditionState::kTrue) &&
|
||||
(!mDimensionInCondition.has_field() ||
|
||||
conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) !=
|
||||
conditionDimensionKeySet.end());
|
||||
VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
|
||||
noteConditionChanged(pair.first, conditionMet, timestamp);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,15 @@ namespace statsd {
|
||||
class MaxDurationTracker : public DurationTracker {
|
||||
public:
|
||||
MaxDurationTracker(const ConfigKey& key, const int64_t& id,
|
||||
const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
|
||||
int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
|
||||
uint64_t bucketSizeNs, bool conditionSliced,
|
||||
const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
|
||||
int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
|
||||
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
|
||||
|
||||
MaxDurationTracker(const MaxDurationTracker& tracker) = default;
|
||||
|
||||
unique_ptr<DurationTracker> clone(const uint64_t eventTime) override;
|
||||
|
||||
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
|
||||
const ConditionKey& conditionKey) override;
|
||||
void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
|
||||
@@ -41,7 +46,7 @@ public:
|
||||
|
||||
bool flushIfNeeded(
|
||||
uint64_t timestampNs,
|
||||
std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override;
|
||||
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
|
||||
|
||||
void onSlicedConditionMayChange(const uint64_t timestamp) override;
|
||||
void onConditionChanged(bool condition, const uint64_t timestamp) override;
|
||||
|
||||
@@ -25,17 +25,25 @@ namespace statsd {
|
||||
using std::pair;
|
||||
|
||||
OringDurationTracker::OringDurationTracker(
|
||||
const ConfigKey& key, const int64_t& id, const HashableDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
|
||||
const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex,
|
||||
const FieldMatcher& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs,
|
||||
uint64_t bucketSizeNs, bool conditionSliced,
|
||||
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
|
||||
: DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
|
||||
bucketSizeNs, conditionSliced, anomalyTrackers),
|
||||
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
|
||||
currentBucketStartNs, bucketSizeNs, conditionSliced, anomalyTrackers),
|
||||
mStarted(),
|
||||
mPaused() {
|
||||
mLastStartTime = 0;
|
||||
}
|
||||
|
||||
unique_ptr<DurationTracker> OringDurationTracker::clone(const uint64_t eventTime) {
|
||||
auto clonedTracker = make_unique<OringDurationTracker>(*this);
|
||||
clonedTracker->mLastStartTime = eventTime;
|
||||
clonedTracker->mDuration = 0;
|
||||
return clonedTracker;
|
||||
}
|
||||
|
||||
bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
|
||||
// ===========GuardRail==============
|
||||
// 1. Report the tuple count if the tuple count > soft limit
|
||||
@@ -45,7 +53,7 @@ bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
|
||||
if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
|
||||
size_t newTupleCount = mConditionKeyMap.size() + 1;
|
||||
StatsdStats::getInstance().noteMetricDimensionSize(
|
||||
mConfigKey, hashDimensionsValue(mTrackerId, mEventKey.getDimensionsValue()),
|
||||
mConfigKey, hashMetricDimensionKey(mTrackerId, mEventKey),
|
||||
newTupleCount);
|
||||
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
|
||||
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
|
||||
@@ -76,7 +84,6 @@ void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condi
|
||||
if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
|
||||
mConditionKeyMap[key] = conditionKey;
|
||||
}
|
||||
|
||||
VLOG("Oring: %s start, condition %d", key.c_str(), condition);
|
||||
}
|
||||
|
||||
@@ -128,7 +135,7 @@ void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
|
||||
}
|
||||
|
||||
bool OringDurationTracker::flushIfNeeded(
|
||||
uint64_t eventTime, unordered_map<HashableDimensionKey, vector<DurationBucket>>* output) {
|
||||
uint64_t eventTime, unordered_map<MetricDimensionKey, vector<DurationBucket>>* output) {
|
||||
if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
|
||||
return false;
|
||||
}
|
||||
@@ -184,8 +191,14 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp)
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
|
||||
ConditionState::kTrue) {
|
||||
std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
|
||||
ConditionState conditionState =
|
||||
mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
|
||||
mDimensionInCondition, &conditionDimensionKeySet);
|
||||
if (conditionState != ConditionState::kTrue ||
|
||||
(mDimensionInCondition.has_field() &&
|
||||
conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) ==
|
||||
conditionDimensionKeySet.end())) {
|
||||
startedToPaused.push_back(*it);
|
||||
it = mStarted.erase(it);
|
||||
VLOG("Key %s started -> paused", key.c_str());
|
||||
@@ -210,8 +223,14 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp)
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
|
||||
ConditionState::kTrue) {
|
||||
std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
|
||||
ConditionState conditionState =
|
||||
mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
|
||||
mDimensionInCondition, &conditionDimensionKeySet);
|
||||
if (conditionState == ConditionState::kTrue &&
|
||||
(!mDimensionInCondition.has_field() ||
|
||||
conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition())
|
||||
!= conditionDimensionKeySet.end())) {
|
||||
pausedToStarted.push_back(*it);
|
||||
it = mPaused.erase(it);
|
||||
VLOG("Key %s paused -> started", key.c_str());
|
||||
|
||||
@@ -28,11 +28,15 @@ namespace statsd {
|
||||
class OringDurationTracker : public DurationTracker {
|
||||
public:
|
||||
OringDurationTracker(const ConfigKey& key, const int64_t& id,
|
||||
const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
|
||||
int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
|
||||
uint64_t bucketSizeNs, bool conditionSliced,
|
||||
const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
|
||||
int conditionIndex, const FieldMatcher& dimensionInCondition, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs, bool conditionSliced,
|
||||
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
|
||||
|
||||
OringDurationTracker(const OringDurationTracker& tracker) = default;
|
||||
|
||||
unique_ptr<DurationTracker> clone(const uint64_t eventTime) override;
|
||||
|
||||
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
|
||||
const ConditionKey& conditionKey) override;
|
||||
void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
|
||||
@@ -44,7 +48,7 @@ public:
|
||||
|
||||
bool flushIfNeeded(
|
||||
uint64_t timestampNs,
|
||||
std::unordered_map<HashableDimensionKey, std::vector<DurationBucket>>* output) override;
|
||||
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
|
||||
|
||||
int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
|
||||
const uint64_t currentTimestamp) const override;
|
||||
|
||||
@@ -54,6 +54,9 @@ const int FIELD_ID_MIN_PULL_INTERVAL_SEC = 4;
|
||||
|
||||
void writeDimensionsValueProtoToStream(const DimensionsValue& dimensionsValue,
|
||||
ProtoOutputStream* protoOutput) {
|
||||
if (!dimensionsValue.has_field()) {
|
||||
return;
|
||||
}
|
||||
protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, dimensionsValue.field());
|
||||
switch (dimensionsValue.value_case()) {
|
||||
case DimensionsValue::ValueCase::kValueStr:
|
||||
@@ -103,6 +106,9 @@ const int FIELD_CHILD = 3;
|
||||
|
||||
void writeFieldProtoToStream(
|
||||
const Field& field, util::ProtoOutputStream* protoOutput) {
|
||||
if (!field.has_field()) {
|
||||
return;
|
||||
}
|
||||
protoOutput->write(FIELD_TYPE_INT32 | FIELD_FIELD, field.field());
|
||||
if (field.has_position_index()) {
|
||||
protoOutput->write(FIELD_TYPE_INT32 | FIELD_POSITION_INDEX, field.position_index());
|
||||
|
||||
@@ -28,13 +28,14 @@ namespace os {
|
||||
namespace statsd {
|
||||
|
||||
const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey();
|
||||
const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey();
|
||||
|
||||
// Minimum bucket size in seconds
|
||||
const long kMinBucketSizeSec = 5 * 60;
|
||||
|
||||
typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey;
|
||||
|
||||
typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
|
||||
typedef std::unordered_map<MetricDimensionKey, int64_t> DimToValMap;
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
|
||||
@@ -56,7 +56,7 @@ void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
|
||||
|
||||
void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
|
||||
const Subscription& subscription,
|
||||
const HashableDimensionKey& dimKey) const {
|
||||
const MetricDimensionKey& dimKey) const {
|
||||
// Reminder about ids:
|
||||
// subscription id - name of the Subscription (that ties the Alert to the broadcast)
|
||||
// subscription rule_id - the name of the Alert (that triggers the broadcast)
|
||||
@@ -92,7 +92,7 @@ void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
|
||||
void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
|
||||
const ConfigKey& configKey,
|
||||
const Subscription& subscription,
|
||||
const HashableDimensionKey& dimKey) const {
|
||||
const MetricDimensionKey& dimKey) const {
|
||||
VLOG("SubscriberReporter::sendBroadcastLocked called.");
|
||||
if (mStatsCompanionService == nullptr) {
|
||||
ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService.");
|
||||
@@ -107,8 +107,8 @@ void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
|
||||
}
|
||||
|
||||
StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
|
||||
const HashableDimensionKey& dimKey) {
|
||||
return protoToStatsDimensionsValue(dimKey.getDimensionsValue());
|
||||
const MetricDimensionKey& dimKey) {
|
||||
return protoToStatsDimensionsValue(dimKey.getDimensionKeyInWhat().getDimensionsValue());
|
||||
}
|
||||
|
||||
StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
*/
|
||||
void alertBroadcastSubscriber(const ConfigKey& configKey,
|
||||
const Subscription& subscription,
|
||||
const HashableDimensionKey& dimKey) const;
|
||||
const MetricDimensionKey& dimKey) const;
|
||||
|
||||
private:
|
||||
SubscriberReporter() {};
|
||||
@@ -101,7 +101,7 @@ private:
|
||||
void sendBroadcastLocked(const sp<android::IBinder>& intentSender,
|
||||
const ConfigKey& configKey,
|
||||
const Subscription& subscription,
|
||||
const HashableDimensionKey& dimKey) const;
|
||||
const MetricDimensionKey& dimKey) const;
|
||||
|
||||
/** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */
|
||||
static StatsDimensionsValue protoToStatsDimensionsValue(
|
||||
@@ -109,7 +109,7 @@ private:
|
||||
|
||||
/** Converts a HashableDimensionKey to a StatsDimensionsValue. */
|
||||
static StatsDimensionsValue protoToStatsDimensionsValue(
|
||||
const HashableDimensionKey& dimKey);
|
||||
const MetricDimensionKey& dimKey);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -33,14 +33,14 @@ namespace statsd {
|
||||
|
||||
const ConfigKey kConfigKey(0, 12345);
|
||||
|
||||
HashableDimensionKey getMockDimensionKey(int key, string value) {
|
||||
MetricDimensionKey getMockMetricDimensionKey(int key, string value) {
|
||||
DimensionsValue dimensionsValue;
|
||||
dimensionsValue.set_field(key);
|
||||
dimensionsValue.set_value_str(value);
|
||||
return HashableDimensionKey(dimensionsValue);
|
||||
return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY);
|
||||
}
|
||||
|
||||
void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list,
|
||||
void AddValueToBucket(const std::vector<std::pair<MetricDimensionKey, long>>& key_value_pair_list,
|
||||
std::shared_ptr<DimToValMap> bucket) {
|
||||
for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) {
|
||||
(*bucket)[itr->first] += itr->second;
|
||||
@@ -48,7 +48,7 @@ void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, long>>&
|
||||
}
|
||||
|
||||
std::shared_ptr<DimToValMap> MockBucket(
|
||||
const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list) {
|
||||
const std::vector<std::pair<MetricDimensionKey, long>>& key_value_pair_list) {
|
||||
std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
|
||||
AddValueToBucket(key_value_pair_list, bucket);
|
||||
return bucket;
|
||||
@@ -56,7 +56,7 @@ std::shared_ptr<DimToValMap> MockBucket(
|
||||
|
||||
// Returns the value, for the given key, in that bucket, or 0 if not present.
|
||||
int64_t getBucketValue(const std::shared_ptr<DimToValMap>& bucket,
|
||||
const HashableDimensionKey& key) {
|
||||
const MetricDimensionKey& key) {
|
||||
const auto& itr = bucket->find(key);
|
||||
if (itr != bucket->end()) {
|
||||
return itr->second;
|
||||
@@ -68,14 +68,14 @@ int64_t getBucketValue(const std::shared_ptr<DimToValMap>& bucket,
|
||||
bool detectAnomaliesPass(AnomalyTracker& tracker,
|
||||
const int64_t& bucketNum,
|
||||
const std::shared_ptr<DimToValMap>& currentBucket,
|
||||
const std::set<const HashableDimensionKey>& trueList,
|
||||
const std::set<const HashableDimensionKey>& falseList) {
|
||||
for (HashableDimensionKey key : trueList) {
|
||||
const std::set<const MetricDimensionKey>& trueList,
|
||||
const std::set<const MetricDimensionKey>& falseList) {
|
||||
for (MetricDimensionKey key : trueList) {
|
||||
if (!tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (HashableDimensionKey key : falseList) {
|
||||
for (MetricDimensionKey key : falseList) {
|
||||
if (tracker.detectAnomaly(bucketNum, key, getBucketValue(currentBucket, key))) {
|
||||
return false;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ void detectAndDeclareAnomalies(AnomalyTracker& tracker,
|
||||
void checkRefractoryTimes(AnomalyTracker& tracker,
|
||||
const int64_t& currTimestampNs,
|
||||
const int32_t& refractoryPeriodSec,
|
||||
const std::unordered_map<HashableDimensionKey, int64_t>& timestamps) {
|
||||
const std::unordered_map<MetricDimensionKey, int64_t>& timestamps) {
|
||||
for (const auto& kv : timestamps) {
|
||||
if (kv.second < 0) {
|
||||
// Make sure that, if there is a refractory period, it is already past.
|
||||
@@ -124,9 +124,9 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
|
||||
alert.set_trigger_if_sum_gt(2);
|
||||
|
||||
AnomalyTracker anomalyTracker(alert, kConfigKey);
|
||||
HashableDimensionKey keyA = getMockDimensionKey(1, "a");
|
||||
HashableDimensionKey keyB = getMockDimensionKey(1, "b");
|
||||
HashableDimensionKey keyC = getMockDimensionKey(1, "c");
|
||||
MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a");
|
||||
MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b");
|
||||
MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c");
|
||||
|
||||
int64_t eventTimestamp0 = 10 * NS_PER_SEC;
|
||||
int64_t eventTimestamp1 = bucketSizeNs + 11 * NS_PER_SEC;
|
||||
@@ -269,11 +269,11 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) {
|
||||
alert.set_trigger_if_sum_gt(2);
|
||||
|
||||
AnomalyTracker anomalyTracker(alert, kConfigKey);
|
||||
HashableDimensionKey keyA = getMockDimensionKey(1, "a");
|
||||
HashableDimensionKey keyB = getMockDimensionKey(1, "b");
|
||||
HashableDimensionKey keyC = getMockDimensionKey(1, "c");
|
||||
HashableDimensionKey keyD = getMockDimensionKey(1, "d");
|
||||
HashableDimensionKey keyE = getMockDimensionKey(1, "e");
|
||||
MetricDimensionKey keyA = getMockMetricDimensionKey(1, "a");
|
||||
MetricDimensionKey keyB = getMockMetricDimensionKey(1, "b");
|
||||
MetricDimensionKey keyC = getMockMetricDimensionKey(1, "c");
|
||||
MetricDimensionKey keyD = getMockMetricDimensionKey(1, "d");
|
||||
MetricDimensionKey keyE = getMockMetricDimensionKey(1, "e");
|
||||
|
||||
std::shared_ptr<DimToValMap> bucket9 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
|
||||
std::shared_ptr<DimToValMap> bucket16 = MockBucket({{keyB, 4}});
|
||||
|
||||
@@ -78,7 +78,7 @@ void makeWakeLockEvent(
|
||||
std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey(
|
||||
const Position position,
|
||||
const std::vector<int> &uids, const string& conditionName) {
|
||||
std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap;
|
||||
std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap;
|
||||
std::vector<int> uid_indexes;
|
||||
switch(position) {
|
||||
case Position::FIRST:
|
||||
@@ -265,6 +265,9 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
|
||||
TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
|
||||
for (Position position :
|
||||
{ Position::ANY, Position::FIRST, Position::LAST}) {
|
||||
FieldMatcher dimensionInCondition;
|
||||
std::unordered_set<HashableDimensionKey> dimensionKeys;
|
||||
|
||||
SimplePredicate simplePredicate = getWakeLockHeldCondition(
|
||||
true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
|
||||
position);
|
||||
@@ -307,7 +310,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
|
||||
const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
|
||||
|
||||
// another wake lock acquired by this uid
|
||||
@@ -361,7 +365,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
|
||||
|
||||
// query again
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
|
||||
|
||||
}
|
||||
@@ -369,6 +374,9 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
|
||||
}
|
||||
|
||||
TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
|
||||
FieldMatcher dimensionInCondition;
|
||||
std::unordered_set<HashableDimensionKey> dimensionKeys;
|
||||
|
||||
SimplePredicate simplePredicate = getWakeLockHeldCondition(
|
||||
true /*nesting*/, true /*default to false*/, false /*slice output by uid*/,
|
||||
Position::ANY /* position */);
|
||||
@@ -410,7 +418,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
|
||||
ConditionKey queryKey;
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
|
||||
|
||||
// another wake lock acquired by this uid
|
||||
@@ -452,13 +461,17 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
|
||||
|
||||
// query again
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
dimensionKeys.clear();
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
|
||||
}
|
||||
|
||||
TEST(SimpleConditionTrackerTest, TestStopAll) {
|
||||
for (Position position :
|
||||
{Position::ANY, Position::FIRST, Position::LAST}) {
|
||||
FieldMatcher dimensionInCondition;
|
||||
std::unordered_set<HashableDimensionKey> dimensionKeys;
|
||||
SimplePredicate simplePredicate = getWakeLockHeldCondition(
|
||||
true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
|
||||
position);
|
||||
@@ -502,7 +515,8 @@ TEST(SimpleConditionTrackerTest, TestStopAll) {
|
||||
const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName);
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
|
||||
|
||||
// another wake lock acquired by uid2
|
||||
@@ -528,8 +542,9 @@ TEST(SimpleConditionTrackerTest, TestStopAll) {
|
||||
// TEST QUERY
|
||||
const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName);
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
|
||||
|
||||
|
||||
@@ -550,15 +565,15 @@ TEST(SimpleConditionTrackerTest, TestStopAll) {
|
||||
// TEST QUERY
|
||||
const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName);
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
|
||||
|
||||
// TEST QUERY
|
||||
const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName);
|
||||
conditionCache[0] = ConditionState::kNotEvaluated;
|
||||
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, conditionCache);
|
||||
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
|
||||
conditionCache, dimensionKeys);
|
||||
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
|
||||
}
|
||||
|
||||
|
||||
149
cmds/statsd/tests/dimension_test.cpp
Normal file
149
cmds/statsd/tests/dimension_test.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright (C) 2017 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 "dimension.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace android::os::statsd;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
TEST(DimensionTest, subLeafNodes) {
|
||||
DimensionsValue dimension;
|
||||
int tagId = 100;
|
||||
dimension.set_field(tagId);
|
||||
auto child = dimension.mutable_value_tuple()->add_dimensions_value();
|
||||
child->set_field(1);
|
||||
child->set_value_int(2000);
|
||||
|
||||
child = dimension.mutable_value_tuple()->add_dimensions_value();
|
||||
child->set_field(3);
|
||||
child->set_value_str("test");
|
||||
|
||||
child = dimension.mutable_value_tuple()->add_dimensions_value();
|
||||
child->set_field(4);
|
||||
auto grandChild = child->mutable_value_tuple()->add_dimensions_value();
|
||||
grandChild->set_field(1);
|
||||
grandChild->set_value_float(1.3f);
|
||||
grandChild = child->mutable_value_tuple()->add_dimensions_value();
|
||||
grandChild->set_field(3);
|
||||
grandChild->set_value_str("tag");
|
||||
|
||||
child = dimension.mutable_value_tuple()->add_dimensions_value();
|
||||
child->set_field(6);
|
||||
child->set_value_bool(false);
|
||||
|
||||
DimensionsValue sub_dimension;
|
||||
FieldMatcher matcher;
|
||||
|
||||
// Tag id not matched.
|
||||
matcher.set_field(tagId + 1);
|
||||
EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Field not exist.
|
||||
matcher.Clear();
|
||||
matcher.set_field(tagId);
|
||||
matcher.add_child()->set_field(5);
|
||||
EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Field exists.
|
||||
matcher.Clear();
|
||||
matcher.set_field(tagId);
|
||||
matcher.add_child()->set_field(3);
|
||||
EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Field exists.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
matcher.add_child()->set_field(6);
|
||||
EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Field exists.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
matcher.add_child()->set_field(1);
|
||||
EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Not leaf field.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
matcher.add_child()->set_field(4);
|
||||
EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Grand-child leaf field not exist.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
auto childMatcher = matcher.add_child();
|
||||
childMatcher->set_field(4);
|
||||
childMatcher->add_child()->set_field(2);
|
||||
EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Grand-child leaf field.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
childMatcher = matcher.add_child();
|
||||
childMatcher->set_field(4);
|
||||
childMatcher->add_child()->set_field(1);
|
||||
EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
childMatcher = matcher.add_child();
|
||||
childMatcher->set_field(4);
|
||||
childMatcher->add_child()->set_field(3);
|
||||
EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Multiple grand-child fields.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
childMatcher = matcher.add_child();
|
||||
childMatcher->set_field(4);
|
||||
childMatcher->add_child()->set_field(3);
|
||||
childMatcher->add_child()->set_field(1);
|
||||
EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Multiple fields.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
childMatcher = matcher.add_child();
|
||||
childMatcher->set_field(4);
|
||||
childMatcher->add_child()->set_field(3);
|
||||
childMatcher->add_child()->set_field(1);
|
||||
matcher.add_child()->set_field(3);
|
||||
EXPECT_TRUE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
|
||||
// Subset of the fields not exist.
|
||||
matcher.Clear();
|
||||
sub_dimension.Clear();
|
||||
matcher.set_field(tagId);
|
||||
childMatcher = matcher.add_child();
|
||||
childMatcher->set_field(4);
|
||||
childMatcher->add_child()->set_field(3);
|
||||
childMatcher->add_child()->set_field(1);
|
||||
matcher.add_child()->set_field(2);
|
||||
EXPECT_FALSE(getSubDimension(dimension, matcher, &sub_dimension));
|
||||
}
|
||||
|
||||
#else
|
||||
GTEST_LOG_(INFO) << "This test does nothing.\n";
|
||||
#endif
|
||||
725
cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
Normal file
725
cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
Normal file
@@ -0,0 +1,725 @@
|
||||
// Copyright (C) 2017 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 <gtest/gtest.h>
|
||||
|
||||
#include "src/StatsLogProcessor.h"
|
||||
#include "src/stats_log_util.h"
|
||||
#include "tests/statsd_test_util.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
namespace {
|
||||
|
||||
StatsdConfig CreateCountMetricWithNoLinkConfig() {
|
||||
StatsdConfig config;
|
||||
auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher();
|
||||
*config.add_atom_matcher() = screenBrightnessChangeAtomMatcher;
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
|
||||
|
||||
auto screenIsOffPredicate = CreateScreenIsOffPredicate();
|
||||
*config.add_predicate() = screenIsOffPredicate;
|
||||
|
||||
auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
|
||||
// The predicate is dimensioning by any attribution node and both by uid and tag.
|
||||
*holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
|
||||
CreateAttributionUidAndTagDimensions(
|
||||
android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
|
||||
*config.add_predicate() = holdingWakelockPredicate;
|
||||
|
||||
auto combinationPredicate = config.add_predicate();
|
||||
combinationPredicate->set_id(987654);
|
||||
combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
|
||||
addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
|
||||
addPredicateToPredicateCombination(holdingWakelockPredicate, combinationPredicate);
|
||||
|
||||
auto metric = config.add_count_metric();
|
||||
metric->set_id(StringToId("ScreenBrightnessChangeMetric"));
|
||||
metric->set_what(screenBrightnessChangeAtomMatcher.id());
|
||||
metric->set_condition(combinationPredicate->id());
|
||||
*metric->mutable_dimensions_in_what() = CreateDimensions(
|
||||
android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */});
|
||||
*metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions(
|
||||
android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
|
||||
metric->set_bucket(ONE_MINUTE);
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(DimensionInConditionE2eTest, TestCountMetricNoLink) {
|
||||
ConfigKey cfgKey;
|
||||
auto config = CreateCountMetricWithNoLinkConfig();
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs =
|
||||
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
|
||||
|
||||
auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
|
||||
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
|
||||
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
|
||||
|
||||
std::vector<AttributionNode> attributions1 =
|
||||
{CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(222, "GMSCoreModule2")};
|
||||
|
||||
std::vector<AttributionNode> attributions2 =
|
||||
{CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(555, "GMSCoreModule2")};
|
||||
|
||||
std::vector<std::unique_ptr<LogEvent>> events;
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + bucketSizeNs + 1));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs - 10));
|
||||
|
||||
events.push_back(CreateAcquireWakelockEvent(
|
||||
attributions1, "wl1", bucketStartTimeNs + 200));
|
||||
events.push_back(CreateReleaseWakelockEvent(
|
||||
attributions1, "wl1", bucketStartTimeNs + bucketSizeNs + 1));
|
||||
|
||||
events.push_back(CreateAcquireWakelockEvent(
|
||||
attributions2, "wl2", bucketStartTimeNs + bucketSizeNs - 100));
|
||||
events.push_back(CreateReleaseWakelockEvent(
|
||||
attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 50));
|
||||
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
123, bucketStartTimeNs + 11));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
123, bucketStartTimeNs + 101));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
123, bucketStartTimeNs + 201));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
456, bucketStartTimeNs + 203));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
456, bucketStartTimeNs + bucketSizeNs - 99));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
456, bucketStartTimeNs + bucketSizeNs - 2));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
789, bucketStartTimeNs + bucketSizeNs - 1));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
456, bucketStartTimeNs + bucketSizeNs + 2));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
789, bucketStartTimeNs + 2 * bucketSizeNs - 11));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
789, bucketStartTimeNs + 2 * bucketSizeNs - 9));
|
||||
events.push_back(CreateScreenBrightnessChangedEvent(
|
||||
789, bucketStartTimeNs + 2 * bucketSizeNs - 1));
|
||||
|
||||
sortLogEventsByTimestamp(&events);
|
||||
|
||||
for (const auto& event : events) {
|
||||
processor->OnLogEvent(event.get());
|
||||
}
|
||||
|
||||
ConfigMetricsReportList reports;
|
||||
processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
|
||||
|
||||
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(), 7);
|
||||
auto data = countMetrics.data(0);
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs );
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123);
|
||||
EXPECT_FALSE(data.dimensions_in_condition().has_field());
|
||||
|
||||
data = countMetrics.data(1);
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 123);
|
||||
ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111);
|
||||
|
||||
data = countMetrics.data(2);
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 3);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456);
|
||||
ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111);
|
||||
|
||||
data = countMetrics.data(3);
|
||||
EXPECT_EQ(data.bucket_info_size(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 456);
|
||||
ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333);
|
||||
|
||||
data = countMetrics.data(4);
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
|
||||
EXPECT_FALSE(data.dimensions_in_condition().has_field());
|
||||
|
||||
data = countMetrics.data(5);
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
|
||||
ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 111);
|
||||
|
||||
data = countMetrics.data(6);
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 789);
|
||||
ValidateAttributionUidDimension(data.dimensions_in_condition(), android::util::WAKELOCK_STATE_CHANGED, 333);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
StatsdConfig CreateCountMetricWithLinkConfig() {
|
||||
StatsdConfig config;
|
||||
auto appCrashMatcher = CreateProcessCrashAtomMatcher();
|
||||
*config.add_atom_matcher() = appCrashMatcher;
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncStartAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncEndAtomMatcher();
|
||||
|
||||
auto screenIsOffPredicate = CreateScreenIsOffPredicate();
|
||||
auto isSyncingPredicate = CreateIsSyncingPredicate();
|
||||
auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
*syncDimension = CreateAttributionUidAndTagDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
syncDimension->add_child()->set_field(2 /* name field*/);
|
||||
|
||||
*config.add_predicate() = screenIsOffPredicate;
|
||||
*config.add_predicate() = isSyncingPredicate;
|
||||
auto combinationPredicate = config.add_predicate();
|
||||
combinationPredicate->set_id(987654);
|
||||
combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
|
||||
addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
|
||||
addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
|
||||
|
||||
auto metric = config.add_count_metric();
|
||||
metric->set_bucket(ONE_MINUTE);
|
||||
metric->set_id(StringToId("AppCrashMetric"));
|
||||
metric->set_what(appCrashMatcher.id());
|
||||
metric->set_condition(combinationPredicate->id());
|
||||
*metric->mutable_dimensions_in_what() = CreateDimensions(
|
||||
android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1 /* uid */});
|
||||
*metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
|
||||
// Links between crash atom and condition of app is in syncing.
|
||||
auto links = metric->add_links();
|
||||
links->set_condition(isSyncingPredicate.id());
|
||||
auto dimensionWhat = links->mutable_fields_in_what();
|
||||
dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
dimensionWhat->add_child()->set_field(1); // uid field.
|
||||
*links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(DimensionInConditionE2eTest, TestCountMetricWithLink) {
|
||||
ConfigKey cfgKey;
|
||||
auto config = CreateCountMetricWithLinkConfig();
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs =
|
||||
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
|
||||
|
||||
auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
|
||||
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
|
||||
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
|
||||
std::vector<AttributionNode> attributions1 =
|
||||
{CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(222, "GMSCoreModule2")};
|
||||
|
||||
std::vector<AttributionNode> attributions2 =
|
||||
{CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(555, "GMSCoreModule2")};
|
||||
|
||||
std::vector<std::unique_ptr<LogEvent>> events;
|
||||
|
||||
events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 11));
|
||||
events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 101));
|
||||
events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 101));
|
||||
|
||||
events.push_back(CreateAppCrashEvent(222, bucketStartTimeNs + 201));
|
||||
events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 211));
|
||||
events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 211));
|
||||
|
||||
events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + 401));
|
||||
events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + 401));
|
||||
events.push_back(CreateAppCrashEvent(555, bucketStartTimeNs + 401));
|
||||
|
||||
events.push_back(CreateAppCrashEvent(111, bucketStartTimeNs + bucketSizeNs + 301));
|
||||
events.push_back(CreateAppCrashEvent(333, bucketStartTimeNs + bucketSizeNs + 301));
|
||||
|
||||
events.push_back(CreateAppCrashEvent(777, bucketStartTimeNs + bucketSizeNs + 701));
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 700));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
|
||||
events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
|
||||
bucketStartTimeNs + bucketSizeNs + 300));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
|
||||
events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
|
||||
bucketStartTimeNs + bucketSizeNs - 1));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 400));
|
||||
events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
|
||||
bucketStartTimeNs + bucketSizeNs + 600));
|
||||
|
||||
sortLogEventsByTimestamp(&events);
|
||||
|
||||
for (const auto& event : events) {
|
||||
processor->OnLogEvent(event.get());
|
||||
}
|
||||
|
||||
ConfigMetricsReportList reports;
|
||||
processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
|
||||
|
||||
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(), 5);
|
||||
auto data = countMetrics.data(0);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
|
||||
EXPECT_FALSE(data.dimensions_in_condition().has_field());
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
|
||||
data = countMetrics.data(1);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
|
||||
ValidateAttributionUidAndTagDimension(
|
||||
data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1");
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
|
||||
data = countMetrics.data(2);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 222);
|
||||
EXPECT_FALSE(data.dimensions_in_condition().has_field());
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
|
||||
data = countMetrics.data(3);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333);
|
||||
ValidateAttributionUidAndTagDimension(
|
||||
data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2");
|
||||
EXPECT_EQ(data.bucket_info_size(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
|
||||
data = countMetrics.data(4);
|
||||
EXPECT_EQ(data.dimensions_in_what().field(), android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 777);
|
||||
EXPECT_FALSE(data.dimensions_in_condition().has_field());
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).count(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType aggregationType) {
|
||||
StatsdConfig config;
|
||||
*config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncStartAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncEndAtomMatcher();
|
||||
|
||||
auto inBatterySaverModePredicate = CreateBatterySaverModePredicate();
|
||||
|
||||
auto screenIsOffPredicate = CreateScreenIsOffPredicate();
|
||||
auto isSyncingPredicate = CreateIsSyncingPredicate();
|
||||
auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
*syncDimension = CreateAttributionUidAndTagDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
syncDimension->add_child()->set_field(2 /* name field */);
|
||||
|
||||
*config.add_predicate() = inBatterySaverModePredicate;
|
||||
*config.add_predicate() = screenIsOffPredicate;
|
||||
*config.add_predicate() = isSyncingPredicate;
|
||||
auto combinationPredicate = config.add_predicate();
|
||||
combinationPredicate->set_id(987654);
|
||||
combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
|
||||
addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
|
||||
addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
|
||||
|
||||
auto metric = config.add_duration_metric();
|
||||
metric->set_bucket(ONE_MINUTE);
|
||||
metric->set_id(StringToId("BatterySaverModeDurationMetric"));
|
||||
metric->set_what(inBatterySaverModePredicate.id());
|
||||
metric->set_condition(combinationPredicate->id());
|
||||
*metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink) {
|
||||
for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
|
||||
ConfigKey cfgKey;
|
||||
auto config = CreateDurationMetricConfigNoLink(aggregationType);
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs =
|
||||
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
|
||||
|
||||
auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
|
||||
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
|
||||
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
|
||||
|
||||
std::vector<AttributionNode> attributions1 =
|
||||
{CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(222, "GMSCoreModule2")};
|
||||
|
||||
std::vector<AttributionNode> attributions2 =
|
||||
{CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(555, "GMSCoreModule2")};
|
||||
|
||||
std::vector<std::unique_ptr<LogEvent>> events;
|
||||
|
||||
events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 1));
|
||||
events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 101));
|
||||
events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 110));
|
||||
|
||||
events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 201));
|
||||
events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 500));
|
||||
|
||||
events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 600));
|
||||
events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 850));
|
||||
|
||||
events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + bucketSizeNs + 870));
|
||||
events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + bucketSizeNs + 900));
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 800));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
|
||||
events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
|
||||
bucketStartTimeNs + bucketSizeNs + 300));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
|
||||
events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
|
||||
bucketStartTimeNs + bucketSizeNs - 1));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401));
|
||||
events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
|
||||
bucketStartTimeNs + bucketSizeNs + 700));
|
||||
|
||||
sortLogEventsByTimestamp(&events);
|
||||
|
||||
for (const auto& event : events) {
|
||||
processor->OnLogEvent(event.get());
|
||||
}
|
||||
|
||||
ConfigMetricsReportList reports;
|
||||
processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
|
||||
|
||||
EXPECT_EQ(reports.reports_size(), 1);
|
||||
EXPECT_EQ(reports.reports(0).metrics_size(), 1);
|
||||
StatsLogReport::DurationMetricDataWrapper metrics;
|
||||
sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics);
|
||||
|
||||
EXPECT_EQ(metrics.data_size(), 3);
|
||||
auto data = metrics.data(0);
|
||||
EXPECT_FALSE(data.dimensions_in_what().has_field());
|
||||
EXPECT_FALSE(data.dimensions_in_condition().has_field());
|
||||
EXPECT_EQ(data.bucket_info_size(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30);
|
||||
EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
|
||||
data = metrics.data(1);
|
||||
EXPECT_FALSE(data.dimensions_in_what().has_field());
|
||||
ValidateAttributionUidAndTagDimension(
|
||||
data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1");
|
||||
EXPECT_EQ(data.bucket_info_size(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300);
|
||||
EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
|
||||
data = metrics.data(2);
|
||||
EXPECT_FALSE(data.dimensions_in_what().has_field());
|
||||
ValidateAttributionUidAndTagDimension(
|
||||
data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2");
|
||||
EXPECT_EQ(data.bucket_info_size(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
|
||||
EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType aggregationType) {
|
||||
StatsdConfig config;
|
||||
*config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncStartAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncEndAtomMatcher();
|
||||
|
||||
auto screenIsOffPredicate = CreateScreenIsOffPredicate();
|
||||
auto isSyncingPredicate = CreateIsSyncingPredicate();
|
||||
auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
*syncDimension = CreateAttributionUidAndTagDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
syncDimension->add_child()->set_field(2 /* name field */);
|
||||
|
||||
auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
|
||||
*isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
|
||||
CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
|
||||
|
||||
*config.add_predicate() = screenIsOffPredicate;
|
||||
*config.add_predicate() = isSyncingPredicate;
|
||||
*config.add_predicate() = isInBackgroundPredicate;
|
||||
auto combinationPredicate = config.add_predicate();
|
||||
combinationPredicate->set_id(987654);
|
||||
combinationPredicate->mutable_combination()->set_operation(LogicalOperation::OR);
|
||||
addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
|
||||
addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
|
||||
|
||||
auto metric = config.add_duration_metric();
|
||||
metric->set_bucket(ONE_MINUTE);
|
||||
metric->set_id(StringToId("AppInBackgroundMetric"));
|
||||
metric->set_what(isInBackgroundPredicate.id());
|
||||
metric->set_condition(combinationPredicate->id());
|
||||
*metric->mutable_dimensions_in_what() = CreateDimensions(
|
||||
android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
|
||||
*metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
|
||||
// Links between crash atom and condition of app is in syncing.
|
||||
auto links = metric->add_links();
|
||||
links->set_condition(isSyncingPredicate.id());
|
||||
auto dimensionWhat = links->mutable_fields_in_what();
|
||||
dimensionWhat->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
|
||||
dimensionWhat->add_child()->set_field(1); // uid field.
|
||||
*links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink) {
|
||||
for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
|
||||
ConfigKey cfgKey;
|
||||
auto config = CreateDurationMetricConfigWithLink(aggregationType);
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs =
|
||||
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
|
||||
|
||||
auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
|
||||
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
|
||||
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
|
||||
|
||||
std::vector<AttributionNode> attributions1 =
|
||||
{CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(222, "GMSCoreModule2")};
|
||||
|
||||
std::vector<AttributionNode> attributions2 =
|
||||
{CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
|
||||
CreateAttribution(555, "GMSCoreModule2")};
|
||||
|
||||
std::vector<std::unique_ptr<LogEvent>> events;
|
||||
|
||||
events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 101));
|
||||
events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + 110));
|
||||
|
||||
events.push_back(CreateMoveToBackgroundEvent(111, bucketStartTimeNs + 201));
|
||||
events.push_back(CreateMoveToForegroundEvent(111, bucketStartTimeNs + bucketSizeNs + 100));
|
||||
|
||||
events.push_back(CreateMoveToBackgroundEvent(333, bucketStartTimeNs + 399));
|
||||
events.push_back(CreateMoveToForegroundEvent(333, bucketStartTimeNs + bucketSizeNs + 800));
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + 100));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 202));
|
||||
events.push_back(CreateScreenStateChangedEvent(
|
||||
android::view::DISPLAY_STATE_OFF, bucketStartTimeNs + bucketSizeNs + 801));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail", bucketStartTimeNs + 200));
|
||||
events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
|
||||
bucketStartTimeNs + bucketSizeNs + 300));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc", bucketStartTimeNs + 400));
|
||||
events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
|
||||
bucketStartTimeNs + bucketSizeNs - 1));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail", bucketStartTimeNs + 401));
|
||||
events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
|
||||
bucketStartTimeNs + bucketSizeNs + 700));
|
||||
|
||||
sortLogEventsByTimestamp(&events);
|
||||
|
||||
for (const auto& event : events) {
|
||||
processor->OnLogEvent(event.get());
|
||||
}
|
||||
|
||||
ConfigMetricsReportList reports;
|
||||
processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &reports);
|
||||
|
||||
EXPECT_EQ(reports.reports_size(), 1);
|
||||
EXPECT_EQ(reports.reports(0).metrics_size(), 1);
|
||||
StatsLogReport::DurationMetricDataWrapper metrics;
|
||||
sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metrics);
|
||||
|
||||
EXPECT_EQ(metrics.data_size(), 3);
|
||||
auto data = metrics.data(0);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
|
||||
EXPECT_FALSE(data.dimensions_in_condition().has_field());
|
||||
EXPECT_EQ(data.bucket_info_size(), 1);
|
||||
EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
|
||||
data = metrics.data(1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
|
||||
ValidateAttributionUidAndTagDimension(
|
||||
data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111, "App1");
|
||||
EXPECT_EQ(data.bucket_info_size(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
|
||||
EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
|
||||
data = metrics.data(2);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
|
||||
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333);
|
||||
ValidateAttributionUidAndTagDimension(
|
||||
data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333, "App2");
|
||||
EXPECT_EQ(data.bucket_info_size(), 2);
|
||||
EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401);
|
||||
EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs);
|
||||
EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
|
||||
EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs);
|
||||
EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
GTEST_LOG_(INFO) << "This test does nothing.\n";
|
||||
#endif
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -44,9 +44,10 @@ StatsdConfig CreateStatsdConfig() {
|
||||
auto screenIsOffPredicate = CreateScreenIsOffPredicate();
|
||||
|
||||
auto isSyncingPredicate = CreateIsSyncingPredicate();
|
||||
*isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions() =
|
||||
CreateDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {1 /* uid field */, 2 /* name field*/});
|
||||
auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
*syncDimension = CreateAttributionUidDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
syncDimension->add_child()->set_field(2 /* name field*/);
|
||||
|
||||
auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
|
||||
*isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
|
||||
@@ -78,9 +79,8 @@ StatsdConfig CreateStatsdConfig() {
|
||||
auto dimensionWhat = links->mutable_fields_in_what();
|
||||
dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
dimensionWhat->add_child()->set_field(1); // uid field.
|
||||
auto dimensionCondition = links->mutable_fields_in_condition();
|
||||
dimensionCondition->set_field(android::util::SYNC_STATE_CHANGED);
|
||||
dimensionCondition->add_child()->set_field(1); // uid field.
|
||||
*links->mutable_fields_in_condition() = CreateAttributionUidDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
|
||||
// Links between crash atom and condition of app is in background.
|
||||
links = countMetric->add_links();
|
||||
@@ -88,7 +88,7 @@ StatsdConfig CreateStatsdConfig() {
|
||||
dimensionWhat = links->mutable_fields_in_what();
|
||||
dimensionWhat->set_field(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
|
||||
dimensionWhat->add_child()->set_field(1); // uid field.
|
||||
dimensionCondition = links->mutable_fields_in_condition();
|
||||
auto dimensionCondition = links->mutable_fields_in_condition();
|
||||
dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
|
||||
dimensionCondition->add_child()->set_field(1); // uid field.
|
||||
return config;
|
||||
@@ -132,12 +132,14 @@ TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks) {
|
||||
CreateScreenStateChangedEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
|
||||
bucketStartTimeNs + 2 * bucketSizeNs - 100);
|
||||
|
||||
std::vector<AttributionNode> attributions =
|
||||
{CreateAttribution(appUid, "App1"), CreateAttribution(appUid + 1, "GMSCoreModule1")};
|
||||
auto syncOnEvent1 =
|
||||
CreateSyncStartEvent(appUid, "ReadEmail", bucketStartTimeNs + 50);
|
||||
CreateSyncStartEvent(attributions, "ReadEmail", bucketStartTimeNs + 50);
|
||||
auto syncOffEvent1 =
|
||||
CreateSyncEndEvent(appUid, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
|
||||
CreateSyncEndEvent(attributions, "ReadEmail", bucketStartTimeNs + bucketSizeNs + 300);
|
||||
auto syncOnEvent2 =
|
||||
CreateSyncStartEvent(appUid, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
|
||||
CreateSyncStartEvent(attributions, "ReadDoc", bucketStartTimeNs + bucketSizeNs + 2000);
|
||||
|
||||
auto moveToBackgroundEvent1 =
|
||||
CreateMoveToBackgroundEvent(appUid, bucketStartTimeNs + 15);
|
||||
|
||||
@@ -67,9 +67,9 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
|
||||
// Flushes.
|
||||
countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
|
||||
EXPECT_EQ(1UL, buckets.size());
|
||||
EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
|
||||
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
|
||||
@@ -80,10 +80,10 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
|
||||
countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY].size());
|
||||
const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY][1];
|
||||
EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
|
||||
const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1];
|
||||
EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs);
|
||||
EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs);
|
||||
EXPECT_EQ(1LL, bucketInfo2.mCount);
|
||||
@@ -91,9 +91,9 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
|
||||
// nothing happens in bucket 3. we should not record anything for bucket 3.
|
||||
countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
|
||||
EXPECT_EQ(2UL, buckets3.size());
|
||||
}
|
||||
|
||||
@@ -124,10 +124,10 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) {
|
||||
|
||||
countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
{
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
|
||||
EXPECT_EQ(1UL, buckets.size());
|
||||
const auto& bucketInfo = buckets[0];
|
||||
EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
|
||||
@@ -167,9 +167,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
|
||||
{getMockedDimensionKey(conditionTagId, 2, "222")};
|
||||
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
|
||||
EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse));
|
||||
|
||||
EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
|
||||
EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue));
|
||||
|
||||
CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard,
|
||||
bucketStartTimeNs);
|
||||
@@ -181,9 +181,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
|
||||
countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
|
||||
EXPECT_EQ(1UL, buckets.size());
|
||||
const auto& bucketInfo = buckets[0];
|
||||
EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
|
||||
@@ -229,13 +229,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
|
||||
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
|
||||
|
||||
// One event in bucket #2. No alarm as bucket #0 is trashed out.
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
|
||||
|
||||
// Two events in bucket #3.
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
|
||||
@@ -244,13 +244,13 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
// Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
|
||||
event5.GetTimestampNs() / NS_PER_SEC + refPeriodSec);
|
||||
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7);
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
|
||||
event7.GetTimestampNs() / NS_PER_SEC + refPeriodSec);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ TEST(DurationMetricTrackerTest, TestNoCondition) {
|
||||
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
|
||||
durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
|
||||
durationProducer.mPastBuckets.end());
|
||||
const auto& buckets = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
|
||||
EXPECT_EQ(2UL, buckets.size());
|
||||
EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
|
||||
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
|
||||
@@ -107,9 +107,9 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) {
|
||||
durationProducer.onMatchedLogEvent(2 /* stop index*/, event4);
|
||||
durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
|
||||
durationProducer.mPastBuckets.end());
|
||||
const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
|
||||
EXPECT_EQ(1UL, buckets2.size());
|
||||
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs);
|
||||
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs);
|
||||
|
||||
@@ -114,9 +114,9 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) {
|
||||
key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")};
|
||||
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
|
||||
EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse));
|
||||
|
||||
EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
|
||||
EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue));
|
||||
|
||||
EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
|
||||
EXPECT_EQ(13L,
|
||||
gaugeProducer.mCurrentSlicedBucket->begin()->
|
||||
second.front().mFields->begin()->second.value_int());
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
|
||||
|
||||
std::shared_ptr<LogEvent> event2 =
|
||||
std::make_shared<LogEvent>(tagId, bucketStartTimeNs + bucketSizeNs + 20);
|
||||
@@ -231,7 +231,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
|
||||
EXPECT_EQ(15L,
|
||||
gaugeProducer.mCurrentSlicedBucket->begin()->
|
||||
second.front().mFields->begin()->second.value_int());
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
|
||||
event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
|
||||
|
||||
std::shared_ptr<LogEvent> event3 =
|
||||
@@ -245,7 +245,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
|
||||
EXPECT_EQ(26L,
|
||||
gaugeProducer.mCurrentSlicedBucket->begin()->
|
||||
second.front().mFields->begin()->second.value_int());
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
|
||||
event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
|
||||
|
||||
// The event4 does not have the gauge field. Thus the current bucket value is 0.
|
||||
|
||||
@@ -41,22 +41,24 @@ const ConfigKey kConfigKey(0, 12345);
|
||||
|
||||
const int TagId = 1;
|
||||
|
||||
const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
|
||||
const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
|
||||
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
|
||||
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
|
||||
const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
|
||||
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
|
||||
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
|
||||
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
int64_t metricId = 1;
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {});
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
|
||||
false, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
|
||||
// Event starts again. This would not change anything as it already starts.
|
||||
@@ -75,16 +77,22 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestStopAll) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
|
||||
const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
|
||||
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
|
||||
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
|
||||
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
int64_t metricId = 1;
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {});
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
|
||||
false, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
|
||||
|
||||
@@ -105,21 +113,26 @@ TEST(MaxDurationTrackerTest, TestStopAll) {
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
|
||||
const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
|
||||
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
|
||||
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
int64_t metricId = 1;
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {});
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
|
||||
false, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
// The event starts.
|
||||
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
|
||||
|
||||
// Starts again. Does not change anything.
|
||||
// Starts again. Does not DEFAULT_DIMENSION_KEY anything.
|
||||
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
|
||||
ConditionKey());
|
||||
|
||||
@@ -135,16 +148,21 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
|
||||
const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
|
||||
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
|
||||
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
int64_t metricId = 1;
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {});
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
|
||||
true, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
// 2 starts
|
||||
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
|
||||
@@ -160,7 +178,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
|
||||
EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
|
||||
|
||||
// real stop now.
|
||||
tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
|
||||
tracker.noteStop(DEFAULT_DIMENSION_KEY,
|
||||
bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
|
||||
|
||||
EXPECT_EQ(3u, buckets[eventKey].size());
|
||||
@@ -170,16 +189,20 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
|
||||
const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
|
||||
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
|
||||
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
ConditionKey conditionKey1;
|
||||
HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 2, "maps");
|
||||
MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
|
||||
conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
|
||||
|
||||
EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4
|
||||
EXPECT_CALL(*wizard, query(_, conditionKey1, _, _)) // #4
|
||||
.WillOnce(Return(ConditionState::kFalse));
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
@@ -187,8 +210,8 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
|
||||
int64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
int64_t metricId = 1;
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
|
||||
bucketSizeNs, true, {});
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
false, bucketStartTimeNs, bucketSizeNs, true, {});
|
||||
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
|
||||
|
||||
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
|
||||
@@ -204,6 +227,10 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
|
||||
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
|
||||
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
|
||||
FieldMatcher dimensionInCondition;
|
||||
int64_t metricId = 1;
|
||||
Alert alert;
|
||||
alert.set_id(101);
|
||||
@@ -213,7 +240,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
|
||||
const int32_t refPeriodSec = 1;
|
||||
alert.set_refractory_period_secs(refPeriodSec);
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
|
||||
@@ -221,8 +248,8 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
|
||||
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
|
||||
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {anomalyTracker});
|
||||
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
|
||||
true, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
|
||||
|
||||
tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey());
|
||||
tracker.noteStop(key1, eventStartTimeNs + 10, false);
|
||||
|
||||
@@ -38,24 +38,26 @@ namespace statsd {
|
||||
const ConfigKey kConfigKey(0, 12345);
|
||||
const int TagId = 1;
|
||||
const int64_t metricId = 123;
|
||||
const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
|
||||
TEST(OringDurationTrackerTest, TestDurationOverlap) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
uint64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
|
||||
bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
false, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
|
||||
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
|
||||
@@ -71,16 +73,23 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestDurationNested) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
true, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
|
||||
@@ -95,16 +104,23 @@ TEST(OringDurationTrackerTest, TestDurationNested) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestStopAll) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
true, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
|
||||
tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
|
||||
@@ -118,17 +134,24 @@ TEST(OringDurationTrackerTest, TestStopAll) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
uint64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
|
||||
bucketSizeNs, false, {});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
true, bucketStartTimeNs, bucketSizeNs, false, {});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
|
||||
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
|
||||
@@ -150,23 +173,30 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestDurationConditionChange) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
ConditionKey key1;
|
||||
key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
|
||||
|
||||
EXPECT_CALL(*wizard, query(_, key1)) // #4
|
||||
EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4
|
||||
.WillOnce(Return(ConditionState::kFalse));
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
uint64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
|
||||
bucketStartTimeNs, bucketSizeNs, true, {});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
false, bucketStartTimeNs, bucketSizeNs, true, {});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
|
||||
|
||||
@@ -181,25 +211,32 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
ConditionKey key1;
|
||||
key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
|
||||
|
||||
EXPECT_CALL(*wizard, query(_, key1))
|
||||
EXPECT_CALL(*wizard, query(_, key1, _, _))
|
||||
.Times(2)
|
||||
.WillOnce(Return(ConditionState::kFalse))
|
||||
.WillOnce(Return(ConditionState::kTrue));
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
uint64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
|
||||
bucketStartTimeNs, bucketSizeNs, true, {});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
false, bucketStartTimeNs, bucketSizeNs, true, {});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
|
||||
// condition to false; record duration 5n
|
||||
@@ -216,22 +253,29 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
ConditionKey key1;
|
||||
key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
|
||||
|
||||
EXPECT_CALL(*wizard, query(_, key1)) // #4
|
||||
EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4
|
||||
.WillOnce(Return(ConditionState::kFalse));
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
|
||||
bucketSizeNs, true, {});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
true, bucketStartTimeNs, bucketSizeNs, true, {});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
|
||||
@@ -249,6 +293,13 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
Alert alert;
|
||||
alert.set_id(101);
|
||||
alert.set_metric_id(1);
|
||||
@@ -256,7 +307,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
|
||||
alert.set_num_buckets(2);
|
||||
alert.set_refractory_period_secs(1);
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
|
||||
@@ -264,8 +315,8 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
|
||||
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
|
||||
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
|
||||
bucketSizeNs, true, {anomalyTracker});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
true, bucketStartTimeNs, bucketSizeNs, true, {anomalyTracker});
|
||||
|
||||
// Nothing in the past bucket.
|
||||
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
|
||||
@@ -310,6 +361,12 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
Alert alert;
|
||||
alert.set_id(101);
|
||||
alert.set_metric_id(1);
|
||||
@@ -318,7 +375,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
|
||||
const int32_t refPeriodSec = 45;
|
||||
alert.set_refractory_period_secs(refPeriodSec);
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
|
||||
@@ -326,8 +383,8 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
|
||||
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
|
||||
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
|
||||
bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
|
||||
tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
|
||||
@@ -352,6 +409,13 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
|
||||
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
|
||||
|
||||
const std::vector<HashableDimensionKey> kConditionKey1 =
|
||||
{getMockedDimensionKey(TagId, 1, "maps")};
|
||||
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
|
||||
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
|
||||
FieldMatcher dimensionInCondition;
|
||||
Alert alert;
|
||||
alert.set_id(101);
|
||||
alert.set_metric_id(1);
|
||||
@@ -360,7 +424,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
|
||||
const int32_t refPeriodSec = 45;
|
||||
alert.set_refractory_period_secs(refPeriodSec);
|
||||
|
||||
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
|
||||
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
ConditionKey conkey;
|
||||
conkey[StringToId("APP_BACKGROUND")] = kConditionKey1;
|
||||
@@ -369,8 +433,9 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
|
||||
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
|
||||
sp<DurationAnomalyTracker> anomalyTracker = new DurationAnomalyTracker(alert, kConfigKey);
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
|
||||
bucketStartTimeNs, bucketSizeNs, false, {anomalyTracker});
|
||||
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
|
||||
true /*nesting*/, bucketStartTimeNs, bucketSizeNs, false,
|
||||
{anomalyTracker});
|
||||
|
||||
tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
|
||||
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
|
||||
|
||||
@@ -299,26 +299,26 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) {
|
||||
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
|
||||
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
|
||||
// Value sum == 30 <= 130.
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
|
||||
|
||||
// One event in bucket #2. No alarm as bucket #0 is trashed out.
|
||||
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
|
||||
// Value sum == 130 <= 130.
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U);
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
|
||||
|
||||
// Three events in bucket #3.
|
||||
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
|
||||
// Anomaly at event 4 since Value sum == 131 > 130!
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
|
||||
event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
|
||||
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5);
|
||||
// Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4.
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
|
||||
event4->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
|
||||
|
||||
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6);
|
||||
// Anomaly at event 6 since Value sum == 160 > 130 and after refractory period.
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY),
|
||||
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
|
||||
event6->GetTimestampNs() / NS_PER_SEC + refPeriodSec);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@ HashableDimensionKey getMockedDimensionKey(int tagId, int key, string value) {
|
||||
return HashableDimensionKey(dimensionsValue);
|
||||
}
|
||||
|
||||
MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, string value) {
|
||||
DimensionsValue dimensionsValue;
|
||||
dimensionsValue.set_field(tagId);
|
||||
dimensionsValue.mutable_value_tuple()->add_dimensions_value()->set_field(key);
|
||||
dimensionsValue.mutable_value_tuple()->mutable_dimensions_value(0)->set_value_str(value);
|
||||
return MetricDimensionKey(HashableDimensionKey(dimensionsValue), DEFAULT_DIMENSION_KEY);
|
||||
}
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -25,10 +25,12 @@ namespace statsd {
|
||||
|
||||
class MockConditionWizard : public ConditionWizard {
|
||||
public:
|
||||
MOCK_METHOD2(
|
||||
MOCK_METHOD4(
|
||||
query,
|
||||
ConditionState(const int conditionIndex,
|
||||
const ConditionKey& conditionParameters));
|
||||
const ConditionKey& conditionParameters,
|
||||
const FieldMatcher& dimensionFields,
|
||||
std::unordered_set<HashableDimensionKey> *dimensionKeySet));
|
||||
};
|
||||
|
||||
class MockStatsPullerManager : public StatsPullerManager {
|
||||
@@ -39,6 +41,7 @@ public:
|
||||
};
|
||||
|
||||
HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
|
||||
MetricDimensionKey getMockedMetricDimensionKey(int tagId, int key, std::string value);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "statsd_test_util.h"
|
||||
|
||||
namespace android {
|
||||
@@ -27,6 +26,22 @@ AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
|
||||
return atom_matcher;
|
||||
}
|
||||
|
||||
AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
|
||||
AtomMatcher atom_matcher;
|
||||
atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
|
||||
auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
|
||||
simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED);
|
||||
return atom_matcher;
|
||||
}
|
||||
|
||||
AtomMatcher CreateUidProcessStateChangedAtomMatcher() {
|
||||
AtomMatcher atom_matcher;
|
||||
atom_matcher.set_id(StringToId("UidProcessStateChanged"));
|
||||
auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
|
||||
simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED);
|
||||
return atom_matcher;
|
||||
}
|
||||
|
||||
AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
|
||||
WakelockStateChanged::State state) {
|
||||
AtomMatcher atom_matcher;
|
||||
@@ -47,6 +62,30 @@ AtomMatcher CreateReleaseWakelockAtomMatcher() {
|
||||
return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE);
|
||||
}
|
||||
|
||||
AtomMatcher CreateBatterySaverModeStateChangedAtomMatcher(
|
||||
const string& name, BatterySaverModeStateChanged::State 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(android::util::BATTERY_SAVER_MODE_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 CreateBatterySaverModeStartAtomMatcher() {
|
||||
return CreateBatterySaverModeStateChangedAtomMatcher(
|
||||
"BatterySaverModeStart", BatterySaverModeStateChanged::ON);
|
||||
}
|
||||
|
||||
|
||||
AtomMatcher CreateBatterySaverModeStopAtomMatcher() {
|
||||
return CreateBatterySaverModeStateChangedAtomMatcher(
|
||||
"BatterySaverModeStop", BatterySaverModeStateChanged::OFF);
|
||||
}
|
||||
|
||||
|
||||
AtomMatcher CreateScreenStateChangedAtomMatcher(
|
||||
const string& name, android::view::DisplayStateEnum state) {
|
||||
AtomMatcher atom_matcher;
|
||||
@@ -59,6 +98,7 @@ AtomMatcher CreateScreenStateChangedAtomMatcher(
|
||||
return atom_matcher;
|
||||
}
|
||||
|
||||
|
||||
AtomMatcher CreateScreenTurnedOnAtomMatcher() {
|
||||
return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn",
|
||||
android::view::DisplayStateEnum::DISPLAY_STATE_ON);
|
||||
@@ -128,6 +168,13 @@ AtomMatcher CreateProcessCrashAtomMatcher() {
|
||||
"ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED);
|
||||
}
|
||||
|
||||
Predicate CreateBatterySaverModePredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(StringToId("BatterySaverIsOn"));
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Predicate CreateScreenIsOnPredicate() {
|
||||
Predicate predicate;
|
||||
@@ -218,6 +265,31 @@ std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
|
||||
return event;
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs) {
|
||||
auto event = std::make_unique<LogEvent>(
|
||||
android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs);
|
||||
EXPECT_TRUE(event->write(BatterySaverModeStateChanged::ON));
|
||||
event->init();
|
||||
return event;
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) {
|
||||
auto event = std::make_unique<LogEvent>(
|
||||
android::util::BATTERY_SAVER_MODE_STATE_CHANGED, timestampNs);
|
||||
EXPECT_TRUE(event->write(BatterySaverModeStateChanged::OFF));
|
||||
event->init();
|
||||
return event;
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
|
||||
int level, uint64_t timestampNs) {
|
||||
auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs);
|
||||
EXPECT_TRUE(event->write(level));
|
||||
event->init();
|
||||
return event;
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
|
||||
const std::vector<AttributionNode>& attributions, const string& wakelockName,
|
||||
const WakelockStateChanged::State state, uint64_t timestampNs) {
|
||||
@@ -267,9 +339,10 @@ std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t ti
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
|
||||
const int uid, const string& name, const SyncStateChanged::State state, uint64_t timestampNs) {
|
||||
const std::vector<AttributionNode>& attributions,
|
||||
const string& name, const SyncStateChanged::State state, uint64_t timestampNs) {
|
||||
auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
|
||||
event->write(uid);
|
||||
event->write(attributions);
|
||||
event->write(name);
|
||||
event->write(state);
|
||||
event->init();
|
||||
@@ -277,13 +350,13 @@ std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateSyncStartEvent(
|
||||
const int uid, const string& name, uint64_t timestampNs){
|
||||
return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::ON, timestampNs);
|
||||
const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs){
|
||||
return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs);
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateSyncEndEvent(
|
||||
const int uid, const string& name, uint64_t timestampNs) {
|
||||
return CreateSyncStateChangedEvent(uid, name, SyncStateChanged::OFF, timestampNs);
|
||||
const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs) {
|
||||
return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs);
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateProcessLifeCycleStateChangedEvent(
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "statslog.h"
|
||||
#include "src/logd/LogEvent.h"
|
||||
@@ -26,6 +27,18 @@ namespace statsd {
|
||||
// Create AtomMatcher proto to simply match a specific atom type.
|
||||
AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
|
||||
|
||||
// Create AtomMatcher proto for screen brightness state changed.
|
||||
AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for starting battery save mode.
|
||||
AtomMatcher CreateBatterySaverModeStartAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for stopping battery save mode.
|
||||
AtomMatcher CreateBatterySaverModeStopAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for process state changed.
|
||||
AtomMatcher CreateUidProcessStateChangedAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for acquiring wakelock.
|
||||
AtomMatcher CreateAcquireWakelockAtomMatcher();
|
||||
|
||||
@@ -59,6 +72,9 @@ Predicate CreateScreenIsOnPredicate();
|
||||
// Create Predicate proto for screen is off.
|
||||
Predicate CreateScreenIsOffPredicate();
|
||||
|
||||
// Create Predicate proto for battery saver mode.
|
||||
Predicate CreateBatterySaverModePredicate();
|
||||
|
||||
// Create Predicate proto for holding wakelock.
|
||||
Predicate CreateHoldingWakelockPredicate();
|
||||
|
||||
@@ -86,6 +102,15 @@ FieldMatcher CreateAttributionUidDimensions(const int atomId,
|
||||
std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
|
||||
const android::view::DisplayStateEnum state, uint64_t timestampNs);
|
||||
|
||||
// Create log event for screen brightness state changed.
|
||||
std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
|
||||
int level, uint64_t timestampNs);
|
||||
|
||||
// Create log event when battery saver starts.
|
||||
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 for app moving to background.
|
||||
std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs);
|
||||
|
||||
@@ -94,11 +119,11 @@ std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t ti
|
||||
|
||||
// Create log event when the app sync starts.
|
||||
std::unique_ptr<LogEvent> CreateSyncStartEvent(
|
||||
const int uid, const string& name, uint64_t timestampNs);
|
||||
const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs);
|
||||
|
||||
// Create log event when the app sync ends.
|
||||
std::unique_ptr<LogEvent> CreateSyncEndEvent(
|
||||
const int uid, const string& name, uint64_t timestampNs);
|
||||
const std::vector<AttributionNode>& attributions, const string& name, uint64_t timestampNs);
|
||||
|
||||
// Create log event when the app sync ends.
|
||||
std::unique_ptr<LogEvent> CreateAppCrashEvent(
|
||||
@@ -136,9 +161,12 @@ void ValidateAttributionUidAndTagDimension(
|
||||
|
||||
template <typename T>
|
||||
void sortMetricDataByDimensionsValue(const T& metricData, T* sortedMetricData) {
|
||||
std::map<HashableDimensionKey, int> dimensionIndexMap;
|
||||
std::map<MetricDimensionKey, int> dimensionIndexMap;
|
||||
for (int i = 0; i < metricData.data_size(); ++i) {
|
||||
dimensionIndexMap.insert(std::make_pair(metricData.data(i).dimensions_in_what(), i));
|
||||
dimensionIndexMap.insert(std::make_pair(
|
||||
MetricDimensionKey(HashableDimensionKey(metricData.data(i).dimensions_in_what()),
|
||||
HashableDimensionKey(metricData.data(i).dimensions_in_condition())),
|
||||
i));
|
||||
}
|
||||
for (const auto& itr : dimensionIndexMap) {
|
||||
*sortedMetricData->add_data() = metricData.data(itr.second);
|
||||
|
||||
Reference in New Issue
Block a user