// Copyright (C) 2021 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 #include "src/StatsLogProcessor.h" #include "tests/statsd_test_util.h" namespace android { namespace os { namespace statsd { #ifdef __ANDROID__ using namespace std; namespace { unique_ptr CreateTestAtomReportedEvent(const uint64_t timestampNs, const long longField, const string& stringField) { return CreateTestAtomReportedEvent( timestampNs, /* attributionUids */ {1001}, /* attributionTags */ {"app1"}, /* intField */ 0, longField, /* floatField */ 0.0f, stringField, /* boolField */ false, TestAtomReported::OFF, /* bytesField */ {}, /* repeatedIntField */ {}, /* repeatedLongField */ {}, /* repeatedFloatField */ {}, /* repeatedStringField */ {}, /* repeatedBoolField */ {}, /* repeatedBoolFieldLength */ 0, /* repeatedEnumField */ {}); } } // anonymous namespace. class KllMetricE2eTest : public ::testing::Test { protected: void SetUp() override { key = ConfigKey(123, 987); bucketStartTimeNs = getElapsedRealtimeNs(); bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL; whatMatcher = CreateScreenBrightnessChangedAtomMatcher(); metric = createKllMetric("ScreenBrightness", whatMatcher, /*valueField=*/1, /*condition=*/nullopt); *config.add_atom_matcher() = whatMatcher; *config.add_kll_metric() = metric; events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 5 * NS_PER_SEC, 5)); events.push_back( CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, 15)); events.push_back( CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 40)); } ConfigKey key; uint64_t bucketStartTimeNs; uint64_t bucketSizeNs; AtomMatcher whatMatcher; KllMetric metric; StatsdConfig config; vector> events; }; TEST_F(KllMetricE2eTest, TestSimpleMetric) { const sp processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); for (auto& event : events) { processor->OnLogEvent(event.get()); } uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs; ConfigMetricsReportList reports; vector buffer; processor->onDumpReport(key, dumpTimeNs, /*include_current_bucket*/ false, true, ADB_DUMP, FAST, &buffer); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); ASSERT_EQ(reports.reports_size(), 1); ConfigMetricsReport report = reports.reports(0); ASSERT_EQ(report.metrics_size(), 1); StatsLogReport metricReport = report.metrics(0); EXPECT_TRUE(metricReport.has_estimated_data_bytes()); EXPECT_EQ(metricReport.metric_id(), metric.id()); EXPECT_TRUE(metricReport.has_kll_metrics()); ASSERT_EQ(metricReport.kll_metrics().data_size(), 1); KllMetricData data = metricReport.kll_metrics().data(0); ASSERT_EQ(data.bucket_info_size(), 1); KllBucketInfo bucket = data.bucket_info(0); EXPECT_EQ(bucket.start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(bucket.end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(bucket.sketches_size(), 1); EXPECT_EQ(metricReport.kll_metrics().skipped_size(), 0); } TEST_F(KllMetricE2eTest, TestMetricWithDimensions) { whatMatcher = CreateSimpleAtomMatcher("TestAtomReported", util::TEST_ATOM_REPORTED); metric = createKllMetric("TestAtomMetric", whatMatcher, /* kllField */ 3, /* condition */ nullopt); *metric.mutable_dimensions_in_what() = CreateDimensions(util::TEST_ATOM_REPORTED, {5 /* string_field */}); config.clear_atom_matcher(); *config.add_atom_matcher() = whatMatcher; config.clear_kll_metric(); *config.add_kll_metric() = metric; events.clear(); events.push_back(CreateTestAtomReportedEvent(bucketStartTimeNs + 5 * NS_PER_SEC, 5l, "dim_1")); events.push_back(CreateTestAtomReportedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, 6l, "dim_2")); events.push_back(CreateTestAtomReportedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 7l, "dim_1")); const sp processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); for (auto& event : events) { processor->OnLogEvent(event.get()); } uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs; ConfigMetricsReportList reports; vector buffer; processor->onDumpReport(key, dumpTimeNs, /*include_current_bucket*/ false, true, ADB_DUMP, FAST, &buffer); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); ASSERT_EQ(reports.reports_size(), 1); ConfigMetricsReport report = reports.reports(0); ASSERT_EQ(report.metrics_size(), 1); StatsLogReport metricReport = report.metrics(0); EXPECT_EQ(metricReport.metric_id(), metric.id()); EXPECT_TRUE(metricReport.has_kll_metrics()); ASSERT_EQ(metricReport.kll_metrics().data_size(), 2); KllMetricData data = metricReport.kll_metrics().data(0); ASSERT_EQ(data.bucket_info_size(), 1); KllBucketInfo bucket = data.bucket_info(0); EXPECT_EQ(bucket.start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(bucket.end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(bucket.sketches_size(), 1); EXPECT_EQ(metricReport.kll_metrics().skipped_size(), 0); EXPECT_EQ(data.dimensions_in_what().field(), util::TEST_ATOM_REPORTED); ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 5); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), "dim_1"); data = metricReport.kll_metrics().data(1); ASSERT_EQ(data.bucket_info_size(), 1); bucket = data.bucket_info(0); EXPECT_EQ(bucket.start_bucket_elapsed_nanos(), bucketStartTimeNs); EXPECT_EQ(bucket.end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(bucket.sketches_size(), 1); EXPECT_EQ(metricReport.kll_metrics().skipped_size(), 0); EXPECT_EQ(data.dimensions_in_what().field(), util::TEST_ATOM_REPORTED); ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 5); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(), "dim_2"); } TEST_F(KllMetricE2eTest, TestInitWithKllFieldPositionALL) { // Create config. StatsdConfig config; AtomMatcher testAtomReportedMatcher = CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); *config.add_atom_matcher() = testAtomReportedMatcher; // Create kll metric. int64_t metricId = 123456; KllMetric* kllMetric = config.add_kll_metric(); kllMetric->set_id(metricId); kllMetric->set_bucket(TimeUnit::FIVE_MINUTES); kllMetric->set_what(testAtomReportedMatcher.id()); *kllMetric->mutable_kll_field() = CreateRepeatedDimensions( util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL}); // Initialize StatsLogProcessor. const uint64_t bucketStartTimeNs = 10000000000; // 0:10 int uid = 12345; int64_t cfgId = 98765; ConfigKey cfgKey(uid, cfgId); sp processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); // Config initialization fails. ASSERT_EQ(0, processor->mMetricsManagers.size()); } TEST_F(KllMetricE2eTest, TestDimensionalSampling) { ShardOffsetProvider::getInstance().setShardOffset(5); // Create config. StatsdConfig config; AtomMatcher bleScanResultReceivedMatcher = CreateSimpleAtomMatcher( "BleScanResultReceivedAtomMatcher", util::BLE_SCAN_RESULT_RECEIVED); *config.add_atom_matcher() = bleScanResultReceivedMatcher; // Create kll metric. KllMetric sampledKllMetric = createKllMetric("KllSampledBleScanResultsPerUid", bleScanResultReceivedMatcher, /*num_results=*/2, nullopt); *sampledKllMetric.mutable_dimensions_in_what() = CreateAttributionUidDimensions(util::BLE_SCAN_RESULT_RECEIVED, {Position::FIRST}); *sampledKllMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() = CreateAttributionUidDimensions(util::BLE_SCAN_RESULT_RECEIVED, {Position::FIRST}); sampledKllMetric.mutable_dimensional_sampling_info()->set_shard_count(2); *config.add_kll_metric() = sampledKllMetric; // Initialize StatsLogProcessor. const uint64_t bucketStartTimeNs = 10000000000; // 0:10 int uid = 12345; int64_t cfgId = 98765; ConfigKey cfgKey(uid, cfgId); sp processor = CreateStatsLogProcessor( bucketStartTimeNs, bucketStartTimeNs, config, cfgKey, nullptr, 0, new UidMap()); int appUid1 = 1001; // odd hash value int appUid2 = 1002; // even hash value int appUid3 = 1003; // odd hash value std::vector> events; events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, {appUid1}, {"tag1"}, 10)); events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, {appUid2}, {"tag2"}, 10)); events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 60 * NS_PER_SEC, {appUid3}, {"tag3"}, 10)); events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 120 * NS_PER_SEC, {appUid1}, {"tag1"}, 11)); events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 140 * NS_PER_SEC, {appUid2}, {"tag2"}, 12)); events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 160 * NS_PER_SEC, {appUid3}, {"tag3"}, 13)); // Send log events to StatsLogProcessor. for (auto& event : events) { processor->OnLogEvent(event.get()); } // Check dump report. vector buffer; ConfigMetricsReportList reports; processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, FAST, &buffer); ASSERT_GT(buffer.size(), 0); EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); backfillDimensionPath(&reports); backfillStringInReport(&reports); backfillStartEndTimestamp(&reports); backfillAggregatedAtoms(&reports); ConfigMetricsReport report = reports.reports(0); ASSERT_EQ(report.metrics_size(), 1); StatsLogReport metricReport = report.metrics(0); EXPECT_EQ(metricReport.metric_id(), sampledKllMetric.id()); EXPECT_TRUE(metricReport.has_kll_metrics()); StatsLogReport::KllMetricDataWrapper kllMetrics; sortMetricDataByDimensionsValue(metricReport.kll_metrics(), &kllMetrics); ASSERT_EQ(kllMetrics.data_size(), 2); EXPECT_EQ(kllMetrics.skipped_size(), 0); // Only Uid 1 and 3 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0 KllMetricData data = kllMetrics.data(0); ValidateAttributionUidDimension(data.dimensions_in_what(), util::BLE_SCAN_RESULT_RECEIVED, appUid1); ValidateKllBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, {2}, 0); data = kllMetrics.data(1); ValidateAttributionUidDimension(data.dimensions_in_what(), util::BLE_SCAN_RESULT_RECEIVED, appUid3); ValidateKllBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, {2}, 0); } #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif } // namespace statsd } // namespace os } // namespace android