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:
@@ -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) \
|
||||
|
||||
110
cmds/statsd/src/HashableDimensionKey.cpp
Normal file
110
cmds/statsd/src/HashableDimensionKey.cpp
Normal 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
|
||||
102
cmds/statsd/src/HashableDimensionKey.h
Normal file
102
cmds/statsd/src/HashableDimensionKey.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
34
cmds/statsd/tests/metrics/metrics_test_helper.cpp
Normal file
34
cmds/statsd/tests/metrics/metrics_test_helper.cpp
Normal 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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user