blob: 8da3b6f07bbe16cf0a0a66128a80e50f97a6bb0a [file] [log] [blame]
/*
* Copyright 2020 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 "IoPerfCollection.h"
#include "MockProcStat.h"
#include "MockUidStatsCollector.h"
#include "MockWatchdogServiceHelper.h"
#include "PackageInfoTestUtils.h"
#include <WatchdogProperties.sysprop.h>
#include <android-base/file.h>
#include <gmock/gmock.h>
#include <utils/RefBase.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <type_traits>
#include <vector>
namespace android {
namespace automotive {
namespace watchdog {
using ::android::RefBase;
using ::android::sp;
using ::android::automotive::watchdog::internal::PackageInfo;
using ::android::base::ReadFdToString;
using ::android::base::Result;
using ::testing::_;
using ::testing::AllOf;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::ExplainMatchResult;
using ::testing::Field;
using ::testing::IsSubsetOf;
using ::testing::Matcher;
using ::testing::Return;
using ::testing::Test;
using ::testing::UnorderedElementsAreArray;
using ::testing::VariantWith;
namespace {
MATCHER_P(IoStatsEq, expected, "") {
return ExplainMatchResult(AllOf(Field("bytes", &UserPackageStats::IoStats::bytes,
ElementsAreArray(expected.bytes)),
Field("fsync", &UserPackageStats::IoStats::fsync,
ElementsAreArray(expected.fsync))),
arg, result_listener);
}
MATCHER_P(ProcessCountEq, expected, "") {
return ExplainMatchResult(AllOf(Field("comm", &UserPackageStats::ProcStats::ProcessCount::comm,
Eq(expected.comm)),
Field("count",
&UserPackageStats::ProcStats::ProcessCount::count,
Eq(expected.count))),
arg, result_listener);
}
MATCHER_P(ProcStatsEq, expected, "") {
std::vector<Matcher<const UserPackageStats::ProcStats::ProcessCount&>> processCountMatchers;
for (const auto& processCount : expected.topNProcesses) {
processCountMatchers.push_back(ProcessCountEq(processCount));
}
return ExplainMatchResult(AllOf(Field("count", &UserPackageStats::ProcStats::count,
Eq(expected.count)),
Field("topNProcesses",
&UserPackageStats::ProcStats::topNProcesses,
ElementsAreArray(processCountMatchers))),
arg, result_listener);
}
MATCHER_P(UserPackageStatsEq, expected, "") {
const auto uidMatcher = Field("uid", &UserPackageStats::uid, Eq(expected.uid));
const auto packageNameMatcher =
Field("genericPackageName", &UserPackageStats::genericPackageName,
Eq(expected.genericPackageName));
return std::visit(
[&](const auto& stats) -> bool {
using T = std::decay_t<decltype(stats)>;
if constexpr (std::is_same_v<T, UserPackageStats::IoStats>) {
return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher,
Field("stats:IoStats", &UserPackageStats::stats,
VariantWith<UserPackageStats::IoStats>(
IoStatsEq(stats)))),
arg, result_listener);
} else if constexpr (std::is_same_v<T, UserPackageStats::ProcStats>) {
return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher,
Field("stats:ProcStats",
&UserPackageStats::stats,
VariantWith<UserPackageStats::ProcStats>(
ProcStatsEq(stats)))),
arg, result_listener);
}
*result_listener << "Unexpected variant in UserPackageStats::stats";
return false;
},
expected.stats);
}
MATCHER_P(UserPackageSummaryStatsEq, expected, "") {
const auto& userPackageStatsMatchers = [&](const std::vector<UserPackageStats>& stats) {
std::vector<Matcher<const UserPackageStats&>> matchers;
for (const auto& curStats : stats) {
matchers.push_back(UserPackageStatsEq(curStats));
}
return ElementsAreArray(matchers);
};
const auto& totalIoStatsArrayMatcher = [&](const int64_t expected[][UID_STATES]) {
std::vector<Matcher<const int64_t[UID_STATES]>> matchers;
for (int i = 0; i < METRIC_TYPES; ++i) {
matchers.push_back(ElementsAreArray(expected[i], UID_STATES));
}
return ElementsAreArray(matchers);
};
return ExplainMatchResult(AllOf(Field("topNIoReads", &UserPackageSummaryStats::topNIoReads,
userPackageStatsMatchers(expected.topNIoReads)),
Field("topNIoWrites", &UserPackageSummaryStats::topNIoWrites,
userPackageStatsMatchers(expected.topNIoWrites)),
Field("topNIoBlocked", &UserPackageSummaryStats::topNIoBlocked,
userPackageStatsMatchers(expected.topNIoBlocked)),
Field("topNMajorFaults",
&UserPackageSummaryStats::topNMajorFaults,
userPackageStatsMatchers(expected.topNMajorFaults)),
Field("totalIoStats", &UserPackageSummaryStats::totalIoStats,
totalIoStatsArrayMatcher(expected.totalIoStats)),
Field("taskCountByUid",
&UserPackageSummaryStats::taskCountByUid,
IsSubsetOf(expected.taskCountByUid)),
Field("totalMajorFaults",
&UserPackageSummaryStats::totalMajorFaults,
Eq(expected.totalMajorFaults)),
Field("majorFaultsPercentChange",
&UserPackageSummaryStats::majorFaultsPercentChange,
Eq(expected.majorFaultsPercentChange))),
arg, result_listener);
}
MATCHER_P(SystemSummaryStatsEq, expected, "") {
return ExplainMatchResult(AllOf(Field("cpuIoWaitTime", &SystemSummaryStats::cpuIoWaitTime,
Eq(expected.cpuIoWaitTime)),
Field("totalCpuTime", &SystemSummaryStats::totalCpuTime,
Eq(expected.totalCpuTime)),
Field("ioBlockedProcessCount",
&SystemSummaryStats::ioBlockedProcessCount,
Eq(expected.ioBlockedProcessCount)),
Field("totalProcessCount",
&SystemSummaryStats::totalProcessCount,
Eq(expected.totalProcessCount))),
arg, result_listener);
}
MATCHER_P(PerfStatsRecordEq, expected, "") {
return ExplainMatchResult(AllOf(Field(&PerfStatsRecord::systemSummaryStats,
SystemSummaryStatsEq(expected.systemSummaryStats)),
Field(&PerfStatsRecord::userPackageSummaryStats,
UserPackageSummaryStatsEq(
expected.userPackageSummaryStats))),
arg, result_listener);
}
const std::vector<Matcher<const PerfStatsRecord&>> constructPerfStatsRecordMatchers(
const std::vector<PerfStatsRecord>& records) {
std::vector<Matcher<const PerfStatsRecord&>> matchers;
for (const auto& record : records) {
matchers.push_back(PerfStatsRecordEq(record));
}
return matchers;
}
MATCHER_P(CollectionInfoEq, expected, "") {
return ExplainMatchResult(AllOf(Field("maxCacheSize", &CollectionInfo::maxCacheSize,
Eq(expected.maxCacheSize)),
Field("records", &CollectionInfo::records,
ElementsAreArray(constructPerfStatsRecordMatchers(
expected.records)))),
arg, result_listener);
}
int countOccurrences(std::string str, std::string subStr) {
size_t pos = 0;
int occurrences = 0;
while ((pos = str.find(subStr, pos)) != std::string::npos) {
++occurrences;
pos += subStr.length();
}
return occurrences;
}
std::tuple<std::vector<UidStats>, UserPackageSummaryStats> sampleUidStats(int multiplier = 1) {
/* The number of returned sample stats are less that the top N stats per category/sub-category.
* The top N stats per category/sub-category is set to % during test setup. Thus, the default
* testing behavior is # reported stats < top N stats.
*/
const auto int64Multiplier = [&](int64_t bytes) -> int64_t {
return static_cast<int64_t>(bytes * multiplier);
};
const auto uint64Multiplier = [&](uint64_t count) -> uint64_t {
return static_cast<uint64_t>(count * multiplier);
};
std::vector<UidStats>
uidStats{{.packageInfo = constructPackageInfo("mount", 1009),
.ioStats = {/*fgRdBytes=*/0,
/*bgRdBytes=*/int64Multiplier(14'000),
/*fgWrBytes=*/0,
/*bgWrBytes=*/int64Multiplier(16'000),
/*fgFsync=*/0, /*bgFsync=*/int64Multiplier(100)},
.procStats = {.totalMajorFaults = uint64Multiplier(11'000),
.totalTasksCount = 1,
.ioBlockedTasksCount = 1,
.processStatsByPid =
{{/*pid=*/100,
{/*comm=*/"disk I/O", /*startTime=*/234,
/*totalMajorFaults=*/uint64Multiplier(11'000),
/*totalTasksCount=*/1,
/*ioBlockedTasksCount=*/1}}}}},
{.packageInfo =
constructPackageInfo("com.google.android.car.kitchensink", 1002001),
.ioStats = {/*fgRdBytes=*/0,
/*bgRdBytes=*/int64Multiplier(3'400),
/*fgWrBytes=*/0,
/*bgWrBytes=*/int64Multiplier(6'700),
/*fgFsync=*/0,
/*bgFsync=*/int64Multiplier(200)},
.procStats = {.totalMajorFaults = uint64Multiplier(22'445),
.totalTasksCount = 5,
.ioBlockedTasksCount = 3,
.processStatsByPid =
{{/*pid=*/1000,
{/*comm=*/"KitchenSinkApp", /*startTime=*/467,
/*totalMajorFaults=*/uint64Multiplier(12'345),
/*totalTasksCount=*/2,
/*ioBlockedTasksCount=*/1}},
{/*pid=*/1001,
{/*comm=*/"CTS", /*startTime=*/789,
/*totalMajorFaults=*/uint64Multiplier(10'100),
/*totalTasksCount=*/3,
/*ioBlockedTasksCount=*/2}}}}},
{.packageInfo = constructPackageInfo("", 1012345),
.ioStats = {/*fgRdBytes=*/int64Multiplier(1'000),
/*bgRdBytes=*/int64Multiplier(4'200),
/*fgWrBytes=*/int64Multiplier(300),
/*bgWrBytes=*/int64Multiplier(5'600),
/*fgFsync=*/int64Multiplier(600),
/*bgFsync=*/int64Multiplier(300)},
.procStats = {.totalMajorFaults = uint64Multiplier(50'900),
.totalTasksCount = 4,
.ioBlockedTasksCount = 2,
.processStatsByPid =
{{/*pid=*/2345,
{/*comm=*/"MapsApp", /*startTime=*/6789,
/*totalMajorFaults=*/uint64Multiplier(50'900),
/*totalTasksCount=*/4,
/*ioBlockedTasksCount=*/2}}}}},
{.packageInfo = constructPackageInfo("com.google.radio", 1015678),
.ioStats = {/*fgRdBytes=*/0,
/*bgRdBytes=*/0,
/*fgWrBytes=*/0,
/*bgWrBytes=*/0,
/*fgFsync=*/0, /*bgFsync=*/0},
.procStats = {.totalMajorFaults = 0,
.totalTasksCount = 4,
.ioBlockedTasksCount = 0,
.processStatsByPid = {
{/*pid=*/2345,
{/*comm=*/"RadioApp", /*startTime=*/19789,
/*totalMajorFaults=*/0,
/*totalTasksCount=*/4,
/*ioBlockedTasksCount=*/0}}}}}};
UserPackageSummaryStats userPackageSummaryStats{
.topNIoReads =
{{1009, "mount",
UserPackageStats::IoStats{{0, int64Multiplier(14'000)},
{0, int64Multiplier(100)}}},
{1012345, "1012345",
UserPackageStats::IoStats{{int64Multiplier(1'000), int64Multiplier(4'200)},
{int64Multiplier(600), int64Multiplier(300)}}},
{1002001, "com.google.android.car.kitchensink",
UserPackageStats::IoStats{{0, int64Multiplier(3'400)},
{0, int64Multiplier(200)}}}},
.topNIoWrites =
{{1009, "mount",
UserPackageStats::IoStats{{0, int64Multiplier(16'000)},
{0, int64Multiplier(100)}}},
{1002001, "com.google.android.car.kitchensink",
UserPackageStats::IoStats{{0, int64Multiplier(6'700)},
{0, int64Multiplier(200)}}},
{1012345, "1012345",
UserPackageStats::IoStats{{int64Multiplier(300), int64Multiplier(5'600)},
{int64Multiplier(600), int64Multiplier(300)}}}},
.topNIoBlocked = {{1002001, "com.google.android.car.kitchensink",
UserPackageStats::ProcStats{3, {{"CTS", 2}, {"KitchenSinkApp", 1}}}},
{1012345, "1012345",
UserPackageStats::ProcStats{2, {{"MapsApp", 2}}}},
{1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}}},
.topNMajorFaults =
{{1012345, "1012345",
UserPackageStats::ProcStats{uint64Multiplier(50'900),
{{"MapsApp", uint64Multiplier(50'900)}}}},
{1002001, "com.google.android.car.kitchensink",
UserPackageStats::ProcStats{uint64Multiplier(22'445),
{{"KitchenSinkApp", uint64Multiplier(12'345)},
{"CTS", uint64Multiplier(10'100)}}}},
{1009, "mount",
UserPackageStats::ProcStats{uint64Multiplier(11'000),
{{"disk I/O", uint64Multiplier(11'000)}}}}},
.totalIoStats = {{int64Multiplier(1'000), int64Multiplier(21'600)},
{int64Multiplier(300), int64Multiplier(28'300)},
{int64Multiplier(600), int64Multiplier(600)}},
.taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}},
.totalMajorFaults = uint64Multiplier(84'345),
.majorFaultsPercentChange = 0.0,
};
return std::make_tuple(uidStats, userPackageSummaryStats);
}
std::tuple<ProcStatInfo, SystemSummaryStats> sampleProcStat(int multiplier = 1) {
const auto uint64Multiplier = [&](uint64_t bytes) -> uint64_t {
return static_cast<uint64_t>(bytes * multiplier);
};
const auto uint32Multiplier = [&](uint32_t bytes) -> uint32_t {
return static_cast<uint32_t>(bytes * multiplier);
};
ProcStatInfo procStatInfo{/*cpuStats=*/{uint64Multiplier(2'900), uint64Multiplier(7'900),
uint64Multiplier(4'900), uint64Multiplier(8'900),
/*ioWaitTime=*/uint64Multiplier(5'900),
uint64Multiplier(6'966), uint64Multiplier(7'980), 0, 0,
uint64Multiplier(2'930)},
/*runnableProcessCount=*/uint32Multiplier(100),
/*ioBlockedProcessCount=*/uint32Multiplier(57)};
SystemSummaryStats systemSummaryStats{/*cpuIoWaitTime=*/uint64Multiplier(5'900),
/*totalCpuTime=*/uint64Multiplier(48'376),
/*ioBlockedProcessCount=*/uint32Multiplier(57),
/*totalProcessCount=*/uint32Multiplier(157)};
return std::make_tuple(procStatInfo, systemSummaryStats);
}
} // namespace
namespace internal {
class IoPerfCollectionPeer : public RefBase {
public:
explicit IoPerfCollectionPeer(sp<IoPerfCollection> collector) : mCollector(collector) {}
IoPerfCollectionPeer() = delete;
~IoPerfCollectionPeer() {
mCollector->terminate();
mCollector.clear();
}
Result<void> init() { return mCollector->init(); }
void setTopNStatsPerCategory(int value) { mCollector->mTopNStatsPerCategory = value; }
void setTopNStatsPerSubcategory(int value) { mCollector->mTopNStatsPerSubcategory = value; }
const CollectionInfo& getBoottimeCollectionInfo() {
Mutex::Autolock lock(mCollector->mMutex);
return mCollector->mBoottimeCollection;
}
const CollectionInfo& getPeriodicCollectionInfo() {
Mutex::Autolock lock(mCollector->mMutex);
return mCollector->mPeriodicCollection;
}
const CollectionInfo& getCustomCollectionInfo() {
Mutex::Autolock lock(mCollector->mMutex);
return mCollector->mCustomCollection;
}
private:
sp<IoPerfCollection> mCollector;
};
} // namespace internal
class IoPerfCollectionTest : public Test {
protected:
void SetUp() override {
mMockUidStatsCollector = sp<MockUidStatsCollector>::make();
mMockProcStat = sp<MockProcStat>::make();
mCollector = sp<IoPerfCollection>::make();
mCollectorPeer = sp<internal::IoPerfCollectionPeer>::make(mCollector);
ASSERT_RESULT_OK(mCollectorPeer->init());
mCollectorPeer->setTopNStatsPerCategory(5);
mCollectorPeer->setTopNStatsPerSubcategory(5);
}
void TearDown() override {
mMockUidStatsCollector.clear();
mMockProcStat.clear();
mCollector.clear();
mCollectorPeer.clear();
}
void checkDumpContents(int wantedEmptyCollectionInstances) {
TemporaryFile dump;
ASSERT_RESULT_OK(mCollector->onDump(dump.fd));
checkDumpFd(wantedEmptyCollectionInstances, dump.fd);
}
void checkCustomDumpContents() {
TemporaryFile dump;
ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(dump.fd));
checkDumpFd(/*wantedEmptyCollectionInstances=*/0, dump.fd);
}
private:
void checkDumpFd(int wantedEmptyCollectionInstances, int fd) {
lseek(fd, 0, SEEK_SET);
std::string dumpContents;
ASSERT_TRUE(ReadFdToString(fd, &dumpContents));
ASSERT_FALSE(dumpContents.empty());
ASSERT_EQ(countOccurrences(dumpContents, kEmptyCollectionMessage),
wantedEmptyCollectionInstances)
<< "Dump contents: " << dumpContents;
}
protected:
sp<MockUidStatsCollector> mMockUidStatsCollector;
sp<MockProcStat> mMockProcStat;
sp<IoPerfCollection> mCollector;
sp<internal::IoPerfCollectionPeer> mCollectorPeer;
};
TEST_F(IoPerfCollectionTest, TestOnBoottimeCollection) {
const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
ASSERT_RESULT_OK(mCollector->onBoottimeCollection(now, mMockUidStatsCollector, mMockProcStat));
const auto actual = mCollectorPeer->getBoottimeCollectionInfo();
const CollectionInfo expected{
.maxCacheSize = std::numeric_limits<std::size_t>::max(),
.records = {{
.systemSummaryStats = systemSummaryStats,
.userPackageSummaryStats = userPackageSummaryStats,
}},
};
EXPECT_THAT(actual, CollectionInfoEq(expected))
<< "Boottime collection info doesn't match.\nExpected:\n"
<< expected.toString() << "\nActual:\n"
<< actual.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
<< "Periodic collection shouldn't be reported";
}
TEST_F(IoPerfCollectionTest, TestOnPeriodicCollection) {
const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
mMockUidStatsCollector, mMockProcStat));
const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
const CollectionInfo expected{
.maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
kDefaultPeriodicCollectionBufferSize)),
.records = {{
.systemSummaryStats = systemSummaryStats,
.userPackageSummaryStats = userPackageSummaryStats,
}},
};
EXPECT_THAT(actual, CollectionInfoEq(expected))
<< "Periodic collection info doesn't match.\nExpected:\n"
<< expected.toString() << "\nActual:\n"
<< actual.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
<< "Boot-time collection shouldn't be reported";
}
TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithoutPackageFilter) {
const auto [uidStats, userPackageSummaryStats] = sampleUidStats();
const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE, {},
mMockUidStatsCollector, mMockProcStat));
const auto actual = mCollectorPeer->getCustomCollectionInfo();
CollectionInfo expected{
.maxCacheSize = std::numeric_limits<std::size_t>::max(),
.records = {{
.systemSummaryStats = systemSummaryStats,
.userPackageSummaryStats = userPackageSummaryStats,
}},
};
EXPECT_THAT(actual, CollectionInfoEq(expected))
<< "Custom collection info doesn't match.\nExpected:\n"
<< expected.toString() << "\nActual:\n"
<< actual.toString();
ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported";
TemporaryFile customDump;
ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd));
// Should clear the cache.
ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
expected.records.clear();
const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
<< "Custom collection should be cleared.";
}
TEST_F(IoPerfCollectionTest, TestOnCustomCollectionWithPackageFilter) {
// Filter by package name should ignore this limit with package filter.
mCollectorPeer->setTopNStatsPerCategory(1);
const auto [uidStats, _] = sampleUidStats();
const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
ASSERT_RESULT_OK(mCollector->onCustomCollection(now, SystemState::NORMAL_MODE,
{"mount", "com.google.android.car.kitchensink"},
mMockUidStatsCollector, mMockProcStat));
const auto actual = mCollectorPeer->getCustomCollectionInfo();
UserPackageSummaryStats userPackageSummaryStats{
.topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}},
{1002001, "com.google.android.car.kitchensink",
UserPackageStats::IoStats{{0, 3'400}, {0, 200}}}},
.topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}},
{1002001, "com.google.android.car.kitchensink",
UserPackageStats::IoStats{{0, 6'700}, {0, 200}}}},
.topNIoBlocked = {{1009, "mount", UserPackageStats::ProcStats{1, {{"disk I/O", 1}}}},
{1002001, "com.google.android.car.kitchensink",
UserPackageStats::ProcStats{3,
{{"CTS", 2}, {"KitchenSinkApp", 1}}}}},
.topNMajorFaults =
{{1009, "mount", UserPackageStats::ProcStats{11'000, {{"disk I/O", 11'000}}}},
{1002001, "com.google.android.car.kitchensink",
UserPackageStats::ProcStats{22'445,
{{"KitchenSinkApp", 12'345}, {"CTS", 10'100}}}}},
.totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
.taskCountByUid = {{1009, 1}, {1002001, 5}},
.totalMajorFaults = 84'345,
.majorFaultsPercentChange = 0.0,
};
CollectionInfo expected{
.maxCacheSize = std::numeric_limits<std::size_t>::max(),
.records = {{
.systemSummaryStats = systemSummaryStats,
.userPackageSummaryStats = userPackageSummaryStats,
}},
};
EXPECT_THAT(actual, CollectionInfoEq(expected))
<< "Custom collection info doesn't match.\nExpected:\n"
<< expected.toString() << "\nActual:\n"
<< actual.toString();
ASSERT_NO_FATAL_FAILURE(checkCustomDumpContents()) << "Custom collection should be reported";
TemporaryFile customDump;
ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(customDump.fd));
// Should clear the cache.
ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
expected.records.clear();
const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
<< "Custom collection should be cleared.";
}
TEST_F(IoPerfCollectionTest, TestOnPeriodicCollectionWithTrimmingStatsAfterTopN) {
mCollectorPeer->setTopNStatsPerCategory(1);
mCollectorPeer->setTopNStatsPerSubcategory(1);
const auto [uidStats, _] = sampleUidStats();
const auto [procStatInfo, systemSummaryStats] = sampleProcStat();
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(uidStats));
EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(procStatInfo));
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
mMockUidStatsCollector, mMockProcStat));
const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
UserPackageSummaryStats userPackageSummaryStats{
.topNIoReads = {{1009, "mount", UserPackageStats::IoStats{{0, 14'000}, {0, 100}}}},
.topNIoWrites = {{1009, "mount", UserPackageStats::IoStats{{0, 16'000}, {0, 100}}}},
.topNIoBlocked = {{1002001, "com.google.android.car.kitchensink",
UserPackageStats::ProcStats{3, {{"CTS", 2}}}}},
.topNMajorFaults = {{1012345, "1012345",
UserPackageStats::ProcStats{50'900, {{"MapsApp", 50'900}}}}},
.totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
.taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}},
.totalMajorFaults = 84'345,
.majorFaultsPercentChange = 0.0,
};
const CollectionInfo expected{
.maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
kDefaultPeriodicCollectionBufferSize)),
.records = {{
.systemSummaryStats = systemSummaryStats,
.userPackageSummaryStats = userPackageSummaryStats,
}},
};
EXPECT_THAT(actual, CollectionInfoEq(expected))
<< "Periodic collection info doesn't match.\nExpected:\n"
<< expected.toString() << "\nActual:\n"
<< actual.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
<< "Boot-time collection shouldn't be reported";
}
TEST_F(IoPerfCollectionTest, TestConsecutiveOnPeriodicCollection) {
const auto [firstUidStats, firstUserPackageSummaryStats] = sampleUidStats();
const auto [firstProcStatInfo, firstSystemSummaryStats] = sampleProcStat();
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(firstUidStats));
EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(firstProcStatInfo));
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
mMockUidStatsCollector, mMockProcStat));
auto [secondUidStats, secondUserPackageSummaryStats] = sampleUidStats(/*multiplier=*/2);
const auto [secondProcStatInfo, secondSystemSummaryStats] = sampleProcStat(/*multiplier=*/2);
secondUserPackageSummaryStats.majorFaultsPercentChange =
(static_cast<double>(secondUserPackageSummaryStats.totalMajorFaults -
firstUserPackageSummaryStats.totalMajorFaults) /
static_cast<double>(firstUserPackageSummaryStats.totalMajorFaults)) *
100.0;
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(secondUidStats));
EXPECT_CALL(*mMockProcStat, deltaStats()).WillOnce(Return(secondProcStatInfo));
ASSERT_RESULT_OK(mCollector->onPeriodicCollection(now, SystemState::NORMAL_MODE,
mMockUidStatsCollector, mMockProcStat));
const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
const CollectionInfo expected{
.maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
kDefaultPeriodicCollectionBufferSize)),
.records = {{.systemSummaryStats = firstSystemSummaryStats,
.userPackageSummaryStats = firstUserPackageSummaryStats},
{.systemSummaryStats = secondSystemSummaryStats,
.userPackageSummaryStats = secondUserPackageSummaryStats}},
};
EXPECT_THAT(actual, CollectionInfoEq(expected))
<< "Periodic collection info doesn't match.\nExpected:\n"
<< expected.toString() << "\nActual:\n"
<< actual.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/1))
<< "Boot-time collection shouldn't be reported";
}
} // namespace watchdog
} // namespace automotive
} // namespace android