Remove the hacky HashableDimensionKey.

+ Add a real HashableDimensionKey as a wrapper of the dimension.
So we can get rid of the maps that we kept.

Pay down technical debt and reduce memory usage.

Test: statsd_test & manual
Change-Id: I233280cf1e2ce93da6a8cd4e8514abb066f4016d
This commit is contained in:
Yao Chen
2017-12-19 16:46:36 -08:00
parent d10f7b1c7b
commit d5aa01b371
24 changed files with 443 additions and 252 deletions

View File

@@ -54,7 +54,7 @@ statsd_common_src := \
src/storage/StorageManager.cpp \
src/StatsLogProcessor.cpp \
src/StatsService.cpp \
src/stats_util.cpp \
src/HashableDimensionKey.cpp \
src/guardrail/MemoryLeakTrackUtil.cpp \
src/guardrail/StatsdStats.cpp
@@ -174,7 +174,8 @@ LOCAL_SRC_FILES := \
tests/metrics/EventMetricProducer_test.cpp \
tests/metrics/ValueMetricProducer_test.cpp \
tests/metrics/GaugeMetricProducer_test.cpp \
tests/guardrail/StatsdStats_test.cpp
tests/guardrail/StatsdStats_test.cpp \
tests/metrics/metrics_test_helper.cpp
LOCAL_STATIC_LIBRARIES := \
$(statsd_common_static_libraries) \

View File

@@ -0,0 +1,110 @@
/*
* 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 "HashableDimensionKey.h"
namespace android {
namespace os {
namespace statsd {
using std::string;
string HashableDimensionKey::toString() const {
string flattened;
for (const auto& pair : mKeyValuePairs) {
flattened += std::to_string(pair.key());
flattened += ":";
switch (pair.value_case()) {
case KeyValuePair::ValueCase::kValueStr:
flattened += pair.value_str();
break;
case KeyValuePair::ValueCase::kValueInt:
flattened += std::to_string(pair.value_int());
break;
case KeyValuePair::ValueCase::kValueLong:
flattened += std::to_string(pair.value_long());
break;
case KeyValuePair::ValueCase::kValueBool:
flattened += std::to_string(pair.value_bool());
break;
case KeyValuePair::ValueCase::kValueFloat:
flattened += std::to_string(pair.value_float());
break;
default:
break;
}
flattened += "|";
}
return flattened;
}
bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
const auto& keyValue2 = that.getKeyValuePairs();
if (mKeyValuePairs.size() != keyValue2.size()) {
return false;
}
for (size_t i = 0; i < keyValue2.size(); i++) {
const auto& kv1 = mKeyValuePairs[i];
const auto& kv2 = keyValue2[i];
if (kv1.key() != kv2.key()) {
return false;
}
if (kv1.value_case() != kv2.value_case()) {
return false;
}
switch (kv1.value_case()) {
case KeyValuePair::ValueCase::kValueStr:
if (kv1.value_str() != kv2.value_str()) {
return false;
}
break;
case KeyValuePair::ValueCase::kValueInt:
if (kv1.value_int() != kv2.value_int()) {
return false;
}
break;
case KeyValuePair::ValueCase::kValueLong:
if (kv1.value_long() != kv2.value_long()) {
return false;
}
break;
case KeyValuePair::ValueCase::kValueBool:
if (kv1.value_bool() != kv2.value_bool()) {
return false;
}
break;
case KeyValuePair::ValueCase::kValueFloat: {
if (kv1.value_float() != kv2.value_float()) {
return false;
}
break;
}
case KeyValuePair::ValueCase::VALUE_NOT_SET:
break;
}
}
return true;
};
bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
return toString().compare(that.toString()) < 0;
};
} // namespace statsd
} // namespace os
} // namespace android

View File

@@ -0,0 +1,102 @@
/*
* 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.
*/
#pragma once
#include <utils/JenkinsHash.h>
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
namespace android {
namespace os {
namespace statsd {
class HashableDimensionKey {
public:
explicit HashableDimensionKey(const std::vector<KeyValuePair>& keyValuePairs)
: mKeyValuePairs(keyValuePairs){};
HashableDimensionKey(){};
HashableDimensionKey(const HashableDimensionKey& that)
: mKeyValuePairs(that.getKeyValuePairs()){};
HashableDimensionKey& operator=(const HashableDimensionKey& from) = default;
std::string toString() const;
inline const std::vector<KeyValuePair>& getKeyValuePairs() const {
return mKeyValuePairs;
}
bool operator==(const HashableDimensionKey& that) const;
bool operator<(const HashableDimensionKey& that) const;
inline const char* c_str() const {
return toString().c_str();
}
private:
std::vector<KeyValuePair> mKeyValuePairs;
};
} // namespace statsd
} // namespace os
} // namespace android
namespace std {
using android::os::statsd::HashableDimensionKey;
using android::os::statsd::KeyValuePair;
template <>
struct hash<HashableDimensionKey> {
std::size_t operator()(const HashableDimensionKey& key) const {
android::hash_t hash = 0;
for (const auto& pair : key.getKeyValuePairs()) {
hash = android::JenkinsHashMix(hash, android::hash_type(pair.key()));
hash = android::JenkinsHashMix(
hash, android::hash_type(static_cast<int32_t>(pair.value_case())));
switch (pair.value_case()) {
case KeyValuePair::ValueCase::kValueStr:
hash = android::JenkinsHashMix(
hash,
static_cast<uint32_t>(std::hash<std::string>()(pair.value_str())));
break;
case KeyValuePair::ValueCase::kValueInt:
hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_int()));
break;
case KeyValuePair::ValueCase::kValueLong:
hash = android::JenkinsHashMix(
hash, android::hash_type(static_cast<int64_t>(pair.value_long())));
break;
case KeyValuePair::ValueCase::kValueBool:
hash = android::JenkinsHashMix(hash, android::hash_type(pair.value_bool()));
break;
case KeyValuePair::ValueCase::kValueFloat: {
float floatVal = pair.value_float();
hash = android::JenkinsHashMixBytes(hash, (uint8_t*)&floatVal, sizeof(float));
break;
}
case KeyValuePair::ValueCase::VALUE_NOT_SET:
break;
}
}
return hash;
}
};
} // namespace std

