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:
TreeHugger Robot
2017-11-21 01:33:24 +00:00
committed by Android (Google) Code Review
42 changed files with 1831 additions and 676 deletions

View File

@@ -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)

View File

@@ -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");

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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];

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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;

View File

@@ -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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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);