1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "IoPerfCollection.h"
18 #include "MockProcStat.h"
19 #include "MockUidStatsCollector.h"
20 #include "MockWatchdogServiceHelper.h"
21 #include "PackageInfoTestUtils.h"
22 
23 #include <WatchdogProperties.sysprop.h>
24 #include <android-base/file.h>
25 #include <gmock/gmock.h>
26 #include <utils/RefBase.h>
27 
28 #include <sys/types.h>
29 #include <unistd.h>
30 
31 #include <string>
32 #include <type_traits>
33 #include <vector>
34 
35 namespace android {
36 namespace automotive {
37 namespace watchdog {
38 
39 using ::android::RefBase;
40 using ::android::sp;
41 using ::android::automotive::watchdog::internal::PackageInfo;
42 using ::android::base::ReadFdToString;
43 using ::android::base::Result;
44 using ::testing::_;
45 using ::testing::AllOf;
46 using ::testing::ElementsAreArray;
47 using ::testing::Eq;
48 using ::testing::ExplainMatchResult;
49 using ::testing::Field;
50 using ::testing::IsSubsetOf;
51 using ::testing::Matcher;
52 using ::testing::Return;
53 using ::testing::Test;
54 using ::testing::UnorderedElementsAreArray;
55 using ::testing::VariantWith;
56 
57 namespace {
58 
59 MATCHER_P(IoStatsEq, expected, "") {
60     return ExplainMatchResult(AllOf(Field("bytes", &UserPackageStats::IoStats::bytes,
61                                           ElementsAreArray(expected.bytes)),
62                                     Field("fsync", &UserPackageStats::IoStats::fsync,
63                                           ElementsAreArray(expected.fsync))),
64                               arg, result_listener);
65 }
66 
67 MATCHER_P(ProcessCountEq, expected, "") {
68     return ExplainMatchResult(AllOf(Field("comm", &UserPackageStats::ProcStats::ProcessCount::comm,
69                                           Eq(expected.comm)),
70                                     Field("count",
71                                           &UserPackageStats::ProcStats::ProcessCount::count,
72                                           Eq(expected.count))),
73                               arg, result_listener);
74 }
75 
76 MATCHER_P(ProcStatsEq, expected, "") {
77     std::vector<Matcher<const UserPackageStats::ProcStats::ProcessCount&>> processCountMatchers;
78     for (const auto& processCount : expected.topNProcesses) {
79         processCountMatchers.push_back(ProcessCountEq(processCount));
80     }
81     return ExplainMatchResult(AllOf(Field("count", &UserPackageStats::ProcStats::count,
82                                           Eq(expected.count)),
83                                     Field("topNProcesses",
84                                           &UserPackageStats::ProcStats::topNProcesses,
85                                           ElementsAreArray(processCountMatchers))),
86                               arg, result_listener);
87 }
88 
89 MATCHER_P(UserPackageStatsEq, expected, "") {
90     const auto uidMatcher = Field("uid", &UserPackageStats::uid, Eq(expected.uid));
91     const auto packageNameMatcher =
92             Field("genericPackageName", &UserPackageStats::genericPackageName,
93                   Eq(expected.genericPackageName));
94     return std::visit(
__anoncab421ad0202(const auto& stats) 95             [&](const auto& stats) -> bool {
96                 using T = std::decay_t<decltype(stats)>;
97                 if constexpr (std::is_same_v<T, UserPackageStats::IoStats>) {
98                     return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher,
99                                                     Field("stats:IoStats", &UserPackageStats::stats,
100                                                           VariantWith<UserPackageStats::IoStats>(
101                                                                   IoStatsEq(stats)))),
102                                               arg, result_listener);
103                 } else if constexpr (std::is_same_v<T, UserPackageStats::ProcStats>) {
104                     return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher,
105                                                     Field("stats:ProcStats",
106                                                           &UserPackageStats::stats,
107                                                           VariantWith<UserPackageStats::ProcStats>(
108                                                                   ProcStatsEq(stats)))),
109                                               arg, result_listener);
110                 }
111                 *result_listener << "Unexpected variant in UserPackageStats::stats";
112                 return false;
113             },
114             expected.stats);
115 }
116 
117 MATCHER_P(UserPackageSummaryStatsEq, expected, "") {
__anoncab421ad0302(const std::vector<UserPackageStats>& stats) 118     const auto& userPackageStatsMatchers = [&](const std::vector<UserPackageStats>& stats) {
119         std::vector<Matcher<const UserPackageStats&>> matchers;
120         for (const auto& curStats : stats) {
121             matchers.push_back(UserPackageStatsEq(curStats));
122         }
123         return ElementsAreArray(matchers);
124     };
__anoncab421ad0402(const int64_t expected[][UID_STATES]) 125     const auto& totalIoStatsArrayMatcher = [&](const int64_t expected[][UID_STATES]) {
126         std::vector<Matcher<const int64_t[UID_STATES]>> matchers;
127         for (int i = 0; i < METRIC_TYPES; ++i) {
128             matchers.push_back(ElementsAreArray(expected[i], UID_STATES));
129         }
130         return ElementsAreArray(matchers);
131     };
132     return ExplainMatchResult(AllOf(Field("topNIoReads", &UserPackageSummaryStats::topNIoReads,
133                                           userPackageStatsMatchers(expected.topNIoReads)),
134                                     Field("topNIoWrites", &UserPackageSummaryStats::topNIoWrites,
135                                           userPackageStatsMatchers(expected.topNIoWrites)),
136                                     Field("topNIoBlocked", &UserPackageSummaryStats::topNIoBlocked,
137                                           userPackageStatsMatchers(expected.topNIoBlocked)),
138                                     Field("topNMajorFaults",
139                                           &UserPackageSummaryStats::topNMajorFaults,
140                                           userPackageStatsMatchers(expected.topNMajorFaults)),
141                                     Field("totalIoStats", &UserPackageSummaryStats::totalIoStats,
142                                           totalIoStatsArrayMatcher(expected.totalIoStats)),
143                                     Field("taskCountByUid",
144                                           &UserPackageSummaryStats::taskCountByUid,
145                                           IsSubsetOf(expected.taskCountByUid)),
146                                     Field("totalMajorFaults",
147                                           &UserPackageSummaryStats::totalMajorFaults,
148                                           Eq(expected.totalMajorFaults)),
149                                     Field("majorFaultsPercentChange",
150                                           &UserPackageSummaryStats::majorFaultsPercentChange,
151                                           Eq(expected.majorFaultsPercentChange))),
152                               arg, result_listener);
153 }
154 
155 MATCHER_P(SystemSummaryStatsEq, expected, "") {
156     return ExplainMatchResult(AllOf(Field("cpuIoWaitTime", &SystemSummaryStats::cpuIoWaitTime,
157                                           Eq(expected.cpuIoWaitTime)),
158                                     Field("totalCpuTime", &SystemSummaryStats::totalCpuTime,
159                                           Eq(expected.totalCpuTime)),
160                                     Field("ioBlockedProcessCount",
161                                           &SystemSummaryStats::ioBlockedProcessCount,
162                                           Eq(expected.ioBlockedProcessCount)),
163                                     Field("totalProcessCount",
164                                           &SystemSummaryStats::totalProcessCount,
165                                           Eq(expected.totalProcessCount))),
166                               arg, result_listener);
167 }
168 
169 MATCHER_P(PerfStatsRecordEq, expected, "") {
170     return ExplainMatchResult(AllOf(Field(&PerfStatsRecord::systemSummaryStats,
171                                           SystemSummaryStatsEq(expected.systemSummaryStats)),
172                                     Field(&PerfStatsRecord::userPackageSummaryStats,
173                                           UserPackageSummaryStatsEq(
174                                                   expected.userPackageSummaryStats))),
175                               arg, result_listener);
176 }
177 
constructPerfStatsRecordMatchers(const std::vector<PerfStatsRecord> & records)178 const std::vector<Matcher<const PerfStatsRecord&>> constructPerfStatsRecordMatchers(
179         const std::vector<PerfStatsRecord>& records) {
180     std::vector<Matcher<const PerfStatsRecord&>> matchers;
181     for (const auto& record : records) {
182         matchers.push_back(PerfStatsRecordEq(record));
183     }
184     return matchers;
185 }
186 
187 MATCHER_P(CollectionInfoEq, expected, "") {
188     return ExplainMatchResult(AllOf(Field("maxCacheSize", &CollectionInfo::maxCacheSize,
189                                           Eq(expected.maxCacheSize)),
190                                     Field("records", &CollectionInfo::records,
191                                           ElementsAreArray(constructPerfStatsRecordMatchers(
192                                                   expected.records)))),
193                               arg, result_listener);
194 }
195 
countOccurrences(std::string str,std::string subStr)196 int countOccurrences(std::string str, std::string subStr) {
197     size_t pos = 0;
198     int occurrences = 0;
199     while ((pos = str.find(subStr, pos)) != std::string::npos) {
200         ++occurrences;
201         pos += subStr.length();
202     }
203     return occurrences;
204 }
205 
sampleUidStats(int multiplier=1)206 std::tuple<std::vector<UidStats>, UserPackageSummaryStats> sampleUidStats(int multiplier = 1) {
207     /* The number of returned sample stats are less that the top N stats per category/sub-category.
208      * The top N stats per category/sub-category is set to % during test setup. Thus, the default
209      * testing behavior is # reported stats < top N stats.
210      */
211     const auto int64Multiplier = [&](int64_t bytes) -> int64_t {
212         return static_cast<int64_t>(bytes * multiplier);
213     };
214     const auto uint64Multiplier = [&](uint64_t count) -> uint64_t {
215         return static_cast<uint64_t>(count * multiplier);
216     };
217     std::vector<UidStats>
218             uidStats{{.packageInfo = constructPackageInfo("mount", 1009),
219                       .ioStats = {/*fgRdBytes=*/0,
220                                   /*bgRdBytes=*/int64Multiplier(14'000),
221                                   /*fgWrBytes=*/0,
222                                   /*bgWrBytes=*/int64Multiplier(16'000),
223                                   /*fgFsync=*/0, /*bgFsync=*/int64Multiplier(100)},
224                       .procStats = {.totalMajorFaults = uint64Multiplier(11'000),
225                                     .totalTasksCount = 1,
226                                     .ioBlockedTasksCount = 1,
227                                     .processStatsByPid =
228                                             {{/*pid=*/100,
229                                               {/*comm=*/"disk I/O", /*startTime=*/234,
230                                                /*totalMajorFaults=*/uint64Multiplier(11'000),
231                                                /*totalTasksCount=*/1,
232                                                /*ioBlockedTasksCount=*/1}}}}},
233                      {.packageInfo =
234                               constructPackageInfo("com.google.android.car.kitchensink", 1002001),
235                       .ioStats = {/*fgRdBytes=*/0,
236                                   /*bgRdBytes=*/int64Multiplier(3'400),
237                                   /*fgWrBytes=*/0,
238                                   /*bgWrBytes=*/int64Multiplier(6'700),
239                                   /*fgFsync=*/0,
240                                   /*bgFsync=*/int64Multiplier(200)},
241                       .procStats = {.totalMajorFaults = uint64Multiplier(22'445),
242                                     .totalTasksCount = 5,
243                                     .ioBlockedTasksCount = 3,
244                                     .processStatsByPid =
245                                             {{/*pid=*/1000,
246                                               {/*comm=*/"KitchenSinkApp", /*startTime=*/467,
247                                                /*totalMajorFaults=*/uint64Multiplier(12'345),
248                                                /*totalTasksCount=*/2,
249                                                /*ioBlockedTasksCount=*/1}},
250                                              {/*pid=*/1001,
251                                               {/*comm=*/"CTS", /*startTime=*/789,
252                                                /*totalMajorFaults=*/uint64Multiplier(10'100),
253                                                /*totalTasksCount=*/3,
254                                                /*ioBlockedTasksCount=*/2}}}}},
255                      {.packageInfo = constructPackageInfo("", 1012345),
256                       .ioStats = {/*fgRdBytes=*/int64Multiplier(1'000),
257                                   /*bgRdBytes=*/int64Multiplier(4'200),
258                                   /*fgWrBytes=*/int64Multiplier(300),
259                                   /*bgWrBytes=*/int64Multiplier(5'600),
260                                   /*fgFsync=*/int64Multiplier(600),
261                                   /*bgFsync=*/int64Multiplier(300)},
262                       .procStats = {.totalMajorFaults = uint64Multiplier(50'900),
263                                     .totalTasksCount = 4,
264                                     .ioBlockedTasksCount = 2,
265                                     .processStatsByPid =
266                                             {{/*pid=*/2345,
267                                               {/*comm=*/"MapsApp", /*startTime=*/6789,
268                                                /*totalMajorFaults=*/uint64Multiplier(50'900),
269                                                /*totalTasksCount=*/4,
270                                                /*ioBlockedTasksCount=*/2}}}}},
271                      {.packageInfo = constructPackageInfo("com.google.radio", 1015678),
272                       .ioStats = {/*fgRdBytes=*/0,
273                                   /*bgRdBytes=*/0,
274                                   /*fgWrBytes=*/0,
275                                   /*bgWrBytes=*/0,
276                                   /*fgFsync=*/0, /*bgFsync=*/0},
277                       .procStats = {.totalMajorFaults = 0,
278                                     .totalTasksCount = 4,
279                                     .ioBlockedTasksCount = 0,
280                                     .processStatsByPid = {
281                                             {/*pid=*/2345,
282                                              {/*comm=*/"RadioApp", /*startTime=*/19789,
283                                               /*totalMajorFaults=*/0,
284                                               /*totalTasksCount=*/4,
285                                               /*ioBlockedTasksCount=*/0}}}}}};
286 
287     UserPackageSummaryStats userPackageSummaryStats{
288             .topNIoReads =
289                     {{1009, "mount",
290                       UserPackageStats::IoStats{{0, int64Multiplier(14'000)},
291                                                 {0, int64Multiplier(100)}}},
292                      {1012345, "1012345",
293                       UserPackageStats::IoStats{{int64Multiplier(1'000), int64Multiplier(4'200)},
294                                                 {int64Multiplier(600), int64Multiplier(300)}}},
295                      {1002001, "com.google.android.car.kitchensink",
296                       UserPackageStats::IoStats{{0, int64Multiplier(3'400)},
297                                                 {0, int64Multiplier(200)}}}},
298             .topNIoWrites =
299                     {{1009, "mount",
300                       UserPackageStats::IoStats{{0, int64Multiplier(16'000)},
301                                                 {0, int64Multiplier(100)}}},
302                      {1002001, "com.google.android.car.kitchensink",
303                       UserPackageStats::IoStats{{0, int64Multiplier(6'700)},
304                                                 {0, int64Multiplier(200)}}},
305                      {1012345, "1012345",
306                       UserPackageStats::IoStats{{int64Multiplier(300), int64Multiplier(5'600)},
307                                                 {int64Multiplier(600), int64Multiplier(300)}}}},
308             .topNIoBlocked = {{1002001, "com.google.android.car.kitchensink",
309                                UserPackageStats::ProcStats{3, {{"CTS", 2}, {"KitchenSinkApp", 1}}}},
310                               {1012345, "1012345",
311                                UserPackageStats::ProcStats{2, {{"MapsApp", 2}}}},
312                               {1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}}},
313             .topNMajorFaults =
314                     {{1012345, "1012345",
315                       UserPackageStats::ProcStats{uint64Multiplier(50'900),
316                                                   {{"MapsApp", uint64Multiplier(50'900)}}}},
317                      {1002001, "com.google.android.car.kitchensink",
318                       UserPackageStats::ProcStats{uint64Multiplier(22'445),
319                                                   {{"KitchenSinkApp", uint64Multiplier(12'345)},
320                                                    {"CTS", uint64Multiplier(10'100)}}}},
321                      {1009, "mount",
322                       UserPackageStats::ProcStats{uint64Multiplier(11'000),
323                                                   {{"disk I/O", uint64Multiplier(11'000)}}}}},
324             .totalIoStats = {{int64Multiplier(1'000), int64Multiplier(21'600)},
325                              {int64Multiplier(300), int64Multiplier(28'300)},
326                              {int64Multiplier(600), int64Multiplier(600)}},
327             .taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}},
328             .totalMajorFaults = uint64Multiplier(84'345),
329             .majorFaultsPercentChange = 0.0,
330     };
331     return std::make_tuple(uidStats, userPackageSummaryStats);
332 }
333 
sampleProcStat(int multiplier=1)334 std::tuple<ProcStatInfo, SystemSummaryStats> sampleProcStat(int multiplier = 1) {
335     const auto uint64Multiplier = [&](uint64_t bytes) -> uint64_t {
336         return static_cast<uint64_t>(bytes * multiplier);
337     };
338     const auto uint32Multiplier = [&](uint32_t bytes) -> uint32_t {
339         return static_cast<uint32_t>(bytes * multiplier);
340     };
341     ProcStatInfo procStatInfo{/*cpuStats=*/{uint64Multiplier(2'900), uint64Multiplier(7'900),
342                                             uint64Multiplier(4'900), uint64Multiplier(8'900),
343                                             /*ioWaitTime=*/uint64Multiplier(5'900),
344                                             uint64Multiplier(6'966), uint64Multiplier(7'980), 0, 0,
345                                             uint64Multiplier(2'930)},
346                               /*runnableProcessCount=*/uint32Multiplier(100),
347                               /*ioBlockedProcessCount=*/uint32Multiplier(57)};
348     SystemSummaryStats systemSummaryStats{/*cpuIoWaitTime=*/uint64Multiplier(5'900),
349                                           /*totalCpuTime=*/uint64Multiplier(48'376),
350                                           /*ioBlockedProcessCount=*/uint32Multiplier(57),
351                                           /*totalProcessCount=*/uint32Multiplier(157)};
352     return std::make_tuple(procStatInfo, systemSummaryStats);
353 }
354 
355 }  // namespace
356 
357 namespace internal {
358 
359 class IoPerfCollectionPeer : public RefBase {
360 public:
IoPerfCollectionPeer(sp<IoPerfCollection> collector)361     explicit IoPerfCollectionPeer(sp<IoPerfCollection> collector) : mCollector(collector) {}
362 
363     IoPerfCollectionPeer() = delete;
~IoPerfCollectionPeer()364     ~IoPerfCollectionPeer() {
365         mCollector->terminate();
366         mCollector.clear();
367     }
368 
init()369     Result<void> init() { return mCollector->init(); }
370 
setTopNStatsPerCategory(int value)371     void setTopNStatsPerCategory(int value) { mCollector->mTopNStatsPerCategory = value; }
372 
setTopNStatsPerSubcategory(int value)373     void setTopNStatsPerSubcategory(int value) { mCollector->mTopNStatsPerSubcategory = value; }
374 
getBoottimeCollectionInfo()375     const CollectionInfo& getBoottimeCollectionInfo() {
376         Mutex::Autolock lock(mCollector->mMutex);
377         return mCollector->mBoottimeCollection;
378     }
379 
getPeriodicCollectionInfo()380     const CollectionInfo& getPeriodicCollectionInfo() {
381         Mutex::Autolock lock(mCollector->mMutex);
382         return mCollector->mPeriodicCollection;
383     }
384 
getCustomCollectionInfo()385     const CollectionInfo& getCustomCollectionInfo() {
386         Mutex::Autolock lock(mCollector->mMutex);
387         return mCollector->mCustomCollection;
388     }
389 
390 private:
391     sp<IoPerfCollection> mCollector;
392 };
393 
394 }  // namespace internal
395 
396 class IoPerfCollectionTest : public Test {
397 protected:
SetUp()398     void SetUp() override {
399         mMockUidStatsCollector = sp<MockUidStatsCollector>::make();
400         mMockProcStat = sp<MockProcStat>::make();
401         mCollector = sp<IoPerfCollection>::make();
402         mCollectorPeer = sp<internal::IoPerfCollectionPeer>::make(mCollector);
403         ASSERT_RESULT_OK(mCollectorPeer->init());
404         mCollectorPeer->setTopNStatsPerCategory(5);
405         mCollectorPeer->setTopNStatsPerSubcategory(5);
406     }
407 
TearDown()408     void TearDown() override {
409         mMockUidStatsCollector.clear();
410         mMockProcStat.clear();
411         mCollector.clear();
412         mCollectorPeer.clear();
413     }
414 
checkDumpContents(int wantedEmptyCollectionInstances)415     void checkDumpContents(int wantedEmptyCollectionInstances) {
416         TemporaryFile dump;
417         ASSERT_RESULT_OK(mCollector->onDump(dump.fd));
418 
419         checkDumpFd(wantedEmptyCollectionInstances, dump.fd);
420     }
421 
checkCustomDumpContents()422     void checkCustomDumpContents() {
423         TemporaryFile dump;
424         ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(dump.fd));
425 
426         checkDumpFd(/*wantedEmptyCollectionInstances=*/0, dump.fd);
427     }
428 
429 private:
checkDumpFd(int wantedEmptyCollectionInstances,int fd)430     void checkDumpFd(int wantedEmptyCollectionInstances, int fd) {
431         lseek(fd, 0, SEEK_SET);
432         std::string dumpContents;
433         ASSERT_TRUE(ReadFdToString(fd, &dumpContents));
434         ASSERT_FALSE(dumpContents.empty());
435 
436         ASSERT_EQ(countOccurrences(dumpContents, kEmptyCollectionMessage),
437                   wantedEmptyCollectionInstances)
438                 << "Dump contents: " << dumpContents;
439     }
440 
441 protected:
442     sp<MockUidStatsCollector> mMockUidStatsCollector;
443     sp<MockProcStat> mMockProcStat;
444     sp<IoPerfCollection> mCollector;
445     sp<internal::IoPerfCollectionPeer> mCollectorPeer;
446 };
447 
TEST_F(IoPerfCollectionTest,TestOnBoottimeCollection)448 TEST_F(IoPerfCollectionTest, TestOnBoottimeCollection) {
449     const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
450     const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
451 
452     EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
453     EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
454 
455     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
456     ASSERT_RESULT_OK(mCollector->onBoottimeCollection(now, mMockUidStatsCollector, mMockProcStat));
457 
458     const auto actual = mCollectorPeer->getBoottimeCollectionInfo();
459 
460     const CollectionInfo expected{
461             .maxCacheSize = std::numeric_limits<std::size_t>::max(),
462             .records = {{
463                     .systemSummaryStats = systemSummaryStats,
464                     .userPackageSummaryStats = userPackageSummaryStats,
465             }},
466     };
467 
468     EXPECT_THAT(actual, CollectionInfoEq(expected))
469             << "Boottime collection info doesn't match.\nExpected:\n"
470             << expected.toString() << "\nActual:\n"
471             << actual.toString();
472 
473     ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
474             << "Periodic collection shouldn't be reported";
475 }
476 
TEST_F(IoPerfCollectionTest,TestOnPeriodicCollection)477 TEST_F(IoPerfCollectionTest, TestOnPeriodicCollection) {
478     const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
479     const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
480 
481     EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
482     EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
483 
484     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
485     ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
486                                                       mMockUidStatsCollector, mMockProcStat));
487 
488     const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
489 
490     const CollectionInfo expected{
491             .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
492                     kDefaultPeriodicCollectionBufferSize)),
493             .records = {{
494                     .systemSummaryStats = systemSummaryStats,
495                     .userPackageSummaryStats = userPackageSummaryStats,
496             }},
497     };
498 
499     EXPECT_THAT(actual, CollectionInfoEq(expected))
500             << "Periodic collection info doesn't match.\nExpected:\n"
501             << expected.toString() << "\nActual:\n"
502             << actual.toString();
503 
504     ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
505             << "Boot-time collection shouldn't be reported";
506 }
507 
TEST_F(IoPerfCollectionTest,TestOnCustomCollectionWithoutPackageFilter)508 TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithoutPackageFilter) {
509     const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
510     const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
511 
512     EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
513     EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
514 
515     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
516     ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE, {},
517                                                     mMockUidStatsCollector, mMockProcStat));
518 
519     const auto actual = mCollectorPeer->getCustomCollectionInfo();
520 
521     CollectionInfo expected{
522             .maxCacheSize = std::numeric_limits<std::size_t>::max(),
523             .records = {{
524                     .systemSummaryStats = systemSummaryStats,
525                     .userPackageSummaryStats = userPackageSummaryStats,
526             }},
527     };
528 
529     EXPECT_THAT(actual, CollectionInfoEq(expected))
530             << "Custom collection info doesn't match.\nExpected:\n"
531             << expected.toString() << "\nActual:\n"
532             << actual.toString();
533 
534     ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported";
535 
536     TemporaryFile customDump;
537     ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd));
538 
539     // Should clear the cache.
540     ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
541 
542     expected.records.clear();
543     const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
544     EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
545             << "Custom collection should be cleared.";
546 }
547 
TEST_F(IoPerfCollectionTest,TestOnCustomCollectionWithPackageFilter)548 TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithPackageFilter) {
549     // Filter by package name should ignore this limit with package filter.
550     mCollectorPeer->setTopNStatsPerCategory(1);
551 
552     const auto [uidStats, _] = sampleUidStats();
553     const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
554 
555     EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
556     EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
557 
558     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
559     ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE,
560                                                     {"mount", "com.google.android.car.kitchensink"},
561                                                     mMockUidStatsCollector, mMockProcStat));
562 
563     const auto actual = mCollectorPeer->getCustomCollectionInfo();
564 
565     UserPackageSummaryStats userPackageSummaryStats{
566             .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}},
567                             {1002001, "com.google.android.car.kitchensink",
568                              UserPackageStats::IoStats{{0, 3'400}, {0, 200}}}},
569             .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}},
570                              {1002001, "com.google.android.car.kitchensink",
571                               UserPackageStats::IoStats{{0, 6'700}, {0, 200}}}},
572             .topNIoBlocked = {{1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}},
573                               {1002001, "com.google.android.car.kitchensink",
574                                UserPackageStats::ProcStats{3,
575                                                            {{"CTS", 2}, {"KitchenSinkApp", 1}}}}},
576             .topNMajorFaults =
577                     {{1009, "mount", UserPackageStats::ProcStats{11'000, {{"disk I/O", 11'000}}}},
578                      {1002001, "com.google.android.car.kitchensink",
579                       UserPackageStats::ProcStats{22'445,
580                                                   {{"KitchenSinkApp", 12'345}, {"CTS", 10'100}}}}},
581             .totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
582             .taskCountByUid = {{1009, 1}, {1002001, 5}},
583             .totalMajorFaults = 84'345,
584             .majorFaultsPercentChange = 0.0,
585     };
586 
587     CollectionInfo expected{
588             .maxCacheSize = std::numeric_limits<std::size_t>::max(),
589             .records = {{
590                     .systemSummaryStats = systemSummaryStats,
591                     .userPackageSummaryStats = userPackageSummaryStats,
592             }},
593     };
594 
595     EXPECT_THAT(actual, CollectionInfoEq(expected))
596             << "Custom collection info doesn't match.\nExpected:\n"
597             << expected.toString() << "\nActual:\n"
598             << actual.toString();
599 
600     ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported";
601 
602     TemporaryFile customDump;
603     ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd));
604 
605     // Should clear the cache.
606     ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
607 
608     expected.records.clear();
609     const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
610     EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
611             << "Custom collection should be cleared.";
612 }
613 
TEST_F(IoPerfCollectionTest,TestOnPeriodicCollectionWithTrimmingStatsAfterTopN)614 TEST_F(IoPerfCollectionTest, TestOnPeriodicCollectionWithTrimmingStatsAfterTopN) {
615     mCollectorPeer->setTopNStatsPerCategory(1);
616     mCollectorPeer->setTopNStatsPerSubcategory(1);
617 
618     const auto [uidStats, _] = sampleUidStats();
619     const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
620 
621     EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
622     EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
623 
624     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
625     ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
626                                                       mMockUidStatsCollector, mMockProcStat));
627 
628     const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
629 
630     UserPackageSummaryStats userPackageSummaryStats{
631             .topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}}},
632             .topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}}},
633             .topNIoBlocked = {{1002001, "com.google.android.car.kitchensink",
634                                UserPackageStats::ProcStats{3, {{"CTS", 2}}}}},
635             .topNMajorFaults = {{1012345, "1012345",
636                                  UserPackageStats::ProcStats{50'900, {{"MapsApp", 50'900}}}}},
637             .totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
638             .taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}},
639             .totalMajorFaults = 84'345,
640             .majorFaultsPercentChange = 0.0,
641     };
642 
643     const CollectionInfo expected{
644             .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
645                     kDefaultPeriodicCollectionBufferSize)),
646             .records = {{
647                     .systemSummaryStats = systemSummaryStats,
648                     .userPackageSummaryStats = userPackageSummaryStats,
649             }},
650     };
651 
652     EXPECT_THAT(actual, CollectionInfoEq(expected))
653             << "Periodic collection info doesn't match.\nExpected:\n"
654             << expected.toString() << "\nActual:\n"
655             << actual.toString();
656 
657     ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
658             << "Boot-time collection shouldn't be reported";
659 }
660 
TEST_F(IoPerfCollectionTest,TestConsecutiveOnPeriodicCollection)661 TEST_F(IoPerfCollectionTest, TestConsecutiveOnPeriodicCollection) {
662     const auto [firstUidStats, firstUserPackageSummaryStats] = sampleUidStats();
663     const auto [firstProcStatInfo, firstSystemSummaryStats] = sampleProcStat();
664 
665     EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(firstUidStats));
666     EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(firstProcStatInfo));
667 
668     time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
669     ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
670                                                       mMockUidStatsCollector, mMockProcStat));
671 
672     auto [secondUidStats, secondUserPackageSummaryStats] = sampleUidStats(/*multiplier=*/2);
673     const auto [secondProcStatInfo, secondSystemSummaryStats] = sampleProcStat(/*multiplier=*/2);
674 
675     secondUserPackageSummaryStats.majorFaultsPercentChange =
676             (static_cast<double>(secondUserPackageSummaryStats.totalMajorFaults -
677                                  firstUserPackageSummaryStats.totalMajorFaults) /
678              static_cast<double>(firstUserPackageSummaryStats.totalMajorFaults)) *
679             100.0;
680 
681     EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(secondUidStats));
682     EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(secondProcStatInfo));
683 
684     ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
685                                                       mMockUidStatsCollector, mMockProcStat));
686 
687     const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
688 
689     const CollectionInfo expected{
690             .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
691                     kDefaultPeriodicCollectionBufferSize)),
692             .records = {{.systemSummaryStats = firstSystemSummaryStats,
693                          .userPackageSummaryStats = firstUserPackageSummaryStats},
694                         {.systemSummaryStats = secondSystemSummaryStats,
695                          .userPackageSummaryStats = secondUserPackageSummaryStats}},
696     };
697 
698     EXPECT_THAT(actual, CollectionInfoEq(expected))
699             << "Periodic collection info doesn't match.\nExpected:\n"
700             << expected.toString() << "\nActual:\n"
701             << actual.toString();
702 
703     ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
704             << "Boot-time collection shouldn't be reported";
705 }
706 
707 }  // namespace watchdog
708 }  // namespace automotive
709 }  // namespace android
710