Partial config update for alerts/subscriptions

Preserves alerts if the definition didn't change and the underlying
metric didnt change.

All alerts need to be readded to the metric b/c metrics will always
clear the alerts on config updates. In the existing code, the metric
producer created the AnomalyTracker (since duration metrics need
DurationAnomalyTrackers). Preserved alerts already have an
AnomalyTracker. Therefore, we need a second overload of
addAnomalyTracker within MetricProducer to add existing ones. Note that
we don't need to worry about the type of AnomlayTracker, since if the
alert is preserved, both the AnomalyTracker and metric must not have
changed, so they will both be the correct type.

Subscriptions are also redone - alerts will clear their subscriptions,
and all subscriptions will be readded.

For duration metric, we now need to propagate adding anomaly trackers to
the DurationTrackers since it's now possible to add anomaly trackers
after the durationTrackers have been created.

Test: atest statsd_test
Bug: 162323547
Change-Id: Id3afa04b252f3a3523114407a389be25b9171a97
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 619752c..6aa410b 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -37,14 +37,6 @@
 AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
         : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
     VLOG("AnomalyTracker() called");
-    if (mAlert.num_buckets() <= 0) {
-        ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets());
-        return;
-    }
-    if (!mAlert.has_trigger_if_sum_gt()) {
-        ALOGE("Cannot create AnomalyTracker without threshold");
-        return;
-    }
     resetStorage();  // initialization
 }
 
@@ -52,6 +44,10 @@
     VLOG("~AnomalyTracker() called");
 }
 
+void AnomalyTracker::onConfigUpdated() {
+    mSubscriptions.clear();
+}
+
 void AnomalyTracker::resetStorage() {
     VLOG("resetStorage() called.");
     mPastBuckets.clear();
@@ -259,6 +255,15 @@
     return false;
 }
 
+std::pair<bool, uint64_t> AnomalyTracker::getProtoHash() const {
+    string serializedAlert;
+    if (!mAlert.SerializeToString(&serializedAlert)) {
+        ALOGW("Unable to serialize alert %lld", (long long)mAlert.id());
+        return {false, 0};
+    }
+    return {true, Hash64(serializedAlert)};
+}
+
 void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t metric_id,
                                        int64_t metricValue) {
     triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions);
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index bf36a3b..9a578ee 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -16,15 +16,15 @@
 
 #pragma once
 
-#include <stdlib.h>
-
 #include <gtest/gtest_prod.h>
+#include <stdlib.h>
 #include <utils/RefBase.h>
 
 #include "AlarmMonitor.h"
 #include "config/ConfigKey.h"
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"    // Alert
 #include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"  // AlertMetadata
+#include "hash.h"
 #include "stats_util.h"  // HashableDimensionKey and DimToValMap
 
 namespace android {
@@ -41,6 +41,9 @@
 
     virtual ~AnomalyTracker();
 
+    // Reset appropriate state on a config update. Clear subscriptions so they can be reset.
+    void onConfigUpdated();
+
     // Add subscriptions that depend on this alert.
     void addSubscription(const Subscription& subscription) {
         mSubscriptions.push_back(subscription);
@@ -106,6 +109,26 @@
         return mNumOfPastBuckets;
     }
 
+    std::pair<bool, uint64_t> getProtoHash() const;
+
+    // Sets an alarm for the given timestamp.
+    // Replaces previous alarm if one already exists.
+    virtual void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) {
+        return;  // The base AnomalyTracker class doesn't have alarms.
+    }
+
+    // Stops the alarm.
+    // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed),
+    // declare the anomaly now.
+    virtual void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) {
+        return;  // The base AnomalyTracker class doesn't have alarms.
+    }
+
+    // Stop all the alarms owned by this tracker. Does not declare any anomalies.
+    virtual void cancelAllAlarms() {
+        return;  // The base AnomalyTracker class doesn't have alarms.
+    }
+
     // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
     // and removes it from firedAlarms. Does NOT remove the alarm from the AlarmMonitor.
     virtual void informAlarmsFired(const int64_t& timestampNs,
@@ -197,6 +220,8 @@
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
+
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index 686d8f9..4641914 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -34,15 +34,15 @@
 
     // Sets an alarm for the given timestamp.
     // Replaces previous alarm if one already exists.
-    void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime);
+    void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) override;
 
     // Stops the alarm.
     // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed),
     // declare the anomaly now.
-    void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs);
+    void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) override;
 
     // Stop all the alarms owned by this tracker. Does not declare any anomalies.
-    void cancelAllAlarms();
+    void cancelAllAlarms() override;
 
     // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
     // and removes it from firedAlarms. The AlarmMonitor is not informed.
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index b2c0b32..8869241 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -234,14 +234,26 @@
             return nullptr;
         }
     }
-    sp<DurationAnomalyTracker> anomalyTracker =
-        new DurationAnomalyTracker(alert, mConfigKey, anomalyAlarmMonitor);
-    if (anomalyTracker != nullptr) {
-        mAnomalyTrackers.push_back(anomalyTracker);
-    }
+    sp<AnomalyTracker> anomalyTracker =
+            new DurationAnomalyTracker(alert, mConfigKey, anomalyAlarmMonitor);
+    addAnomalyTrackerLocked(anomalyTracker);
     return anomalyTracker;
 }
 
+// Adds an AnomalyTracker that has already been created.
+// Note: this gets called on config updates, and will only get called if the metric and the
+// associated alert are preserved, which means the AnomalyTracker must be a DurationAnomalyTracker.
+void DurationMetricProducer::addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    addAnomalyTrackerLocked(anomalyTracker);
+}
+
+void DurationMetricProducer::addAnomalyTrackerLocked(sp<AnomalyTracker>& anomalyTracker) {
+    mAnomalyTrackers.push_back(anomalyTracker);
+    for (const auto& [_, durationTracker] : mCurrentSlicedDurationTrackerMap) {
+        durationTracker->addAnomalyTracker(anomalyTracker);
+    }
+}
 void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
                                             const HashableDimensionKey& primaryKey,
                                             const FieldValue& oldState,
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 01198a9..5feb09f 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -55,6 +55,8 @@
     sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
                                          const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
 
