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