View File

@@ -279,7 +279,7 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
}
// outputKey is the output key values. e.g, uid:1234
const HashableDimensionKey outputKey = getHashableKey(getDimensionKey(event, mOutputDimension));
const HashableDimensionKey outputKey(getDimensionKey(event, mOutputDimension));
handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
}

View File

@@ -109,7 +109,7 @@ HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
kv.set_key(link.key_in_condition(i).key());
}
return getHashableKey(dimensionKey);
return HashableDimensionKey(dimensionKey);
}
} // namespace statsd

View File

@@ -107,17 +107,14 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
for (const auto& counter : mPastBuckets) {
const HashableDimensionKey& hashableKey = counter.first;
const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
auto it = mDimensionKeyMap.find(hashableKey);
if (it == mDimensionKeyMap.end()) {
ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());

View File

@@ -159,18 +159,14 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
auto it = mDimensionKeyMap.find(hashableKey);
if (it == mDimensionKeyMap.end()) {
ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
@@ -260,7 +256,7 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked(
return;
}
HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
HashableDimensionKey atomKey(getDimensionKey(event, mInternalDimension));
if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
if (hitGuardRailLocked(eventKey)) {

View File

@@ -128,18 +128,14 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
auto it = mDimensionKeyMap.find(hashableKey);
if (it == mDimensionKeyMap.end()) {
ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
VLOG(" dimension key %s", hashableKey.c_str());
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());

View File

@@ -32,12 +32,7 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo
if (mDimension.size() > 0) {
vector<KeyValuePair> key = getDimensionKey(event, mDimension);
eventKey = getHashableKey(key);
// Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
// expects vector<KeyValuePair>.
if (mDimensionKeyMap.find(eventKey) == mDimensionKeyMap.end()) {
mDimensionKeyMap[eventKey] = key;
}
eventKey = HashableDimensionKey(key);
} else {
eventKey = DEFAULT_DIMENSION_KEY;
}
@@ -58,7 +53,6 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo
} else {
condition = mCondition;
}
onMatchedLogEventInternalLocked(matcherIndex, eventKey, conditionKeys, condition, event);
}

View File

@@ -49,10 +49,7 @@ public:
mCondition(conditionIndex >= 0 ? false : true),
mConditionSliced(false),
mWizard(wizard),
mConditionTrackerIndex(conditionIndex) {
// reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
mDimensionKeyMap[DEFAULT_DIMENSION_KEY] = std::vector<KeyValuePair>();
};
mConditionTrackerIndex(conditionIndex){};
virtual ~MetricProducer(){};
void notifyAppUpgrade(const string& apk, const int uid, const int64_t version) override{
@@ -145,10 +142,6 @@ protected:
std::vector<KeyMatcher> mDimension; // The dimension defined in statsd_config
// Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
// that StatsLogReport wants.
std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
std::vector<MetricConditionLink> mConditionLinks;
std::vector<sp<AnomalyTracker>> mAnomalyTrackers;

View File

@@ -132,16 +132,12 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
VLOG(" dimension key %s", hashableKey.c_str());
auto it = mDimensionKeyMap.find(hashableKey);
if (it == mDimensionKeyMap.end()) {
ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
const vector<KeyValuePair>& kvs = hashableKey.getKeyValuePairs();
long long wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
for (const auto& kv : kvs) {
long long dimensionToken = protoOutput->start(
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());

View File

@@ -42,7 +42,7 @@ bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
// 1. Report the tuple count if the tuple count > soft limit
if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mInfos.size() + 1;
StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {

View File

@@ -45,7 +45,7 @@ bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
}
if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mConditionKeyMap.size() + 1;
StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey,
StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mName + mEventKey.toString(),
newTupleCount);
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {

View File

@@ -1,57 +0,0 @@
/*
* 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 "stats_util.h"
namespace android {
namespace os {
namespace statsd {
// There is no existing hash function for the dimension key ("repeated KeyValuePair").
// Temporarily use a string concatenation as the hashable key.
// TODO: Find a better hash function for std::vector<KeyValuePair>.
HashableDimensionKey getHashableKey(std::vector<KeyValuePair> keys) {
std::string flattened;
for (const KeyValuePair& pair : keys) {
flattened += std::to_string(pair.key());
flattened += ":";
switch (pair.value_case()) {
case KeyValuePair::ValueCase::kValueStr:
flattened += pair.value_str();
break;
case KeyValuePair::ValueCase::kValueInt:
flattened += std::to_string(pair.value_int());
break;
case KeyValuePair::ValueCase::kValueLong:
flattened += std::to_string(pair.value_long());
break;
case KeyValuePair::ValueCase::kValueBool:
flattened += std::to_string(pair.value_bool());
break;
case KeyValuePair::ValueCase::kValueFloat:
flattened += std::to_string(pair.value_float());
break;
default:
break;
}
flattened += "|";
}
return flattened;
}
} // namespace statsd
} // namespace os
} // namespace android

View File

@@ -16,8 +16,9 @@
#pragma once
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include <sstream>
#include "HashableDimensionKey.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "logd/LogReader.h"
#include <unordered_map>
@@ -26,13 +27,11 @@ namespace android {
namespace os {
namespace statsd {
#define DEFAULT_DIMENSION_KEY ""
const HashableDimensionKey DEFAULT_DIMENSION_KEY = HashableDimensionKey(vector<KeyValuePair>());
// Minimum bucket size in seconds
const long kMinBucketSizeSec = 5 * 60;
typedef std::string HashableDimensionKey;
typedef std::map<std::string, HashableDimensionKey> ConditionKey;
typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
@@ -67,13 +66,6 @@ typedef struct EventKV {
} EventKV;
typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<EventKV>> DimToEventKVMap;
EventMetricData parse(log_msg msg);
int getTagId(log_msg msg);
std::string getHashableKey(std::vector<KeyValuePair> key);
} // namespace statsd
} // namespace os
} // namespace android

View File

@@ -13,6 +13,7 @@
// limitations under the License.
#include "src/anomaly/AnomalyTracker.h"
#include "../metrics/metrics_test_helper.h"
#include <gtest/gtest.h>
#include <stdio.h>
@@ -32,7 +33,18 @@ namespace statsd {
const ConfigKey kConfigKey(0, "test");
void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair_list,
HashableDimensionKey getMockDimensionKey(int key, string value) {
KeyValuePair pair;
pair.set_key(key);
pair.set_value_str(value);
vector<KeyValuePair> pairs;
pairs.push_back(pair);
return HashableDimensionKey(pairs);
}
void AddValueToBucket(const std::vector<std::pair<HashableDimensionKey, 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;
@@ -40,7 +52,7 @@ void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair
}
std::shared_ptr<DimToValMap> MockBucket(
const std::vector<std::pair<string, long>>& key_value_pair_list) {
const std::vector<std::pair<HashableDimensionKey, long>>& key_value_pair_list) {
std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
AddValueToBucket(key_value_pair_list, bucket);
return bucket;
@@ -54,20 +66,23 @@ 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");
std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
std::shared_ptr<DimToValMap> bucket0 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
int64_t eventTimestamp0 = 10;
std::shared_ptr<DimToValMap> bucket1 = MockBucket({{"a", 1}});
std::shared_ptr<DimToValMap> bucket1 = MockBucket({{keyA, 1}});
int64_t eventTimestamp1 = bucketSizeNs + 11;
std::shared_ptr<DimToValMap> bucket2 = MockBucket({{"b", 1}});
std::shared_ptr<DimToValMap> bucket2 = MockBucket({{keyB, 1}});
int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
std::shared_ptr<DimToValMap> bucket3 = MockBucket({{"a", 2}});
std::shared_ptr<DimToValMap> bucket3 = MockBucket({{keyA, 2}});
int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
std::shared_ptr<DimToValMap> bucket4 = MockBucket({{"b", 1}});
std::shared_ptr<DimToValMap> bucket4 = MockBucket({{keyB, 1}});
int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
std::shared_ptr<DimToValMap> bucket5 = MockBucket({{"a", 2}});
std::shared_ptr<DimToValMap> bucket5 = MockBucket({{keyA, 2}});
int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
std::shared_ptr<DimToValMap> bucket6 = MockBucket({{"a", 2}});
std::shared_ptr<DimToValMap> bucket6 = MockBucket({{keyA, 2}});
int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
@@ -79,9 +94,9 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
// Adds past bucket #0
anomalyTracker.addPastBucket(bucket0, 0);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
@@ -90,9 +105,9 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
// Adds past bucket #0 again. The sum does not change.
anomalyTracker.addPastBucket(bucket0, 0);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3u);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 0LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
@@ -102,9 +117,9 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
anomalyTracker.addPastBucket(bucket1, 1);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -113,9 +128,9 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
anomalyTracker.addPastBucket(bucket1, 1);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 1L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -124,8 +139,8 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
anomalyTracker.addPastBucket(bucket2, 2);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 2L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
// Within refractory period.
@@ -135,8 +150,8 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
anomalyTracker.addPastBucket(bucket3, 3L);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 3L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
@@ -145,8 +160,8 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
anomalyTracker.addPastBucket(bucket4, 4);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 4L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
@@ -155,8 +170,8 @@ TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
anomalyTracker.addPastBucket(bucket5, 5);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 5L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
// Within refractory period.
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
@@ -171,13 +186,18 @@ 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");
std::shared_ptr<DimToValMap> bucket9 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
std::shared_ptr<DimToValMap> bucket16 = MockBucket({{"b", 4}});
std::shared_ptr<DimToValMap> bucket18 = MockBucket({{"b", 1}, {"c", 1}});
std::shared_ptr<DimToValMap> bucket20 = MockBucket({{"b", 3}, {"c", 1}});
std::shared_ptr<DimToValMap> bucket25 = MockBucket({{"d", 1}});
std::shared_ptr<DimToValMap> bucket28 = MockBucket({{"e", 2}});
std::shared_ptr<DimToValMap> bucket9 = MockBucket({{keyA, 1}, {keyB, 2}, {keyC, 1}});
std::shared_ptr<DimToValMap> bucket16 = MockBucket({{keyB, 4}});
std::shared_ptr<DimToValMap> bucket18 = MockBucket({{keyB, 1}, {keyC, 1}});
std::shared_ptr<DimToValMap> bucket20 = MockBucket({{keyB, 3}, {keyC, 1}});
std::shared_ptr<DimToValMap> bucket25 = MockBucket({{keyD, 1}});
std::shared_ptr<DimToValMap> bucket28 = MockBucket({{keyE, 2}});
int64_t eventTimestamp1 = bucketSizeNs * 8 + 1;
int64_t eventTimestamp2 = bucketSizeNs * 15 + 11;
@@ -196,9 +216,9 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) {
anomalyTracker.addPastBucket(bucket9, 9);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 9L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 3UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("a"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyA), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 2LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
@@ -211,27 +231,27 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) {
anomalyTracker.addPastBucket(bucket16, 16);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
// Within refractory period.
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 4LL);
// Add past bucket #18
anomalyTracker.addPastBucket(bucket18, 18);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 18L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
@@ -239,12 +259,12 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) {
anomalyTracker.addPastBucket(bucket18, 18);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 19L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
// Within refractory period.
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
@@ -253,8 +273,8 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) {
anomalyTracker.addPastBucket(bucket20, 20);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 20L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 3LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyB), 3LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyC), 1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
@@ -265,7 +285,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) {
anomalyTracker.addPastBucket(bucket25, 25);
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("d"), 1LL);
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets(keyD), 1LL);
EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
@@ -274,7 +294,7 @@ TEST(AnomalyTrackerTest, TestSparseBuckets) {
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
// Updates current bucket #28.
(*bucket28)["e"] = 5;
(*bucket28)[keyE] = 5;
EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);

View File

@@ -63,7 +63,7 @@ map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid,
vector<KeyValuePair> kv_list;
kv_list.push_back(kv1);
map<string, HashableDimensionKey> queryKey;
queryKey[conditionName] = getHashableKey(kv_list);
queryKey[conditionName] = HashableDimensionKey(kv_list);
return queryKey;
}

View File

@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "metrics_test_helper.h"
#include "src/metrics/CountMetricProducer.h"
#include "metrics_test_helper.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -150,13 +150,13 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
event1.write("111"); // uid
event1.init();
ConditionKey key1;
key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
LogEvent event2(1, bucketStartTimeNs + 10);
event2.write("222"); // uid
event2.init();
ConditionKey key2;
key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));

View File

@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "metrics_test_helper.h"
#include "src/metrics/EventMetricProducer.h"
#include "metrics_test_helper.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -100,13 +100,13 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) {
event1.write("111"); // uid
event1.init();
ConditionKey key1;
key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
key1["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "111");
LogEvent event2(1, bucketStartTimeNs + 10);
event2.write("222"); // uid
event2.init();
ConditionKey key2;
key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
key2["APP_IN_BACKGROUND_PER_UID"] = getMockedDimensionKey(2, "222");
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));

View File

@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/metrics/GaugeMetricProducer.h"
#include "logd/LogEvent.h"
#include "metrics_test_helper.h"
#include "src/metrics/GaugeMetricProducer.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>

View File

@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/metrics/duration_helper/MaxDurationTracker.h"
#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
#include "src/metrics/duration_helper/MaxDurationTracker.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -37,13 +37,18 @@ namespace os {
namespace statsd {
const ConfigKey kConfigKey(0, "test");
const string eventKey = "event";
const HashableDimensionKey eventKey = getMockedDimensionKey(0, "1");
const HashableDimensionKey conditionKey = getMockedDimensionKey(4, "1");
const HashableDimensionKey key1 = getMockedDimensionKey(1, "1");
const HashableDimensionKey key2 = getMockedDimensionKey(1, "2");
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey key1;
ConditionKey conditionKey1;
conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -51,15 +56,15 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart("1", true, bucketStartTimeNs, key1);
tracker.noteStart(key1, true, bucketStartTimeNs, conditionKey1);
// Event starts again. This would not change anything as it already starts.
tracker.noteStart("1", true, bucketStartTimeNs + 3, key1);
tracker.noteStart(key1, true, bucketStartTimeNs + 3, conditionKey1);
// Stopped.
tracker.noteStop("1", bucketStartTimeNs + 10, false);
tracker.noteStop(key1, bucketStartTimeNs + 10, false);
// Another event starts in this bucket.
tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
tracker.noteStop("2", bucketStartTimeNs + 40, false /*stop all*/);
tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -71,7 +76,8 @@ TEST(MaxDurationTrackerTest, TestStopAll) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey key1;
ConditionKey conditionKey1;
conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -79,10 +85,10 @@ TEST(MaxDurationTrackerTest, TestStopAll) {
MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, false, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
tracker.noteStart(key1, true, bucketStartTimeNs + 1, conditionKey1);
// Another event starts in this bucket.
tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
tracker.noteStart(key2, true, bucketStartTimeNs + 20, conditionKey1);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets);
tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
EXPECT_TRUE(tracker.mInfos.empty());
@@ -101,7 +107,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey key1;
ConditionKey conditionKey1;
conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -110,14 +117,16 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
bucketSizeNs, {});
// The event starts.
tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
// Starts again. Does not change anything.
tracker.noteStart("", true, bucketStartTimeNs + bucketSizeNs + 1, key1);
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
conditionKey1);
// The event stops at early 4th bucket.
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets);
tracker.noteStop("", bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/);
tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20,
false /*stop all*/);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
EXPECT_EQ(3u, buckets[eventKey].size());
EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[eventKey][0].mDuration);
@@ -129,7 +138,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
ConditionKey key1;
ConditionKey conditionKey1;
conditionKey1["condition"] = conditionKey;
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -138,10 +148,10 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
bucketSizeNs, {});
// 2 starts
tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
tracker.noteStart("", true, bucketStartTimeNs + 10, key1);
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, conditionKey1);
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, conditionKey1);
// one stop
tracker.noteStop("", bucketStartTimeNs + 20, false /*stop all*/);
tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets);
@@ -151,7 +161,7 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
// real stop now.
tracker.noteStop("", 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());
@@ -163,10 +173,11 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
ConditionKey conditionKey1;
HashableDimensionKey eventKey = getMockedDimensionKey(2, "maps");
conditionKey1["APP_BACKGROUND"] = conditionKey;
EXPECT_CALL(*wizard, query(_, key1)) // #4
EXPECT_CALL(*wizard, query(_, conditionKey1)) // #4
.WillOnce(Return(ConditionState::kFalse));
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -180,11 +191,11 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
bucketSizeNs, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
tracker.noteStop(key1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -195,15 +206,15 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
Alert alert;
alert.set_name("alert");
alert.set_metric_name("1");
alert.set_metric_name("metric");
alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
alert.set_number_of_buckets(2);
alert.set_refractory_period_secs(1);
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
ConditionKey conditionKey1;
conditionKey1["APP_BACKGROUND"] = conditionKey;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -212,14 +223,14 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
MaxDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, -1, true, bucketStartTimeNs,
bucketSizeNs, {anomalyTracker});
tracker.noteStart("1", true, eventStartTimeNs, key1);
tracker.noteStop("1", eventStartTimeNs + 10, false);
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
tracker.noteStop(key1, eventStartTimeNs + 10, false);
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
EXPECT_EQ(10LL, tracker.mDuration);
tracker.noteStart("2", true, eventStartTimeNs + 20, key1);
tracker.noteStart(key2, true, eventStartTimeNs + 20, conditionKey1);
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, &buckets);
tracker.noteStop("2", eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
tracker.noteStop(key2, eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC, false);
EXPECT_EQ((long long)(4 * NS_PER_SEC + 1LL), tracker.mDuration);
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs,
(long long)(eventStartTimeNs + 2 * bucketSizeNs + 3 * NS_PER_SEC));

