Home | History | Annotate | Download | only in metrics
      1 // Copyright (C) 2017 The Android Open Source Project
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //      http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 #include "src/metrics/duration_helper/MaxDurationTracker.h"
     16 #include "src/condition/ConditionWizard.h"
     17 #include "metrics_test_helper.h"
     18 #include "tests/statsd_test_util.h"
     19 
     20 #include <gmock/gmock.h>
     21 #include <gtest/gtest.h>
     22 #include <stdio.h>
     23 #include <set>
     24 #include <unordered_map>
     25 #include <vector>
     26 
     27 using namespace android::os::statsd;
     28 using namespace testing;
     29 using android::sp;
     30 using std::set;
     31 using std::unordered_map;
     32 using std::vector;
     33 
     34 #ifdef __ANDROID__
     35 
     36 namespace android {
     37 namespace os {
     38 namespace statsd {
     39 
     40 const ConfigKey kConfigKey(0, 12345);
     41 
     42 const int TagId = 1;
     43 
     44 const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
     45 const HashableDimensionKey conditionKey = getMockedDimensionKey(TagId, 4, "1");
     46 const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     47 const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
     48 const int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     49 
     50 TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
     51     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
     52     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     53     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
     54 
     55     vector<Matcher> dimensionInCondition;
     56 
     57     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     58 
     59     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
     60 
     61     int64_t bucketStartTimeNs = 10000000000;
     62     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
     63     int64_t bucketNum = 0;
     64 
     65     int64_t metricId = 1;
     66     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
     67                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
     68                                false, false, {});
     69 
     70     tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
     71     // Event starts again. This would not change anything as it already starts.
     72     tracker.noteStart(key1, true, bucketStartTimeNs + 3, ConditionKey());
     73     // Stopped.
     74     tracker.noteStop(key1, bucketStartTimeNs + 10, false);
     75 
     76     // Another event starts in this bucket.
     77     tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey());
     78     tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/);
     79 
     80     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
     81     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
     82     EXPECT_EQ(1u, buckets[eventKey].size());
     83     EXPECT_EQ(20LL, buckets[eventKey][0].mDuration);
     84 }
     85 
     86 TEST(MaxDurationTrackerTest, TestStopAll) {
     87     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
     88     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
     89     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
     90 
     91     vector<Matcher> dimensionInCondition;
     92     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     93 
     94     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
     95 
     96     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     97     int64_t bucketStartTimeNs = 10000000000;
     98     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
     99     int64_t bucketNum = 0;
    100 
    101     int64_t metricId = 1;
    102     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
    103                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
    104                                false, false, {});
    105 
    106     tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
    107 
    108     // Another event starts in this bucket.
    109     tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey());
    110     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, &buckets);
    111     tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
    112     EXPECT_TRUE(tracker.mInfos.empty());
    113     EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
    114 
    115     tracker.flushIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 40, &buckets);
    116     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
    117     EXPECT_EQ(1u, buckets[eventKey].size());
    118     EXPECT_EQ(bucketSizeNs + 40 - 1, buckets[eventKey][0].mDuration);
    119     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
    120     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
    121 }
    122 
    123 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
    124     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
    125     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
    126     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
    127     vector<Matcher> dimensionInCondition;
    128     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
    129 
    130     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
    131 
    132     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
    133     int64_t bucketStartTimeNs = 10000000000;
    134     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
    135     int64_t bucketNum = 0;
    136 
    137     int64_t metricId = 1;
    138     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
    139                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
    140                                false, false, {});
    141 
    142     // The event starts.
    143     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
    144 
    145     // Starts again. Does not DEFAULT_DIMENSION_KEY anything.
    146     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
    147                       ConditionKey());
    148 
    149     // The event stops at early 4th bucket.
    150     // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls
    151     // flushIfneeded.
    152     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 20, &buckets);
    153     tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + (3 * bucketSizeNs) + 20,
    154                      false /*stop all*/);
    155     EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
    156 
    157     tracker.flushIfNeeded(bucketStartTimeNs + 4 * bucketSizeNs, &buckets);
    158     EXPECT_EQ(1u, buckets[eventKey].size());
    159     EXPECT_EQ((3 * bucketSizeNs) + 20 - 1, buckets[eventKey][0].mDuration);
    160     EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[eventKey][0].mBucketStartNs);
    161     EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs, buckets[eventKey][0].mBucketEndNs);
    162 }
    163 
    164 TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
    165     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
    166     const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
    167     const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
    168     vector<Matcher> dimensionInCondition;
    169     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
    170 
    171     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
    172 
    173     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
    174     int64_t bucketStartTimeNs = 10000000000;
    175     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
    176     int64_t bucketNum = 0;
    177 
    178     int64_t metricId = 1;
    179     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
    180                                true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
    181                                false, false, {});
    182 
    183     // 2 starts
    184     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
    185     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, ConditionKey());
    186     // one stop
    187     tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
    188 
    189     tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1, &buckets);
    190     // Because of nesting, still not stopped.
    191     EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
    192 
    193     // real stop now.
    194     tracker.noteStop(DEFAULT_DIMENSION_KEY,
    195                      bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
    196     tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1, &buckets);
    197 
    198     EXPECT_EQ(1u, buckets[eventKey].size());
    199     EXPECT_EQ(2 * bucketSizeNs + 5 - 1, buckets[eventKey][0].mDuration);
    200 }
    201 
    202 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
    203     const HashableDimensionKey conditionDimKey = key1;
    204 
    205     vector<Matcher> dimensionInCondition;
    206     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
    207 
    208     ConditionKey conditionKey1;
    209     MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 1, "1");
    210     conditionKey1[StringToId("APP_BACKGROUND")] = conditionDimKey;
    211 
    212     /**
    213     Start in first bucket, stop in second bucket. Condition turns on and off in the first bucket
    214     and again turns on and off in the second bucket.
    215     */
    216     int64_t bucketStartTimeNs = 10000000000;
    217     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
    218     int64_t eventStartTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
    219     int64_t conditionStarts1 = bucketStartTimeNs + 11 * NS_PER_SEC;
    220     int64_t conditionStops1 = bucketStartTimeNs + 14 * NS_PER_SEC;
    221     int64_t conditionStarts2 = bucketStartTimeNs + bucketSizeNs + 5 * NS_PER_SEC;
    222     int64_t conditionStops2 = conditionStarts2 + 10 * NS_PER_SEC;
    223     int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC;
    224 
    225     int64_t metricId = 1;
    226     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
    227                                false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
    228                                false, {});
    229     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
    230 
    231     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
    232     tracker.noteConditionChanged(key1, true, conditionStarts1);
    233     tracker.noteConditionChanged(key1, false, conditionStops1);
    234     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
    235     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
    236     EXPECT_EQ(0U, buckets.size());
    237 
    238     tracker.noteConditionChanged(key1, true, conditionStarts2);
    239     tracker.noteConditionChanged(key1, false, conditionStops2);
    240     tracker.noteStop(key1, eventStopTimeNs, false);
    241     tracker.flushIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1, &buckets);
    242     EXPECT_EQ(1U, buckets.size());
    243     vector<DurationBucket> item = buckets.begin()->second;
    244     EXPECT_EQ(1UL, item.size());
    245     EXPECT_EQ((int64_t)(13LL * NS_PER_SEC), item[0].mDuration);
    246 }
    247 
    248 TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
    249     vector<Matcher> dimensionInCondition;
    250     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
    251 
    252     ConditionKey conditionKey1;
    253     MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
    254     conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
    255 
    256     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
    257 
    258     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
    259     int64_t bucketStartTimeNs = 10000000000;
    260     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
    261     int64_t bucketNum = 0;
    262     int64_t eventStartTimeNs = 13000000000;
    263     int64_t durationTimeNs = 2 * 1000;
    264 
    265     int64_t metricId = 1;
    266     Alert alert;
    267     alert.set_id(101);
    268     alert.set_metric_id(1);
    269     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
    270     alert.set_num_buckets(2);
    271     const int32_t refPeriodSec = 45;
    272     alert.set_refractory_period_secs(refPeriodSec);
    273     sp<AlarmMonitor> alarmMonitor;
    274     sp<DurationAnomalyTracker> anomalyTracker =
    275         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
    276     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
    277                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
    278                                true, false, {anomalyTracker});
    279 
    280     tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
    281     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
    282     EXPECT_EQ((long long)(53ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
    283 
    284     // Remove the anomaly alarm when the duration is no longer fully met.
    285     tracker.noteConditionChanged(key1, false, eventStartTimeNs + 15 * NS_PER_SEC);
    286     EXPECT_EQ(0U, anomalyTracker->mAlarms.size());
    287 
    288     // Since the condition was off for 10 seconds, the anomaly should trigger 10 sec later.
    289     tracker.noteConditionChanged(key1, true, eventStartTimeNs + 25 * NS_PER_SEC);
    290     EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
    291     alarm = anomalyTracker->mAlarms.begin()->second;
    292     EXPECT_EQ((long long)(63ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
    293 }
    294 
    295 // This tests that we correctly compute the predicted time of an anomaly assuming that the current
    296 // state continues forward as-is.
    297 TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) {
    298     vector<Matcher> dimensionInCondition;
    299     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
    300 
    301     ConditionKey conditionKey1;
    302     MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
    303     conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
    304     ConditionKey conditionKey2;
    305     conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2");
    306 
    307     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
    308 
    309     /**
    310      * Suppose we have two sub-dimensions that we're taking the MAX over. In the first of these
    311      * nested dimensions, we enter the pause state after 3 seconds. When we resume, the second
    312      * dimension has already been running for 4 seconds. Thus, we have 40-4=36 seconds remaining
    313      * before we trigger the anomaly.
    314      */
    315     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
    316     int64_t bucketStartTimeNs = 10000000000;
    317     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
    318     int64_t bucketNum = 0;
    319     int64_t eventStartTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC;  // Condition is off at start.
    320     int64_t conditionStarts1 = bucketStartTimeNs + 11 * NS_PER_SEC;
    321     int64_t conditionStops1 = bucketStartTimeNs + 14 * NS_PER_SEC;
    322     int64_t conditionStarts2 = bucketStartTimeNs + 20 * NS_PER_SEC;
    323     int64_t eventStartTimeNs2 = conditionStarts2 - 4 * NS_PER_SEC;
    324 
    325     int64_t metricId = 1;
    326     Alert alert;
    327     alert.set_id(101);
    328     alert.set_metric_id(1);
    329     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
    330     alert.set_num_buckets(2);
    331     const int32_t refPeriodSec = 45;
    332     alert.set_refractory_period_secs(refPeriodSec);
    333     sp<AlarmMonitor> alarmMonitor;
    334     sp<DurationAnomalyTracker> anomalyTracker =
    335         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
    336     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
    337                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
    338                                true, false, {anomalyTracker});
    339 
    340     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
    341     tracker.noteConditionChanged(key1, true, conditionStarts1);
    342     tracker.noteConditionChanged(key1, false, conditionStops1);
    343     tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);  // Condition is on already.
    344     tracker.noteConditionChanged(key1, true, conditionStarts2);
    345     EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
    346     auto alarm = anomalyTracker->mAlarms.begin()->second;
    347     int64_t anomalyFireTimeSec = alarm->timestampSec;
    348     EXPECT_EQ(conditionStarts2 + 36 * NS_PER_SEC,
    349             (long long)anomalyFireTimeSec * NS_PER_SEC);
    350 
    351     // Now we test the calculation now that there's a refractory period.
    352     // At the correct time, declare the anomaly. This will set a refractory period. Make sure it
    353     // gets correctly taken into account in future predictAnomalyTimestampNs calculations.
    354     std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm});
    355     anomalyTracker->informAlarmsFired(anomalyFireTimeSec * NS_PER_SEC, firedAlarms);
    356     EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
    357     int64_t refractoryPeriodEndsSec = anomalyFireTimeSec + refPeriodSec;
    358     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), refractoryPeriodEndsSec);
    359 
    360     // Now stop and start again. Make sure the new predictAnomalyTimestampNs takes into account
    361     // the refractory period correctly.
    362     int64_t eventStopTimeNs = anomalyFireTimeSec * NS_PER_SEC + 10;
    363     tracker.noteStop(key1, eventStopTimeNs, false);
    364     tracker.noteStop(key2, eventStopTimeNs, false);
    365     tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1);
    366     // Anomaly is ongoing, but we're still in the refractory period.
    367     EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
    368     alarm = anomalyTracker->mAlarms.begin()->second;
    369     EXPECT_EQ(refractoryPeriodEndsSec, (long long)(alarm->timestampSec));
    370 
    371     // Makes sure it is correct after the refractory period is over.
    372     tracker.noteStop(key1, eventStopTimeNs + 2000000, false);
    373     int64_t justBeforeRefPeriodNs = (refractoryPeriodEndsSec - 2) * NS_PER_SEC;
    374     tracker.noteStart(key1, true, justBeforeRefPeriodNs, conditionKey1);
    375     alarm = anomalyTracker->mAlarms.begin()->second;
    376     EXPECT_EQ(justBeforeRefPeriodNs + 40 * NS_PER_SEC,
    377                 (unsigned long long)(alarm->timestampSec * NS_PER_SEC));
    378 }
    379 
    380 // Suppose that within one tracker there are two dimensions A and B.
    381 // Suppose A starts, then B starts, and then A stops. We still need to set an anomaly based on the
    382 // elapsed duration of B.
    383 TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) {
    384     vector<Matcher> dimensionInCondition;
    385     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
    386 
    387     ConditionKey conditionKey1;
    388     MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
    389     conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
    390     ConditionKey conditionKey2;
    391     conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2");
    392 
    393     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
    394 
    395     /**
    396      * Suppose we have two sub-dimensions that we're taking the MAX over. In the first of these
    397      * nested dimensions, are started for 8 seconds. When we stop, the other nested dimension has
    398      * been started for 5 seconds. So we can only allow 35 more seconds from now.
    399      */
    400     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
    401     int64_t bucketStartTimeNs = 10000000000;
    402     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
    403     int64_t bucketNum = 0;
    404     int64_t eventStartTimeNs1 = bucketStartTimeNs + 5 * NS_PER_SEC;  // Condition is off at start.
    405     int64_t eventStopTimeNs1 = bucketStartTimeNs + 13 * NS_PER_SEC;
    406     int64_t eventStartTimeNs2 = bucketStartTimeNs + 8 * NS_PER_SEC;
    407 
    408     int64_t metricId = 1;
    409     Alert alert;
    410     alert.set_id(101);
    411     alert.set_metric_id(1);
    412     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
    413     alert.set_num_buckets(2);
    414     const int32_t refPeriodSec = 45;
    415     alert.set_refractory_period_secs(refPeriodSec);
    416     sp<AlarmMonitor> alarmMonitor;
    417     sp<DurationAnomalyTracker> anomalyTracker =
    418         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
    419     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
    420                                false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
    421                                true, false, {anomalyTracker});
    422 
    423     tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
    424     tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
    425     tracker.noteStop(key1, eventStopTimeNs1, false);
    426     EXPECT_EQ(1U, anomalyTracker->mAlarms.size());
    427     auto alarm = anomalyTracker->mAlarms.begin()->second;
    428     EXPECT_EQ(eventStopTimeNs1 + 35 * NS_PER_SEC,
    429               (unsigned long long)(alarm->timestampSec * NS_PER_SEC));
    430 }
    431 
    432 }  // namespace statsd
    433 }  // namespace os
    434 }  // namespace android
    435 #else
    436 GTEST_LOG_(INFO) << "This test does nothing.\n";
    437 #endif