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/CountMetricProducer.h" 16 #include "src/stats_log_util.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 <math.h> 23 #include <stdio.h> 24 #include <vector> 25 26 using namespace testing; 27 using android::sp; 28 using std::set; 29 using std::unordered_map; 30 using std::vector; 31 32 #ifdef __ANDROID__ 33 34 namespace android { 35 namespace os { 36 namespace statsd { 37 38 const ConfigKey kConfigKey(0, 12345); 39 40 TEST(CountMetricProducerTest, TestFirstBucket) { 41 CountMetric metric; 42 metric.set_id(1); 43 metric.set_bucket(ONE_MINUTE); 44 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); 45 46 CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, 47 5, 600 * NS_PER_SEC + NS_PER_SEC/2); 48 EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs); 49 EXPECT_EQ(10, countProducer.mCurrentBucketNum); 50 EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs()); 51 } 52 53 TEST(CountMetricProducerTest, TestNonDimensionalEvents) { 54 int64_t bucketStartTimeNs = 10000000000; 55 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; 56 int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; 57 int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; 58 int tagId = 1; 59 60 CountMetric metric; 61 metric.set_id(1); 62 metric.set_bucket(ONE_MINUTE); 63 64 LogEvent event1(tagId, bucketStartTimeNs + 1); 65 event1.init(); 66 LogEvent event2(tagId, bucketStartTimeNs + 2); 67 event2.init(); 68 69 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); 70 71 CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, 72 bucketStartTimeNs, bucketStartTimeNs); 73 74 // 2 events in bucket 1. 75 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); 76 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); 77 78 // Flushes at event #2. 79 countProducer.flushIfNeededLocked(bucketStartTimeNs + 2); 80 EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); 81 82 // Flushes. 83 countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); 84 EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); 85 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != 86 countProducer.mPastBuckets.end()); 87 const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; 88 EXPECT_EQ(1UL, buckets.size()); 89 EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); 90 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); 91 EXPECT_EQ(2LL, buckets[0].mCount); 92 93 // 1 matched event happens in bucket 2. 94 LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2); 95 event3.init(); 96 97 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); 98 countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); 99 EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); 100 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != 101 countProducer.mPastBuckets.end()); 102 EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); 103 const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1]; 104 EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs); 105 EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs); 106 EXPECT_EQ(1LL, bucketInfo2.mCount); 107 108 // nothing happens in bucket 3. we should not record anything for bucket 3. 109 countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); 110 EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); 111 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != 112 countProducer.mPastBuckets.end()); 113 const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; 114 EXPECT_EQ(2UL, buckets3.size()); 115 } 116 117 TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { 118 int64_t bucketStartTimeNs = 10000000000; 119 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; 120 121 CountMetric metric; 122 metric.set_id(1); 123 metric.set_bucket(ONE_MINUTE); 124 metric.set_condition(StringToId("SCREEN_ON")); 125 126 LogEvent event1(1, bucketStartTimeNs + 1); 127 event1.init(); 128 129 LogEvent event2(1, bucketStartTimeNs + 10); 130 event2.init(); 131 132 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); 133 134 CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs, bucketStartTimeNs); 135 136 countProducer.onConditionChanged(true, bucketStartTimeNs); 137 countProducer.onMatchedLogEvent(1 /*matcher index*/, event1); 138 EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); 139 140 countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2); 141 // Upon this match event, the matched event1 is flushed. 142 countProducer.onMatchedLogEvent(1 /*matcher index*/, event2); 143 EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); 144 145 countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); 146 EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); 147 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != 148 countProducer.mPastBuckets.end()); 149 { 150 const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; 151 EXPECT_EQ(1UL, buckets.size()); 152 const auto& bucketInfo = buckets[0]; 153 EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); 154 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); 155 EXPECT_EQ(1LL, bucketInfo.mCount); 156 } 157 } 158 159 TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { 160 int64_t bucketStartTimeNs = 10000000000; 161 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; 162 163 int tagId = 1; 164 int conditionTagId = 2; 165 166 CountMetric metric; 167 metric.set_id(1); 168 metric.set_bucket(ONE_MINUTE); 169 metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); 170 MetricConditionLink* link = metric.add_links(); 171 link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); 172 buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); 173 buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); 174 175 LogEvent event1(tagId, bucketStartTimeNs + 1); 176 event1.write("111"); // uid 177 event1.init(); 178 ConditionKey key1; 179 key1[StringToId("APP_IN_BACKGROUND_PER_UID")] = 180 {getMockedDimensionKey(conditionTagId, 2, "111")}; 181 182 LogEvent event2(tagId, bucketStartTimeNs + 10); 183 event2.write("222"); // uid 184 event2.init(); 185 ConditionKey key2; 186 key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = 187 {getMockedDimensionKey(conditionTagId, 2, "222")}; 188 189 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); 190 EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse)); 191 192 EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue)); 193 194 CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard, 195 bucketStartTimeNs, bucketStartTimeNs); 196 197 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); 198 countProducer.flushIfNeededLocked(bucketStartTimeNs + 1); 199 EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); 200 201 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); 202 countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); 203 EXPECT_EQ(1UL, countProducer.mPastBuckets.size()); 204 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != 205 countProducer.mPastBuckets.end()); 206 const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; 207 EXPECT_EQ(1UL, buckets.size()); 208 const auto& bucketInfo = buckets[0]; 209 EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs); 210 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs); 211 EXPECT_EQ(1LL, bucketInfo.mCount); 212 } 213 214 TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { 215 sp<AlarmMonitor> alarmMonitor; 216 int64_t bucketStartTimeNs = 10000000000; 217 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; 218 int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; 219 220 int tagId = 1; 221 int conditionTagId = 2; 222 223 CountMetric metric; 224 metric.set_id(1); 225 metric.set_bucket(ONE_MINUTE); 226 Alert alert; 227 alert.set_num_buckets(3); 228 alert.set_trigger_if_sum_gt(2); 229 LogEvent event1(tagId, bucketStartTimeNs + 1); 230 event1.write("111"); // uid 231 event1.init(); 232 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); 233 CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, 234 bucketStartTimeNs, bucketStartTimeNs); 235 236 sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); 237 EXPECT_TRUE(anomalyTracker != nullptr); 238 239 // Bucket is flushed yet. 240 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); 241 EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); 242 EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); 243 244 // App upgrade forces bucket flush. 245 // Check that there's a past bucket and the bucket end is not adjusted. 246 countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); 247 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); 248 EXPECT_EQ((long long)bucketStartTimeNs, 249 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); 250 EXPECT_EQ((long long)eventUpgradeTimeNs, 251 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); 252 EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); 253 // Anomaly tracker only contains full buckets. 254 EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); 255 256 int64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs(); 257 // Next event occurs in same bucket as partial bucket created. 258 LogEvent event2(tagId, bucketStartTimeNs + 59 * NS_PER_SEC + 10); 259 event2.write("222"); // uid 260 event2.init(); 261 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); 262 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); 263 EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); 264 EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); 265 266 // Third event in following bucket. 267 LogEvent event3(tagId, bucketStartTimeNs + 62 * NS_PER_SEC + 10); 268 event3.write("333"); // uid 269 event3.init(); 270 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); 271 EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); 272 EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs); 273 EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); 274 } 275 276 TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { 277 int64_t bucketStartTimeNs = 10000000000; 278 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; 279 int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; 280 281 int tagId = 1; 282 int conditionTagId = 2; 283 284 CountMetric metric; 285 metric.set_id(1); 286 metric.set_bucket(ONE_MINUTE); 287 LogEvent event1(tagId, bucketStartTimeNs + 1); 288 event1.write("111"); // uid 289 event1.init(); 290 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); 291 CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, 292 bucketStartTimeNs, bucketStartTimeNs); 293 294 // Bucket is flushed yet. 295 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); 296 EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); 297 298 // App upgrade forces bucket flush. 299 // Check that there's a past bucket and the bucket end is not adjusted. 300 countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); 301 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); 302 EXPECT_EQ((int64_t)bucketStartTimeNs, 303 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); 304 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, 305 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); 306 EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); 307 308 // Next event occurs in same bucket as partial bucket created. 309 LogEvent event2(tagId, bucketStartTimeNs + 70 * NS_PER_SEC + 10); 310 event2.write("222"); // uid 311 event2.init(); 312 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); 313 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); 314 315 // Third event in following bucket. 316 LogEvent event3(tagId, bucketStartTimeNs + 121 * NS_PER_SEC + 10); 317 event3.write("333"); // uid 318 event3.init(); 319 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); 320 EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); 321 EXPECT_EQ((int64_t)eventUpgradeTimeNs, 322 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs); 323 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, 324 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs); 325 } 326 327 TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { 328 sp<AlarmMonitor> alarmMonitor; 329 Alert alert; 330 alert.set_id(11); 331 alert.set_metric_id(1); 332 alert.set_trigger_if_sum_gt(2); 333 alert.set_num_buckets(2); 334 const int32_t refPeriodSec = 1; 335 alert.set_refractory_period_secs(refPeriodSec); 336 337 int64_t bucketStartTimeNs = 10000000000; 338 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; 339 int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; 340 int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; 341 342 CountMetric metric; 343 metric.set_id(1); 344 metric.set_bucket(ONE_MINUTE); 345 346 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); 347 CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, 348 bucketStartTimeNs, bucketStartTimeNs); 349 350 sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); 351 352 int tagId = 1; 353 LogEvent event1(tagId, bucketStartTimeNs + 1); 354 event1.init(); 355 LogEvent event2(tagId, bucketStartTimeNs + 2); 356 event2.init(); 357 LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1); 358 event3.init(); 359 LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1); 360 event4.init(); 361 LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2); 362 event5.init(); 363 LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3); 364 event6.init(); 365 LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC); 366 event7.init(); 367 368 // Two events in bucket #0. 369 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); 370 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); 371 372 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); 373 EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second); 374 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); 375 376 // One event in bucket #2. No alarm as bucket #0 is trashed out. 377 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); 378 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); 379 EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second); 380 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); 381 382 // Two events in bucket #3. 383 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); 384 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5); 385 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6); 386 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); 387 EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second); 388 // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6 389 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 390 std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); 391 392 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7); 393 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size()); 394 EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second); 395 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 396 std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); 397 } 398 399 } // namespace statsd 400 } // namespace os 401 } // namespace android 402 #else 403 GTEST_LOG_(INFO) << "This test does nothing.\n"; 404 #endif 405