View File

@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/metrics/duration_helper/OringDurationTracker.h"
#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
#include "src/metrics/duration_helper/OringDurationTracker.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -35,13 +35,17 @@ namespace os {
namespace statsd {
const ConfigKey kConfigKey(0, "test");
const string eventKey = "event";
const HashableDimensionKey eventKey = getMockedDimensionKey(0, "event");
const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(1, "maps");
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(3, "maps");
TEST(OringDurationTrackerTest, TestDurationOverlap) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -53,12 +57,12 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1); // overlapping wl
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -70,7 +74,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -81,11 +85,11 @@ TEST(OringDurationTrackerTest, TestDurationNested) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, key1); // overlapping wl
tracker.noteStop("2:maps", eventStartTimeNs + 2000, false);
tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -97,7 +101,7 @@ TEST(OringDurationTrackerTest, TestStopAll) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -108,8 +112,8 @@ TEST(OringDurationTrackerTest, TestStopAll) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, key1); // overlapping wl
tracker.noteStopAll(eventStartTimeNs + 2003);
@@ -123,7 +127,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
@@ -135,18 +139,18 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, &buckets);
tracker.noteStart("2:maps", true, eventStartTimeNs + 2 * bucketSizeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, key1);
EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
EXPECT_EQ(2u, buckets[eventKey].size());
EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 10, false);
tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 12, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false);
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
EXPECT_EQ(2u, buckets[eventKey].size());
@@ -158,7 +162,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1)) // #4
.WillOnce(Return(ConditionState::kFalse));
@@ -173,11 +177,11 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -189,7 +193,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1))
.Times(2)
@@ -206,13 +210,13 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, false,
bucketStartTimeNs, bucketSizeNs, {});
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
// condition to false; record duration 5n
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
// condition to true.
tracker.onSlicedConditionMayChange(eventStartTimeNs + 1000);
// 2nd duration: 1000ns
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -224,7 +228,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
EXPECT_CALL(*wizard, query(_, key1)) // #4
.WillOnce(Return(ConditionState::kFalse));
@@ -238,14 +242,14 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true, bucketStartTimeNs,
bucketSizeNs, {});
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
tracker.noteStop("2:maps", eventStartTimeNs + 3, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false);
tracker.onSlicedConditionMayChange(eventStartTimeNs + 15);
tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -264,7 +268,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -274,22 +278,22 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
bucketSizeNs, {anomalyTracker});
// Nothing in the past bucket.
tracker.noteStart("", true, eventStartTimeNs, key1);
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
tracker.noteStop("", eventStartTimeNs + 3, false);
tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false);
EXPECT_EQ(0u, buckets[eventKey].size());
uint64_t event1StartTimeNs = eventStartTimeNs + 10;
tracker.noteStart("1", true, event1StartTimeNs, key1);
tracker.noteStart(kEventKey1, true, event1StartTimeNs, key1);
// No past buckets. The anomaly will happen in bucket #0.
EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
uint64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
tracker.flushIfNeeded(event1StopTimeNs, &buckets);
tracker.noteStop("1", event1StopTimeNs, false);
tracker.noteStop(kEventKey1, event1StopTimeNs, false);
EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
EXPECT_EQ(1u, buckets[eventKey].size());
@@ -301,16 +305,16 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
// One past buckets. The anomaly will happen in bucket #1.
uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
tracker.noteStart("1", true, event2StartTimeNs, key1);
tracker.noteStart(kEventKey1, true, event2StartTimeNs, key1);
EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
bucket1Duration),
tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
tracker.noteStop("1", event2StartTimeNs + 1, false);
tracker.noteStop(kEventKey1, event2StartTimeNs + 1, false);
// Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
// bucket #2.
uint64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
tracker.noteStart("1", true, event3StartTimeNs, key1);
tracker.noteStart(kEventKey1, true, event3StartTimeNs, key1);
EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
}
@@ -326,7 +330,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetection) {
unordered_map<HashableDimensionKey, vector<DurationBucket>> buckets;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey key1;
key1["APP_BACKGROUND"] = "1:maps|";
key1["APP_BACKGROUND"] = kConditionKey1;
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
@@ -335,21 +339,21 @@ TEST(OringDurationTrackerTest, TestAnomalyDetection) {
OringDurationTracker tracker(kConfigKey, "metric", eventKey, wizard, 1, true /*nesting*/,
bucketStartTimeNs, bucketSizeNs, {anomalyTracker});
tracker.noteStart("", true, eventStartTimeNs, key1);
tracker.noteStop("", eventStartTimeNs + 10, false);
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, key1);
tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 10, false);
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
EXPECT_TRUE(tracker.mStarted.empty());
EXPECT_EQ(10LL, tracker.mDuration);
EXPECT_EQ(0u, tracker.mStarted.size());
tracker.noteStart("", true, eventStartTimeNs + 20, key1);
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs + 20, key1);
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
EXPECT_EQ((long long)(51ULL * NS_PER_SEC),
(long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, &buckets);
tracker.noteStop("", eventStartTimeNs + 2 * bucketSizeNs + 25, false);
EXPECT_EQ(anomalyTracker->getSumOverPastBuckets("event"), (long long)(bucketSizeNs));
tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
anomalyTracker->mLastAlarmTimestampNs);
}

View File

@@ -0,0 +1,34 @@
// 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 "metrics_test_helper.h"
namespace android {
namespace os {
namespace statsd {
HashableDimensionKey getMockedDimensionKey(int key, string value) {
KeyValuePair pair;
pair.set_key(key);
pair.set_value_str(value);
vector<KeyValuePair> pairs;
pairs.push_back(pair);
return HashableDimensionKey(pairs);
}
} // namespace statsd
} // namespace os
} // namespace android

View File

@@ -38,6 +38,8 @@ public:
MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
};
HashableDimensionKey getMockedDimensionKey(int key, std::string value);
} // namespace statsd
} // namespace os
} // namespace android
} // namespace android