1 // Copyright (C) 2024 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 <gmock/gmock.h>
16 #include <gtest/gtest.h>
17
18 #include <map>
19 #include <set>
20 #include <vector>
21
22 #include "src/metrics/parsing_utils/metrics_manager_util.h"
23 #include "src/stats_log.pb.h"
24 #include "src/statsd_config.pb.h"
25 #include "statsd_test_util.h"
26
27 using namespace testing;
28 using android::sp;
29 using std::map;
30 using std::set;
31 using std::vector;
32
33 #ifdef __ANDROID__
34
35 namespace android {
36 namespace os {
37 namespace statsd {
38
39 namespace {
40 constexpr int kConfigId = 12345;
41 const ConfigKey kConfigKey(0, kConfigId);
42
43 constexpr long kTimeBaseSec = 1000;
44 constexpr int64_t kBucketStartTimeNs = 10000000000;
45 constexpr int64_t kAtomsLogTimeNs = kBucketStartTimeNs + 10;
46 constexpr int64_t kReportRequestTimeNs = kBucketStartTimeNs + 100;
47
48 constexpr int32_t kAppUid = AID_APP_START + 1;
49 constexpr int32_t kAtomId = 2;
50 constexpr int32_t kInterestAtomId = 3;
51 constexpr int32_t kNotInterestedMetricId = 3;
52 constexpr int32_t kInterestedMetricId = 4;
53 constexpr int32_t kUnusedAtomId = kInterestAtomId + 100;
54
55 const string kAppName = "TestApp";
56 const set<int32_t> kAppUids = {kAppUid, kAppUid + 10000};
57 const map<string, set<int32_t>> kPkgToUids = {{kAppName, kAppUids}};
58
buildGoodEventConfig()59 StatsdConfig buildGoodEventConfig() {
60 StatsdConfig config;
61 config.set_id(kConfigId);
62
63 {
64 AtomMatcher* eventMatcher = config.add_atom_matcher();
65 eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
66 SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
67 simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
68
69 EventMetric* metric = config.add_event_metric();
70 metric->set_id(kNotInterestedMetricId);
71 metric->set_what(StringToId("SCREEN_IS_ON"));
72 }
73
74 {
75 const int64_t matcherId = StringToId("CUSTOM_EVENT" + std::to_string(kInterestAtomId));
76 AtomMatcher* eventMatcher = config.add_atom_matcher();
77 eventMatcher->set_id(matcherId);
78 SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
79 simpleAtomMatcher->set_atom_id(kInterestAtomId);
80
81 EventMetric* metric = config.add_event_metric();
82 metric->set_id(kInterestedMetricId);
83 metric->set_what(matcherId);
84 }
85
86 return config;
87 }
88
getMetricsReport(MetricsManager & metricsManager)89 ConfigMetricsReport getMetricsReport(MetricsManager& metricsManager) {
90 ProtoOutputStream output;
91 metricsManager.onDumpReport(kReportRequestTimeNs, kReportRequestTimeNs,
92 /*include_current_partial_bucket*/ true, /*erase_data*/ true,
93 /*dumpLatency*/ NO_TIME_CONSTRAINTS, nullptr, &output);
94
95 ConfigMetricsReport metricsReport;
96 outputStreamToProto(&output, &metricsReport);
97 return metricsReport;
98 }
99
100 } // anonymous namespace
101
102 /**
103 * @brief Variety of test parameters combination represents always allowed atom (true/false)
104 * logged by allowed package uid (true, false) to be tested with SocketLossInfo atom propagation
105 */
106
107 enum class AllowedFromAnyUidFlag { NOT_ALLOWED = 0, ALLOWED };
108
109 enum class AllowedFromSpecificUidFlag { NOT_ALLOWED = 0, ALLOWED };
110
111 template <typename T>
112 struct TypedParam {
113 T value;
114 string label;
115
operator Tandroid::os::statsd::TypedParam116 operator T() const {
117 return value;
118 }
119 };
120
121 using AllowedFromAnyUidFlagParam = TypedParam<AllowedFromAnyUidFlag>;
122 using AllowedFromSpecificUidFlagParam = TypedParam<AllowedFromSpecificUidFlag>;
123
124 class SocketLossInfoTest
125 : public testing::TestWithParam<
126 std::tuple<AllowedFromAnyUidFlagParam, AllowedFromSpecificUidFlagParam>> {
127 protected:
128 std::shared_ptr<MetricsManager> mMetricsManager;
129
SetUp()130 void SetUp() override {
131 StatsdConfig config;
132
133 // creates config with 2 metrics where one dedicated event metric interested in
134 // kInterestAtomId
135 config = buildGoodEventConfig();
136
137 // Test parametrized on allowed_atom (true/false)
138 if (isAtomAllowedFromAnyUid()) {
139 config.add_whitelisted_atom_ids(kAtomId);
140 config.add_whitelisted_atom_ids(kInterestAtomId);
141 }
142
143 // Test parametrized on allowed_package (true/false)
144 if (isAtomFromAllowedUid()) {
145 config.add_allowed_log_source(kAppName);
146 }
147
148 sp<MockUidMap> uidMap = new StrictMock<MockUidMap>();
149 EXPECT_CALL(*uidMap, getAppUid(_)).WillRepeatedly(Invoke([](const string& pkg) {
150 const auto& it = kPkgToUids.find(pkg);
151 if (it != kPkgToUids.end()) {
152 return it->second;
153 }
154 return set<int32_t>();
155 }));
156 sp<StatsPullerManager> pullerManager = new StatsPullerManager();
157 sp<AlarmMonitor> anomalyAlarmMonitor;
158 sp<AlarmMonitor> periodicAlarmMonitor;
159
160 mMetricsManager = std::make_shared<MetricsManager>(
161 kConfigKey, config, kTimeBaseSec, kTimeBaseSec, uidMap, pullerManager,
162 anomalyAlarmMonitor, periodicAlarmMonitor);
163
164 EXPECT_TRUE(mMetricsManager->isConfigValid());
165 }
166
isAtomAllowedFromAnyUid() const167 bool isAtomAllowedFromAnyUid() const {
168 return std::get<0>(GetParam()) == AllowedFromAnyUidFlag::ALLOWED;
169 }
170
isAtomFromAllowedUid() const171 bool isAtomFromAllowedUid() const {
172 return std::get<1>(GetParam()) == AllowedFromSpecificUidFlag::ALLOWED;
173 }
174
isAtomLoggingAllowed() const175 bool isAtomLoggingAllowed() const {
176 return isAtomAllowedFromAnyUid() || isAtomFromAllowedUid();
177 }
178 };
179
180 INSTANTIATE_TEST_SUITE_P(
181 SocketLossInfoTest, SocketLossInfoTest,
182 testing::Combine(testing::ValuesIn<AllowedFromAnyUidFlagParam>({
183 {AllowedFromAnyUidFlag::NOT_ALLOWED, "NotAllowedFromAnyUid"},
184 {AllowedFromAnyUidFlag::ALLOWED, "AllowedFromAnyUid"},
185 }),
186 testing::ValuesIn<AllowedFromSpecificUidFlagParam>({
187 {AllowedFromSpecificUidFlag::NOT_ALLOWED,
188 "NotAllowedFromSpecificUid"},
189 {AllowedFromSpecificUidFlag::ALLOWED, "AllowedFromSpecificUid"},
190 })),
__anonaef9c6420302(const testing::TestParamInfo<SocketLossInfoTest::ParamType>& info) 191 [](const testing::TestParamInfo<SocketLossInfoTest::ParamType>& info) {
192 return std::get<0>(info.param).label + std::get<1>(info.param).label;
193 });
194
TEST_P(SocketLossInfoTest,PropagationTest)195 TEST_P(SocketLossInfoTest, PropagationTest) {
196 LogEvent eventOfInterest(kAppUid /* uid */, 0 /* pid */);
197 CreateNoValuesLogEvent(&eventOfInterest, kInterestAtomId /* atom id */, 0 /* timestamp */);
198 EXPECT_EQ(mMetricsManager->checkLogCredentials(eventOfInterest), isAtomLoggingAllowed());
199 EXPECT_EQ(mMetricsManager->mAllMetricProducers[0]->mDataCorruptedDueToSocketLoss,
200 MetricProducer::DataCorruptionSeverity::kNone);
201
202 const auto eventSocketLossReported = createSocketLossInfoLogEvent(kAppUid, kInterestAtomId);
203
204 // the STATS_SOCKET_LOSS_REPORTED on its own will not be propagated/consumed by any metric
205 EXPECT_EQ(mMetricsManager->checkLogCredentials(*eventSocketLossReported.get()),
206 isAtomFromAllowedUid());
207
208 // the loss info for an atom kInterestAtomId will be evaluated even when
209 // STATS_SOCKET_LOSS_REPORTED atom is not explicitly in allowed list
210 mMetricsManager->onLogEvent(*eventSocketLossReported.get());
211
212 // check that corresponding event metric was properly updated (or not) with loss info
213 for (const auto& metricProducer : mMetricsManager->mAllMetricProducers) {
214 if (metricProducer->getMetricId() == kInterestedMetricId) {
215 EXPECT_EQ(metricProducer->mDataCorruptedDueToSocketLoss !=
216 MetricProducer::DataCorruptionSeverity::kNone,
217 isAtomLoggingAllowed());
218 continue;
219 }
220 EXPECT_EQ(metricProducer->mDataCorruptedDueToSocketLoss,
221 MetricProducer::DataCorruptionSeverity::kNone);
222 }
223 }
224
TEST_P(SocketLossInfoTest,TestNotifyOnlyInterestedMetrics)225 TEST_P(SocketLossInfoTest, TestNotifyOnlyInterestedMetrics) {
226 const auto eventSocketLossReported = createSocketLossInfoLogEvent(kAppUid, kUnusedAtomId);
227
228 mMetricsManager->onLogEvent(*eventSocketLossReported.get());
229 ConfigMetricsReport metricsReport = getMetricsReport(*mMetricsManager);
230 EXPECT_EQ(metricsReport.metrics_size(), 2);
231 EXPECT_THAT(metricsReport.metrics(),
232 Each(Property(&StatsLogReport::data_corrupted_reason_size, 0)));
233
234 const auto usedEventSocketLossReported = createSocketLossInfoLogEvent(kAppUid, kInterestAtomId);
235 mMetricsManager->onLogEvent(*usedEventSocketLossReported.get());
236
237 metricsReport = getMetricsReport(*mMetricsManager);
238 ASSERT_EQ(metricsReport.metrics_size(), 2);
239 for (const auto& statsLogReport : metricsReport.metrics()) {
240 if (statsLogReport.metric_id() == kInterestedMetricId && isAtomLoggingAllowed()) {
241 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
242 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_SOCKET_LOSS);
243 continue;
244 }
245 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
246 }
247 }
248
TEST_P(SocketLossInfoTest,TestNotifyInterestedMetricsWithNewLoss)249 TEST_P(SocketLossInfoTest, TestNotifyInterestedMetricsWithNewLoss) {
250 auto usedEventSocketLossReported = createSocketLossInfoLogEvent(kAppUid, kInterestAtomId);
251 mMetricsManager->onLogEvent(*usedEventSocketLossReported.get());
252
253 ConfigMetricsReport metricsReport = getMetricsReport(*mMetricsManager);
254 ASSERT_EQ(metricsReport.metrics_size(), 2);
255 for (const auto& statsLogReport : metricsReport.metrics()) {
256 if (statsLogReport.metric_id() == kInterestedMetricId && isAtomLoggingAllowed()) {
257 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
258 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_SOCKET_LOSS);
259 continue;
260 }
261 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
262 }
263
264 // new socket loss event as result event metric should be notified about loss again
265 usedEventSocketLossReported = createSocketLossInfoLogEvent(kAppUid, kInterestAtomId);
266 mMetricsManager->onLogEvent(*usedEventSocketLossReported.get());
267
268 metricsReport = getMetricsReport(*mMetricsManager);
269 ASSERT_EQ(metricsReport.metrics_size(), 2);
270 for (const auto& statsLogReport : metricsReport.metrics()) {
271 if (statsLogReport.metric_id() == kInterestedMetricId && isAtomLoggingAllowed()) {
272 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
273 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_SOCKET_LOSS);
274 continue;
275 }
276 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
277 }
278 }
279
TEST_P(SocketLossInfoTest,TestDoNotNotifyInterestedMetricsIfNoUpdate)280 TEST_P(SocketLossInfoTest, TestDoNotNotifyInterestedMetricsIfNoUpdate) {
281 auto usedEventSocketLossReported = createSocketLossInfoLogEvent(kAppUid, kInterestAtomId);
282 mMetricsManager->onLogEvent(*usedEventSocketLossReported.get());
283
284 ConfigMetricsReport metricsReport = getMetricsReport(*mMetricsManager);
285 ASSERT_EQ(metricsReport.metrics_size(), 2);
286 for (const auto& statsLogReport : metricsReport.metrics()) {
287 if (statsLogReport.metric_id() == kInterestedMetricId && isAtomLoggingAllowed()) {
288 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
289 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_SOCKET_LOSS);
290 continue;
291 }
292 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
293 }
294
295 // no more dropped events as result event metric should not be notified about loss events
296
297 metricsReport = getMetricsReport(*mMetricsManager);
298 EXPECT_EQ(metricsReport.metrics_size(), 2);
299 EXPECT_THAT(metricsReport.metrics(),
300 Each(Property(&StatsLogReport::data_corrupted_reason_size, 0)));
301 }
302
303 class DataCorruptionQueueOverflowTest : public testing::Test {
304 protected:
305 std::shared_ptr<MetricsManager> mMetricsManager;
306
SetUp()307 void SetUp() override {
308 StatsdStats::getInstance().reset();
309
310 sp<UidMap> uidMap;
311 sp<StatsPullerManager> pullerManager = new StatsPullerManager();
312 sp<AlarmMonitor> anomalyAlarmMonitor;
313 sp<AlarmMonitor> periodicAlarmMonitor;
314
315 // there will be one event metric interested in kInterestAtomId
316 StatsdConfig config = buildGoodEventConfig();
317
318 mMetricsManager = std::make_shared<MetricsManager>(
319 kConfigKey, config, kTimeBaseSec, kTimeBaseSec, uidMap, pullerManager,
320 anomalyAlarmMonitor, periodicAlarmMonitor);
321
322 EXPECT_TRUE(mMetricsManager->isConfigValid());
323 }
324
TearDown()325 void TearDown() override {
326 StatsdStats::getInstance().reset();
327 }
328 };
329
TEST_F(DataCorruptionQueueOverflowTest,TestNotifyOnlyInterestedMetrics)330 TEST_F(DataCorruptionQueueOverflowTest, TestNotifyOnlyInterestedMetrics) {
331 StatsdStats::getInstance().noteEventQueueOverflow(kAtomsLogTimeNs, kInterestAtomId,
332 /*isSkipped*/ false);
333
334 StatsdStats::getInstance().noteEventQueueOverflow(kAtomsLogTimeNs, kUnusedAtomId,
335 /*isSkipped*/ false);
336
337 EXPECT_TRUE(mMetricsManager->mQueueOverflowAtomsStats.empty());
338 ConfigMetricsReport metricsReport = getMetricsReport(*mMetricsManager);
339 ASSERT_EQ(metricsReport.metrics_size(), 2);
340 ASSERT_EQ(mMetricsManager->mQueueOverflowAtomsStats.size(), 1);
341 EXPECT_EQ(mMetricsManager->mQueueOverflowAtomsStats[kInterestAtomId], 1);
342
343 for (const auto& statsLogReport : metricsReport.metrics()) {
344 if (statsLogReport.metric_id() == kInterestedMetricId) {
345 ASSERT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
346 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW);
347 continue;
348 }
349 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
350 }
351 }
352
TEST_F(DataCorruptionQueueOverflowTest,TestNotifyInterestedMetricsWithNewLoss)353 TEST_F(DataCorruptionQueueOverflowTest, TestNotifyInterestedMetricsWithNewLoss) {
354 StatsdStats::getInstance().noteEventQueueOverflow(kAtomsLogTimeNs, kInterestAtomId,
355 /*isSkipped*/ false);
356
357 ConfigMetricsReport metricsReport = getMetricsReport(*mMetricsManager);
358 ASSERT_EQ(metricsReport.metrics_size(), 2);
359 ASSERT_EQ(mMetricsManager->mQueueOverflowAtomsStats.size(), 1);
360 EXPECT_EQ(mMetricsManager->mQueueOverflowAtomsStats[kInterestAtomId], 1);
361
362 for (const auto& statsLogReport : metricsReport.metrics()) {
363 if (statsLogReport.metric_id() == kInterestedMetricId) {
364 ASSERT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
365 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW);
366 continue;
367 }
368 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
369 }
370
371 // new dropped event as result event metric should be notified about loss events
372 StatsdStats::getInstance().noteEventQueueOverflow(kAtomsLogTimeNs + 100, kInterestAtomId,
373 /*isSkipped*/ false);
374
375 metricsReport = getMetricsReport(*mMetricsManager);
376 ASSERT_EQ(metricsReport.metrics_size(), 2);
377 ASSERT_EQ(mMetricsManager->mQueueOverflowAtomsStats.size(), 1);
378 EXPECT_EQ(mMetricsManager->mQueueOverflowAtomsStats[kInterestAtomId], 2);
379
380 for (const auto& statsLogReport : metricsReport.metrics()) {
381 if (statsLogReport.metric_id() == kInterestedMetricId) {
382 ASSERT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
383 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW);
384 continue;
385 }
386 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
387 }
388 }
389
TEST_F(DataCorruptionQueueOverflowTest,TestDoNotNotifyInterestedMetricsIfNoUpdate)390 TEST_F(DataCorruptionQueueOverflowTest, TestDoNotNotifyInterestedMetricsIfNoUpdate) {
391 StatsdStats::getInstance().noteEventQueueOverflow(kAtomsLogTimeNs, kInterestAtomId,
392 /*isSkipped*/ false);
393
394 ConfigMetricsReport metricsReport = getMetricsReport(*mMetricsManager);
395 ASSERT_EQ(metricsReport.metrics_size(), 2);
396 ASSERT_EQ(mMetricsManager->mQueueOverflowAtomsStats.size(), 1);
397 EXPECT_EQ(mMetricsManager->mQueueOverflowAtomsStats[kInterestAtomId], 1);
398
399 for (const auto& statsLogReport : metricsReport.metrics()) {
400 if (statsLogReport.metric_id() == kInterestedMetricId) {
401 ASSERT_EQ(statsLogReport.data_corrupted_reason_size(), 1);
402 EXPECT_EQ(statsLogReport.data_corrupted_reason(0), DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW);
403 continue;
404 }
405 EXPECT_EQ(statsLogReport.data_corrupted_reason_size(), 0);
406 }
407
408 // no more dropped events as result event metric should not be notified about loss events
409
410 metricsReport = getMetricsReport(*mMetricsManager);
411 ASSERT_EQ(mMetricsManager->mQueueOverflowAtomsStats.size(), 1);
412 EXPECT_EQ(mMetricsManager->mQueueOverflowAtomsStats[kInterestAtomId], 1);
413 EXPECT_EQ(metricsReport.metrics_size(), 2);
414 EXPECT_THAT(metricsReport.metrics(),
415 Each(Property(&StatsLogReport::data_corrupted_reason_size, 0)));
416 }
417
418 } // namespace statsd
419 } // namespace os
420 } // namespace android
421
422 #else
423 GTEST_LOG_(INFO) << "This test does nothing.\n";
424 #endif
425