+    void addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker) override;
+
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
                         const HashableDimensionKey& primaryKey, const FieldValue& oldState,
                         const FieldValue& newState) override;
@@ -128,6 +130,8 @@
             std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
             std::vector<int>& metricsWithActivation) override;
 
+    void addAnomalyTrackerLocked(sp<AnomalyTracker>& anomalyTracker);
+
     const DurationMetric_AggregationType mAggregationType;
 
     // Index of the SimpleAtomMatcher which defines the start.
@@ -164,9 +168,6 @@
     std::unique_ptr<DurationTracker> createDurationTracker(
             const MetricDimensionKey& eventKey) const;
 
-    // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
-    std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
-
     // Util function to check whether the specified dimension hits the guardrail.
     bool hitGuardRailLocked(const MetricDimensionKey& newKey);
 
@@ -185,6 +186,7 @@
     FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket);
 
     FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 95a7d40..5b321a0 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -101,6 +101,7 @@
     }
     mEventActivationMap = newEventActivationMap;
     mEventDeactivationMap = newEventDeactivationMap;
+    mAnomalyTrackers.clear();
     return true;
 }
 
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 92c1a6e..0dc8eda 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -155,6 +155,7 @@
     // Update appropriate state on config updates. Primarily, all indices need to be updated.
     // This metric and all of its dependencies are guaranteed to be preserved across the update.
     // This function also updates several maps used by metricsManager.
+    // This function clears all anomaly trackers. All anomaly trackers need to be added again.
     bool onConfigUpdated(
             const StatsdConfig& config, const int configIndex, const int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
@@ -237,9 +238,6 @@
                 dumpLatency, str_set, protoOutput);
     }
 
-    // Update appropriate state on config updates. Primarily, all indices need to be updated.
-    // This metric and all of its dependencies are guaranteed to be preserved across the update.
-    // This function also updates several maps used by metricsManager.
     virtual bool onConfigUpdatedLocked(
             const StatsdConfig& config, const int configIndex, const int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
@@ -338,16 +336,20 @@
         return mSlicedStateAtoms;
     }
 
-    /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
+    /* Adds an AnomalyTracker and returns it. */
     virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
                                                  const sp<AlarmMonitor>& anomalyAlarmMonitor) {
         std::lock_guard<std::mutex> lock(mMutex);
         sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
-        if (anomalyTracker != nullptr) {
-            mAnomalyTrackers.push_back(anomalyTracker);
-        }
+        mAnomalyTrackers.push_back(anomalyTracker);
         return anomalyTracker;
     }
