Merge "1/ Duration anomaly tracker with alarm. 2/ Init anomaly from config based on the public language. 3/ Unit tests for anomaly detection in count/gauge producer. 4/ Revisit the duration tracker logic."
This commit is contained in:
committed by
Android (Google) Code Review
commit
f1f708f186
@@ -21,6 +21,7 @@ statsd_common_src := \
|
||||
src/statsd_config.proto \
|
||||
src/atoms_copy.proto \
|
||||
src/anomaly/AnomalyMonitor.cpp \
|
||||
src/anomaly/AnomalyTracker.cpp \
|
||||
src/condition/CombinationConditionTracker.cpp \
|
||||
src/condition/condition_util.cpp \
|
||||
src/condition/SimpleConditionTracker.cpp \
|
||||
@@ -39,7 +40,6 @@ statsd_common_src := \
|
||||
src/matchers/CombinationLogMatchingTracker.cpp \
|
||||
src/matchers/matcher_util.cpp \
|
||||
src/matchers/SimpleLogMatchingTracker.cpp \
|
||||
src/anomaly/DiscreteAnomalyTracker.cpp \
|
||||
src/metrics/MetricProducer.cpp \
|
||||
src/metrics/EventMetricProducer.cpp \
|
||||
src/metrics/CountMetricProducer.cpp \
|
||||
@@ -178,4 +178,3 @@ statsd_common_aidl_includes:=
|
||||
statsd_common_c_includes:=
|
||||
|
||||
include $(BUILD_NATIVE_TEST)
|
||||
|
||||
|
||||
@@ -50,13 +50,24 @@ const int FIELD_ID_UID = 1;
|
||||
const int FIELD_ID_NAME = 2;
|
||||
|
||||
StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
|
||||
const sp<AnomalyMonitor>& anomalyMonitor,
|
||||
const std::function<void(const ConfigKey&)>& sendBroadcast)
|
||||
: mUidMap(uidMap), mSendBroadcast(sendBroadcast) {
|
||||
: mUidMap(uidMap), mAnomalyMonitor(anomalyMonitor), mSendBroadcast(sendBroadcast) {
|
||||
}
|
||||
|
||||
StatsLogProcessor::~StatsLogProcessor() {
|
||||
}
|
||||
|
||||
void StatsLogProcessor::onAnomalyAlarmFired(
|
||||
const uint64_t timestampNs,
|
||||
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet) {
|
||||
for (const auto& anomaly : anomalySet) {
|
||||
for (const auto& itr : mMetricsManagers) {
|
||||
itr.second->onAnomalyAlarmFired(timestampNs, anomaly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: what if statsd service restarts? How do we know what logs are already processed before?
|
||||
void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
|
||||
// pass the event to metrics managers.
|
||||
@@ -93,6 +104,7 @@ void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig
|
||||
unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
|
||||
if (newMetricsManager->isConfigValid()) {
|
||||
mUidMap->OnConfigUpdated(key);
|
||||
newMetricsManager->setAnomalyMonitor(mAnomalyMonitor);
|
||||
mMetricsManagers[key] = std::move(newMetricsManager);
|
||||
// Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
|
||||
ALOGD("StatsdConfig valid");
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace statsd {
|
||||
|
||||
class StatsLogProcessor : public ConfigListener {
|
||||
public:
|
||||
StatsLogProcessor(const sp<UidMap>& uidMap,
|
||||
StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor,
|
||||
const std::function<void(const ConfigKey&)>& sendBroadcast);
|
||||
virtual ~StatsLogProcessor();
|
||||
|
||||
@@ -42,8 +42,11 @@ public:
|
||||
void OnConfigRemoved(const ConfigKey& key);
|
||||
|
||||
size_t GetMetricsSize(const ConfigKey& key);
|
||||
|
||||
|
||||
void onDumpReport(const ConfigKey& key, vector<uint8_t>* outData);
|
||||
void onAnomalyAlarmFired(
|
||||
const uint64_t timestampNs,
|
||||
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet);
|
||||
|
||||
private:
|
||||
mutable mutex mBroadcastTimesMutex;
|
||||
@@ -54,6 +57,8 @@ private:
|
||||
|
||||
sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid.
|
||||
|
||||
sp<AnomalyMonitor> mAnomalyMonitor;
|
||||
|
||||
/* Max *serialized* size of the logs kept in memory before flushing through binder call.
|
||||
Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
|
||||
So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
|
||||
|
||||
@@ -73,7 +73,7 @@ StatsService::StatsService(const sp<Looper>& handlerLooper)
|
||||
{
|
||||
mUidMap = new UidMap();
|
||||
mConfigManager = new ConfigManager();
|
||||
mProcessor = new StatsLogProcessor(mUidMap, [this](const ConfigKey& key) {
|
||||
mProcessor = new StatsLogProcessor(mUidMap, mAnomalyMonitor, [this](const ConfigKey& key) {
|
||||
auto sc = getStatsCompanionService();
|
||||
auto receiver = mConfigManager->GetConfigReceiver(key);
|
||||
if (sc == nullptr) {
|
||||
@@ -554,7 +554,10 @@ Status StatsService::informAnomalyAlarmFired() {
|
||||
|
||||
if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
|
||||
// TODO: check through all counters/timers and see if an anomaly has indeed occurred.
|
||||
|
||||
uint64_t currentTimeNs = time(nullptr) * NS_PER_SEC;
|
||||
std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet =
|
||||
mAnomalyMonitor->onAlarmFired(currentTimeNs);
|
||||
mProcessor->onAnomalyAlarmFired(currentTimeNs, anomalySet);
|
||||
return Status::ok();
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,11 @@ int64_t AnomalyMonitor::secToMs(uint32_t timeSec) {
|
||||
return ((int64_t)timeSec) * 1000;
|
||||
}
|
||||
|
||||
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::onAlarmFired(
|
||||
uint64_t timestampNs) {
|
||||
return popSoonerThan(static_cast<uint32_t>(timestampNs));
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANOMALY_MONITOR_H
|
||||
#define ANOMALY_MONITOR_H
|
||||
#pragma once
|
||||
|
||||
#include "anomaly/indexed_priority_queue.h"
|
||||
|
||||
@@ -23,6 +22,8 @@
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -114,6 +115,8 @@ public:
|
||||
return mRegisteredAlarmTimeSec;
|
||||
}
|
||||
|
||||
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> onAlarmFired(uint64_t timestampNs);
|
||||
|
||||
private:
|
||||
std::mutex mLock;
|
||||
|
||||
@@ -154,5 +157,3 @@ private:
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
#endif // ANOMALY_MONITOR_H
|
||||
|
||||
276
cmds/statsd/src/anomaly/AnomalyTracker.cpp
Normal file
276
cmds/statsd/src/anomaly/AnomalyTracker.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG true // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "AnomalyTracker.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
AnomalyTracker::AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs)
|
||||
: mAlert(alert),
|
||||
mBucketSizeNs(bucketSizeNs),
|
||||
mNumOfPastPackets(mAlert.number_of_buckets() - 1) {
|
||||
VLOG("AnomalyTracker() called");
|
||||
if (mAlert.number_of_buckets() <= 0) {
|
||||
ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
|
||||
(long long)mAlert.number_of_buckets());
|
||||
return;
|
||||
}
|
||||
if (mBucketSizeNs <= 0) {
|
||||
ALOGE("Cannot create DiscreteAnomalyTracker with bucket size %lld ",
|
||||
(long long)mBucketSizeNs);
|
||||
return;
|
||||
}
|
||||
if (!mAlert.has_trigger_if_sum_gt()) {
|
||||
ALOGE("Cannot create DiscreteAnomalyTracker without threshold");
|
||||
return;
|
||||
}
|
||||
reset(); // initialization
|
||||
}
|
||||
|
||||
AnomalyTracker::~AnomalyTracker() {
|
||||
VLOG("~AnomalyTracker() called");
|
||||
stopAllAlarms();
|
||||
}
|
||||
|
||||
void AnomalyTracker::reset() {
|
||||
VLOG("reset() called.");
|
||||
stopAllAlarms();
|
||||
mPastBuckets.clear();
|
||||
// Excludes the current bucket.
|
||||
mPastBuckets.resize(mNumOfPastPackets);
|
||||
mSumOverPastBuckets.clear();
|
||||
mMostRecentBucketNum = -1;
|
||||
mLastAlarmTimestampNs = -1;
|
||||
}
|
||||
|
||||
size_t AnomalyTracker::index(int64_t bucketNum) const {
|
||||
return bucketNum % mNumOfPastPackets;
|
||||
}
|
||||
|
||||
void AnomalyTracker::flushPastBuckets(const int64_t& latestPastBucketNum) {
|
||||
VLOG("addPastBucket() called.");
|
||||
if (latestPastBucketNum <= mMostRecentBucketNum - mNumOfPastPackets) {
|
||||
ALOGE("Cannot add a past bucket %lld units in past", (long long)latestPastBucketNum);
|
||||
return;
|
||||
}
|
||||
|
||||
// The past packets are ancient. Empty out old mPastBuckets[i] values and reset
|
||||
// mSumOverPastBuckets.
|
||||
if (latestPastBucketNum - mMostRecentBucketNum >= mNumOfPastPackets) {
|
||||
mPastBuckets.clear();
|
||||
mPastBuckets.resize(mNumOfPastPackets);
|
||||
mSumOverPastBuckets.clear();
|
||||
} else {
|
||||
for (int64_t i = std::max(0LL, (long long)(mMostRecentBucketNum - mNumOfPastPackets + 1));
|
||||
i <= latestPastBucketNum - mNumOfPastPackets; i++) {
|
||||
const int idx = index(i);
|
||||
subtractBucketFromSum(mPastBuckets[idx]);
|
||||
mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
|
||||
}
|
||||
}
|
||||
|
||||
// It is an update operation.
|
||||
if (latestPastBucketNum <= mMostRecentBucketNum &&
|
||||
latestPastBucketNum > mMostRecentBucketNum - mNumOfPastPackets) {
|
||||
subtractBucketFromSum(mPastBuckets[index(latestPastBucketNum)]);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
|
||||
const int64_t& bucketNum) {
|
||||
flushPastBuckets(bucketNum);
|
||||
|
||||
auto& bucket = mPastBuckets[index(bucketNum)];
|
||||
if (bucket == nullptr) {
|
||||
bucket = std::make_shared<DimToValMap>();
|
||||
}
|
||||
bucket->insert({key, bucketValue});
|
||||
addBucketToSum(bucket);
|
||||
mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
|
||||
}
|
||||
|
||||
void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucketValues,
|
||||
const int64_t& bucketNum) {
|
||||
VLOG("addPastBucket() called.");
|
||||
flushPastBuckets(bucketNum);
|
||||
// Replace the oldest bucket with the new bucket we are adding.
|
||||
mPastBuckets[index(bucketNum)] = bucketValues;
|
||||
addBucketToSum(bucketValues);
|
||||
mMostRecentBucketNum = std::max(mMostRecentBucketNum, bucketNum);
|
||||
}
|
||||
|
||||
void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
|
||||
if (bucket == nullptr) {
|
||||
return;
|
||||
}
|
||||
// For each dimension present in the bucket, subtract its value from its corresponding sum.
|
||||
for (const auto& keyValuePair : *bucket) {
|
||||
auto itr = mSumOverPastBuckets.find(keyValuePair.first);
|
||||
if (itr == mSumOverPastBuckets.end()) {
|
||||
continue;
|
||||
}
|
||||
itr->second -= keyValuePair.second;
|
||||
// TODO: No need to look up the object twice like this. Use a var.
|
||||
if (itr->second == 0) {
|
||||
mSumOverPastBuckets.erase(itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
|
||||
if (bucket == nullptr) {
|
||||
return;
|
||||
}
|
||||
// For each dimension present in the bucket, add its value to its corresponding sum.
|
||||
for (const auto& keyValuePair : *bucket) {
|
||||
mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t AnomalyTracker::getPastBucketValue(const HashableDimensionKey& key,
|
||||
const int64_t& bucketNum) const {
|
||||
const auto& bucket = mPastBuckets[index(bucketNum)];
|
||||
if (bucket == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
const auto& itr = bucket->find(key);
|
||||
return itr == bucket->end() ? 0 : itr->second;
|
||||
}
|
||||
|
||||
int64_t AnomalyTracker::getSumOverPastBuckets(const HashableDimensionKey& key) const {
|
||||
const auto& itr = mSumOverPastBuckets.find(key);
|
||||
if (itr != mSumOverPastBuckets.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
|
||||
const DimToValMap& currentBucket) {
|
||||
if (currentBucketNum > mMostRecentBucketNum + 1) {
|
||||
addPastBucket(nullptr, currentBucketNum - 1);
|
||||
}
|
||||
for (auto itr = currentBucket.begin(); itr != currentBucket.end(); itr++) {
|
||||
if (itr->second + getSumOverPastBuckets(itr->first) > mAlert.trigger_if_sum_gt()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// In theory, we also need to check the dimsions not in the current bucket. In single-thread
|
||||
// mode, usually we could avoid the following loops.
|
||||
for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
|
||||
if (itr->second > mAlert.trigger_if_sum_gt()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, const HashableDimensionKey& key,
|
||||
const int64_t& currentBucketValue) {
|
||||
if (currentBucketNum > mMostRecentBucketNum + 1) {
|
||||
addPastBucket(key, 0, currentBucketNum - 1);
|
||||
}
|
||||
return getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
|
||||
}
|
||||
|
||||
void AnomalyTracker::declareAnomaly(const uint64_t& timestamp) {
|
||||
if (mLastAlarmTimestampNs >= 0 &&
|
||||
timestamp - mLastAlarmTimestampNs <= mAlert.refractory_period_secs() * NS_PER_SEC) {
|
||||
VLOG("Skipping anomaly check since within refractory period");
|
||||
return;
|
||||
}
|
||||
// TODO(guardrail): Consider guarding against too short refractory periods.
|
||||
mLastAlarmTimestampNs = timestamp;
|
||||
|
||||
if (mAlert.has_incidentd_details()) {
|
||||
// TODO: Can construct a name based on the criteria (and/or relay the criteria).
|
||||
ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
|
||||
// TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
|
||||
} else {
|
||||
ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
|
||||
const uint64_t& timestamp) {
|
||||
auto itr = mAlarms.find(dimensionKey);
|
||||
if (itr == mAlarms.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itr->second != nullptr &&
|
||||
static_cast<uint32_t>(timestamp / NS_PER_SEC) >= itr->second->timestampSec) {
|
||||
declareAnomaly(timestamp);
|
||||
stopAlarm(dimensionKey);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
|
||||
const int64_t& currBucketNum,
|
||||
const HashableDimensionKey& key,
|
||||
const int64_t& currentBucketValue) {
|
||||
if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
|
||||
declareAnomaly(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::detectAndDeclareAnomaly(const uint64_t& timestamp,
|
||||
const int64_t& currBucketNum,
|
||||
const DimToValMap& currentBucket) {
|
||||
if (detectAnomaly(currBucketNum, currentBucket)) {
|
||||
declareAnomaly(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::startAlarm(const HashableDimensionKey& dimensionKey,
|
||||
const uint64_t& timestamp) {
|
||||
sp<const AnomalyAlarm> alarm = new AnomalyAlarm{static_cast<uint32_t>(timestamp / NS_PER_SEC)};
|
||||
mAlarms.insert({dimensionKey, alarm});
|
||||
if (mAnomalyMonitor != nullptr) {
|
||||
mAnomalyMonitor->add(alarm);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::stopAlarm(const HashableDimensionKey& dimensionKey) {
|
||||
auto itr = mAlarms.find(dimensionKey);
|
||||
if (itr != mAlarms.end()) {
|
||||
mAlarms.erase(dimensionKey);
|
||||
}
|
||||
if (mAnomalyMonitor != nullptr) {
|
||||
mAnomalyMonitor->remove(itr->second);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::stopAllAlarms() {
|
||||
std::set<HashableDimensionKey> keys;
|
||||
for (auto itr = mAlarms.begin(); itr != mAlarms.end(); ++itr) {
|
||||
keys.insert(itr->first);
|
||||
}
|
||||
for (auto key : keys) {
|
||||
stopAlarm(key);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
156
cmds/statsd/src/anomaly/AnomalyTracker.h
Normal file
156
cmds/statsd/src/anomaly/AnomalyTracker.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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 <gtest/gtest_prod.h>
|
||||
#include "AnomalyMonitor.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
|
||||
#include "stats_util.h" // HashableDimensionKey and DimToValMap
|
||||
|
||||
#include <memory> // unique_ptr
|
||||
#include <stdlib.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::unordered_map;
|
||||
using std::shared_ptr;
|
||||
|
||||
// This anomaly track assmues that all values are non-negative.
|
||||
class AnomalyTracker : public virtual RefBase {
|
||||
public:
|
||||
AnomalyTracker(const Alert& alert, const int64_t& bucketSizeNs);
|
||||
|
||||
virtual ~AnomalyTracker();
|
||||
|
||||
// Adds a bucket.
|
||||
// Bucket index starts from 0.
|
||||
void addPastBucket(std::shared_ptr<DimToValMap> bucketValues, const int64_t& bucketNum);
|
||||
void addPastBucket(const HashableDimensionKey& key, const int64_t& bucketValue,
|
||||
const int64_t& bucketNum);
|
||||
|
||||
// Returns true if detected anomaly for the existing buckets on one or more dimension keys.
|
||||
bool detectAnomaly(const int64_t& currBucketNum, const DimToValMap& currentBucket);
|
||||
bool detectAnomaly(const int64_t& currBucketNum, const HashableDimensionKey& key,
|
||||
const int64_t& currentBucketValue);
|
||||
|
||||
// Informs incidentd about the detected alert.
|
||||
void declareAnomaly(const uint64_t& timestamp);
|
||||
|
||||
// Detects the alert and informs the incidentd when applicable.
|
||||
void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
|
||||
const DimToValMap& currentBucket);
|
||||
void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
|
||||
const HashableDimensionKey& key,
|
||||
const int64_t& currentBucketValue);
|
||||
|
||||
// Starts the alarm at the given timestamp.
|
||||
void startAlarm(const HashableDimensionKey& dimensionKey, const uint64_t& eventTime);
|
||||
// Stops the alarm.
|
||||
void stopAlarm(const HashableDimensionKey& dimensionKey);
|
||||
|
||||
// Stop all the alarms owned by this tracker.
|
||||
void stopAllAlarms();
|
||||
|
||||
// Init the anmaly monitor which is shared across anomaly trackers.
|
||||
inline void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
|
||||
mAnomalyMonitor = anomalyMonitor;
|
||||
}
|
||||
|
||||
// Declares the anomaly when the alarm expired given the current timestamp.
|
||||
void declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey,
|
||||
const uint64_t& timestamp);
|
||||
|
||||
// Helper function to return the sum value of past buckets at given dimension.
|
||||
int64_t getSumOverPastBuckets(const HashableDimensionKey& key) const;
|
||||
|
||||
// Helper function to return the value for a past bucket.
|
||||
int64_t getPastBucketValue(const HashableDimensionKey& key, const int64_t& bucketNum) const;
|
||||
|
||||
// Returns the anomaly threshold.
|
||||
inline int64_t getAnomalyThreshold() const {
|
||||
return mAlert.trigger_if_sum_gt();
|
||||
}
|
||||
|
||||
// Helper function to return the last alarm timestamp.
|
||||
inline int64_t getLastAlarmTimestampNs() const {
|
||||
return mLastAlarmTimestampNs;
|
||||
}
|
||||
|
||||
inline int getNumOfPastPackets() const {
|
||||
return mNumOfPastPackets;
|
||||
}
|
||||
|
||||
protected:
|
||||
void flushPastBuckets(const int64_t& currBucketNum);
|
||||
// statsd_config.proto Alert message that defines this tracker.
|
||||
const Alert mAlert;
|
||||
|
||||
// Bucket duration in ns.
|
||||
int64_t mBucketSizeNs = 0;
|
||||
|
||||
// The number of past packets to track in the anomaly detection.
|
||||
int mNumOfPastPackets = 0;
|
||||
|
||||
// The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
|
||||
// are still active.
|
||||
std::unordered_map<HashableDimensionKey, sp<const AnomalyAlarm>> mAlarms;
|
||||
|
||||
// Anomaly alarm monitor.
|
||||
sp<AnomalyMonitor> mAnomalyMonitor;
|
||||
|
||||
// The exisiting bucket list.
|
||||
std::vector<shared_ptr<DimToValMap>> mPastBuckets;
|
||||
|
||||
// Sum over all existing buckets cached in mPastBuckets.
|
||||
DimToValMap mSumOverPastBuckets;
|
||||
|
||||
// The bucket number of the last added bucket.
|
||||
int64_t mMostRecentBucketNum = -1;
|
||||
|
||||
// The timestamp when the last anomaly was declared.
|
||||
int64_t mLastAlarmTimestampNs = -1;
|
||||
|
||||
// Add the information in the given bucket to mSumOverPastBuckets.
|
||||
void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
|
||||
|
||||
// Subtract the information in the given bucket from mSumOverPastBuckets
|
||||
// and remove any items with value 0.
|
||||
void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
|
||||
|
||||
// Calculates the corresponding bucket index within the circular array.
|
||||
size_t index(int64_t bucketNum) const;
|
||||
|
||||
// Resets all data. For use when all the data gets stale.
|
||||
void reset();
|
||||
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,153 +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.
|
||||
*/
|
||||
|
||||
#define DEBUG true // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "DiscreteAnomalyTracker.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
DiscreteAnomalyTracker::DiscreteAnomalyTracker(const Alert& alert) : mAlert(alert) {
|
||||
VLOG("DiscreteAnomalyTracker() called");
|
||||
if (mAlert.number_of_buckets() <= 0) {
|
||||
ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
|
||||
(long long)mAlert.number_of_buckets());
|
||||
return;
|
||||
}
|
||||
mPastBuckets.resize(mAlert.number_of_buckets());
|
||||
reset(); // initialization
|
||||
}
|
||||
|
||||
DiscreteAnomalyTracker::~DiscreteAnomalyTracker() {
|
||||
VLOG("~DiscreteAnomalyTracker() called");
|
||||
}
|
||||
|
||||
void DiscreteAnomalyTracker::reset() {
|
||||
VLOG("reset() called.");
|
||||
mPastBuckets.clear();
|
||||
mPastBuckets.resize(mAlert.number_of_buckets());
|
||||
mSumOverPastBuckets.clear();
|
||||
mCurrentBucketIndex = -1;
|
||||
mLastAlarmAtBucketIndex = -1;
|
||||
mAnomalyDeclared = 0;
|
||||
}
|
||||
|
||||
size_t DiscreteAnomalyTracker::index(int64_t bucketNum) {
|
||||
return bucketNum % mAlert.number_of_buckets();
|
||||
}
|
||||
|
||||
void DiscreteAnomalyTracker::addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues,
|
||||
int64_t bucketIndex) {
|
||||
VLOG("addPastBucket() called.");
|
||||
if (bucketIndex <= mCurrentBucketIndex - mAlert.number_of_buckets()) {
|
||||
ALOGE("Cannot add a past bucket %lld units in past", (long long)bucketIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty out old mPastBuckets[i] values and update mSumOverPastBuckets.
|
||||
if (bucketIndex - mCurrentBucketIndex >= mAlert.number_of_buckets()) {
|
||||
mPastBuckets.clear();
|
||||
mPastBuckets.resize(mAlert.number_of_buckets());
|
||||
mSumOverPastBuckets.clear();
|
||||
} else {
|
||||
for (int64_t i = std::max(
|
||||
0LL, (long long)(mCurrentBucketIndex - mAlert.number_of_buckets() + 1));
|
||||
i < bucketIndex - mAlert.number_of_buckets(); i++) {
|
||||
const int idx = index(i);
|
||||
subtractBucketFromSum(mPastBuckets[idx]);
|
||||
mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
|
||||
}
|
||||
}
|
||||
subtractBucketFromSum(mPastBuckets[index(bucketIndex)]);
|
||||
mPastBuckets[index(bucketIndex)] = nullptr; // release (but not clear) the old bucket.
|
||||
|
||||
// Replace the oldest bucket with the new bucket we are adding.
|
||||
mPastBuckets[index(bucketIndex)] = BucketValues;
|
||||
addBucketToSum(BucketValues);
|
||||
|
||||
mCurrentBucketIndex = std::max(mCurrentBucketIndex, bucketIndex);
|
||||
}
|
||||
|
||||
void DiscreteAnomalyTracker::subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket) {
|
||||
if (bucket == nullptr) {
|
||||
return;
|
||||
}
|
||||
// For each dimension present in the bucket, subtract its value from its corresponding sum.
|
||||
for (const auto& keyValuePair : *bucket) {
|
||||
auto itr = mSumOverPastBuckets.find(keyValuePair.first);
|
||||
if (itr == mSumOverPastBuckets.end()) {
|
||||
continue;
|
||||
}
|
||||
itr->second -= keyValuePair.second;
|
||||
// TODO: No need to look up the object twice like this. Use a var.
|
||||
if (itr->second == 0) {
|
||||
mSumOverPastBuckets.erase(itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteAnomalyTracker::addBucketToSum(const shared_ptr<const DimToValMap>& bucket) {
|
||||
if (bucket == nullptr) {
|
||||
return;
|
||||
}
|
||||
// For each dimension present in the bucket, add its value to its corresponding sum.
|
||||
for (const auto& keyValuePair : *bucket) {
|
||||
mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
|
||||
}
|
||||
}
|
||||
|
||||
bool DiscreteAnomalyTracker::detectAnomaly() {
|
||||
for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
|
||||
if (mAlert.has_trigger_if_sum_gt() && itr->second > mAlert.trigger_if_sum_gt()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DiscreteAnomalyTracker::declareAndDeclareAnomaly() {
|
||||
if (detectAnomaly()) {
|
||||
declareAnomaly();
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteAnomalyTracker::declareAnomaly() {
|
||||
if (mLastAlarmAtBucketIndex >= 0 && mCurrentBucketIndex - mLastAlarmAtBucketIndex <=
|
||||
(long long)mAlert.refractory_period_in_buckets()) {
|
||||
VLOG("Skipping anomaly check since within refractory period");
|
||||
return;
|
||||
}
|
||||
mAnomalyDeclared++;
|
||||
// TODO(guardrail): Consider guarding against too short refractory periods.
|
||||
mLastAlarmAtBucketIndex = mCurrentBucketIndex;
|
||||
|
||||
if (mAlert.has_incidentd_details()) {
|
||||
// TODO: Can construct a name based on the criteria (and/or relay the criteria).
|
||||
ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
|
||||
// TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
|
||||
} else {
|
||||
ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,91 +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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
|
||||
#include "stats_util.h" // HashableDimensionKey and DimToValMap
|
||||
|
||||
#include <memory> // unique_ptr
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::unordered_map;
|
||||
using std::shared_ptr;
|
||||
|
||||
// This anomaly track assmues that all values are non-negative.
|
||||
class DiscreteAnomalyTracker {
|
||||
public:
|
||||
DiscreteAnomalyTracker(const Alert& alert);
|
||||
|
||||
virtual ~DiscreteAnomalyTracker();
|
||||
|
||||
// Adds a new bucket or updates an existing bucket.
|
||||
// Bucket index starts from 0.
|
||||
void addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues, int64_t bucketIndex);
|
||||
|
||||
// Returns true if detected anomaly for the existing buckets on one or more dimension keys.
|
||||
bool detectAnomaly();
|
||||
|
||||
// Informs incidentd about the detected alert.
|
||||
void declareAnomaly();
|
||||
|
||||
// Detects the alert and informs the incidentd when applicable.
|
||||
void declareAndDeclareAnomaly();
|
||||
|
||||
private:
|
||||
// statsd_config.proto Alert message that defines this tracker.
|
||||
const Alert mAlert;
|
||||
|
||||
// The exisiting bucket list.
|
||||
std::vector<shared_ptr<const DimToValMap>> mPastBuckets;
|
||||
|
||||
// Sum over all existing buckets cached in mPastBuckets.
|
||||
DimToValMap mSumOverPastBuckets;
|
||||
|
||||
// Current bucket index of the current anomaly detection window. Bucket index starts from 0.
|
||||
int64_t mCurrentBucketIndex = -1;
|
||||
|
||||
// The bucket index when the last anomaly was declared.
|
||||
int64_t mLastAlarmAtBucketIndex = -1;
|
||||
|
||||
// The total number of declared anomalies.
|
||||
int64_t mAnomalyDeclared = 0;
|
||||
|
||||
// Add the information in the given bucket to mSumOverPastBuckets.
|
||||
void addBucketToSum(const shared_ptr<const DimToValMap>& bucket);
|
||||
|
||||
// Subtract the information in the given bucket from mSumOverPastBuckets
|
||||
// and remove any items with value 0.
|
||||
void subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket);
|
||||
|
||||
// Calculates the corresponding bucket index within the circular array.
|
||||
size_t index(int64_t bucketNum);
|
||||
|
||||
// Resets all data. For use when all the data gets stale.
|
||||
void reset();
|
||||
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -28,7 +26,7 @@ namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
/** Defines a hash function for sp<AA>, returning the hash of the underlying pointer. */
|
||||
/** Defines a hash function for sp<const AA>, returning the hash of the underlying pointer. */
|
||||
template <class AA>
|
||||
struct SpHash {
|
||||
size_t operator()(const sp<const AA>& k) const {
|
||||
@@ -39,7 +37,7 @@ struct SpHash {
|
||||
/**
|
||||
* Min priority queue for generic type AA.
|
||||
* Unlike a regular priority queue, this class is also capable of removing interior elements.
|
||||
* @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
|
||||
* @tparam Comparator must implement [bool operator()(sp< AA> a, sp< AA> b)], returning
|
||||
* whether a should be closer to the top of the queue than b.
|
||||
*/
|
||||
template <class AA, class Comparator>
|
||||
@@ -104,7 +102,6 @@ void indexed_priority_queue<AA, Comparator>::remove(sp<const AA> a) {
|
||||
if (!contains(a)) return;
|
||||
size_t idx = indices[a];
|
||||
if (idx >= pq.size()) {
|
||||
ALOGE("indexed_priority_queue: Invalid index in map of indices.");
|
||||
return;
|
||||
}
|
||||
if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
|
||||
@@ -193,7 +190,6 @@ void indexed_priority_queue<AA, Comparator>::sift_down(size_t idx) {
|
||||
template <class AA, class Comparator>
|
||||
bool indexed_priority_queue<AA, Comparator>::higher(size_t idx1, size_t idx2) const {
|
||||
if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
|
||||
ALOGE("indexed_priority_queue: Attempting to access invalid index");
|
||||
return false; // got to do something.
|
||||
}
|
||||
return Comparator()(pq[idx1], pq[idx2]);
|
||||
@@ -208,7 +204,6 @@ bool indexed_priority_queue<AA, Comparator>::contains(sp<const AA> a) const {
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::swap_indices(size_t i, size_t j) {
|
||||
if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
|
||||
ALOGE("indexed_priority_queue: Attempting to swap invalid index");
|
||||
return;
|
||||
}
|
||||
sp<const AA> val_i = pq[i];
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#define DEBUG true // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "../anomaly/DiscreteAnomalyTracker.h"
|
||||
#include "CountMetricProducer.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
@@ -114,7 +113,7 @@ std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() {
|
||||
// Dump current bucket if it's stale.
|
||||
// If current bucket is still on-going, don't force dump current bucket.
|
||||
// In finish(), We can force dump current bucket.
|
||||
flushCounterIfNeeded(endTime);
|
||||
flushIfNeeded(endTime);
|
||||
VLOG("metric %s dump report now...", mMetric.name().c_str());
|
||||
|
||||
for (const auto& counter : mPastBuckets) {
|
||||
@@ -170,7 +169,6 @@ std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() {
|
||||
|
||||
startNewProtoOutputStream(endTime);
|
||||
mPastBuckets.clear();
|
||||
mByteSize = 0;
|
||||
|
||||
return buffer;
|
||||
|
||||
@@ -188,7 +186,7 @@ void CountMetricProducer::onMatchedLogEventInternal(
|
||||
const LogEvent& event, bool scheduledPull) {
|
||||
uint64_t eventTimeNs = event.GetTimestampNs();
|
||||
|
||||
flushCounterIfNeeded(eventTimeNs);
|
||||
flushIfNeeded(eventTimeNs);
|
||||
|
||||
if (condition == false) {
|
||||
return;
|
||||
@@ -205,41 +203,41 @@ void CountMetricProducer::onMatchedLogEventInternal(
|
||||
count++;
|
||||
}
|
||||
|
||||
VLOG("metric %s %s->%d", mMetric.name().c_str(), eventKey.c_str(),
|
||||
(*mCurrentSlicedCounter)[eventKey]);
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
|
||||
mCurrentSlicedCounter->find(eventKey)->second);
|
||||
}
|
||||
|
||||
VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(),
|
||||
(long long)(*mCurrentSlicedCounter)[eventKey]);
|
||||
}
|
||||
|
||||
// When a new matched event comes in, we check if event falls into the current
|
||||
// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
|
||||
void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
|
||||
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
|
||||
void CountMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
|
||||
if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// adjust the bucket start time
|
||||
// TODO: This (and addPastBucket to which it goes) doesn't really need to be an int64.
|
||||
uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
|
||||
|
||||
CountBucket info;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
|
||||
info.mBucketNum = mCurrentBucketNum;
|
||||
for (const auto& counter : *mCurrentSlicedCounter) {
|
||||
info.mCount = counter.second;
|
||||
auto& bucketList = mPastBuckets[counter.first];
|
||||
bucketList.push_back(info);
|
||||
VLOG("metric %s, dump key value: %s -> %d", mMetric.name().c_str(), counter.first.c_str(),
|
||||
counter.second);
|
||||
mByteSize += sizeof(info);
|
||||
VLOG("metric %s, dump key value: %s -> %lld", mMetric.name().c_str(), counter.first.c_str(),
|
||||
(long long)counter.second);
|
||||
}
|
||||
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->addOrUpdateBucket(mCurrentSlicedCounter, mCurrentBucketNum);
|
||||
tracker->declareAndDeclareAnomaly();
|
||||
tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
|
||||
}
|
||||
|
||||
// Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
|
||||
mCurrentSlicedCounter = std::make_shared<DimToValMap>();
|
||||
|
||||
uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
|
||||
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
|
||||
@@ -250,7 +248,11 @@ void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
|
||||
// greater than actual data size as it contains each dimension of
|
||||
// CountMetricData is duplicated.
|
||||
size_t CountMetricProducer::byteSize() {
|
||||
return mByteSize;
|
||||
size_t totalSize = 0;
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
totalSize += pair.second.size() * kBucketSize;
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
#include <android/util/ProtoOutputStream.h>
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include "../anomaly/AnomalyTracker.h"
|
||||
#include "../condition/ConditionTracker.h"
|
||||
#include "../matchers/matcher_util.h"
|
||||
#include "../anomaly/DiscreteAnomalyTracker.h"
|
||||
#include "MetricProducer.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "stats_util.h"
|
||||
@@ -36,6 +36,7 @@ struct CountBucket {
|
||||
int64_t mBucketStartNs;
|
||||
int64_t mBucketEndNs;
|
||||
int64_t mCount;
|
||||
uint64_t mBucketNum;
|
||||
};
|
||||
|
||||
class CountMetricProducer : public MetricProducer {
|
||||
@@ -50,6 +51,8 @@ public:
|
||||
|
||||
void finish() override;
|
||||
|
||||
void flushIfNeeded(const uint64_t newEventTime) override;
|
||||
|
||||
// TODO: Pass a timestamp as a parameter in onDumpReport.
|
||||
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
|
||||
|
||||
@@ -76,18 +79,15 @@ private:
|
||||
// TODO: Add a lock to mPastBuckets.
|
||||
std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
|
||||
|
||||
size_t mByteSize;
|
||||
|
||||
// The current bucket.
|
||||
std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
|
||||
|
||||
vector<std::unique_ptr<DiscreteAnomalyTracker>> mAnomalyTrackers;
|
||||
|
||||
void flushCounterIfNeeded(const uint64_t newEventTime);
|
||||
static const size_t kBucketSize = sizeof(CountBucket{});
|
||||
|
||||
FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetection);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -110,16 +110,16 @@ void DurationMetricProducer::startNewProtoOutputStream(long long startTime) {
|
||||
}
|
||||
|
||||
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
|
||||
vector<DurationBucket>& bucket) {
|
||||
const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) {
|
||||
switch (mMetric.aggregation_type()) {
|
||||
case DurationMetric_AggregationType_SUM:
|
||||
return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
|
||||
mCurrentBucketStartTimeNs, mBucketSizeNs,
|
||||
bucket);
|
||||
return make_unique<OringDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
|
||||
mNested, mCurrentBucketStartTimeNs,
|
||||
mBucketSizeNs, mAnomalyTrackers, bucket);
|
||||
case DurationMetric_AggregationType_MAX_SPARSE:
|
||||
return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
|
||||
mCurrentBucketStartTimeNs, mBucketSizeNs,
|
||||
bucket);
|
||||
return make_unique<MaxDurationTracker>(eventKey, mWizard, mConditionTrackerIndex,
|
||||
mNested, mCurrentBucketStartTimeNs,
|
||||
mBucketSizeNs, mAnomalyTrackers, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,6 @@ void DurationMetricProducer::finish() {
|
||||
void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
|
||||
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
|
||||
// Now for each of the on-going event, check if the condition has changed for them.
|
||||
flushIfNeeded(eventTime);
|
||||
for (auto& pair : mCurrentSlicedDuration) {
|
||||
pair.second->onSlicedConditionMayChange(eventTime);
|
||||
}
|
||||
@@ -142,27 +141,11 @@ void DurationMetricProducer::onConditionChanged(const bool conditionMet, const u
|
||||
mCondition = conditionMet;
|
||||
// TODO: need to populate the condition change time from the event which triggers the condition
|
||||
// change, instead of using current time.
|
||||
|
||||
flushIfNeeded(eventTime);
|
||||
for (auto& pair : mCurrentSlicedDuration) {
|
||||
pair.second->onConditionChanged(conditionMet, eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
|
||||
const vector<KeyValuePair>& key,
|
||||
const vector<DurationBucketInfo>& buckets) {
|
||||
DurationMetricData* data = wrapper.add_data();
|
||||
for (const auto& kv : key) {
|
||||
data->add_dimension()->CopyFrom(kv);
|
||||
}
|
||||
for (const auto& bucket : buckets) {
|
||||
data->add_bucket_info()->CopyFrom(bucket);
|
||||
VLOG("\t bucket [%lld - %lld] duration(ns): %lld", bucket.start_bucket_nanos(),
|
||||
bucket.end_bucket_nanos(), bucket.duration_nanos());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
|
||||
long long endTime = time(nullptr) * NS_PER_SEC;
|
||||
|
||||
@@ -231,7 +214,6 @@ void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
|
||||
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
VLOG("flushing...........");
|
||||
for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end();) {
|
||||
if (it->second->flushIfNeeded(eventTime)) {
|
||||
@@ -244,6 +226,7 @@ void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
|
||||
|
||||
int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
|
||||
mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onMatchedLogEventInternal(
|
||||
@@ -262,7 +245,7 @@ void DurationMetricProducer::onMatchedLogEventInternal(
|
||||
HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
|
||||
|
||||
if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
|
||||
mCurrentSlicedDuration[eventKey] = createDurationTracker(mPastBuckets[eventKey]);
|
||||
mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey, mPastBuckets[eventKey]);
|
||||
}
|
||||
|
||||
auto it = mCurrentSlicedDuration.find(eventKey);
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef DURATION_METRIC_PRODUCER_H
|
||||
#define DURATION_METRIC_PRODUCER_H
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
|
||||
|
||||
void finish() override;
|
||||
void flushIfNeeded(const uint64_t newEventTime) override;
|
||||
|
||||
// TODO: Pass a timestamp as a parameter in onDumpReport.
|
||||
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
|
||||
@@ -95,11 +96,8 @@ private:
|
||||
std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
|
||||
mCurrentSlicedDuration;
|
||||
|
||||
void flushDurationIfNeeded(const uint64_t newEventTime);
|
||||
|
||||
std::unique_ptr<DurationTracker> createDurationTracker(std::vector<DurationBucket>& bucket);
|
||||
|
||||
void flushIfNeeded(uint64_t timestamp);
|
||||
std::unique_ptr<DurationTracker> createDurationTracker(const HashableDimensionKey& eventKey,
|
||||
std::vector<DurationBucket>& bucket);
|
||||
|
||||
static const size_t kBucketSize = sizeof(DurationBucket{});
|
||||
};
|
||||
@@ -108,4 +106,3 @@ private:
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
#endif // DURATION_METRIC_PRODUCER_H
|
||||
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
|
||||
|
||||
void finish() override;
|
||||
void flushIfNeeded(const uint64_t newEventTime) override {
|
||||
}
|
||||
|
||||
// TODO: Pass a timestamp as a parameter in onDumpReport.
|
||||
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
|
||||
|
||||
@@ -63,8 +63,9 @@ const int FIELD_ID_END_BUCKET_NANOS = 2;
|
||||
const int FIELD_ID_GAUGE = 3;
|
||||
|
||||
GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
|
||||
const sp<ConditionWizard>& wizard, const int pullTagId)
|
||||
: MetricProducer((time(nullptr) * NS_PER_SEC), conditionIndex, wizard),
|
||||
const sp<ConditionWizard>& wizard, const int pullTagId,
|
||||
const int64_t startTimeNs)
|
||||
: MetricProducer(startTimeNs, conditionIndex, wizard),
|
||||
mMetric(metric),
|
||||
mPullTagId(pullTagId) {
|
||||
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
|
||||
@@ -114,7 +115,7 @@ std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
|
||||
// Dump current bucket if it's stale.
|
||||
// If current bucket is still on-going, don't force dump current bucket.
|
||||
// In finish(), We can force dump current bucket.
|
||||
flushGaugeIfNeededLocked(time(nullptr) * NS_PER_SEC);
|
||||
flushIfNeeded(time(nullptr) * NS_PER_SEC);
|
||||
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
const HashableDimensionKey& hashableKey = pair.first;
|
||||
@@ -168,7 +169,6 @@ std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
|
||||
|
||||
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
|
||||
mPastBuckets.clear();
|
||||
mByteSize = 0;
|
||||
|
||||
return buffer;
|
||||
|
||||
@@ -178,15 +178,18 @@ std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
|
||||
void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
|
||||
AutoMutex _l(mLock);
|
||||
VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
|
||||
flushIfNeeded(eventTime);
|
||||
mCondition = conditionMet;
|
||||
|
||||
// Push mode. Nothing to do.
|
||||
// Push mode. No need to proactively pull the gauge data.
|
||||
if (mPullTagId == -1) {
|
||||
return;
|
||||
}
|
||||
// If (1) the condition is not met or (2) we already pulled the gauge metric in the current
|
||||
// bucket, do not pull gauge again.
|
||||
if (!mCondition || mCurrentSlicedBucket.size() > 0) {
|
||||
if (!mCondition) {
|
||||
return;
|
||||
}
|
||||
// Already have gauge metric for the current bucket, do not do it again.
|
||||
if (mCurrentSlicedBucket->size() > 0) {
|
||||
return;
|
||||
}
|
||||
vector<std::shared_ptr<LogEvent>> allData;
|
||||
@@ -197,16 +200,16 @@ void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint
|
||||
for (const auto& data : allData) {
|
||||
onMatchedLogEvent(0, *data, false /*scheduledPull*/);
|
||||
}
|
||||
flushGaugeIfNeededLocked(eventTime);
|
||||
flushIfNeeded(eventTime);
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
|
||||
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
|
||||
}
|
||||
|
||||
long GaugeMetricProducer::getGauge(const LogEvent& event) {
|
||||
int64_t GaugeMetricProducer::getGauge(const LogEvent& event) {
|
||||
status_t err = NO_ERROR;
|
||||
long val = event.GetLong(mMetric.gauge_field(), &err);
|
||||
int64_t val = event.GetLong(mMetric.gauge_field(), &err);
|
||||
if (err == NO_ERROR) {
|
||||
return val;
|
||||
} else {
|
||||
@@ -217,14 +220,9 @@ long GaugeMetricProducer::getGauge(const LogEvent& event) {
|
||||
|
||||
void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
|
||||
AutoMutex mutex(mLock);
|
||||
if (allData.size() == 0) {
|
||||
return;
|
||||
}
|
||||
for (const auto& data : allData) {
|
||||
onMatchedLogEvent(0, *data, true /*scheduledPull*/);
|
||||
}
|
||||
uint64_t eventTime = allData.at(0)->GetTimestampNs();
|
||||
flushGaugeIfNeededLocked(eventTime);
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onMatchedLogEventInternal(
|
||||
@@ -241,15 +239,21 @@ void GaugeMetricProducer::onMatchedLogEventInternal(
|
||||
return;
|
||||
}
|
||||
|
||||
// For gauge metric, we just simply use the latest guage in the given bucket.
|
||||
const long gauge = getGauge(event);
|
||||
if (gauge < 0) {
|
||||
VLOG("Invalid gauge at event Time: %lld", (long long)eventTimeNs);
|
||||
// When the event happens in a new bucket, flush the old buckets.
|
||||
if (eventTimeNs >= mCurrentBucketStartTimeNs + mBucketSizeNs) {
|
||||
flushIfNeeded(eventTimeNs);
|
||||
}
|
||||
|
||||
// For gauge metric, we just simply use the first guage in the given bucket.
|
||||
if (!mCurrentSlicedBucket->empty()) {
|
||||
return;
|
||||
}
|
||||
mCurrentSlicedBucket[eventKey] = gauge;
|
||||
if (mPullTagId < 0) {
|
||||
flushGaugeIfNeededLocked(eventTimeNs);
|
||||
const long gauge = getGauge(event);
|
||||
if (gauge >= 0) {
|
||||
(*mCurrentSlicedBucket)[eventKey] = gauge;
|
||||
}
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, gauge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,39 +262,45 @@ void GaugeMetricProducer::onMatchedLogEventInternal(
|
||||
// bucket.
|
||||
// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
|
||||
// the GaugeMetricProducer while holding the lock.
|
||||
void GaugeMetricProducer::flushGaugeIfNeededLocked(const uint64_t eventTimeNs) {
|
||||
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
|
||||
VLOG("event time is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
|
||||
(long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
|
||||
void GaugeMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
|
||||
if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjusts the bucket start time
|
||||
int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
|
||||
|
||||
GaugeBucket info;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
|
||||
info.mBucketNum = mCurrentBucketNum;
|
||||
|
||||
for (const auto& slice : mCurrentSlicedBucket) {
|
||||
for (const auto& slice : *mCurrentSlicedBucket) {
|
||||
info.mGauge = slice.second;
|
||||
auto& bucketList = mPastBuckets[slice.first];
|
||||
bucketList.push_back(info);
|
||||
mByteSize += sizeof(info);
|
||||
|
||||
VLOG("gauge metric %s, dump key value: %s -> %ld", mMetric.name().c_str(),
|
||||
slice.first.c_str(), slice.second);
|
||||
VLOG("gauge metric %s, dump key value: %s -> %lld", mMetric.name().c_str(),
|
||||
slice.first.c_str(), (long long)slice.second);
|
||||
}
|
||||
// Reset counters
|
||||
mCurrentSlicedBucket.clear();
|
||||
|
||||
// Reset counters
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->addPastBucket(mCurrentSlicedBucket, mCurrentBucketNum);
|
||||
}
|
||||
|
||||
mCurrentSlicedBucket = std::make_shared<DimToValMap>();
|
||||
|
||||
// Adjusts the bucket start time
|
||||
int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
|
||||
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
|
||||
(long long)mCurrentBucketStartTimeNs);
|
||||
}
|
||||
|
||||
size_t GaugeMetricProducer::byteSize() {
|
||||
return mByteSize;
|
||||
size_t totalSize = 0;
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
totalSize += pair.second.size() * kBucketSize;
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include <android/util/ProtoOutputStream.h>
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include "../condition/ConditionTracker.h"
|
||||
#include "../external/PullDataReceiver.h"
|
||||
#include "../external/StatsPullerManager.h"
|
||||
@@ -35,6 +36,7 @@ struct GaugeBucket {
|
||||
int64_t mBucketStartNs;
|
||||
int64_t mBucketEndNs;
|
||||
int64_t mGauge;
|
||||
uint64_t mBucketNum;
|
||||
};
|
||||
|
||||
// This gauge metric producer first register the puller to automatically pull the gauge at the
|
||||
@@ -46,7 +48,8 @@ public:
|
||||
// TODO: Pass in the start time from MetricsManager, it should be consistent
|
||||
// for all metrics.
|
||||
GaugeMetricProducer(const GaugeMetric& countMetric, const int conditionIndex,
|
||||
const sp<ConditionWizard>& wizard, const int pullTagId);
|
||||
const sp<ConditionWizard>& wizard, const int pullTagId,
|
||||
const int64_t startTimeNs);
|
||||
|
||||
virtual ~GaugeMetricProducer();
|
||||
|
||||
@@ -57,6 +60,7 @@ public:
|
||||
void onSlicedConditionMayChange(const uint64_t eventTime) override;
|
||||
|
||||
void finish() override;
|
||||
void flushIfNeeded(const uint64_t newEventTime) override;
|
||||
|
||||
// TODO: Pass a timestamp as a parameter in onDumpReport.
|
||||
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
|
||||
@@ -92,13 +96,15 @@ private:
|
||||
std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
|
||||
|
||||
// The current bucket.
|
||||
std::unordered_map<HashableDimensionKey, long> mCurrentSlicedBucket;
|
||||
std::shared_ptr<DimToValMap> mCurrentSlicedBucket = std::make_shared<DimToValMap>();
|
||||
|
||||
void flushGaugeIfNeededLocked(const uint64_t newEventTime);
|
||||
int64_t getGauge(const LogEvent& event);
|
||||
|
||||
long getGauge(const LogEvent& event);
|
||||
static const size_t kBucketSize = sizeof(GaugeBucket{});
|
||||
|
||||
size_t mByteSize;
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef METRIC_PRODUCER_H
|
||||
#define METRIC_PRODUCER_H
|
||||
|
||||
#include "anomaly/AnomalyTracker.h"
|
||||
#include "condition/ConditionWizard.h"
|
||||
#include "matchers/matcher_util.h"
|
||||
#include "packages/PackageInfoListener.h"
|
||||
@@ -58,6 +59,7 @@ public:
|
||||
// This is called when the metric collecting is done, e.g., when there is a new configuration
|
||||
// coming. MetricProducer should do the clean up, and dump existing data to dropbox.
|
||||
virtual void finish() = 0;
|
||||
virtual void flushIfNeeded(const uint64_t newEventTime) = 0;
|
||||
|
||||
// TODO: Pass a timestamp as a parameter in onDumpReport and update all its
|
||||
// implementations.
|
||||
@@ -72,6 +74,14 @@ public:
|
||||
// state.
|
||||
virtual size_t byteSize() = 0;
|
||||
|
||||
void addAnomalyTracker(sp<AnomalyTracker> tracker) {
|
||||
mAnomalyTrackers.push_back(tracker);
|
||||
}
|
||||
|
||||
int64_t getBuckeSizeInNs() const {
|
||||
return mBucketSizeNs;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint64_t mStartTimeNs;
|
||||
|
||||
@@ -97,6 +107,8 @@ protected:
|
||||
|
||||
std::vector<EventConditionLink> mConditionLinks;
|
||||
|
||||
std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
|
||||
|
||||
/*
|
||||
* Individual metrics can implement their own business logic here. All pre-processing is done.
|
||||
*
|
||||
|
||||
@@ -38,8 +38,8 @@ namespace statsd {
|
||||
|
||||
MetricsManager::MetricsManager(const StatsdConfig& config) {
|
||||
mConfigValid = initStatsdConfig(config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
|
||||
mAllMetricProducers, mConditionToMetricMap, mTrackerToMetricMap,
|
||||
mTrackerToConditionMap);
|
||||
mAllMetricProducers, mAllAnomalyTrackers, mConditionToMetricMap,
|
||||
mTrackerToMetricMap, mTrackerToConditionMap);
|
||||
}
|
||||
|
||||
MetricsManager::~MetricsManager() {
|
||||
@@ -150,6 +150,19 @@ void MetricsManager::onLogEvent(const LogEvent& event) {
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::onAnomalyAlarmFired(const uint64_t timestampNs,
|
||||
sp<const AnomalyAlarm> anomaly) {
|
||||
for (const auto& itr : mAllAnomalyTrackers) {
|
||||
itr->declareAnomaly(timestampNs);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
|
||||
for (auto& itr : mAllAnomalyTrackers) {
|
||||
itr->setAnomalyMonitor(anomalyMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the total byte size of all metrics managed by a single config source.
|
||||
size_t MetricsManager::byteSize() {
|
||||
size_t totalSize = 0;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "anomaly/AnomalyMonitor.h"
|
||||
#include "anomaly/AnomalyTracker.h"
|
||||
#include "condition/ConditionTracker.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "logd/LogEvent.h"
|
||||
@@ -43,6 +45,10 @@ public:
|
||||
// Called when everything should wrap up. We are about to finish (e.g., new config comes).
|
||||
void finish();
|
||||
|
||||
void onAnomalyAlarmFired(const uint64_t timestampNs, sp<const AnomalyAlarm> anomaly);
|
||||
|
||||
void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor);
|
||||
|
||||
// Config source owner can call onDumpReport() to get all the metrics collected.
|
||||
std::vector<std::unique_ptr<std::vector<uint8_t>>> onDumpReport();
|
||||
|
||||
@@ -70,6 +76,9 @@ private:
|
||||
// Hold all metrics from the config.
|
||||
std::vector<sp<MetricProducer>> mAllMetricProducers;
|
||||
|
||||
// Hold all alert trackers.
|
||||
std::vector<sp<AnomalyTracker>> mAllAnomalyTrackers;
|
||||
|
||||
// To make the log processing more efficient, we want to do as much filtering as possible
|
||||
// before we go into individual trackers and conditions to match.
|
||||
|
||||
|
||||
@@ -188,7 +188,6 @@ std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() {
|
||||
|
||||
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
|
||||
mPastBuckets.clear();
|
||||
mByteSize = 0;
|
||||
|
||||
return buffer;
|
||||
|
||||
@@ -215,7 +214,7 @@ void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_
|
||||
for (const auto& data : allData) {
|
||||
onMatchedLogEvent(0, *data, false);
|
||||
}
|
||||
flush_if_needed(eventTime);
|
||||
flushIfNeeded(eventTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -230,12 +229,12 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven
|
||||
uint64_t eventTime = allData.at(0)->GetTimestampNs();
|
||||
// alarm is not accurate and might drift.
|
||||
if (eventTime > mCurrentBucketStartTimeNs + mBucketSizeNs * 3 / 2) {
|
||||
flush_if_needed(eventTime);
|
||||
flushIfNeeded(eventTime);
|
||||
}
|
||||
for (const auto& data : allData) {
|
||||
onMatchedLogEvent(0, *data, true);
|
||||
}
|
||||
flush_if_needed(eventTime);
|
||||
flushIfNeeded(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +281,7 @@ void ValueMetricProducer::onMatchedLogEventInternal(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flush_if_needed(eventTimeNs);
|
||||
flushIfNeeded(eventTimeNs);
|
||||
interval.raw.push_back(make_pair(value, 0));
|
||||
}
|
||||
}
|
||||
@@ -298,18 +297,18 @@ long ValueMetricProducer::get_value(const LogEvent& event) {
|
||||
}
|
||||
}
|
||||
|
||||
void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
|
||||
void ValueMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
|
||||
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
|
||||
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
|
||||
(long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
|
||||
return;
|
||||
}
|
||||
|
||||
VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
|
||||
(int)mCurrentSlicedBucket.size());
|
||||
ValueBucket info;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
|
||||
info.mBucketNum = mCurrentBucketNum;
|
||||
|
||||
int tainted = 0;
|
||||
for (const auto& slice : mCurrentSlicedBucket) {
|
||||
@@ -329,23 +328,29 @@ void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
|
||||
// it will auto create new vector of ValuebucketInfo if the key is not found.
|
||||
auto& bucketList = mPastBuckets[slice.first];
|
||||
bucketList.push_back(info);
|
||||
mByteSize += sizeof(info);
|
||||
}
|
||||
|
||||
// Reset counters
|
||||
mCurrentSlicedBucket.swap(mNextSlicedBucket);
|
||||
mNextSlicedBucket.clear();
|
||||
|
||||
int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
|
||||
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
|
||||
if (numBucketsForward > 1) {
|
||||
VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
|
||||
}
|
||||
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
|
||||
VLOG("metric %s: new bucket start time: %lld", mMetric.name().c_str(),
|
||||
(long long)mCurrentBucketStartTimeNs);
|
||||
}
|
||||
|
||||
size_t ValueMetricProducer::byteSize() {
|
||||
return mByteSize;
|
||||
size_t totalSize = 0;
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
totalSize += pair.second.size() * kBucketSize;
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -33,6 +33,7 @@ struct ValueBucket {
|
||||
int64_t mBucketStartNs;
|
||||
int64_t mBucketEndNs;
|
||||
int64_t mValue;
|
||||
uint64_t mBucketNum;
|
||||
};
|
||||
|
||||
class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
|
||||
@@ -46,6 +47,7 @@ public:
|
||||
void onConditionChanged(const bool condition, const uint64_t eventTime) override;
|
||||
|
||||
void finish() override;
|
||||
void flushIfNeeded(const uint64_t eventTimeNs) override;
|
||||
|
||||
// TODO: Pass a timestamp as a parameter in onDumpReport.
|
||||
std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
|
||||
@@ -102,9 +104,7 @@ private:
|
||||
|
||||
long get_value(const LogEvent& event);
|
||||
|
||||
void flush_if_needed(const uint64_t eventTimeNs);
|
||||
|
||||
size_t mByteSize;
|
||||
static const size_t kBucketSize = sizeof(ValueBucket{});
|
||||
|
||||
FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
|
||||
FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef DURATION_TRACKER_H
|
||||
#define DURATION_TRACKER_H
|
||||
|
||||
#include "anomaly/AnomalyTracker.h"
|
||||
#include "condition/ConditionWizard.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
@@ -50,23 +51,28 @@ struct DurationInfo {
|
||||
};
|
||||
|
||||
struct DurationBucket {
|
||||
int64_t mBucketStartNs;
|
||||
int64_t mBucketEndNs;
|
||||
int64_t mDuration;
|
||||
uint64_t mBucketStartNs;
|
||||
uint64_t mBucketEndNs;
|
||||
uint64_t mDuration;
|
||||
uint64_t mBucketNum;
|
||||
};
|
||||
|
||||
class DurationTracker {
|
||||
public:
|
||||
DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
|
||||
DurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
|
||||
int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
|
||||
uint64_t bucketSizeNs, const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
|
||||
std::vector<DurationBucket>& bucket)
|
||||
: mWizard(wizard),
|
||||
: mEventKey(eventKey),
|
||||
mWizard(wizard),
|
||||
mConditionTrackerIndex(conditionIndex),
|
||||
mBucketSizeNs(bucketSizeNs),
|
||||
mNested(nesting),
|
||||
mCurrentBucketStartTimeNs(currentBucketStartNs),
|
||||
mBucket(bucket),
|
||||
mDuration(0){};
|
||||
mDuration(0),
|
||||
mCurrentBucketNum(0),
|
||||
mAnomalyTrackers(anomalyTrackers){};
|
||||
virtual ~DurationTracker(){};
|
||||
virtual void noteStart(const HashableDimensionKey& key, bool condition,
|
||||
const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
|
||||
@@ -79,7 +85,58 @@ public:
|
||||
// events, so that the owner can safely remove the tracker.
|
||||
virtual bool flushIfNeeded(uint64_t timestampNs) = 0;
|
||||
|
||||
// Predict the anomaly timestamp given the current status.
|
||||
virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
|
||||
const uint64_t currentTimestamp) const = 0;
|
||||
|
||||
protected:
|
||||
// Starts the anomaly alarm.
|
||||
void startAnomalyAlarm(const uint64_t eventTime) {
|
||||
for (auto& anomalyTracker : mAnomalyTrackers) {
|
||||
if (anomalyTracker != nullptr) {
|
||||
anomalyTracker->startAlarm(mEventKey,
|
||||
predictAnomalyTimestampNs(*anomalyTracker, eventTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stops the anomaly alarm.
|
||||
void stopAnomalyAlarm() {
|
||||
for (auto& anomalyTracker : mAnomalyTrackers) {
|
||||
if (anomalyTracker != nullptr) {
|
||||
anomalyTracker->stopAlarm(mEventKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
|
||||
for (auto& anomalyTracker : mAnomalyTrackers) {
|
||||
if (anomalyTracker != nullptr) {
|
||||
anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void detectAndDeclareAnomaly(const uint64_t& timestamp, const int64_t& currBucketNum,
|
||||
const int64_t& currentBucketValue) {
|
||||
for (auto& anomalyTracker : mAnomalyTrackers) {
|
||||
if (anomalyTracker != nullptr) {
|
||||
anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mEventKey,
|
||||
currentBucketValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void declareAnomalyIfAlarmExpired(const uint64_t& timestamp) {
|
||||
for (auto& anomalyTracker : mAnomalyTrackers) {
|
||||
if (anomalyTracker != nullptr) {
|
||||
anomalyTracker->declareAnomalyIfAlarmExpired(mEventKey, timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HashableDimensionKey mEventKey;
|
||||
|
||||
sp<ConditionWizard> mWizard;
|
||||
|
||||
const int mConditionTrackerIndex;
|
||||
@@ -93,6 +150,13 @@ protected:
|
||||
std::vector<DurationBucket>& mBucket; // where to write output
|
||||
|
||||
int64_t mDuration; // current recorded duration result
|
||||
|
||||
uint64_t mCurrentBucketNum;
|
||||
|
||||
std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
|
||||
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -23,14 +23,18 @@ namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
|
||||
MaxDurationTracker::MaxDurationTracker(const HashableDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
|
||||
const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
|
||||
std::vector<DurationBucket>& bucket)
|
||||
: DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket) {
|
||||
: DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
|
||||
anomalyTrackers, bucket) {
|
||||
}
|
||||
|
||||
void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
|
||||
const uint64_t eventTime, const ConditionKey& conditionKey) {
|
||||
flushIfNeeded(eventTime);
|
||||
// this will construct a new DurationInfo if this key didn't exist.
|
||||
DurationInfo& duration = mInfos[key];
|
||||
duration.conditionKeys = conditionKey;
|
||||
@@ -56,8 +60,11 @@ void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool conditi
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
|
||||
bool forceStop) {
|
||||
flushIfNeeded(eventTime);
|
||||
declareAnomalyIfAlarmExpired(eventTime);
|
||||
VLOG("MaxDuration: key %s stop", key.c_str());
|
||||
if (mInfos.find(key) == mInfos.end()) {
|
||||
// we didn't see a start event before. do nothing.
|
||||
@@ -78,6 +85,7 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_
|
||||
(long long)duration.lastStartTime, (long long)eventTime,
|
||||
(long long)durationTime);
|
||||
duration.lastDuration = duration.lastDuration + durationTime;
|
||||
duration.lastStartTime = -1;
|
||||
VLOG(" record duration: %lld ", (long long)duration.lastDuration);
|
||||
}
|
||||
break;
|
||||
@@ -93,6 +101,7 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_
|
||||
|
||||
if (duration.lastDuration > mDuration) {
|
||||
mDuration = duration.lastDuration;
|
||||
detectAndDeclareAnomaly(eventTime, mCurrentBucketNum, mDuration);
|
||||
VLOG("Max: new max duration: %lld", (long long)mDuration);
|
||||
}
|
||||
// Once an atom duration ends, we erase it. Next time, if we see another atom event with the
|
||||
@@ -101,9 +110,14 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_
|
||||
mInfos.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
|
||||
for (auto& pair : mInfos) {
|
||||
noteStop(pair.first, eventTime, true);
|
||||
std::set<HashableDimensionKey> keys;
|
||||
for (const auto& pair : mInfos) {
|
||||
keys.insert(pair.first);
|
||||
}
|
||||
for (auto& key : keys) {
|
||||
noteStop(key, eventTime, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +136,7 @@ bool MaxDurationTracker::flushIfNeeded(uint64_t eventTime) {
|
||||
DurationBucket info;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
info.mBucketEndNs = endTime;
|
||||
|
||||
info.mBucketNum = mCurrentBucketNum;
|
||||
|
||||
uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
|
||||
mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
|
||||
@@ -165,6 +179,7 @@ bool MaxDurationTracker::flushIfNeeded(uint64_t eventTime) {
|
||||
if (mDuration != 0) {
|
||||
info.mDuration = mDuration;
|
||||
mBucket.push_back(info);
|
||||
addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
|
||||
VLOG(" final duration for last bucket: %lld", (long long)mDuration);
|
||||
}
|
||||
|
||||
@@ -174,11 +189,15 @@ bool MaxDurationTracker::flushIfNeeded(uint64_t eventTime) {
|
||||
DurationBucket info;
|
||||
info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
|
||||
info.mBucketEndNs = endTime + mBucketSizeNs * i;
|
||||
info.mBucketNum = mCurrentBucketNum + i;
|
||||
info.mDuration = mBucketSizeNs;
|
||||
mBucket.push_back(info);
|
||||
addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
|
||||
VLOG(" filling gap bucket with duration %lld", (long long)mBucketSizeNs);
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
// If this tracker has no pending events, tell owner to remove.
|
||||
return !hasPendingEvent;
|
||||
}
|
||||
@@ -204,6 +223,8 @@ void MaxDurationTracker::onConditionChanged(bool condition, const uint64_t times
|
||||
|
||||
void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
|
||||
const uint64_t timestamp) {
|
||||
flushIfNeeded(timestamp);
|
||||
declareAnomalyIfAlarmExpired(timestamp);
|
||||
auto it = mInfos.find(key);
|
||||
if (it == mInfos.end()) {
|
||||
return;
|
||||
@@ -215,7 +236,6 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b
|
||||
if (!conditionMet) {
|
||||
it->second.state = DurationState::kPaused;
|
||||
it->second.lastDuration += (timestamp - it->second.lastStartTime);
|
||||
|
||||
VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
|
||||
}
|
||||
break;
|
||||
@@ -232,6 +252,16 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (it->second.lastDuration > mDuration) {
|
||||
mDuration = it->second.lastDuration;
|
||||
detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
|
||||
const uint64_t currentTimestamp) const {
|
||||
ALOGE("Max duration producer does not support anomaly timestamp prediction!!!");
|
||||
return currentTimestamp;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -28,8 +28,10 @@ namespace statsd {
|
||||
// they stop or bucket expires.
|
||||
class MaxDurationTracker : public DurationTracker {
|
||||
public:
|
||||
MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
|
||||
MaxDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
|
||||
int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
|
||||
uint64_t bucketSizeNs,
|
||||
const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
|
||||
std::vector<DurationBucket>& bucket);
|
||||
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
|
||||
const ConditionKey& conditionKey) override;
|
||||
@@ -40,11 +42,20 @@ public:
|
||||
void onSlicedConditionMayChange(const uint64_t timestamp) override;
|
||||
void onConditionChanged(bool condition, const uint64_t timestamp) override;
|
||||
|
||||
int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
|
||||
const uint64_t currentTimestamp) const override;
|
||||
|
||||
private:
|
||||
std::map<HashableDimensionKey, DurationInfo> mInfos;
|
||||
|
||||
void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet,
|
||||
const uint64_t timestamp);
|
||||
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestStopAll);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -23,11 +23,14 @@ namespace statsd {
|
||||
|
||||
using std::pair;
|
||||
|
||||
OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
|
||||
OringDurationTracker::OringDurationTracker(const HashableDimensionKey& eventKey,
|
||||
sp<ConditionWizard> wizard, int conditionIndex,
|
||||
bool nesting, uint64_t currentBucketStartNs,
|
||||
uint64_t bucketSizeNs,
|
||||
const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
|
||||
std::vector<DurationBucket>& bucket)
|
||||
: DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket),
|
||||
: DurationTracker(eventKey, wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs,
|
||||
anomalyTrackers, bucket),
|
||||
mStarted(),
|
||||
mPaused() {
|
||||
mLastStartTime = 0;
|
||||
@@ -35,10 +38,12 @@ OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int condi
|
||||
|
||||
void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
|
||||
const uint64_t eventTime, const ConditionKey& conditionKey) {
|
||||
flushIfNeeded(eventTime);
|
||||
if (condition) {
|
||||
if (mStarted.size() == 0) {
|
||||
mLastStartTime = eventTime;
|
||||
VLOG("record first start....");
|
||||
startAnomalyAlarm(eventTime);
|
||||
}
|
||||
mStarted[key]++;
|
||||
} else {
|
||||
@@ -54,6 +59,8 @@ void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condi
|
||||
|
||||
void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
|
||||
const bool stopAll) {
|
||||
flushIfNeeded(timestamp);
|
||||
declareAnomalyIfAlarmExpired(timestamp);
|
||||
VLOG("Oring: %s stop", key.c_str());
|
||||
auto it = mStarted.find(key);
|
||||
if (it != mStarted.end()) {
|
||||
@@ -64,6 +71,8 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint6
|
||||
}
|
||||
if (mStarted.empty()) {
|
||||
mDuration += (timestamp - mLastStartTime);
|
||||
detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
|
||||
mLastStartTime = -1;
|
||||
VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
|
||||
(long long)mDuration);
|
||||
}
|
||||
@@ -76,56 +85,70 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint6
|
||||
mPaused.erase(pausedIt);
|
||||
mConditionKeyMap.erase(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mStarted.empty()) {
|
||||
stopAnomalyAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
|
||||
flushIfNeeded(timestamp);
|
||||
declareAnomalyIfAlarmExpired(timestamp);
|
||||
if (!mStarted.empty()) {
|
||||
mDuration += (timestamp - mLastStartTime);
|
||||
VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
|
||||
(long long)mDuration);
|
||||
detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
|
||||
}
|
||||
|
||||
stopAnomalyAlarm();
|
||||
mStarted.clear();
|
||||
mPaused.clear();
|
||||
mConditionKeyMap.clear();
|
||||
mLastStartTime = -1;
|
||||
}
|
||||
|
||||
bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
|
||||
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
|
||||
if (eventTime < mCurrentBucketStartTimeNs + mBucketSizeNs) {
|
||||
return false;
|
||||
}
|
||||
VLOG("OringDurationTracker Flushing.............");
|
||||
// adjust the bucket start time
|
||||
int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
|
||||
DurationBucket info;
|
||||
uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
info.mBucketEndNs = endTime;
|
||||
|
||||
uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
|
||||
mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
|
||||
|
||||
DurationBucket current_info;
|
||||
current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
current_info.mBucketEndNs = current_info.mBucketStartNs + mBucketSizeNs;
|
||||
current_info.mBucketNum = mCurrentBucketNum;
|
||||
// Process the current bucket.
|
||||
if (mStarted.size() > 0) {
|
||||
mDuration += (endTime - mLastStartTime);
|
||||
mDuration += (current_info.mBucketEndNs - mLastStartTime);
|
||||
mLastStartTime = current_info.mBucketEndNs;
|
||||
}
|
||||
if (mDuration != 0) {
|
||||
info.mDuration = mDuration;
|
||||
// it will auto create new vector of CountbucketInfo if the key is not found.
|
||||
mBucket.push_back(info);
|
||||
VLOG(" duration: %lld", (long long)mDuration);
|
||||
if (mDuration > 0) {
|
||||
current_info.mDuration = mDuration;
|
||||
mBucket.push_back(current_info);
|
||||
addPastBucketToAnomalyTrackers(current_info.mDuration, current_info.mBucketNum);
|
||||
VLOG(" duration: %lld", (long long)current_info.mDuration);
|
||||
}
|
||||
|
||||
if (mStarted.size() > 0) {
|
||||
for (int i = 1; i < numBucketsForward; i++) {
|
||||
DurationBucket info;
|
||||
info.mBucketStartNs = oldBucketStartTimeNs + mBucketSizeNs * i;
|
||||
info.mBucketEndNs = endTime + mBucketSizeNs * i;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs + mBucketSizeNs * i;
|
||||
info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
|
||||
info.mBucketNum = mCurrentBucketNum + i;
|
||||
info.mDuration = mBucketSizeNs;
|
||||
mBucket.push_back(info);
|
||||
VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
|
||||
mLastStartTime = info.mBucketEndNs;
|
||||
if (info.mDuration > 0) {
|
||||
mBucket.push_back(info);
|
||||
addPastBucketToAnomalyTrackers(info.mDuration, info.mBucketNum);
|
||||
VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
mLastStartTime = mCurrentBucketStartTimeNs;
|
||||
mCurrentBucketStartTimeNs += numBucketsForward * mBucketSizeNs;
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
|
||||
mDuration = 0;
|
||||
|
||||
// if all stopped, then tell owner it's safe to remove this tracker.
|
||||
@@ -133,6 +156,8 @@ bool OringDurationTracker::flushIfNeeded(uint64_t eventTime) {
|
||||
}
|
||||
|
||||
void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
|
||||
flushIfNeeded(timestamp);
|
||||
declareAnomalyIfAlarmExpired(timestamp);
|
||||
vector<pair<HashableDimensionKey, int>> startedToPaused;
|
||||
vector<pair<HashableDimensionKey, int>> pausedToStarted;
|
||||
if (!mStarted.empty()) {
|
||||
@@ -154,9 +179,11 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp)
|
||||
}
|
||||
|
||||
if (mStarted.empty()) {
|
||||
mDuration += (timestamp - mLastStartTime);
|
||||
mDuration = (timestamp - mLastStartTime);
|
||||
mLastStartTime = -1;
|
||||
VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
|
||||
(long long)mDuration);
|
||||
detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,26 +210,95 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
if (mStarted.empty() && !pausedToStarted.empty()) {
|
||||
startAnomalyAlarm(timestamp);
|
||||
}
|
||||
mStarted.insert(pausedToStarted.begin(), pausedToStarted.end());
|
||||
mPaused.insert(startedToPaused.begin(), startedToPaused.end());
|
||||
|
||||
if (mStarted.empty()) {
|
||||
stopAnomalyAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
void OringDurationTracker::onConditionChanged(bool condition, const uint64_t timestamp) {
|
||||
flushIfNeeded(timestamp);
|
||||
declareAnomalyIfAlarmExpired(timestamp);
|
||||
if (condition) {
|
||||
if (!mPaused.empty()) {
|
||||
VLOG("Condition true, all started");
|
||||
if (mStarted.empty()) {
|
||||
mLastStartTime = timestamp;
|
||||
mLastStartTime = -1;
|
||||
}
|
||||
if (mStarted.empty() && !mPaused.empty()) {
|
||||
startAnomalyAlarm(timestamp);
|
||||
}
|
||||
mStarted.insert(mPaused.begin(), mPaused.end());
|
||||
mPaused.clear();
|
||||
}
|
||||
} else {
|
||||
if (!mStarted.empty()) {
|
||||
VLOG("Condition false, all paused");
|
||||
mDuration += (timestamp - mLastStartTime);
|
||||
mDuration = (timestamp - mLastStartTime);
|
||||
mLastStartTime = -1;
|
||||
mPaused.insert(mStarted.begin(), mStarted.end());
|
||||
mStarted.clear();
|
||||
detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration);
|
||||
}
|
||||
}
|
||||
if (mStarted.empty()) {
|
||||
stopAnomalyAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
|
||||
const uint64_t eventTimestampNs) const {
|
||||
// TODO: Unit-test this and see if it can be done more efficiently (e.g. use int32).
|
||||
// All variables below represent durations (not timestamps).
|
||||
|
||||
// The time until the current bucket ends. This is how much more 'space' it can hold.
|
||||
const int64_t currRemainingBucketSizeNs =
|
||||
mBucketSizeNs - (eventTimestampNs - mCurrentBucketStartTimeNs);
|
||||
// TODO: This should never be < 0. Document/guard against possible failures if it is.
|
||||
|
||||
const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
|
||||
|
||||
// As we move into the future, old buckets get overwritten (so their old data is erased).
|
||||
|
||||
// Sum of past durations. Will change as we overwrite old buckets.
|
||||
int64_t pastNs = mDuration;
|
||||
pastNs += anomalyTracker.getSumOverPastBuckets(mEventKey);
|
||||
|
||||
// How much of the threshold is still unaccounted after considering pastNs.
|
||||
int64_t leftNs = thresholdNs - pastNs;
|
||||
|
||||
// First deal with the remainder of the current bucket.
|
||||
if (leftNs <= currRemainingBucketSizeNs) { // Predict the anomaly will occur in this bucket.
|
||||
return eventTimestampNs + leftNs;
|
||||
}
|
||||
// The remainder of this bucket contributes, but we must then move to the next bucket.
|
||||
pastNs += currRemainingBucketSizeNs;
|
||||
|
||||
// Now deal with the past buckets, starting with the oldest.
|
||||
for (int futBucketIdx = 0; futBucketIdx < anomalyTracker.getNumOfPastPackets();
|
||||
futBucketIdx++) {
|
||||
// We now overwrite the oldest bucket with the previous 'current', and start a new
|
||||
// 'current'.
|
||||
pastNs -= anomalyTracker.getPastBucketValue(
|
||||
mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastPackets() + futBucketIdx);
|
||||
leftNs = thresholdNs - pastNs;
|
||||
if (leftNs <= mBucketSizeNs) { // Predict anomaly will occur in this bucket.
|
||||
return eventTimestampNs + currRemainingBucketSizeNs + (futBucketIdx * mBucketSizeNs) +
|
||||
leftNs;
|
||||
} else { // This bucket would be entirely filled, and we'll need to move to the next
|
||||
// bucket.
|
||||
pastNs += mBucketSizeNs;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have reached this point, we even have to overwrite the the original current bucket.
|
||||
// Thus, none of the past data will still be extant - pastNs is now 0.
|
||||
return eventTimestampNs + thresholdNs;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -27,8 +27,10 @@ namespace statsd {
|
||||
// Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
|
||||
class OringDurationTracker : public DurationTracker {
|
||||
public:
|
||||
OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
|
||||
uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
|
||||
OringDurationTracker(const HashableDimensionKey& eventKey, sp<ConditionWizard> wizard,
|
||||
int conditionIndex, bool nesting, uint64_t currentBucketStartNs,
|
||||
uint64_t bucketSizeNs,
|
||||
const std::vector<sp<AnomalyTracker>>& anomalyTrackers,
|
||||
std::vector<DurationBucket>& bucket);
|
||||
void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
|
||||
const ConditionKey& conditionKey) override;
|
||||
@@ -39,6 +41,9 @@ public:
|
||||
void onConditionChanged(bool condition, const uint64_t timestamp) override;
|
||||
bool flushIfNeeded(uint64_t timestampNs) override;
|
||||
|
||||
int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
|
||||
const uint64_t currentTimestamp) const override;
|
||||
|
||||
private:
|
||||
// We don't need to keep track of individual durations. The information that's needed is:
|
||||
// 1) which keys are started. We record the first start time.
|
||||
@@ -49,6 +54,12 @@ private:
|
||||
std::map<HashableDimensionKey, int> mPaused;
|
||||
int64_t mLastStartTime;
|
||||
std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
|
||||
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestDurationConditionChange);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetection);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -190,7 +190,8 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
|
||||
vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
vector<sp<MetricProducer>>& allMetricProducers,
|
||||
unordered_map<int, std::vector<int>>& conditionToMetricMap,
|
||||
unordered_map<int, std::vector<int>>& trackerToMetricMap) {
|
||||
unordered_map<int, std::vector<int>>& trackerToMetricMap,
|
||||
unordered_map<string, int>& metricMap) {
|
||||
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
|
||||
const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
|
||||
config.event_metric_size() + config.value_metric_size();
|
||||
@@ -208,6 +209,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
|
||||
}
|
||||
|
||||
int metricIndex = allMetricProducers.size();
|
||||
metricMap.insert({metric.name(), metricIndex});
|
||||
int trackerIndex;
|
||||
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
|
||||
allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
|
||||
@@ -236,6 +238,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
|
||||
for (int i = 0; i < config.duration_metric_size(); i++) {
|
||||
int metricIndex = allMetricProducers.size();
|
||||
const DurationMetric& metric = config.duration_metric(i);
|
||||
metricMap.insert({metric.name(), metricIndex});
|
||||
|
||||
auto what_it = conditionTrackerMap.find(metric.what());
|
||||
if (what_it == conditionTrackerMap.end()) {
|
||||
@@ -305,6 +308,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
|
||||
for (int i = 0; i < config.event_metric_size(); i++) {
|
||||
int metricIndex = allMetricProducers.size();
|
||||
const EventMetric& metric = config.event_metric(i);
|
||||
metricMap.insert({metric.name(), metricIndex});
|
||||
if (!metric.has_name() || !metric.has_what()) {
|
||||
ALOGW("cannot find the metric name or what in config");
|
||||
return false;
|
||||
@@ -342,6 +346,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
|
||||
}
|
||||
|
||||
int metricIndex = allMetricProducers.size();
|
||||
metricMap.insert({metric.name(), metricIndex});
|
||||
int trackerIndex;
|
||||
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
|
||||
allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
|
||||
@@ -387,6 +392,7 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
|
||||
}
|
||||
|
||||
int metricIndex = allMetricProducers.size();
|
||||
metricMap.insert({metric.name(), metricIndex});
|
||||
int trackerIndex;
|
||||
if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
|
||||
allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
|
||||
@@ -419,21 +425,43 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l
|
||||
}
|
||||
|
||||
sp<MetricProducer> gaugeProducer =
|
||||
new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId);
|
||||
new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
|
||||
allMetricProducers.push_back(gaugeProducer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initAlerts(const StatsdConfig& config, const unordered_map<string, int>& metricProducerMap,
|
||||
vector<sp<MetricProducer>>& allMetricProducers,
|
||||
vector<sp<AnomalyTracker>>& allAnomalyTrackers) {
|
||||
for (int i = 0; i < config.alert_size(); i++) {
|
||||
const Alert& alert = config.alert(i);
|
||||
const auto& itr = metricProducerMap.find(alert.metric_name());
|
||||
if (itr == metricProducerMap.end()) {
|
||||
ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(),
|
||||
alert.metric_name().c_str());
|
||||
return false;
|
||||
}
|
||||
const int metricIndex = itr->second;
|
||||
sp<AnomalyTracker> anomalyTracker =
|
||||
new AnomalyTracker(alert, allMetricProducers[metricIndex]->getBuckeSizeInNs());
|
||||
allMetricProducers[metricIndex]->addAnomalyTracker(anomalyTracker);
|
||||
allAnomalyTrackers.push_back(anomalyTracker);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initStatsdConfig(const StatsdConfig& config, set<int>& allTagIds,
|
||||
vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
|
||||
vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
vector<sp<MetricProducer>>& allMetricProducers,
|
||||
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
|
||||
unordered_map<int, std::vector<int>>& conditionToMetricMap,
|
||||
unordered_map<int, std::vector<int>>& trackerToMetricMap,
|
||||
unordered_map<int, std::vector<int>>& trackerToConditionMap) {
|
||||
unordered_map<string, int> logTrackerMap;
|
||||
unordered_map<string, int> conditionTrackerMap;
|
||||
unordered_map<string, int> metricProducerMap;
|
||||
|
||||
if (!initLogTrackers(config, logTrackerMap, allLogEntryMatchers, allTagIds)) {
|
||||
ALOGE("initLogMatchingTrackers failed");
|
||||
@@ -449,10 +477,14 @@ bool initStatsdConfig(const StatsdConfig& config, set<int>& allTagIds,
|
||||
|
||||
if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allLogEntryMatchers,
|
||||
allConditionTrackers, allMetricProducers, conditionToMetricMap,
|
||||
trackerToMetricMap)) {
|
||||
trackerToMetricMap, metricProducerMap)) {
|
||||
ALOGE("initMetricProducers failed");
|
||||
return false;
|
||||
}
|
||||
if (!initAlerts(config, metricProducerMap, allMetricProducers, allAnomalyTrackers)) {
|
||||
ALOGE("initAlerts failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ bool initStatsdConfig(const StatsdConfig& config, std::set<int>& allTagIds,
|
||||
std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
|
||||
std::vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
std::vector<sp<MetricProducer>>& allMetricProducers,
|
||||
vector<sp<AnomalyTracker>>& allAnomalyTrackers,
|
||||
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
|
||||
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
|
||||
std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
|
||||
|
||||
@@ -35,15 +35,14 @@ typedef std::string HashableDimensionKey;
|
||||
|
||||
typedef std::map<std::string, HashableDimensionKey> ConditionKey;
|
||||
|
||||
// TODO: For P, change int to int64_t.
|
||||
// TODO: Should HashableDimensionKey be marked here as const?
|
||||
typedef std::unordered_map<HashableDimensionKey, int> DimToValMap;
|
||||
typedef std::unordered_map<HashableDimensionKey, int64_t> DimToValMap;
|
||||
|
||||
EventMetricData parse(log_msg msg);
|
||||
|
||||
int getTagId(log_msg msg);
|
||||
|
||||
std::string getHashableKey(std::vector<KeyValuePair> key);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
@@ -113,11 +113,11 @@ message Bucket {
|
||||
}
|
||||
|
||||
message EventConditionLink {
|
||||
optional string condition = 1;
|
||||
optional string condition = 1;
|
||||
|
||||
repeated KeyMatcher key_in_main = 2;
|
||||
repeated KeyMatcher key_in_main = 2;
|
||||
|
||||
repeated KeyMatcher key_in_condition = 3;
|
||||
repeated KeyMatcher key_in_condition = 3;
|
||||
}
|
||||
|
||||
message EventMetric {
|
||||
@@ -217,8 +217,6 @@ message Alert {
|
||||
optional int32 refractory_period_secs = 5;
|
||||
|
||||
optional int64 trigger_if_sum_gt = 6;
|
||||
|
||||
optional int32 refractory_period_in_buckets = 7;
|
||||
}
|
||||
|
||||
message StatsdConfig {
|
||||
|
||||
@@ -20,6 +20,8 @@ using namespace android::os::statsd;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
TEST(AnomalyMonitor, popSoonerThan) {
|
||||
std::string emptyMetricId;
|
||||
std::string emptyDimensionId;
|
||||
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> set;
|
||||
AnomalyMonitor am(2);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ using android::sp;
|
||||
using std::set;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
using android::os::statsd::Condition;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
@@ -71,6 +72,19 @@ StatsdConfig buildGoodConfig() {
|
||||
combination->add_matcher("SCREEN_IS_ON");
|
||||
combination->add_matcher("SCREEN_IS_OFF");
|
||||
|
||||
CountMetric* metric = config.add_count_metric();
|
||||
metric->set_name("3");
|
||||
metric->set_what("SCREEN_IS_ON");
|
||||
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
|
||||
KeyMatcher* keyMatcher = metric->add_dimension();
|
||||
keyMatcher->set_key(1);
|
||||
|
||||
auto alert = config.add_alert();
|
||||
alert->set_name("3");
|
||||
alert->set_metric_name("3");
|
||||
alert->set_number_of_buckets(10);
|
||||
alert->set_refractory_period_secs(100);
|
||||
alert->set_trigger_if_sum_gt(100);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -100,6 +114,29 @@ StatsdConfig buildCircleMatchers() {
|
||||
return config;
|
||||
}
|
||||
|
||||
StatsdConfig buildAlertWithUnknownMetric() {
|
||||
StatsdConfig config;
|
||||
config.set_name("12345");
|
||||
|
||||
LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
|
||||
eventMatcher->set_name("SCREEN_IS_ON");
|
||||
|
||||
CountMetric* metric = config.add_count_metric();
|
||||
metric->set_name("3");
|
||||
metric->set_what("SCREEN_IS_ON");
|
||||
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
|
||||
KeyMatcher* keyMatcher = metric->add_dimension();
|
||||
keyMatcher->set_key(1);
|
||||
|
||||
auto alert = config.add_alert();
|
||||
alert->set_name("3");
|
||||
alert->set_metric_name("2");
|
||||
alert->set_number_of_buckets(10);
|
||||
alert->set_refractory_period_secs(100);
|
||||
alert->set_trigger_if_sum_gt(100);
|
||||
return config;
|
||||
}
|
||||
|
||||
StatsdConfig buildMissingMatchers() {
|
||||
StatsdConfig config;
|
||||
config.set_name("12345");
|
||||
@@ -156,6 +193,12 @@ StatsdConfig buildDimensionMetricsWithMultiTags() {
|
||||
KeyMatcher* keyMatcher = metric->add_dimension();
|
||||
keyMatcher->set_key(1);
|
||||
|
||||
auto alert = config.add_alert();
|
||||
alert->set_name("3");
|
||||
alert->set_metric_name("3");
|
||||
alert->set_number_of_buckets(10);
|
||||
alert->set_refractory_period_secs(100);
|
||||
alert->set_trigger_if_sum_gt(100);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -183,7 +226,7 @@ StatsdConfig buildCircleConditions() {
|
||||
simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
|
||||
1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
|
||||
|
||||
Condition* condition = config.add_condition();
|
||||
auto condition = config.add_condition();
|
||||
condition->set_name("SCREEN_IS_ON");
|
||||
SimpleCondition* simpleCondition = condition->mutable_simple_condition();
|
||||
simpleCondition->set_start("SCREEN_IS_ON");
|
||||
@@ -206,13 +249,16 @@ TEST(MetricsManagerTest, TestGoodConfig) {
|
||||
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
|
||||
vector<sp<ConditionTracker>> allConditionTrackers;
|
||||
vector<sp<MetricProducer>> allMetricProducers;
|
||||
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
|
||||
unordered_map<int, std::vector<int>> conditionToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToConditionMap;
|
||||
|
||||
EXPECT_TRUE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
|
||||
allMetricProducers, conditionToMetricMap, trackerToMetricMap,
|
||||
trackerToConditionMap));
|
||||
allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
|
||||
trackerToMetricMap, trackerToConditionMap));
|
||||
EXPECT_EQ(1u, allMetricProducers.size());
|
||||
EXPECT_EQ(1u, allAnomalyTrackers.size());
|
||||
}
|
||||
|
||||
TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
|
||||
@@ -221,13 +267,14 @@ TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) {
|
||||
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
|
||||
vector<sp<ConditionTracker>> allConditionTrackers;
|
||||
vector<sp<MetricProducer>> allMetricProducers;
|
||||
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
|
||||
unordered_map<int, std::vector<int>> conditionToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToConditionMap;
|
||||
|
||||
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
|
||||
allMetricProducers, conditionToMetricMap, trackerToMetricMap,
|
||||
trackerToConditionMap));
|
||||
allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
|
||||
trackerToMetricMap, trackerToConditionMap));
|
||||
}
|
||||
|
||||
TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
|
||||
@@ -236,13 +283,14 @@ TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
|
||||
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
|
||||
vector<sp<ConditionTracker>> allConditionTrackers;
|
||||
vector<sp<MetricProducer>> allMetricProducers;
|
||||
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
|
||||
unordered_map<int, std::vector<int>> conditionToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToConditionMap;
|
||||
|
||||
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
|
||||
allMetricProducers, conditionToMetricMap, trackerToMetricMap,
|
||||
trackerToConditionMap));
|
||||
allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
|
||||
trackerToMetricMap, trackerToConditionMap));
|
||||
}
|
||||
|
||||
TEST(MetricsManagerTest, TestMissingMatchers) {
|
||||
@@ -251,13 +299,13 @@ TEST(MetricsManagerTest, TestMissingMatchers) {
|
||||
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
|
||||
vector<sp<ConditionTracker>> allConditionTrackers;
|
||||
vector<sp<MetricProducer>> allMetricProducers;
|
||||
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
|
||||
unordered_map<int, std::vector<int>> conditionToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToConditionMap;
|
||||
|
||||
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
|
||||
allMetricProducers, conditionToMetricMap, trackerToMetricMap,
|
||||
trackerToConditionMap));
|
||||
allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
|
||||
trackerToMetricMap, trackerToConditionMap));
|
||||
}
|
||||
|
||||
TEST(MetricsManagerTest, TestCircleConditionDependency) {
|
||||
@@ -266,13 +314,30 @@ TEST(MetricsManagerTest, TestCircleConditionDependency) {
|
||||
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
|
||||
vector<sp<ConditionTracker>> allConditionTrackers;
|
||||
vector<sp<MetricProducer>> allMetricProducers;
|
||||
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
|
||||
unordered_map<int, std::vector<int>> conditionToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToConditionMap;
|
||||
|
||||
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
|
||||
allMetricProducers, conditionToMetricMap, trackerToMetricMap,
|
||||
trackerToConditionMap));
|
||||
allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
|
||||
trackerToMetricMap, trackerToConditionMap));
|
||||
}
|
||||
|
||||
TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
|
||||
StatsdConfig config = buildAlertWithUnknownMetric();
|
||||
set<int> allTagIds;
|
||||
vector<sp<LogMatchingTracker>> allLogEntryMatchers;
|
||||
vector<sp<ConditionTracker>> allConditionTrackers;
|
||||
vector<sp<MetricProducer>> allMetricProducers;
|
||||
std::vector<sp<AnomalyTracker>> allAnomalyTrackers;
|
||||
unordered_map<int, std::vector<int>> conditionToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToMetricMap;
|
||||
unordered_map<int, std::vector<int>> trackerToConditionMap;
|
||||
|
||||
EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
|
||||
allMetricProducers, allAnomalyTrackers, conditionToMetricMap,
|
||||
trackerToMetricMap, trackerToConditionMap));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@@ -34,7 +34,8 @@ const string kApp2 = "app2.sharing.1";
|
||||
|
||||
TEST(UidMapTest, TestIsolatedUID) {
|
||||
sp<UidMap> m = new UidMap();
|
||||
StatsLogProcessor p(m, nullptr);
|
||||
sp<AnomalyMonitor> anomalyMonitor;
|
||||
StatsLogProcessor p(m, anomalyMonitor, nullptr);
|
||||
LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
|
||||
addEvent.write(100); // parent UID
|
||||
addEvent.write(101); // isolated UID
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/anomaly/DiscreteAnomalyTracker.h"
|
||||
#include "src/anomaly/AnomalyTracker.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <stdio.h>
|
||||
@@ -37,7 +37,7 @@ void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<DimToValMap> MockeBucket(
|
||||
std::shared_ptr<DimToValMap> MockBucket(
|
||||
const std::vector<std::pair<string, long>>& key_value_pair_list) {
|
||||
std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
|
||||
AddValueToBucket(key_value_pair_list, bucket);
|
||||
@@ -45,190 +45,240 @@ std::shared_ptr<DimToValMap> MockeBucket(
|
||||
}
|
||||
|
||||
TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
|
||||
const int64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
Alert alert;
|
||||
alert.set_number_of_buckets(3);
|
||||
alert.set_refractory_period_in_buckets(3);
|
||||
alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
|
||||
alert.set_trigger_if_sum_gt(2);
|
||||
|
||||
DiscreteAnomalyTracker anomaly_tracker(alert);
|
||||
AnomalyTracker anomalyTracker(alert, bucketSizeNs);
|
||||
|
||||
std::shared_ptr<DimToValMap> bucket0 = MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}});
|
||||
// Adds bucket #0
|
||||
anomaly_tracker.addOrUpdateBucket(bucket0, 0);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
EXPECT_FALSE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
|
||||
std::shared_ptr<DimToValMap> bucket0 = MockBucket({{"a", 1}, {"b", 2}, {"c", 1}});
|
||||
int64_t eventTimestamp0 = 10;
|
||||
std::shared_ptr<DimToValMap> bucket1 = MockBucket({{"a", 1}});
|
||||
int64_t eventTimestamp1 = bucketSizeNs + 11;
|
||||
std::shared_ptr<DimToValMap> bucket2 = MockBucket({{"b", 1}});
|
||||
int64_t eventTimestamp2 = 2 * bucketSizeNs + 12;
|
||||
std::shared_ptr<DimToValMap> bucket3 = MockBucket({{"a", 2}});
|
||||
int64_t eventTimestamp3 = 3 * bucketSizeNs + 13;
|
||||
std::shared_ptr<DimToValMap> bucket4 = MockBucket({{"b", 1}});
|
||||
int64_t eventTimestamp4 = 4 * bucketSizeNs + 14;
|
||||
std::shared_ptr<DimToValMap> bucket5 = MockBucket({{"a", 2}});
|
||||
int64_t eventTimestamp5 = 5 * bucketSizeNs + 15;
|
||||
std::shared_ptr<DimToValMap> bucket6 = MockBucket({{"a", 2}});
|
||||
int64_t eventTimestamp6 = 6 * bucketSizeNs + 16;
|
||||
|
||||
// Adds bucket #0 again. The sum does not change.
|
||||
anomaly_tracker.addOrUpdateBucket(bucket0, 0);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 0L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
EXPECT_FALSE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0u);
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
|
||||
EXPECT_FALSE(anomalyTracker.detectAnomaly(0, *bucket0));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp0, 0, *bucket0);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
|
||||
|
||||
// Adds bucket #1.
|
||||
std::shared_ptr<DimToValMap> bucket1 = MockeBucket({{"b", 2}});
|
||||
anomaly_tracker.addOrUpdateBucket(bucket1, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
// Alarm.
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
|
||||
// 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.mMostRecentBucketNum, 0LL);
|
||||
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 1, *bucket1);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
|
||||
|
||||
// Adds bucket #1 again. The sum does not change.
|
||||
anomaly_tracker.addOrUpdateBucket(bucket1, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
// Alarm.
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
|
||||
// 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.mMostRecentBucketNum, 0LL);
|
||||
EXPECT_FALSE(anomalyTracker.detectAnomaly(1, *bucket1));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1 + 1, 1, *bucket1);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1L);
|
||||
|
||||
// Adds bucket #2.
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 2);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 2L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
// Adds past bucket #1.
|
||||
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_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 2, *bucket2);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
|
||||
|
||||
// Adds past bucket #1 again. Nothing changes.
|
||||
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_TRUE(anomalyTracker.detectAnomaly(2, *bucket2));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2 + 1, 2, *bucket2);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
|
||||
|
||||
// Adds past bucket #2.
|
||||
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_TRUE(anomalyTracker.detectAnomaly(3, *bucket3));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 3, *bucket3);
|
||||
// Within refractory period.
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
|
||||
|
||||
// Adds bucket #3.
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 3);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 3L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
|
||||
EXPECT_FALSE(anomaly_tracker.detectAnomaly());
|
||||
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_FALSE(anomalyTracker.detectAnomaly(4, *bucket4));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 4, *bucket4);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
|
||||
|
||||
// Adds bucket #3.
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 2}}), 4);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 4L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
// Within refractory period.
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
|
||||
// Adds bucket #4.
|
||||
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_TRUE(anomalyTracker.detectAnomaly(5, *bucket5));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 5, *bucket5);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
|
||||
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 5);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 5L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
// Adds bucket #5.
|
||||
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_TRUE(anomalyTracker.detectAnomaly(6, *bucket6));
|
||||
// Within refractory period.
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 5L);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 6, *bucket6);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp5);
|
||||
}
|
||||
|
||||
TEST(AnomalyTrackerTest, TestSparseBuckets) {
|
||||
const int64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
Alert alert;
|
||||
alert.set_number_of_buckets(3);
|
||||
alert.set_refractory_period_in_buckets(3);
|
||||
alert.set_refractory_period_secs(2 * bucketSizeNs / NS_PER_SEC);
|
||||
alert.set_trigger_if_sum_gt(2);
|
||||
|
||||
DiscreteAnomalyTracker anomaly_tracker(alert);
|
||||
AnomalyTracker anomalyTracker(alert, bucketSizeNs);
|
||||
|
||||
// Add bucket #9
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}}), 9);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 9L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
EXPECT_FALSE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
|
||||
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}});
|
||||
|
||||
// Add bucket #16
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 4}}), 16);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 16L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
|
||||
int64_t eventTimestamp1 = bucketSizeNs * 8 + 1;
|
||||
int64_t eventTimestamp2 = bucketSizeNs * 15 + 11;
|
||||
int64_t eventTimestamp3 = bucketSizeNs * 17 + 1;
|
||||
int64_t eventTimestamp4 = bucketSizeNs * 19 + 2;
|
||||
int64_t eventTimestamp5 = bucketSizeNs * 24 + 3;
|
||||
int64_t eventTimestamp6 = bucketSizeNs * 27 + 3;
|
||||
|
||||
// Add bucket #18
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, -1LL);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
EXPECT_FALSE(anomalyTracker.detectAnomaly(9, *bucket9));
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp1, 9, *bucket9);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, -1);
|
||||
|
||||
// Add past bucket #9
|
||||
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_TRUE(anomalyTracker.detectAnomaly(16, *bucket16));
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp2, 16, *bucket16);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 15L);
|
||||
|
||||
// Add past bucket #16
|
||||
anomalyTracker.addPastBucket(bucket16, 16);
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 16L);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
|
||||
EXPECT_TRUE(anomalyTracker.detectAnomaly(18, *bucket18));
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
|
||||
// Within refractory period.
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp3, 18, *bucket18);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp2);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 4LL);
|
||||
|
||||
// Add bucket #18 again.
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
|
||||
// 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_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);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4, 20, *bucket20);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
|
||||
|
||||
// Add bucket #20
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 3}, {"d", 1}}), 20);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 20L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
|
||||
// Add bucket #18 again. Nothing changes.
|
||||
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_TRUE(anomalyTracker.detectAnomaly(20, *bucket20));
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 2UL);
|
||||
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("b"), 1LL);
|
||||
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("c"), 1LL);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp4 + 1, 20, *bucket20);
|
||||
// Within refractory period.
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
|
||||
|
||||
// Add bucket #25
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"d", 1}}), 25);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 25L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1L);
|
||||
EXPECT_FALSE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
|
||||
// Add past bucket #20
|
||||
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_FALSE(anomalyTracker.detectAnomaly(25, *bucket25));
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 24L);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp5, 25, *bucket25);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
|
||||
|
||||
// Add bucket #28
|
||||
anomaly_tracker.addOrUpdateBucket(MockeBucket({{"e", 5}}), 28);
|
||||
EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 28L);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("e")->second, 5L);
|
||||
EXPECT_TRUE(anomaly_tracker.detectAnomaly());
|
||||
anomaly_tracker.declareAndDeclareAnomaly();
|
||||
EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 3L);
|
||||
EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 28L);
|
||||
// Add past bucket #25
|
||||
anomalyTracker.addPastBucket(bucket25, 25);
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 25L);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 1UL);
|
||||
EXPECT_EQ(anomalyTracker.getSumOverPastBuckets("d"), 1LL);
|
||||
EXPECT_FALSE(anomalyTracker.detectAnomaly(28, *bucket28));
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6, 28, *bucket28);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp4);
|
||||
|
||||
// Updates current bucket #28.
|
||||
(*bucket28)["e"] = 5;
|
||||
EXPECT_TRUE(anomalyTracker.detectAnomaly(28, *bucket28));
|
||||
EXPECT_EQ(anomalyTracker.mMostRecentBucketNum, 27L);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
anomalyTracker.detectAndDeclareAnomaly(eventTimestamp6 + 7, 28, *bucket28);
|
||||
EXPECT_EQ(anomalyTracker.mSumOverPastBuckets.size(), 0UL);
|
||||
EXPECT_EQ(anomalyTracker.mLastAlarmTimestampNs, eventTimestamp6 + 7);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -22,10 +22,12 @@ using namespace android::os::statsd;
|
||||
|
||||
/** struct for template in indexed_priority_queue */
|
||||
struct AATest : public RefBase {
|
||||
AATest(uint32_t val) : val(val) {
|
||||
AATest(uint32_t val, std::string a, std::string b) : val(val), a(a), b(b) {
|
||||
}
|
||||
|
||||
const int val;
|
||||
const std::string a;
|
||||
const std::string b;
|
||||
|
||||
struct Smaller {
|
||||
bool operator()(const sp<const AATest> a, const sp<const AATest> b) const {
|
||||
@@ -36,9 +38,11 @@ struct AATest : public RefBase {
|
||||
|
||||
#ifdef __ANDROID__
|
||||
TEST(indexed_priority_queue, empty_and_size) {
|
||||
std::string emptyMetricId;
|
||||
std::string emptyDimensionId;
|
||||
indexed_priority_queue<AATest, AATest::Smaller> ipq;
|
||||
sp<const AATest> aa4 = new AATest{4};
|
||||
sp<const AATest> aa8 = new AATest{8};
|
||||
sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
|
||||
|
||||
EXPECT_EQ(0u, ipq.size());
|
||||
EXPECT_TRUE(ipq.empty());
|
||||
@@ -61,13 +65,15 @@ TEST(indexed_priority_queue, empty_and_size) {
|
||||
}
|
||||
|
||||
TEST(indexed_priority_queue, top) {
|
||||
std::string emptyMetricId;
|
||||
std::string emptyDimensionId;
|
||||
indexed_priority_queue<AATest, AATest::Smaller> ipq;
|
||||
sp<const AATest> aa2 = new AATest{2};
|
||||
sp<const AATest> aa4 = new AATest{4};
|
||||
sp<const AATest> aa8 = new AATest{8};
|
||||
sp<const AATest> aa12 = new AATest{12};
|
||||
sp<const AATest> aa16 = new AATest{16};
|
||||
sp<const AATest> aa20 = new AATest{20};
|
||||
sp<const AATest> aa2 = new AATest{2, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa8 = new AATest{8, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa12 = new AATest{12, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa16 = new AATest{16, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa20 = new AATest{20, emptyMetricId, emptyDimensionId};
|
||||
|
||||
EXPECT_EQ(ipq.top(), nullptr);
|
||||
|
||||
@@ -113,9 +119,11 @@ TEST(indexed_priority_queue, top) {
|
||||
}
|
||||
|
||||
TEST(indexed_priority_queue, push_same_aa) {
|
||||
std::string emptyMetricId;
|
||||
std::string emptyDimensionId;
|
||||
indexed_priority_queue<AATest, AATest::Smaller> ipq;
|
||||
sp<const AATest> aa4_a = new AATest{4};
|
||||
sp<const AATest> aa4_b = new AATest{4};
|
||||
sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
|
||||
|
||||
ipq.push(aa4_a);
|
||||
EXPECT_EQ(1u, ipq.size());
|
||||
@@ -134,9 +142,11 @@ TEST(indexed_priority_queue, push_same_aa) {
|
||||
}
|
||||
|
||||
TEST(indexed_priority_queue, remove_nonexistant) {
|
||||
std::string emptyMetricId;
|
||||
std::string emptyDimensionId;
|
||||
indexed_priority_queue<AATest, AATest::Smaller> ipq;
|
||||
sp<const AATest> aa4 = new AATest{4};
|
||||
sp<const AATest> aa5 = new AATest{5};
|
||||
sp<const AATest> aa4 = new AATest{4, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa5 = new AATest{5, emptyMetricId, emptyDimensionId};
|
||||
|
||||
ipq.push(aa4);
|
||||
ipq.remove(aa5);
|
||||
@@ -147,8 +157,10 @@ TEST(indexed_priority_queue, remove_nonexistant) {
|
||||
|
||||
TEST(indexed_priority_queue, remove_same_aa) {
|
||||
indexed_priority_queue<AATest, AATest::Smaller> ipq;
|
||||
sp<const AATest> aa4_a = new AATest{4};
|
||||
sp<const AATest> aa4_b = new AATest{4};
|
||||
std::string emptyMetricId;
|
||||
std::string emptyDimensionId;
|
||||
sp<const AATest> aa4_a = new AATest{4, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> aa4_b = new AATest{4, emptyMetricId, emptyDimensionId};
|
||||
|
||||
ipq.push(aa4_a);
|
||||
ipq.push(aa4_b);
|
||||
@@ -184,9 +196,11 @@ TEST(indexed_priority_queue, nulls) {
|
||||
|
||||
TEST(indexed_priority_queue, pop) {
|
||||
indexed_priority_queue<AATest, AATest::Smaller> ipq;
|
||||
sp<const AATest> a = new AATest{1};
|
||||
sp<const AATest> b = new AATest{2};
|
||||
sp<const AATest> c = new AATest{3};
|
||||
std::string emptyMetricId;
|
||||
std::string emptyDimensionId;
|
||||
sp<const AATest> a = new AATest{1, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> b = new AATest{2, emptyMetricId, emptyDimensionId};
|
||||
sp<const AATest> c = new AATest{3, emptyMetricId, emptyDimensionId};
|
||||
|
||||
ipq.push(c);
|
||||
ipq.push(b);
|
||||
|
||||
@@ -54,21 +54,26 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
|
||||
// 2 events in bucket 1.
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
|
||||
countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
|
||||
// Flushes at event #2.
|
||||
countProducer.flushIfNeeded(bucketStartTimeNs + 2);
|
||||
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
|
||||
|
||||
// Flushes.
|
||||
countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
EXPECT_EQ(1UL, buckets.size());
|
||||
const auto& bucketInfo = buckets[0];
|
||||
EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
|
||||
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
|
||||
EXPECT_EQ(2LL, bucketInfo.mCount);
|
||||
EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
|
||||
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
|
||||
EXPECT_EQ(2LL, buckets[0].mCount);
|
||||
|
||||
// 1 matched event happens in bucket 2.
|
||||
LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
|
||||
countProducer.flushCounterIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
|
||||
countProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
@@ -79,7 +84,7 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
|
||||
EXPECT_EQ(1LL, bucketInfo2.mCount);
|
||||
|
||||
// nothing happens in bucket 3. we should not record anything for bucket 3.
|
||||
countProducer.flushCounterIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
|
||||
countProducer.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
@@ -108,20 +113,22 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) {
|
||||
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
|
||||
|
||||
countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
|
||||
// Upon this match event, the matched event1 is flushed.
|
||||
countProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
|
||||
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
|
||||
|
||||
countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
|
||||
countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
EXPECT_EQ(1UL, buckets.size());
|
||||
const auto& bucketInfo = buckets[0];
|
||||
EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
|
||||
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
|
||||
EXPECT_EQ(1LL, bucketInfo.mCount);
|
||||
{
|
||||
const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
|
||||
EXPECT_EQ(1UL, buckets.size());
|
||||
const auto& bucketInfo = buckets[0];
|
||||
EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
|
||||
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
|
||||
EXPECT_EQ(1LL, bucketInfo.mCount);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
|
||||
@@ -158,10 +165,11 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
|
||||
bucketStartTimeNs);
|
||||
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
|
||||
countProducer.flushIfNeeded(bucketStartTimeNs + 1);
|
||||
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
|
||||
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
|
||||
|
||||
countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
|
||||
countProducer.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
|
||||
EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
|
||||
countProducer.mPastBuckets.end());
|
||||
@@ -173,6 +181,68 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
|
||||
EXPECT_EQ(1LL, bucketInfo.mCount);
|
||||
}
|
||||
|
||||
TEST(CountMetricProducerTest, TestAnomalyDetection) {
|
||||
Alert alert;
|
||||
alert.set_name("alert");
|
||||
alert.set_metric_name("1");
|
||||
alert.set_trigger_if_sum_gt(2);
|
||||
alert.set_number_of_buckets(2);
|
||||
alert.set_refractory_period_secs(1);
|
||||
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
|
||||
int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
|
||||
|
||||
sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
|
||||
|
||||
CountMetric metric;
|
||||
metric.set_name("1");
|
||||
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
|
||||
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
|
||||
bucketStartTimeNs);
|
||||
countProducer.addAnomalyTracker(anomalyTracker);
|
||||
|
||||
int tagId = 1;
|
||||
LogEvent event1(tagId, bucketStartTimeNs + 1);
|
||||
LogEvent event2(tagId, bucketStartTimeNs + 2);
|
||||
LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1);
|
||||
LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
|
||||
LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
|
||||
LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
|
||||
LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3 + NS_PER_SEC);
|
||||
|
||||
// Two events in bucket #0.
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
|
||||
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
|
||||
|
||||
// One event in bucket #2. No alarm as bucket #0 is trashed out.
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
|
||||
|
||||
// Two events in bucket #3.
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4, false);
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5, false);
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6, false);
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
// Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event5.GetTimestampNs());
|
||||
|
||||
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7, false);
|
||||
EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
|
||||
EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event7.GetTimestampNs());
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
201
cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
Normal file
201
cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
// 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 "logd/LogEvent.h"
|
||||
#include "metrics_test_helper.h"
|
||||
#include "src/metrics/GaugeMetricProducer.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace testing;
|
||||
using android::sp;
|
||||
using std::set;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
TEST(GaugeMetricProducerTest, TestWithCondition) {
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
|
||||
|
||||
GaugeMetric metric;
|
||||
metric.set_name("1");
|
||||
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
|
||||
metric.set_gauge_field(2);
|
||||
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
GaugeMetricProducer gaugeProducer(metric, 1 /*has condition*/, wizard, -1, bucketStartTimeNs);
|
||||
|
||||
vector<std::shared_ptr<LogEvent>> allData;
|
||||
std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
|
||||
event1->write(1);
|
||||
event1->write(13);
|
||||
event1->init();
|
||||
allData.push_back(event1);
|
||||
|
||||
std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
|
||||
event2->write(1);
|
||||
event2->write(15);
|
||||
event2->init();
|
||||
allData.push_back(event2);
|
||||
|
||||
gaugeProducer.onDataPulled(allData);
|
||||
gaugeProducer.flushIfNeeded(event2->GetTimestampNs() + 1);
|
||||
EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
|
||||
EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size());
|
||||
|
||||
gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 11);
|
||||
gaugeProducer.onConditionChanged(false, bucketStartTimeNs + 21);
|
||||
gaugeProducer.onConditionChanged(true, bucketStartTimeNs + bucketSizeNs + 11);
|
||||
std::shared_ptr<LogEvent> event3 =
|
||||
std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
|
||||
event3->write(1);
|
||||
event3->write(25);
|
||||
event3->init();
|
||||
allData.push_back(event3);
|
||||
gaugeProducer.onDataPulled(allData);
|
||||
gaugeProducer.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 10);
|
||||
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
|
||||
EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
|
||||
// One dimension.
|
||||
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size());
|
||||
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size());
|
||||
EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
|
||||
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
|
||||
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
|
||||
gaugeProducer.mPastBuckets.begin()->second.front().mBucketStartNs);
|
||||
}
|
||||
|
||||
TEST(GaugeMetricProducerTest, TestNoCondition) {
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
|
||||
|
||||
GaugeMetric metric;
|
||||
metric.set_name("1");
|
||||
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
|
||||
metric.set_gauge_field(2);
|
||||
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
|
||||
|
||||
vector<std::shared_ptr<LogEvent>> allData;
|
||||
std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
|
||||
event1->write(1);
|
||||
event1->write(13);
|
||||
event1->init();
|
||||
allData.push_back(event1);
|
||||
|
||||
std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 10);
|
||||
event2->write(1);
|
||||
event2->write(15);
|
||||
event2->init();
|
||||
allData.push_back(event2);
|
||||
|
||||
std::shared_ptr<LogEvent> event3 =
|
||||
std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
|
||||
event3->write(1);
|
||||
event3->write(25);
|
||||
event3->init();
|
||||
allData.push_back(event3);
|
||||
|
||||
gaugeProducer.onDataPulled(allData);
|
||||
// Has one slice
|
||||
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
|
||||
EXPECT_EQ(25L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
|
||||
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size());
|
||||
EXPECT_EQ(13L, gaugeProducer.mPastBuckets.begin()->second.front().mGauge);
|
||||
EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.begin()->second.front().mBucketNum);
|
||||
EXPECT_EQ(25L, gaugeProducer.mPastBuckets.begin()->second.back().mGauge);
|
||||
EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum);
|
||||
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
|
||||
gaugeProducer.mPastBuckets.begin()->second.back().mBucketStartNs);
|
||||
}
|
||||
|
||||
TEST(GaugeMetricProducerTest, TestAnomalyDetection) {
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
GaugeMetric metric;
|
||||
metric.set_name("1");
|
||||
metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
|
||||
metric.set_gauge_field(2);
|
||||
GaugeMetricProducer gaugeProducer(metric, -1 /*no condition*/, wizard, -1, bucketStartTimeNs);
|
||||
|
||||
Alert alert;
|
||||
alert.set_name("alert");
|
||||
alert.set_metric_name("1");
|
||||
alert.set_trigger_if_sum_gt(25);
|
||||
alert.set_number_of_buckets(2);
|
||||
sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
|
||||
gaugeProducer.addAnomalyTracker(anomalyTracker);
|
||||
|
||||
std::shared_ptr<LogEvent> event1 = std::make_shared<LogEvent>(1, bucketStartTimeNs + 1);
|
||||
event1->write(1);
|
||||
event1->write(13);
|
||||
event1->init();
|
||||
|
||||
gaugeProducer.onDataPulled({event1});
|
||||
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
|
||||
EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), -1LL);
|
||||
|
||||
std::shared_ptr<LogEvent> event2 =
|
||||
std::make_shared<LogEvent>(1, bucketStartTimeNs + bucketSizeNs + 10);
|
||||
event2->write(1);
|
||||
event2->write(15);
|
||||
event2->init();
|
||||
|
||||
gaugeProducer.onDataPulled({event2});
|
||||
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
|
||||
EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event2->GetTimestampNs());
|
||||
|
||||
std::shared_ptr<LogEvent> event3 =
|
||||
std::make_shared<LogEvent>(1, bucketStartTimeNs + 2 * bucketSizeNs + 10);
|
||||
event3->write(1);
|
||||
event3->write(24);
|
||||
event3->init();
|
||||
|
||||
gaugeProducer.onDataPulled({event3});
|
||||
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
|
||||
EXPECT_EQ(24L, gaugeProducer.mCurrentSlicedBucket->begin()->second);
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
|
||||
|
||||
// The event4 does not have the gauge field. Thus the current bucket value is 0.
|
||||
std::shared_ptr<LogEvent> event4 =
|
||||
std::make_shared<LogEvent>(1, bucketStartTimeNs + 3 * bucketSizeNs + 10);
|
||||
event4->write(1);
|
||||
event4->init();
|
||||
gaugeProducer.onDataPulled({event4});
|
||||
EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
|
||||
EXPECT_EQ(anomalyTracker->getLastAlarmTimestampNs(), (long long)event3->GetTimestampNs());
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#else
|
||||
GTEST_LOG_(INFO) << "This test does nothing.\n";
|
||||
#endif
|
||||
@@ -45,17 +45,49 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("", true, bucketStartTimeNs, key1);
|
||||
tracker.noteStop("", bucketStartTimeNs + 10, false);
|
||||
tracker.noteStart("1", true, bucketStartTimeNs, key1);
|
||||
// Event starts again. This would not change anything as it already starts.
|
||||
tracker.noteStart("1", true, bucketStartTimeNs + 3, key1);
|
||||
// Stopped.
|
||||
tracker.noteStop("1", bucketStartTimeNs + 10, false);
|
||||
|
||||
tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
|
||||
tracker.noteStop("", bucketStartTimeNs + 40, false);
|
||||
// Another event starts in this bucket.
|
||||
tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
|
||||
tracker.noteStop("2", bucketStartTimeNs + 40, false /*stop all*/);
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(20, buckets[0].mDuration);
|
||||
EXPECT_EQ(20ULL, buckets[0].mDuration);
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestStopAll) {
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
vector<DurationBucket> buckets;
|
||||
ConditionKey key1;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("1", true, bucketStartTimeNs + 1, key1);
|
||||
|
||||
// Another event starts in this bucket.
|
||||
tracker.noteStart("2", true, bucketStartTimeNs + 20, key1);
|
||||
tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
|
||||
EXPECT_TRUE(tracker.mInfos.empty());
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40);
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(40ULL, buckets[1].mDuration);
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
|
||||
@@ -67,14 +99,33 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
MaxDurationTracker tracker("event", wizard, -1, false, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
// The event starts.
|
||||
tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
|
||||
|
||||
// Starts again. Does not change anything.
|
||||
tracker.noteStart("", true, bucketStartTimeNs + bucketSizeNs + 1, key1);
|
||||
|
||||
// Flushes at early 2nd bucket. The event still does not stop yet.
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
|
||||
|
||||
// Flushes at the end of the 2nd bucket. The event still does not stop yet.
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs));
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
|
||||
EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
|
||||
EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
|
||||
EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
|
||||
|
||||
// The event stops at early 4th bucket.
|
||||
tracker.noteStop("", bucketStartTimeNs + (3 * bucketSizeNs) + 20, false /*stop all*/);
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 21);
|
||||
EXPECT_EQ(3u, buckets.size());
|
||||
EXPECT_EQ((unsigned long long)(bucketSizeNs - 1), buckets[0].mDuration);
|
||||
EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[1].mDuration);
|
||||
EXPECT_EQ((unsigned long long)bucketSizeNs, buckets[2].mDuration);
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
|
||||
@@ -86,7 +137,8 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
MaxDurationTracker tracker(wizard, -1, true, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
// 2 starts
|
||||
tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
|
||||
@@ -97,15 +149,17 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
|
||||
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
|
||||
EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
|
||||
// real stop now.
|
||||
tracker.noteStop("", bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1);
|
||||
|
||||
EXPECT_EQ(3u, buckets.size());
|
||||
EXPECT_EQ(5, buckets[2].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
EXPECT_EQ(5ULL, buckets[2].mDuration);
|
||||
}
|
||||
|
||||
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
|
||||
@@ -124,17 +178,65 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
int64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
MaxDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
MaxDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
|
||||
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
|
||||
tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
|
||||
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
|
||||
tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
EXPECT_TRUE(tracker.mInfos.empty());
|
||||
EXPECT_EQ(6LL, tracker.mDuration);
|
||||
|
||||
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
|
||||
tracker.noteStart("2:maps", false, eventStartTimeNs + 3 * bucketSizeNs + 10, key1);
|
||||
EXPECT_EQ(1u, tracker.mInfos.size());
|
||||
for (const auto& itr : tracker.mInfos) {
|
||||
EXPECT_EQ(DurationState::kPaused, itr.second.state);
|
||||
EXPECT_EQ(0LL, itr.second.lastDuration);
|
||||
}
|
||||
EXPECT_EQ(3u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
EXPECT_EQ(6ULL, buckets[2].mDuration);
|
||||
}
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(5, buckets[0].mDuration);
|
||||
TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
|
||||
Alert alert;
|
||||
alert.set_name("alert");
|
||||
alert.set_metric_name("1");
|
||||
alert.set_trigger_if_sum_gt(32 * NS_PER_SEC);
|
||||
alert.set_number_of_buckets(2);
|
||||
alert.set_refractory_period_secs(1);
|
||||
|
||||
vector<DurationBucket> buckets;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
ConditionKey key1;
|
||||
key1["APP_BACKGROUND"] = "1:maps|";
|
||||
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
|
||||
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
|
||||
sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
|
||||
MaxDurationTracker tracker("event", wizard, -1, true, bucketStartTimeNs, bucketSizeNs,
|
||||
{anomalyTracker}, buckets);
|
||||
|
||||
tracker.noteStart("1", true, eventStartTimeNs, key1);
|
||||
tracker.noteStop("1", eventStartTimeNs + 10, false);
|
||||
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
|
||||
EXPECT_EQ(10LL, tracker.mDuration);
|
||||
|
||||
tracker.noteStart("2", true, eventStartTimeNs + 20, key1);
|
||||
tracker.noteStop("2", 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));
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -45,16 +45,18 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) {
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
int64_t durationTimeNs = 2 * 1000;
|
||||
uint64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
|
||||
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
|
||||
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
|
||||
|
||||
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(durationTimeNs, buckets[0].mDuration);
|
||||
}
|
||||
@@ -71,7 +73,8 @@ TEST(OringDurationTrackerTest, TestDurationNested) {
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
|
||||
@@ -81,7 +84,66 @@ TEST(OringDurationTrackerTest, TestDurationNested) {
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(2003, buckets[0].mDuration);
|
||||
EXPECT_EQ(2003ULL, buckets[0].mDuration);
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestStopAll) {
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
ConditionKey key1;
|
||||
key1["APP_BACKGROUND"] = "1:maps|";
|
||||
|
||||
vector<DurationBucket> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
|
||||
tracker.noteStart("3:maps", true, eventStartTimeNs + 10, key1); // overlapping wl
|
||||
|
||||
tracker.noteStopAll(eventStartTimeNs + 2003);
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(2003ULL, buckets[0].mDuration);
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
|
||||
ConditionKey key1;
|
||||
key1["APP_BACKGROUND"] = "1:maps|";
|
||||
|
||||
vector<DurationBucket> buckets;
|
||||
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
uint64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
|
||||
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs + 2 * bucketSizeNs, key1);
|
||||
EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
|
||||
|
||||
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs);
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
|
||||
tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 10, false);
|
||||
tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + 12, false);
|
||||
tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12);
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestDurationConditionChange) {
|
||||
@@ -98,19 +160,19 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) {
|
||||
uint64_t bucketStartTimeNs = 10000000000;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
int64_t durationTimeNs = 2 * 1000;
|
||||
uint64_t durationTimeNs = 2 * 1000;
|
||||
|
||||
OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
OringDurationTracker tracker("event", wizard, 1, false, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
|
||||
|
||||
tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
|
||||
|
||||
tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(5, buckets[0].mDuration);
|
||||
tracker.onSlicedConditionMayChange(eventStartTimeNs + 2 * bucketSizeNs + 5);
|
||||
tracker.noteStop("2:maps", eventStartTimeNs + 2 * bucketSizeNs + durationTimeNs, false);
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + durationTimeNs);
|
||||
EXPECT_EQ(2u, buckets.size());
|
||||
EXPECT_EQ(bucketSizeNs - 1, buckets[0].mDuration);
|
||||
EXPECT_EQ(bucketSizeNs, buckets[1].mDuration);
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
|
||||
@@ -128,7 +190,8 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
|
||||
uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
|
||||
|
||||
OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
|
||||
OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs, {},
|
||||
buckets);
|
||||
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
|
||||
tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
|
||||
@@ -141,7 +204,105 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
|
||||
|
||||
tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(15, buckets[0].mDuration);
|
||||
EXPECT_EQ(15ULL, buckets[0].mDuration);
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
|
||||
Alert alert;
|
||||
alert.set_name("alert");
|
||||
alert.set_metric_name("1");
|
||||
alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
|
||||
alert.set_number_of_buckets(2);
|
||||
alert.set_refractory_period_secs(1);
|
||||
|
||||
vector<DurationBucket> buckets;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
ConditionKey key1;
|
||||
key1["APP_BACKGROUND"] = "1:maps|";
|
||||
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
|
||||
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
|
||||
sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
|
||||
OringDurationTracker tracker("event", wizard, 1, true, bucketStartTimeNs, bucketSizeNs,
|
||||
{anomalyTracker}, buckets);
|
||||
|
||||
// Nothing in the past bucket.
|
||||
tracker.noteStart("", true, eventStartTimeNs, key1);
|
||||
EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
|
||||
tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
|
||||
|
||||
tracker.noteStop("", eventStartTimeNs + 3, false);
|
||||
EXPECT_EQ(0u, buckets.size());
|
||||
|
||||
uint64_t event1StartTimeNs = eventStartTimeNs + 10;
|
||||
tracker.noteStart("1", 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.noteStop("1", event1StopTimeNs, false);
|
||||
EXPECT_EQ(1u, buckets.size());
|
||||
EXPECT_EQ(3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10,
|
||||
buckets[0].mDuration);
|
||||
|
||||
const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10;
|
||||
const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs;
|
||||
|
||||
// One past buckets. The anomaly will happen in bucket #1.
|
||||
uint64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
|
||||
tracker.noteStart("1", 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);
|
||||
|
||||
// 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);
|
||||
EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
|
||||
tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
|
||||
}
|
||||
|
||||
TEST(OringDurationTrackerTest, TestAnomalyDetection) {
|
||||
Alert alert;
|
||||
alert.set_name("alert");
|
||||
alert.set_metric_name("1");
|
||||
alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
|
||||
alert.set_number_of_buckets(2);
|
||||
alert.set_refractory_period_secs(1);
|
||||
|
||||
vector<DurationBucket> buckets;
|
||||
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
|
||||
ConditionKey key1;
|
||||
key1["APP_BACKGROUND"] = "1:maps|";
|
||||
uint64_t bucketStartTimeNs = 10 * NS_PER_SEC;
|
||||
uint64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
|
||||
uint64_t bucketSizeNs = 30 * NS_PER_SEC;
|
||||
|
||||
sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, bucketSizeNs);
|
||||
OringDurationTracker tracker("event", wizard, 1, true /*nesting*/, bucketStartTimeNs,
|
||||
bucketSizeNs, {anomalyTracker}, buckets);
|
||||
|
||||
tracker.noteStart("", true, eventStartTimeNs, key1);
|
||||
tracker.noteStop("", eventStartTimeNs + 10, false);
|
||||
EXPECT_EQ(anomalyTracker->mLastAlarmTimestampNs, -1);
|
||||
EXPECT_TRUE(tracker.mStarted.empty());
|
||||
EXPECT_EQ(-1LL, tracker.mLastStartTime);
|
||||
EXPECT_EQ(10LL, tracker.mDuration);
|
||||
|
||||
EXPECT_EQ(0u, tracker.mStarted.size());
|
||||
|
||||
tracker.noteStart("", 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.noteStop("", eventStartTimeNs + 2 * bucketSizeNs + 25, false);
|
||||
EXPECT_EQ(anomalyTracker->getSumOverPastBuckets("event"), (long long)(bucketSizeNs));
|
||||
EXPECT_EQ((long long)(eventStartTimeNs + 2 * bucketSizeNs + 25),
|
||||
anomalyTracker->mLastAlarmTimestampNs);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
|
||||
@@ -277,7 +277,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
|
||||
EXPECT_EQ(20, curInterval.raw.back().first);
|
||||
EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
|
||||
|
||||
valueProducer.flush_if_needed(bucket3StartTimeNs);
|
||||
valueProducer.flushIfNeeded(bucket3StartTimeNs);
|
||||
EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
|
||||
EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
|
||||
EXPECT_EQ(30, valueProducer.mPastBuckets.begin()->second.back().mValue);
|
||||
|
||||
Reference in New Issue
Block a user