| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "src/guardrail/StatsdStats.h" |
| #include "statslog.h" |
| #include "tests/statsd_test_util.h" |
| |
| #include <gtest/gtest.h> |
| #include <vector> |
| |
| #ifdef __ANDROID__ |
| |
| namespace android { |
| namespace os { |
| namespace statsd { |
| |
| using std::vector; |
| |
| TEST(StatsdStatsTest, TestValidConfigAdd) { |
| StatsdStats stats; |
| ConfigKey key(0, 12345); |
| const int metricsCount = 10; |
| const int conditionsCount = 20; |
| const int matchersCount = 30; |
| const int alertsCount = 10; |
| stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, |
| true /*valid config*/); |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false /*reset stats*/); |
| |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| EXPECT_EQ(1, report.config_stats_size()); |
| const auto& configReport = report.config_stats(0); |
| EXPECT_EQ(0, configReport.uid()); |
| EXPECT_EQ(12345, configReport.id()); |
| EXPECT_EQ(metricsCount, configReport.metric_count()); |
| EXPECT_EQ(conditionsCount, configReport.condition_count()); |
| EXPECT_EQ(matchersCount, configReport.matcher_count()); |
| EXPECT_EQ(alertsCount, configReport.alert_count()); |
| EXPECT_EQ(true, configReport.is_valid()); |
| EXPECT_FALSE(configReport.has_deletion_time_sec()); |
| } |
| |
| TEST(StatsdStatsTest, TestInvalidConfigAdd) { |
| StatsdStats stats; |
| ConfigKey key(0, 12345); |
| const int metricsCount = 10; |
| const int conditionsCount = 20; |
| const int matchersCount = 30; |
| const int alertsCount = 10; |
| stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, |
| false /*bad config*/); |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| EXPECT_EQ(1, report.config_stats_size()); |
| const auto& configReport = report.config_stats(0); |
| // The invalid config should be put into icebox with a deletion time. |
| EXPECT_TRUE(configReport.has_deletion_time_sec()); |
| } |
| |
| TEST(StatsdStatsTest, TestConfigRemove) { |
| StatsdStats stats; |
| ConfigKey key(0, 12345); |
| const int metricsCount = 10; |
| const int conditionsCount = 20; |
| const int matchersCount = 30; |
| const int alertsCount = 10; |
| stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, {}, |
| true); |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| EXPECT_EQ(1, report.config_stats_size()); |
| const auto& configReport = report.config_stats(0); |
| EXPECT_FALSE(configReport.has_deletion_time_sec()); |
| |
| stats.noteConfigRemoved(key); |
| stats.dumpStats(&output, false); |
| good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| EXPECT_EQ(1, report.config_stats_size()); |
| const auto& configReport2 = report.config_stats(0); |
| EXPECT_TRUE(configReport2.has_deletion_time_sec()); |
| } |
| |
| TEST(StatsdStatsTest, TestSubStats) { |
| StatsdStats stats; |
| ConfigKey key(0, 12345); |
| stats.noteConfigReceived(key, 2, 3, 4, 5, {std::make_pair(123, 456)}, true); |
| |
| stats.noteMatcherMatched(key, StringToId("matcher1")); |
| stats.noteMatcherMatched(key, StringToId("matcher1")); |
| stats.noteMatcherMatched(key, StringToId("matcher2")); |
| |
| stats.noteConditionDimensionSize(key, StringToId("condition1"), 250); |
| stats.noteConditionDimensionSize(key, StringToId("condition1"), 240); |
| |
| stats.noteMetricDimensionSize(key, StringToId("metric1"), 201); |
| stats.noteMetricDimensionSize(key, StringToId("metric1"), 202); |
| |
| stats.noteAnomalyDeclared(key, StringToId("alert1")); |
| stats.noteAnomalyDeclared(key, StringToId("alert1")); |
| stats.noteAnomalyDeclared(key, StringToId("alert2")); |
| |
| // broadcast-> 2 |
| stats.noteBroadcastSent(key); |
| stats.noteBroadcastSent(key); |
| |
| // data drop -> 1 |
| stats.noteDataDropped(key, 123); |
| |
| // dump report -> 3 |
| stats.noteMetricsReportSent(key, 0); |
| stats.noteMetricsReportSent(key, 0); |
| stats.noteMetricsReportSent(key, 0); |
| |
| // activation_time_sec -> 2 |
| stats.noteActiveStatusChanged(key, true); |
| stats.noteActiveStatusChanged(key, true); |
| |
| // deactivation_time_sec -> 1 |
| stats.noteActiveStatusChanged(key, false); |
| |
| vector<uint8_t> output; |
| stats.dumpStats(&output, true); // Dump and reset stats |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| EXPECT_EQ(1, report.config_stats_size()); |
| const auto& configReport = report.config_stats(0); |
| EXPECT_EQ(2, configReport.broadcast_sent_time_sec_size()); |
| EXPECT_EQ(1, configReport.data_drop_time_sec_size()); |
| EXPECT_EQ(1, configReport.data_drop_bytes_size()); |
| EXPECT_EQ(123, configReport.data_drop_bytes(0)); |
| EXPECT_EQ(3, configReport.dump_report_time_sec_size()); |
| EXPECT_EQ(3, configReport.dump_report_data_size_size()); |
| EXPECT_EQ(2, configReport.activation_time_sec_size()); |
| EXPECT_EQ(1, configReport.deactivation_time_sec_size()); |
| EXPECT_EQ(1, configReport.annotation_size()); |
| EXPECT_EQ(123, configReport.annotation(0).field_int64()); |
| EXPECT_EQ(456, configReport.annotation(0).field_int32()); |
| |
| EXPECT_EQ(2, configReport.matcher_stats_size()); |
| // matcher1 is the first in the list |
| if (configReport.matcher_stats(0).id() == StringToId("matcher1")) { |
| EXPECT_EQ(2, configReport.matcher_stats(0).matched_times()); |
| EXPECT_EQ(1, configReport.matcher_stats(1).matched_times()); |
| EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(1).id()); |
| } else { |
| // matcher1 is the second in the list. |
| EXPECT_EQ(1, configReport.matcher_stats(0).matched_times()); |
| EXPECT_EQ(StringToId("matcher2"), configReport.matcher_stats(0).id()); |
| |
| EXPECT_EQ(2, configReport.matcher_stats(1).matched_times()); |
| EXPECT_EQ(StringToId("matcher1"), configReport.matcher_stats(1).id()); |
| } |
| |
| EXPECT_EQ(2, configReport.alert_stats_size()); |
| bool alert1first = configReport.alert_stats(0).id() == StringToId("alert1"); |
| EXPECT_EQ(StringToId("alert1"), configReport.alert_stats(alert1first ? 0 : 1).id()); |
| EXPECT_EQ(2, configReport.alert_stats(alert1first ? 0 : 1).alerted_times()); |
| EXPECT_EQ(StringToId("alert2"), configReport.alert_stats(alert1first ? 1 : 0).id()); |
| EXPECT_EQ(1, configReport.alert_stats(alert1first ? 1 : 0).alerted_times()); |
| |
| EXPECT_EQ(1, configReport.condition_stats_size()); |
| EXPECT_EQ(StringToId("condition1"), configReport.condition_stats(0).id()); |
| EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts()); |
| |
| EXPECT_EQ(1, configReport.metric_stats_size()); |
| EXPECT_EQ(StringToId("metric1"), configReport.metric_stats(0).id()); |
| EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts()); |
| |
| // after resetting the stats, some new events come |
| stats.noteMatcherMatched(key, StringToId("matcher99")); |
| stats.noteConditionDimensionSize(key, StringToId("condition99"), 300); |
| stats.noteMetricDimensionSize(key, StringToId("metric99tion99"), 270); |
| stats.noteAnomalyDeclared(key, StringToId("alert99")); |
| |
| // now the config stats should only contain the stats about the new event. |
| stats.dumpStats(&output, false); |
| good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| EXPECT_EQ(1, report.config_stats_size()); |
| const auto& configReport2 = report.config_stats(0); |
| EXPECT_EQ(1, configReport2.matcher_stats_size()); |
| EXPECT_EQ(StringToId("matcher99"), configReport2.matcher_stats(0).id()); |
| EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times()); |
| |
| EXPECT_EQ(1, configReport2.condition_stats_size()); |
| EXPECT_EQ(StringToId("condition99"), configReport2.condition_stats(0).id()); |
| EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts()); |
| |
| EXPECT_EQ(1, configReport2.metric_stats_size()); |
| EXPECT_EQ(StringToId("metric99tion99"), configReport2.metric_stats(0).id()); |
| EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts()); |
| |
| EXPECT_EQ(1, configReport2.alert_stats_size()); |
| EXPECT_EQ(StringToId("alert99"), configReport2.alert_stats(0).id()); |
| EXPECT_EQ(1, configReport2.alert_stats(0).alerted_times()); |
| } |
| |
| TEST(StatsdStatsTest, TestAtomLog) { |
| StatsdStats stats; |
| time_t now = time(nullptr); |
| // old event, we get it from the stats buffer. should be ignored. |
| stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, 1000); |
| |
| stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 1); |
| stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2); |
| stats.noteAtomLogged(android::util::APP_CRASH_OCCURRED, now + 3); |
| |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| |
| EXPECT_EQ(2, report.atom_stats_size()); |
| bool sensorAtomGood = false; |
| bool dropboxAtomGood = false; |
| |
| for (const auto& atomStats : report.atom_stats()) { |
| if (atomStats.tag() == android::util::SENSOR_STATE_CHANGED && atomStats.count() == 3) { |
| sensorAtomGood = true; |
| } |
| if (atomStats.tag() == android::util::APP_CRASH_OCCURRED && atomStats.count() == 1) { |
| dropboxAtomGood = true; |
| } |
| } |
| |
| EXPECT_TRUE(dropboxAtomGood); |
| EXPECT_TRUE(sensorAtomGood); |
| } |
| |
| TEST(StatsdStatsTest, TestNonPlatformAtomLog) { |
| StatsdStats stats; |
| time_t now = time(nullptr); |
| int newAtom1 = android::util::kMaxPushedAtomId + 1; |
| int newAtom2 = android::util::kMaxPushedAtomId + 2; |
| |
| stats.noteAtomLogged(newAtom1, now + 1); |
| stats.noteAtomLogged(newAtom1, now + 2); |
| stats.noteAtomLogged(newAtom2, now + 3); |
| |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| |
| EXPECT_EQ(2, report.atom_stats_size()); |
| bool newAtom1Good = false; |
| bool newAtom2Good = false; |
| |
| for (const auto& atomStats : report.atom_stats()) { |
| if (atomStats.tag() == newAtom1 && atomStats.count() == 2) { |
| newAtom1Good = true; |
| } |
| if (atomStats.tag() == newAtom2 && atomStats.count() == 1) { |
| newAtom2Good = true; |
| } |
| } |
| |
| EXPECT_TRUE(newAtom1Good); |
| EXPECT_TRUE(newAtom2Good); |
| } |
| |
| TEST(StatsdStatsTest, TestPullAtomStats) { |
| StatsdStats stats; |
| |
| stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 3333L); |
| stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 2222L); |
| stats.updateMinPullIntervalSec(android::util::DISK_SPACE, 4444L); |
| |
| stats.notePull(android::util::DISK_SPACE); |
| stats.notePullTime(android::util::DISK_SPACE, 1111L); |
| stats.notePullDelay(android::util::DISK_SPACE, 1111L); |
| stats.notePull(android::util::DISK_SPACE); |
| stats.notePullTime(android::util::DISK_SPACE, 3333L); |
| stats.notePullDelay(android::util::DISK_SPACE, 3335L); |
| stats.notePull(android::util::DISK_SPACE); |
| stats.notePullFromCache(android::util::DISK_SPACE); |
| stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, true); |
| stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, false); |
| stats.notePullerCallbackRegistrationChanged(android::util::DISK_SPACE, true); |
| |
| |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| |
| EXPECT_EQ(1, report.pulled_atom_stats_size()); |
| |
| EXPECT_EQ(android::util::DISK_SPACE, report.pulled_atom_stats(0).atom_id()); |
| EXPECT_EQ(3, report.pulled_atom_stats(0).total_pull()); |
| EXPECT_EQ(1, report.pulled_atom_stats(0).total_pull_from_cache()); |
| EXPECT_EQ(2222L, report.pulled_atom_stats(0).min_pull_interval_sec()); |
| EXPECT_EQ(2222L, report.pulled_atom_stats(0).average_pull_time_nanos()); |
| EXPECT_EQ(3333L, report.pulled_atom_stats(0).max_pull_time_nanos()); |
| EXPECT_EQ(2223L, report.pulled_atom_stats(0).average_pull_delay_nanos()); |
| EXPECT_EQ(3335L, report.pulled_atom_stats(0).max_pull_delay_nanos()); |
| EXPECT_EQ(2L, report.pulled_atom_stats(0).registered_count()); |
| EXPECT_EQ(1L, report.pulled_atom_stats(0).unregistered_count()); |
| } |
| |
| TEST(StatsdStatsTest, TestAtomMetricsStats) { |
| StatsdStats stats; |
| time_t now = time(nullptr); |
| // old event, we get it from the stats buffer. should be ignored. |
| stats.noteBucketDropped(1000L); |
| |
| stats.noteBucketBoundaryDelayNs(1000L, -1L); |
| stats.noteBucketBoundaryDelayNs(1000L, -10L); |
| stats.noteBucketBoundaryDelayNs(1000L, 2L); |
| |
| stats.noteBucketBoundaryDelayNs(1001L, 1L); |
| |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| |
| EXPECT_EQ(2, report.atom_metric_stats().size()); |
| |
| auto atomStats = report.atom_metric_stats(0); |
| EXPECT_EQ(1000L, atomStats.metric_id()); |
| EXPECT_EQ(1L, atomStats.bucket_dropped()); |
| EXPECT_EQ(-10L, atomStats.min_bucket_boundary_delay_ns()); |
| EXPECT_EQ(2L, atomStats.max_bucket_boundary_delay_ns()); |
| |
| auto atomStats2 = report.atom_metric_stats(1); |
| EXPECT_EQ(1001L, atomStats2.metric_id()); |
| EXPECT_EQ(0L, atomStats2.bucket_dropped()); |
| EXPECT_EQ(0L, atomStats2.min_bucket_boundary_delay_ns()); |
| EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns()); |
| } |
| |
| TEST(StatsdStatsTest, TestAnomalyMonitor) { |
| StatsdStats stats; |
| stats.noteRegisteredAnomalyAlarmChanged(); |
| stats.noteRegisteredAnomalyAlarmChanged(); |
| |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| bool good = report.ParseFromArray(&output[0], output.size()); |
| EXPECT_TRUE(good); |
| |
| EXPECT_EQ(2, report.anomaly_alarm_stats().alarms_registered()); |
| } |
| |
| TEST(StatsdStatsTest, TestTimestampThreshold) { |
| StatsdStats stats; |
| vector<int32_t> timestamps; |
| for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { |
| timestamps.push_back(i); |
| } |
| ConfigKey key(0, 12345); |
| stats.noteConfigReceived(key, 2, 3, 4, 5, {}, true); |
| |
| for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) { |
| stats.noteDataDropped(key, timestamps[i]); |
| stats.noteBroadcastSent(key, timestamps[i]); |
| stats.noteMetricsReportSent(key, 0, timestamps[i]); |
| stats.noteActiveStatusChanged(key, true, timestamps[i]); |
| stats.noteActiveStatusChanged(key, false, timestamps[i]); |
| } |
| |
| int32_t newTimestamp = 10000; |
| |
| // now it should trigger removing oldest timestamp |
| stats.noteDataDropped(key, 123, 10000); |
| stats.noteBroadcastSent(key, 10000); |
| stats.noteMetricsReportSent(key, 0, 10000); |
| stats.noteActiveStatusChanged(key, true, 10000); |
| stats.noteActiveStatusChanged(key, false, 10000); |
| |
| EXPECT_TRUE(stats.mConfigStats.find(key) != stats.mConfigStats.end()); |
| const auto& configStats = stats.mConfigStats[key]; |
| |
| size_t maxCount = StatsdStats::kMaxTimestampCount; |
| EXPECT_EQ(maxCount, configStats->broadcast_sent_time_sec.size()); |
| EXPECT_EQ(maxCount, configStats->data_drop_time_sec.size()); |
| EXPECT_EQ(maxCount, configStats->dump_report_stats.size()); |
| EXPECT_EQ(maxCount, configStats->activation_time_sec.size()); |
| EXPECT_EQ(maxCount, configStats->deactivation_time_sec.size()); |
| |
| // the oldest timestamp is the second timestamp in history |
| EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front()); |
| EXPECT_EQ(1, configStats->data_drop_bytes.front()); |
| EXPECT_EQ(1, configStats->dump_report_stats.front().first); |
| EXPECT_EQ(1, configStats->activation_time_sec.front()); |
| EXPECT_EQ(1, configStats->deactivation_time_sec.front()); |
| |
| // the last timestamp is the newest timestamp. |
| EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back()); |
| EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back()); |
| EXPECT_EQ(123, configStats->data_drop_bytes.back()); |
| EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().first); |
| EXPECT_EQ(newTimestamp, configStats->activation_time_sec.back()); |
| EXPECT_EQ(newTimestamp, configStats->deactivation_time_sec.back()); |
| } |
| |
| TEST(StatsdStatsTest, TestSystemServerCrash) { |
| StatsdStats stats; |
| vector<int32_t> timestamps; |
| for (int i = 0; i < StatsdStats::kMaxSystemServerRestarts; i++) { |
| timestamps.push_back(i); |
| stats.noteSystemServerRestart(timestamps[i]); |
| } |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); |
| const int maxCount = StatsdStats::kMaxSystemServerRestarts; |
| EXPECT_EQ(maxCount, (int)report.system_restart_sec_size()); |
| |
| stats.noteSystemServerRestart(StatsdStats::kMaxSystemServerRestarts + 1); |
| output.clear(); |
| stats.dumpStats(&output, false); |
| EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); |
| EXPECT_EQ(maxCount, (int)report.system_restart_sec_size()); |
| EXPECT_EQ(StatsdStats::kMaxSystemServerRestarts + 1, report.system_restart_sec(maxCount - 1)); |
| } |
| |
| TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) { |
| StatsdStats stats; |
| int uid1 = 1; |
| int uid2 = 2; |
| stats.noteActivationBroadcastGuardrailHit(uid1, 10); |
| stats.noteActivationBroadcastGuardrailHit(uid1, 20); |
| |
| // Test that we only keep 20 timestamps. |
| for (int i = 0; i < 100; i++) { |
| stats.noteActivationBroadcastGuardrailHit(uid2, i); |
| } |
| |
| vector<uint8_t> output; |
| stats.dumpStats(&output, false); |
| StatsdStatsReport report; |
| EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); |
| |
| EXPECT_EQ(2, report.activation_guardrail_stats_size()); |
| bool uid1Good = false; |
| bool uid2Good = false; |
| for (const auto& guardrailTimes : report.activation_guardrail_stats()) { |
| if (uid1 == guardrailTimes.uid()) { |
| uid1Good = true; |
| EXPECT_EQ(2, guardrailTimes.guardrail_met_sec_size()); |
| EXPECT_EQ(10, guardrailTimes.guardrail_met_sec(0)); |
| EXPECT_EQ(20, guardrailTimes.guardrail_met_sec(1)); |
| } else if (uid2 == guardrailTimes.uid()) { |
| int maxCount = StatsdStats::kMaxTimestampCount; |
| uid2Good = true; |
| EXPECT_EQ(maxCount, guardrailTimes.guardrail_met_sec_size()); |
| for (int i = 0; i < maxCount; i++) { |
| EXPECT_EQ(100 - maxCount + i, guardrailTimes.guardrail_met_sec(i)); |
| } |
| } else { |
| FAIL() << "Unexpected uid."; |
| } |
| } |
| EXPECT_TRUE(uid1Good); |
| EXPECT_TRUE(uid2Good); |
| } |
| |
| } // namespace statsd |
| } // namespace os |
| } // namespace android |
| #else |
| GTEST_LOG_(INFO) << "This test does nothing.\n"; |
| #endif |