+
+    /* Adds an AnomalyTracker that has already been created */
+    virtual void addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mAnomalyTrackers.push_back(anomalyTracker);
+    }
     // End: getters/setters
 protected:
     /**
@@ -571,6 +573,7 @@
     FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricsMultipleTypes);
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index ab0d286..b1d4397 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -209,6 +209,8 @@
     map<int64_t, uint64_t> newStateProtoHashes;
     vector<sp<MetricProducer>> newMetricProducers;
     unordered_map<int64_t, int> newMetricProducerMap;
+    vector<sp<AnomalyTracker>> newAnomalyTrackers;
+    unordered_map<int64_t, int> newAlertTrackerMap;
     mTagIds.clear();
     mConditionToMetricMap.clear();
     mTrackerToMetricMap.clear();
@@ -221,9 +223,10 @@
             mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
             timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
             mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap,
-            mStateProtoHashes, mTagIds, newAtomMatchingTrackers, newAtomMatchingTrackerMap,
-            newConditionTrackers, newConditionTrackerMap, newMetricProducers, newMetricProducerMap,
-            mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
+            mAllAnomalyTrackers, mAlertTrackerMap, mStateProtoHashes, mTagIds,
+            newAtomMatchingTrackers, newAtomMatchingTrackerMap, newConditionTrackers,
+            newConditionTrackerMap, newMetricProducers, newMetricProducerMap, newAnomalyTrackers,
+            newAlertTrackerMap, mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
             mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
             mMetricIndexesWithActivation, newStateProtoHashes, mNoReportMetricIds);
     mAllAtomMatchingTrackers = newAtomMatchingTrackers;
@@ -233,6 +236,8 @@
     mAllMetricProducers = newMetricProducers;
     mMetricProducerMap = newMetricProducerMap;
     mStateProtoHashes = newStateProtoHashes;
+    mAllAnomalyTrackers = newAnomalyTrackers;
+    mAlertTrackerMap = newAlertTrackerMap;
     return mConfigValid;
 }
 
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 657b2e4..cf1f437 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -71,7 +71,7 @@
                     sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                     int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
                     int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
-                    const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+                    const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
         : mConfigKey(key),
           mTrackerId(id),
           mEventKey(eventKey),
@@ -93,6 +93,7 @@
         sp<ConditionWizard> tmpWizard = mWizard;
         mWizard = wizard;
         mConditionTrackerIndex = conditionTrackerIndex;
+        mAnomalyTrackers.clear();
     };
 
     virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
@@ -120,7 +121,7 @@
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
 
     // Predict the anomaly timestamp given the current status.
-    virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
+    virtual int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
                                               const int64_t currentTimestamp) const = 0;
     // Dump internal states for debugging
     virtual void dumpStates(FILE* out, bool verbose) const = 0;
@@ -132,6 +133,10 @@
     // Replace old value with new value for the given state atom.
     virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
 
+    void addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker) {
+        mAnomalyTrackers.push_back(anomalyTracker);
+    }
+
 protected:
     int64_t getCurrentBucketEndTimeNs() const {
         return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
@@ -218,13 +223,14 @@
 
     bool mHasLinksToAllConditionDimensionsInTracker;
 
-    std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
+    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
 
     FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
 
     FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
+    FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index ee4e167..62f4982 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -30,7 +30,7 @@
                                        int64_t currentBucketStartNs, int64_t currentBucketNum,
                                        int64_t startTimeNs, int64_t bucketSizeNs,
                                        bool conditionSliced, bool fullLink,
-                                       const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+                                       const vector<sp<AnomalyTracker>>& anomalyTrackers)
     : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
                       currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
                       anomalyTrackers) {
@@ -288,7 +288,7 @@
     // Note that we don't update mDuration here since it's only updated during noteStop.
 }
 
-int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
+int64_t MaxDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
                                                       const int64_t currentTimestamp) const {
     // The allowed time we can continue in the current state is the
     // (anomaly threshold) - max(elapsed time of the started mInfos).
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 2891c6e..be2707c 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -29,12 +29,10 @@
 class MaxDurationTracker : public DurationTracker {
 public:
     MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-                       sp<ConditionWizard> wizard, int conditionIndex,
-                       bool nesting,
-                       int64_t currentBucketStartNs, int64_t currentBucketNum,
-                       int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
-                       bool fullLink,
-                       const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
+                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+                       int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
+                       int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
+                       const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
 
     MaxDurationTracker(const MaxDurationTracker& tracker) = default;
 
@@ -57,7 +55,7 @@
     void onStateChanged(const int64_t timestamp, const int32_t atomId,
                         const FieldValue& newState) override;
 
-    int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
+    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 0d49bbc..247e2e0 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -28,7 +28,7 @@
         const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
         sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
         int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
-        bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+        bool fullLink, const vector<sp<AnomalyTracker>>& anomalyTrackers)
     : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
                       currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
                       anomalyTrackers),
@@ -344,9 +344,8 @@
     updateCurrentStateKey(atomId, newState);
 }
 
-int64_t OringDurationTracker::predictAnomalyTimestampNs(
-        const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const {
-
+int64_t OringDurationTracker::predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
+                                                        const int64_t eventTimestampNs) const {
     // The anomaly threshold.
     const int64_t thresholdNs = anomalyTracker.getAnomalyThreshold();
 
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index bd8017a..6eddee7 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -31,7 +31,7 @@
                          int conditionIndex, bool nesting, int64_t currentBucketStartNs,
                          int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
                          bool conditionSliced, bool fullLink,
-                         const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
+                         const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
 
@@ -54,7 +54,7 @@
             int64_t timestampNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
 
-    int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
+    int64_t predictAnomalyTimestampNs(const AnomalyTracker& anomalyTracker,
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 335f775..0c4dc44 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -115,7 +115,6 @@
                                 vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
                                 set<int64_t>& replacedMatchers) {
     const int atomMatcherCount = config.atom_matcher_size();
-
     vector<AtomMatcher> matcherProtos;
     matcherProtos.reserve(atomMatcherCount);
     newAtomMatchingTrackers.reserve(atomMatcherCount);
@@ -891,6 +890,111 @@
     return true;
 }
 
+bool determineAlertUpdateStatus(const Alert& alert,
+                                const unordered_map<int64_t, int>& oldAlertTrackerMap,
+                                const vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
+                                const set<int64_t>& replacedMetrics, UpdateStatus& updateStatus) {
+    // Check if new alert.
+    const auto& oldAnomalyTrackerIt = oldAlertTrackerMap.find(alert.id());
+    if (oldAnomalyTrackerIt == oldAlertTrackerMap.end()) {
+        updateStatus = UPDATE_NEW;
+        return true;
+    }
+
+    // This is an existing alert, check if it has changed.
+    string serializedAlert;
+    if (!alert.SerializeToString(&serializedAlert)) {
+        ALOGW("Unable to serialize alert %lld", (long long)alert.id());
+        return false;
+    }
+    uint64_t newProtoHash = Hash64(serializedAlert);
+    const auto [success, oldProtoHash] =
+            oldAnomalyTrackers[oldAnomalyTrackerIt->second]->getProtoHash();
+    if (!success) {
+        return false;
+    }
+    if (newProtoHash != oldProtoHash) {
+        updateStatus = UPDATE_REPLACE;
+        return true;
+    }
+
+    // Check if the metric this alert relies on has changed.
+    if (replacedMetrics.find(alert.metric_id()) != replacedMetrics.end()) {
+        updateStatus = UPDATE_REPLACE;
+        return true;
+    }
+
+    updateStatus = UPDATE_PRESERVE;
+    return true;
+}
+
+bool updateAlerts(const StatsdConfig& config, const unordered_map<int64_t, int>& metricProducerMap,
+                  const set<int64_t>& replacedMetrics,
+                  const unordered_map<int64_t, int>& oldAlertTrackerMap,
+                  const vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
+                  const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                  vector<sp<MetricProducer>>& allMetricProducers,
+                  unordered_map<int64_t, int>& newAlertTrackerMap,
+                  vector<sp<AnomalyTracker>>& newAnomalyTrackers) {
+    int alertCount = config.alert_size();
+    vector<UpdateStatus> alertUpdateStatuses(alertCount);
+    for (int i = 0; i < alertCount; i++) {
+        if (!determineAlertUpdateStatus(config.alert(i), oldAlertTrackerMap, oldAnomalyTrackers,
+                                        replacedMetrics, alertUpdateStatuses[i])) {
+            return false;
+        }
+    }
+
+    for (int i = 0; i < alertCount; i++) {
+        const Alert& alert = config.alert(i);
+        newAlertTrackerMap[alert.id()] = newAnomalyTrackers.size();
+        switch (alertUpdateStatuses[i]) {
+            case UPDATE_PRESERVE: {
+                // Find the alert and update it.
+                const auto& oldAnomalyTrackerIt = oldAlertTrackerMap.find(alert.id());
+                if (oldAnomalyTrackerIt == oldAlertTrackerMap.end()) {
+                    ALOGW("Could not find AnomalyTracker %lld in the previous config, but "
+                          "expected it to be there",
+                          (long long)alert.id());
+                    return false;
+                }
+                sp<AnomalyTracker> anomalyTracker = oldAnomalyTrackers[oldAnomalyTrackerIt->second];
+                anomalyTracker->onConfigUpdated();
+                // Add the alert to the relevant metric.
+                const auto& metricProducerIt = metricProducerMap.find(alert.metric_id());
+                if (metricProducerIt == metricProducerMap.end()) {
+                    ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(),
+                          (long long)alert.metric_id());
+                    return false;
+                }
+                allMetricProducers[metricProducerIt->second]->addAnomalyTracker(anomalyTracker);
+                newAnomalyTrackers.push_back(anomalyTracker);
+                break;
+            }
+            case UPDATE_REPLACE:
+            case UPDATE_NEW: {
+                optional<sp<AnomalyTracker>> anomalyTracker = createAnomalyTracker(
+                        alert, anomalyAlarmMonitor, metricProducerMap, allMetricProducers);
+                if (!anomalyTracker) {
+                    return false;
+                }
+                newAnomalyTrackers.push_back(anomalyTracker.value());
+                break;
+            }
+            default: {
+                ALOGE("Alert \"%lld\" update state is unknown. This should never happen",
+                      (long long)alert.id());
+                return false;
+            }
+        }
+    }
+    if (!initSubscribersForSubscriptionType(config, Subscription::ALERT, newAlertTrackerMap,
+                                            newAnomalyTrackers)) {
+        return false;
+    }
+    return true;
+}
+
 bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
                         const sp<StatsPullerManager>& pullerManager,
                         const sp<AlarmMonitor>& anomalyAlarmMonitor,
@@ -902,6 +1006,8 @@
                         const unordered_map<int64_t, int>& oldConditionTrackerMap,
                         const vector<sp<MetricProducer>>& oldMetricProducers,
                         const unordered_map<int64_t, int>& oldMetricProducerMap,
+                        const vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
+                        const unordered_map<int64_t, int>& oldAlertTrackerMap,
                         const map<int64_t, uint64_t>& oldStateProtoHashes, set<int>& allTagIds,
                         vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
                         unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -909,6 +1015,8 @@
                         unordered_map<int64_t, int>& newConditionTrackerMap,
                         vector<sp<MetricProducer>>& newMetricProducers,
                         unordered_map<int64_t, int>& newMetricProducerMap,
+                        vector<sp<AnomalyTracker>>& newAnomalyTrackers,
+                        unordered_map<int64_t, int>& newAlertTrackerMap,
                         unordered_map<int, vector<int>>& conditionToMetricMap,
                         unordered_map<int, vector<int>>& trackerToMetricMap,
                         unordered_map<int, vector<int>>& trackerToConditionMap,
@@ -962,7 +1070,14 @@
                        newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                        trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap,
                        deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics)) {
-        ALOGE("initMetricProducers failed");
+        ALOGE("updateMetrics failed");
+        return false;
+    }
+
+    if (!updateAlerts(config, newMetricProducerMap, replacedMetrics, oldAlertTrackerMap,
+                      oldAnomalyTrackers, anomalyAlarmMonitor, newMetricProducers,
+                      newAlertTrackerMap, newAnomalyTrackers)) {
+        ALOGE("updateAlerts failed");
         return false;
     }
 
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
index 3f1c532..b714f58 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -189,6 +189,45 @@
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
         std::vector<int>& metricsWithActivation, std::set<int64_t>& replacedMetrics);
 
+// Function to determine the update status (preserve/replace/new) of an alert.
+// [alert]: the input Alert
+// [oldAlertTrackerMap]: alert id to index mapping in the existing MetricsManager
+// [oldAnomalyTrackers]: stores the existing AnomalyTrackers
+// [replacedMetrics]: set of replaced metric ids. alerts using these metrics must be replaced
+// output:
+// [updateStatus]: update status of the alert. Will be changed from UPDATE_UNKNOWN
+// Returns whether the function was successful or not.
+bool determineAlertUpdateStatus(const Alert& alert,
+                                const std::unordered_map<int64_t, int>& oldAlertTrackerMap,
+                                const std::vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
+                                const std::set<int64_t>& replacedMetrics,
+                                UpdateStatus& updateStatus);
+
+// Update MetricProducers.
+// input:
+// [config]: the input config
+// [metricProducerMap]: metric id to index mapping in the new config
+// [replacedMetrics]: set of metric ids that have changed and were replaced
+// [oldAlertTrackerMap]: alert id to index mapping in the existing MetricsManager.
+// [oldAnomalyTrackers]: stores the existing AnomalyTrackers
+// [anomalyAlarmMonitor]: AlarmMonitor used for duration metric anomaly detection
+// [allMetricProducers]: stores the sp of the metric producers, AnomalyTrackers need to be added.
+// [stateAtomIdMap]: contains the mapping from state ids to atom ids
+// [allStateGroupMaps]: contains the mapping from atom ids and state values to
+//                      state group ids for all states
+// output:
+// [newAlertTrackerMap]: mapping of alert id to index in the new config
+// [newAnomalyTrackers]: contains the list of sp to the AnomalyTrackers created.
+bool updateAlerts(const StatsdConfig& config,
+                  const std::unordered_map<int64_t, int>& metricProducerMap,
+                  const std::set<int64_t>& replacedMetrics,
+                  const std::unordered_map<int64_t, int>& oldAlertTrackerMap,
+                  const std::vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
+                  const sp<AlarmMonitor>& anomalyAlarmMonitor,
+                  std::vector<sp<MetricProducer>>& allMetricProducers,
+                  std::unordered_map<int64_t, int>& newAlertTrackerMap,
+                  std::vector<sp<AnomalyTracker>>& newAnomalyTrackers);
+
 // Updates the existing MetricsManager from a new StatsdConfig.
 // Parameters are the members of MetricsManager. See MetricsManager for declaration.
 bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
@@ -202,6 +241,8 @@
                         const std::unordered_map<int64_t, int>& oldConditionTrackerMap,
                         const std::vector<sp<MetricProducer>>& oldMetricProducers,
                         const std::unordered_map<int64_t, int>& oldMetricProducerMap,
+                        const std::vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
+                        const std::unordered_map<int64_t, int>& oldAlertTrackerMap,
                         const std::map<int64_t, uint64_t>& oldStateProtoHashes,
                         std::set<int>& allTagIds,
                         std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
@@ -210,6 +251,8 @@
                         std::unordered_map<int64_t, int>& newConditionTrackerMap,
                         std::vector<sp<MetricProducer>>& newMetricProducers,
                         std::unordered_map<int64_t, int>& newMetricProducerMap,
+                        std::vector<sp<AnomalyTracker>>& newAlertTrackers,
+                        std::unordered_map<int64_t, int>& newAlertTrackerMap,
                         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
                         std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
                         std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 8fc039a..f15c8b0 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -814,6 +814,35 @@
                                     pullerManager, eventActivationMap, eventDeactivationMap)};
 }
 
+optional<sp<AnomalyTracker>> createAnomalyTracker(
+        const Alert& alert, const sp<AlarmMonitor>& anomalyAlarmMonitor,
+        const unordered_map<int64_t, int>& metricProducerMap,
+        vector<sp<MetricProducer>>& allMetricProducers) {
+    const auto& itr = metricProducerMap.find(alert.metric_id());
+    if (itr == metricProducerMap.end()) {
+        ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(),
+              (long long)alert.metric_id());
+        return nullopt;
+    }
+    if (!alert.has_trigger_if_sum_gt()) {
+        ALOGW("invalid alert: missing threshold");
+        return nullopt;
+    }
+    if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) {
+        ALOGW("invalid alert: threshold=%f num_buckets= %d", alert.trigger_if_sum_gt(),
+              alert.num_buckets());
+        return nullopt;
+    }
+    const int metricIndex = itr->second;
+    sp<MetricProducer> metric = allMetricProducers[metricIndex];
+    sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert, anomalyAlarmMonitor);
+    if (anomalyTracker == nullptr) {
+        // The ALOGW for this invalid alert was already displayed in addAnomalyTracker().
+        return nullopt;
+    }
+    return {anomalyTracker};
+}
+
 bool initAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
                               unordered_map<int64_t, int>& atomMatchingTrackerMap,
                               vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
@@ -1079,49 +1108,17 @@
                 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_id());
-        if (itr == metricProducerMap.end()) {
-            ALOGW("alert \"%lld\" has unknown metric id: \"%lld\"", (long long)alert.id(),
-                  (long long)alert.metric_id());
-            return false;
-        }
-        if (!alert.has_trigger_if_sum_gt()) {
-            ALOGW("invalid alert: missing threshold");
-            return false;
-        }
-        if (alert.trigger_if_sum_gt() < 0 || alert.num_buckets() <= 0) {
-            ALOGW("invalid alert: threshold=%f num_buckets= %d", alert.trigger_if_sum_gt(),
-                  alert.num_buckets());
-            return false;
-        }
-        const int metricIndex = itr->second;
-        sp<MetricProducer> metric = allMetricProducers[metricIndex];
-        sp<AnomalyTracker> anomalyTracker = metric->addAnomalyTracker(alert, anomalyAlarmMonitor);
-        if (anomalyTracker == nullptr) {
-            // The ALOGW for this invalid alert was already displayed in addAnomalyTracker().
-            return false;
-        }
         alertTrackerMap.insert(std::make_pair(alert.id(), allAnomalyTrackers.size()));
-        allAnomalyTrackers.push_back(anomalyTracker);
+        optional<sp<AnomalyTracker>> anomalyTracker = createAnomalyTracker(
+                alert, anomalyAlarmMonitor, metricProducerMap, allMetricProducers);
+        if (!anomalyTracker) {
+            return false;
+        }
+        allAnomalyTrackers.push_back(anomalyTracker.value());
     }
-    for (int i = 0; i < config.subscription_size(); ++i) {
-        const Subscription& subscription = config.subscription(i);
-        if (subscription.rule_type() != Subscription::ALERT) {
-            continue;
-        }
-        if (subscription.subscriber_information_case() ==
-            Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
-            ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id());
-            return false;
-        }
-        const auto& itr = alertTrackerMap.find(subscription.rule_id());
-        if (itr == alertTrackerMap.end()) {
-            ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
-                  (long long)subscription.id(), (long long)subscription.rule_id());
-            return false;
-        }
-        const int anomalyTrackerIndex = itr->second;
-        allAnomalyTrackers[anomalyTrackerIndex]->addSubscription(subscription);
+    if (!initSubscribersForSubscriptionType(config, Subscription::ALERT, alertTrackerMap,
+                                            allAnomalyTrackers)) {
+        return false;
     }
     return true;
 }
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index e4585cd..781a4ef 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -188,6 +188,41 @@
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
         std::vector<int>& metricsWithActivation);
 
+// Creates an AnomalyTracker and adds it to the appropriate metric.
+// Returns an sp to the AnomalyTracker, or nullopt if there was an error.
+optional<sp<AnomalyTracker>> createAnomalyTracker(
+        const Alert& alert, const sp<AlarmMonitor>& anomalyAlarmMonitor,
+        const std::unordered_map<int64_t, int>& metricProducerMap,
+        std::vector<sp<MetricProducer>>& allMetricProducers);
+
+// Templated function for adding subscriptions to alarms or alerts. Returns true if successful.
+template <typename T>
+bool initSubscribersForSubscriptionType(const StatsdConfig& config,
+                                        const Subscription_RuleType ruleType,
+                                        const std::unordered_map<int64_t, int>& ruleMap,
+                                        std::vector<T>& allRules) {
+    for (int i = 0; i < config.subscription_size(); ++i) {
+        const Subscription& subscription = config.subscription(i);
+        if (subscription.rule_type() != ruleType) {
+            continue;
+        }
+        if (subscription.subscriber_information_case() ==
+            Subscription::SubscriberInformationCase::SUBSCRIBER_INFORMATION_NOT_SET) {
+            ALOGW("subscription \"%lld\" has no subscriber info.\"", (long long)subscription.id());
+            return false;
+        }
+        const auto& itr = ruleMap.find(subscription.rule_id());
+        if (itr == ruleMap.end()) {
+            ALOGW("subscription \"%lld\" has unknown rule id: \"%lld\"",
+                  (long long)subscription.id(), (long long)subscription.rule_id());
+            return false;
+        }
+        const int ruleIndex = itr->second;
+        allRules[ruleIndex]->addSubscription(subscription);
+    }
+    return true;
+}
+
 // Helper functions for MetricsManager to initialize from StatsdConfig.
 // *Note*: only initStatsdConfig() should be called from outside.
 // All other functions are intermediate
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index 4fa9bf6..8a1b74b 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -65,13 +65,13 @@
 vector<sp<MetricProducer>> oldMetricProducers;
 unordered_map<int64_t, int> oldMetricProducerMap;
 std::vector<sp<AnomalyTracker>> oldAnomalyTrackers;
+unordered_map<int64_t, int> oldAlertTrackerMap;
 std::vector<sp<AlarmTracker>> oldAlarmTrackers;
 unordered_map<int, std::vector<int>> tmpConditionToMetricMap;
 unordered_map<int, std::vector<int>> tmpTrackerToMetricMap;
 unordered_map<int, std::vector<int>> tmpTrackerToConditionMap;
 unordered_map<int, std::vector<int>> tmpActivationAtomTrackerToMetricMap;
 unordered_map<int, std::vector<int>> tmpDeactivationAtomTrackerToMetricMap;
-unordered_map<int64_t, int> alertTrackerMap;
 vector<int> metricsWithActivation;
 map<int64_t, uint64_t> oldStateHashes;
 std::set<int64_t> noReportMetricIds;
@@ -96,7 +96,7 @@
         tmpTrackerToConditionMap.clear();
         tmpActivationAtomTrackerToMetricMap.clear();
         tmpDeactivationAtomTrackerToMetricMap.clear();
-        alertTrackerMap.clear();
+        oldAlertTrackerMap.clear();
         metricsWithActivation.clear();
         oldStateHashes.clear();
         noReportMetricIds.clear();
@@ -111,7 +111,7 @@
             oldConditionTrackers, oldConditionTrackerMap, oldMetricProducers, oldMetricProducerMap,
             oldAnomalyTrackers, oldAlarmTrackers, tmpConditionToMetricMap, tmpTrackerToMetricMap,
             tmpTrackerToConditionMap, tmpActivationAtomTrackerToMetricMap,
-            tmpDeactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
+            tmpDeactivationAtomTrackerToMetricMap, oldAlertTrackerMap, metricsWithActivation,
             oldStateHashes, noReportMetricIds);
 }
 
@@ -188,6 +188,24 @@
     }
     return metric;
 }
+
+Alert createAlert(string name, int64_t metricId, int buckets, int64_t triggerSum) {
+    Alert alert;
+    alert.set_id(StringToId(name));
+    alert.set_metric_id(metricId);
+    alert.set_num_buckets(buckets);
+    alert.set_trigger_if_sum_gt(triggerSum);
+    return alert;
+}
+
+Subscription createSubscription(string name, Subscription_RuleType type, int64_t ruleId) {
+    Subscription subscription;
+    subscription.set_id(StringToId(name));
+    subscription.set_rule_type(type);
+    subscription.set_rule_id(ruleId);
+    subscription.mutable_broadcast_subscriber_details();
+    return subscription;
+}
 }  // anonymous namespace
 
 TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) {
@@ -3243,6 +3261,220 @@
     // Only reference to the old wizard should be the one in the test.
     EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
 }
+
+TEST_F(ConfigUpdateTest, TestAlertPreserve) {
+    StatsdConfig config;
+    AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+    *config.add_atom_matcher() = whatMatcher;
+
+    *config.add_count_metric() = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {});
+
+    Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1);
+    *config.add_alert() = alert;
+    EXPECT_TRUE(initConfig(config));
+
+    UpdateStatus updateStatus = UPDATE_UNKNOWN;
+    EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers,
+                                           /*replacedMetrics*/ {}, updateStatus));
+    EXPECT_EQ(updateStatus, UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestAlertMetricChanged) {
+    StatsdConfig config;
+    AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+    *config.add_atom_matcher() = whatMatcher;
+
+    CountMetric metric = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {});
+    *config.add_count_metric() = metric;
+
+    Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1);
+    *config.add_alert() = alert;
+    EXPECT_TRUE(initConfig(config));
+
+    UpdateStatus updateStatus = UPDATE_UNKNOWN;
+    EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers,
+                                           /*replacedMetrics*/ {metric.id()}, updateStatus));
+    EXPECT_EQ(updateStatus, UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestAlertDefinitionChanged) {
+    StatsdConfig config;
+    AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+    *config.add_atom_matcher() = whatMatcher;
+
+    *config.add_count_metric() = createCountMetric("VALUE1", whatMatcher.id(), nullopt, {});
+
+    Alert alert = createAlert("Alert1", config.count_metric(0).id(), 1, 1);
+    *config.add_alert() = alert;
+    EXPECT_TRUE(initConfig(config));
+
+    alert.set_num_buckets(2);
+
+    UpdateStatus updateStatus = UPDATE_UNKNOWN;
+    EXPECT_TRUE(determineAlertUpdateStatus(alert, oldAlertTrackerMap, oldAnomalyTrackers,
+                                           /*replacedMetrics*/ {}, updateStatus));
+    EXPECT_EQ(updateStatus, UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateAlerts) {
+    StatsdConfig config;
+    // Add atom matchers/predicates/metrics. These are mostly needed for initStatsdConfig
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+    *config.add_predicate() = CreateScreenIsOnPredicate();
+
+    CountMetric countMetric = createCountMetric("COUNT1", config.atom_matcher(0).id(), nullopt, {});
+    int64_t countMetricId = countMetric.id();
+    *config.add_count_metric() = countMetric;
+
+    DurationMetric durationMetric =
+            createDurationMetric("DURATION1", config.predicate(0).id(), nullopt, {});
+    int64_t durationMetricId = durationMetric.id();
+    *config.add_duration_metric() = durationMetric;
+
+    // Add alerts.
+    // Preserved.
+    Alert alert1 = createAlert("Alert1", durationMetricId, /*buckets*/ 1, /*triggerSum*/ 5000);
+    int64_t alert1Id = alert1.id();
+    *config.add_alert() = alert1;
+
+    // Replaced.
+    Alert alert2 = createAlert("Alert2", countMetricId, /*buckets*/ 1, /*triggerSum*/ 2);
+    int64_t alert2Id = alert2.id();
+    *config.add_alert() = alert2;
+
+    // Replaced.
+    Alert alert3 = createAlert("Alert3", durationMetricId, /*buckets*/ 3, /*triggerSum*/ 5000);
+    int64_t alert3Id = alert3.id();
+    *config.add_alert() = alert3;
+
+    // Add Subscriptions.
+    Subscription subscription1 = createSubscription("S1", Subscription::ALERT, alert1Id);
+    *config.add_subscription() = subscription1;
+    Subscription subscription2 = createSubscription("S2", Subscription::ALERT, alert1Id);
+    *config.add_subscription() = subscription2;
+    Subscription subscription3 = createSubscription("S3", Subscription::ALERT, alert2Id);
+    *config.add_subscription() = subscription3;
+
+    EXPECT_TRUE(initConfig(config));
+
+    // Add a duration tracker to the duration metric to ensure durationTrackers are updated
+    // with the proper anomalyTrackers.
+    unique_ptr<LogEvent> event = CreateScreenStateChangedEvent(
+            timeBaseNs + 1, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    oldMetricProducers[1]->onMatchedLogEvent(0, *event.get());
+
+    // Change the count metric. Causes alert2 to be replaced.
+    config.mutable_count_metric(0)->set_bucket(ONE_DAY);
+    // Change num buckets on alert3, causing replacement.
+    alert3.set_num_buckets(5);
+
+    // New alert.
+    Alert alert4 = createAlert("Alert4", durationMetricId, /*buckets*/ 3, /*triggerSum*/ 10000);
+    int64_t alert4Id = alert4.id();
+
+    // Move subscription2 to be on alert2 and make a new subscription.
+    subscription2.set_rule_id(alert2Id);
+    Subscription subscription4 = createSubscription("S4", Subscription::ALERT, alert2Id);
+
+    // Create the new config. Modify the old one to avoid adding the matchers/predicates.
+    // Add alerts in different order so the map is changed.
+    config.clear_alert();
+    *config.add_alert() = alert4;
+    const int alert4Index = 0;
+    *config.add_alert() = alert3;
+    const int alert3Index = 1;
+    *config.add_alert() = alert1;
+    const int alert1Index = 2;
+    *config.add_alert() = alert2;
+    const int alert2Index = 3;
+
+    // Subscription3 is removed.
+    config.clear_subscription();
+    *config.add_subscription() = subscription4;
+    *config.add_subscription() = subscription2;
+    *config.add_subscription() = subscription1;
+
+    // Output data structures from update metrics. Don't care about the outputs besides
+    // replacedMetrics, but need to do this so that the metrics clear their anomaly trackers.
+    unordered_map<int64_t, int> newMetricProducerMap;
+    vector<sp<MetricProducer>> newMetricProducers;
+    unordered_map<int, vector<int>> conditionToMetricMap;
+    unordered_map<int, vector<int>> trackerToMetricMap;
+    set<int64_t> noReportMetricIds;
+    unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
+    set<int64_t> replacedMetrics;
+    EXPECT_TRUE(updateMetrics(
+            key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+            oldAtomMatchingTrackerMap, oldAtomMatchingTrackerMap, /*replacedMatchers*/ {},
+            oldAtomMatchingTrackers, oldConditionTrackerMap, /*replacedConditions=*/{},
+            oldConditionTrackers, {ConditionState::kUnknown}, /*stateAtomIdMap*/ {},
+            /*allStateGroupMaps=*/{},
+            /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
+            newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+            metricsWithActivation, replacedMetrics));
+
+    EXPECT_EQ(replacedMetrics, set<int64_t>({countMetricId}));
+
+    unordered_map<int64_t, int> newAlertTrackerMap;
+    vector<sp<AnomalyTracker>> newAnomalyTrackers;
+    EXPECT_TRUE(updateAlerts(config, newMetricProducerMap, replacedMetrics, oldAlertTrackerMap,
+                             oldAnomalyTrackers, anomalyAlarmMonitor, newMetricProducers,
+                             newAlertTrackerMap, newAnomalyTrackers));
+
+    unordered_map<int64_t, int> expectedAlertMap = {
+            {alert1Id, alert1Index},
+            {alert2Id, alert2Index},
+            {alert3Id, alert3Index},
+            {alert4Id, alert4Index},
+    };
+    EXPECT_THAT(newAlertTrackerMap, ContainerEq(expectedAlertMap));
+
+    // Make sure preserved alerts are the same.
+    ASSERT_EQ(newAnomalyTrackers.size(), 4);
+    EXPECT_EQ(oldAnomalyTrackers[oldAlertTrackerMap.at(alert1Id)],
+              newAnomalyTrackers[newAlertTrackerMap.at(alert1Id)]);
+
+    // Make sure replaced alerts are different.
+    EXPECT_NE(oldAnomalyTrackers[oldAlertTrackerMap.at(alert2Id)],
+              newAnomalyTrackers[newAlertTrackerMap.at(alert2Id)]);
+    EXPECT_NE(oldAnomalyTrackers[oldAlertTrackerMap.at(alert3Id)],
+              newAnomalyTrackers[newAlertTrackerMap.at(alert3Id)]);
+
+    // Verify the alerts have the correct anomaly trackers.
+    ASSERT_EQ(newMetricProducers.size(), 2);
+    EXPECT_THAT(newMetricProducers[0]->mAnomalyTrackers,
+                UnorderedElementsAre(newAnomalyTrackers[alert2Index]));
+    // For durationMetric, make sure the duration trackers get the updated anomalyTrackers.
+    DurationMetricProducer* durationProducer =
+            static_cast<DurationMetricProducer*>(newMetricProducers[1].get());
+    EXPECT_THAT(
+            durationProducer->mAnomalyTrackers,
+            UnorderedElementsAre(newAnomalyTrackers[alert1Index], newAnomalyTrackers[alert3Index],
+                                 newAnomalyTrackers[alert4Index]));
+    ASSERT_EQ(durationProducer->mCurrentSlicedDurationTrackerMap.size(), 1);
+    for (const auto& durationTrackerIt : durationProducer->mCurrentSlicedDurationTrackerMap) {
+        EXPECT_EQ(durationTrackerIt.second->mAnomalyTrackers, durationProducer->mAnomalyTrackers);
+    }
+
+    // Verify alerts have the correct subscriptions. Use subscription id as proxy for equivalency.
+    vector<int64_t> alert1Subscriptions;
+    for (const Subscription& subscription : newAnomalyTrackers[alert1Index]->mSubscriptions) {
+        alert1Subscriptions.push_back(subscription.id());
+    }
+    EXPECT_THAT(alert1Subscriptions, UnorderedElementsAre(subscription1.id()));
+    vector<int64_t> alert2Subscriptions;
+    for (const Subscription& subscription : newAnomalyTrackers[alert2Index]->mSubscriptions) {
+        alert2Subscriptions.push_back(subscription.id());
+    }
+    EXPECT_THAT(alert2Subscriptions, UnorderedElementsAre(subscription2.id(), subscription4.id()));
+    EXPECT_THAT(newAnomalyTrackers[alert3Index]->mSubscriptions, IsEmpty());
+    EXPECT_THAT(newAnomalyTrackers[alert4Index]->mSubscriptions, IsEmpty());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
index 0d0a896..9e2350b 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -27,6 +27,7 @@
 #include "src/condition/ConditionTracker.h"
 #include "src/matchers/AtomMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
+#include "src/metrics/DurationMetricProducer.h"
 #include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
@@ -793,6 +794,93 @@
     EXPECT_FALSE(tracker->IsSimpleCondition());
 }
 
+TEST(MetricsManagerTest, TestCreateAnomalyTrackerInvalidMetric) {
+    Alert alert;
+    alert.set_id(123);
+    alert.set_metric_id(1);
+    alert.set_trigger_if_sum_gt(1);
+    alert.set_num_buckets(1);
+
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    vector<sp<MetricProducer>> metricProducers;
+    // Pass in empty metric producers, causing an error.
+    EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {}, metricProducers), nullopt);
+}
+
+TEST(MetricsManagerTest, TestCreateAnomalyTrackerNoThreshold) {
+    int64_t metricId = 1;
+    Alert alert;
+    alert.set_id(123);
+    alert.set_metric_id(metricId);
+    alert.set_num_buckets(1);
+
+    CountMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
+            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt);
+}
+
+TEST(MetricsManagerTest, TestCreateAnomalyTrackerMissingBuckets) {
+    int64_t metricId = 1;
+    Alert alert;
+    alert.set_id(123);
+    alert.set_metric_id(metricId);
+    alert.set_trigger_if_sum_gt(1);
+
+    CountMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
+            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt);
+}
+
+TEST(MetricsManagerTest, TestCreateAnomalyTrackerGood) {
+    int64_t metricId = 1;
+    Alert alert;
+    alert.set_id(123);
+    alert.set_metric_id(metricId);
+    alert.set_trigger_if_sum_gt(1);
+    alert.set_num_buckets(1);
+
+    CountMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
+            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    EXPECT_NE(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt);
+}
+
+TEST(MetricsManagerTest, TestCreateAnomalyTrackerDurationTooLong) {
+    int64_t metricId = 1;
+    Alert alert;
+    alert.set_id(123);
+    alert.set_metric_id(metricId);
+    // Impossible for alert to fire since the time is bigger than bucketSize * numBuckets
+    alert.set_trigger_if_sum_gt(MillisToNano(TimeUnitToBucketSizeInMillis(ONE_MINUTE)) + 1);
+    alert.set_num_buckets(1);
+
+    DurationMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
+    FieldMatcher dimensions;
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    vector<sp<MetricProducer>> metricProducers({new DurationMetricProducer(
+            kConfigKey, metric, -1 /*no condition*/, {}, 1 /* start index */, 2 /* stop index */,
+            3 /* stop_all index */, false /*nesting*/, wizard, 0x0123456789, dimensions, 0, 0)});
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android