201 lines
9.5 KiB
C++
201 lines
9.5 KiB
C++
#include "common/memory_pressure.h"
|
|
|
|
#include <QSignalSpy>
|
|
#include <QTest>
|
|
#include <limits>
|
|
|
|
using namespace docview;
|
|
namespace {
|
|
constexpr quint64 MiB = 1024ull * 1024;
|
|
constexpr quint64 GiB = 1024 * MiB;
|
|
constexpr MemorySample Low{8 * GiB, 512 * MiB, true};
|
|
constexpr MemorySample Critical{8 * GiB, 128 * MiB, true};
|
|
constexpr MemorySample Healthy{8 * GiB, 2 * GiB, true};
|
|
} // namespace
|
|
|
|
class MemoryPressureTests : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void parsesAvailableRatherThanFree();
|
|
void rejectsMalformedSamples_data();
|
|
void rejectsMalformedSamples();
|
|
void sustainedPressureAdvancesInOrder();
|
|
void criticalPressureStillAdvancesOneStage();
|
|
void thresholdBoundaries();
|
|
void recoveryRequiresHealthyConsecutiveSamples();
|
|
void invalidSamplePreservesStateAndBreaksStreaks();
|
|
void monitorInjectionAndOneSecondSource();
|
|
void nativeSampleIsConsistent();
|
|
};
|
|
|
|
void MemoryPressureTests::parsesAvailableRatherThanFree() {
|
|
const auto sample = parseLinuxMemoryInfo("MemFree: 1 kB\nMemAvailable:\t2048 kB\n"
|
|
"Other: ignored\nMemTotal: 8192 kB\n");
|
|
QVERIFY(sample.valid);
|
|
QCOMPARE(sample.totalBytes, 8 * MiB);
|
|
QCOMPARE(sample.availableBytes, 2 * MiB);
|
|
const auto empty = parseLinuxMemoryInfo("MemAvailable: 0 kB\nMemTotal: 1 kB");
|
|
QVERIFY(empty.valid);
|
|
QCOMPARE(empty.availableBytes, 0u);
|
|
const auto largest = std::numeric_limits<quint64>::max() / 1024;
|
|
const auto big =
|
|
parseLinuxMemoryInfo("MemTotal: " + QByteArray::number(largest) + " kB\nMemAvailable: 0 kB\n");
|
|
QVERIFY(big.valid);
|
|
QCOMPARE(big.totalBytes, largest * 1024);
|
|
}
|
|
|
|
void MemoryPressureTests::rejectsMalformedSamples_data() {
|
|
QTest::addColumn<QByteArray>("input");
|
|
QTest::newRow("empty") << QByteArray{};
|
|
QTest::newRow("no-total") << QByteArray("MemAvailable: 1 kB\n");
|
|
QTest::newRow("free-is-not-available") << QByteArray("MemTotal: 10 kB\nMemFree: 9 kB\n");
|
|
QTest::newRow("zero-total") << QByteArray("MemTotal: 0 kB\nMemAvailable: 0 kB\n");
|
|
QTest::newRow("available-exceeds-total") << QByteArray("MemTotal: 1 kB\nMemAvailable: 2 kB\n");
|
|
QTest::newRow("duplicate-total") << QByteArray("MemTotal: 2 kB\nMemTotal: 2 kB\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("duplicate-available")
|
|
<< QByteArray("MemTotal: 2 kB\nMemAvailable: 1 kB\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("byte-unit") << QByteArray("MemTotal: 2 B\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("missing-unit") << QByteArray("MemTotal: 2\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("negative") << QByteArray("MemTotal: 2 kB\nMemAvailable: -1 kB\n");
|
|
QTest::newRow("plus-sign") << QByteArray("MemTotal: +2 kB\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("decimal") << QByteArray("MemTotal: 2.0 kB\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("extra-field") << QByteArray("MemTotal: 2 kB extra\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("integer-overflow")
|
|
<< QByteArray("MemTotal: 18446744073709551616 kB\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("byte-overflow") << ("MemTotal: " +
|
|
QByteArray::number(std::numeric_limits<quint64>::max() / 1024 + 1) +
|
|
" kB\nMemAvailable: 1 kB\n");
|
|
QTest::newRow("nul") << (QByteArray("MemTotal: 2 kB\nMemAvailable: 1 kB\n") + '\0');
|
|
QTest::newRow("oversized") << QByteArray(64 * 1024 + 1, 'x');
|
|
}
|
|
void MemoryPressureTests::rejectsMalformedSamples() {
|
|
QFETCH(QByteArray, input);
|
|
QVERIFY(!parseLinuxMemoryInfo(input).valid);
|
|
}
|
|
|
|
void MemoryPressureTests::sustainedPressureAdvancesInOrder() {
|
|
MemoryPressurePolicy policy;
|
|
QCOMPARE(policy.observe(Low), MemoryPressureLevel::NoPrefetch);
|
|
for (const auto next :
|
|
{MemoryPressureLevel::Trim, MemoryPressureLevel::Reduced, MemoryPressureLevel::Stop}) {
|
|
const auto previous = policy.level();
|
|
for (int n = 1; n < MemoryPressurePolicy::SustainedSamplesPerStage; ++n)
|
|
QCOMPARE(policy.observe(Low), previous);
|
|
QCOMPARE(policy.observe(Low), next);
|
|
}
|
|
for (int n = 0; n < 30; ++n)
|
|
QCOMPARE(policy.observe(Low), MemoryPressureLevel::Stop);
|
|
}
|
|
void MemoryPressureTests::criticalPressureStillAdvancesOneStage() {
|
|
MemoryPressurePolicy policy;
|
|
for (const auto expected : {MemoryPressureLevel::NoPrefetch, MemoryPressureLevel::Trim,
|
|
MemoryPressureLevel::Reduced, MemoryPressureLevel::Stop})
|
|
QCOMPARE(policy.observe(Critical), expected);
|
|
QCOMPARE(policy.observe(Critical), MemoryPressureLevel::Stop);
|
|
}
|
|
void MemoryPressureTests::thresholdBoundaries() {
|
|
MemoryPressurePolicy absolute;
|
|
QCOMPARE(absolute.observe({64 * GiB, GiB, true}), MemoryPressureLevel::Normal);
|
|
QCOMPARE(absolute.observe({64 * GiB, GiB - 1, true}), MemoryPressureLevel::NoPrefetch);
|
|
QCOMPARE(absolute.observe({64 * GiB, 256 * MiB, true}), MemoryPressureLevel::NoPrefetch);
|
|
QCOMPARE(absolute.observe({64 * GiB, 256 * MiB - 1, true}), MemoryPressureLevel::Trim);
|
|
|
|
const quint64 tenPercent = (4 * GiB + 9) / 10;
|
|
MemoryPressurePolicy ratio;
|
|
QCOMPARE(ratio.observe({4 * GiB, tenPercent, true}), MemoryPressureLevel::Normal);
|
|
QCOMPARE(ratio.observe({4 * GiB, tenPercent - 1, true}), MemoryPressureLevel::NoPrefetch);
|
|
const quint64 threePercent = (4 * GiB * 3 + 99) / 100;
|
|
QCOMPARE(ratio.observe({4 * GiB, threePercent, true}), MemoryPressureLevel::NoPrefetch);
|
|
QCOMPARE(ratio.observe({4 * GiB, threePercent - 1, true}), MemoryPressureLevel::Trim);
|
|
|
|
MemoryPressurePolicy maximum;
|
|
const auto max = std::numeric_limits<quint64>::max();
|
|
QCOMPARE(maximum.observe({max, max, true}), MemoryPressureLevel::Normal);
|
|
QCOMPARE(maximum.observe({max, 0, true}), MemoryPressureLevel::NoPrefetch);
|
|
QCOMPARE(maximum.observe({max, 0, true}), MemoryPressureLevel::Trim);
|
|
}
|
|
void MemoryPressureTests::recoveryRequiresHealthyConsecutiveSamples() {
|
|
MemoryPressurePolicy policy;
|
|
for (int n = 0; n < 4; ++n)
|
|
policy.observe(Critical);
|
|
const MemorySample hysteresisBand{8 * GiB, GiB, true};
|
|
for (int n = 0; n < 20; ++n)
|
|
QCOMPARE(policy.observe(hysteresisBand), MemoryPressureLevel::Stop);
|
|
for (int n = 1; n < MemoryPressurePolicy::RecoverySamples; ++n)
|
|
QCOMPARE(policy.observe(Healthy), MemoryPressureLevel::Stop);
|
|
QCOMPARE(policy.observe(hysteresisBand), MemoryPressureLevel::Stop);
|
|
for (int n = 1; n < MemoryPressurePolicy::RecoverySamples; ++n)
|
|
QCOMPARE(policy.observe(Healthy), MemoryPressureLevel::Stop);
|
|
QCOMPARE(policy.observe(Healthy), MemoryPressureLevel::Normal);
|
|
QCOMPARE(policy.observe(Low), MemoryPressureLevel::NoPrefetch);
|
|
|
|
// Either margin can establish recovery on small and large memory systems.
|
|
MemoryPressurePolicy large;
|
|
large.observe({64 * GiB, 128 * MiB, true});
|
|
for (int n = 0; n < MemoryPressurePolicy::RecoverySamples; ++n)
|
|
large.observe({64 * GiB, 1536 * MiB, true});
|
|
QCOMPARE(large.level(), MemoryPressureLevel::Normal);
|
|
MemoryPressurePolicy small;
|
|
small.observe({4 * GiB, 128 * MiB, true});
|
|
for (int n = 0; n < MemoryPressurePolicy::RecoverySamples; ++n)
|
|
small.observe({4 * GiB, (4 * GiB * 15 + 99) / 100, true});
|
|
QCOMPARE(small.level(), MemoryPressureLevel::Normal);
|
|
}
|
|
void MemoryPressureTests::invalidSamplePreservesStateAndBreaksStreaks() {
|
|
MemoryPressurePolicy policy;
|
|
policy.observe(Low);
|
|
for (int n = 1; n < MemoryPressurePolicy::SustainedSamplesPerStage; ++n)
|
|
policy.observe(Low);
|
|
for (const MemorySample invalid : {MemorySample{}, MemorySample{0, 0, true}, MemorySample{1, 2, true},
|
|
MemorySample{8 * GiB, 2 * GiB, false}})
|
|
QCOMPARE(policy.observe(invalid), MemoryPressureLevel::NoPrefetch);
|
|
QCOMPARE(policy.observe(Low), MemoryPressureLevel::NoPrefetch);
|
|
for (int n = 1; n < MemoryPressurePolicy::RecoverySamples; ++n)
|
|
policy.observe(Healthy);
|
|
QCOMPARE(policy.observe({}), MemoryPressureLevel::NoPrefetch);
|
|
QCOMPARE(policy.observe(Healthy), MemoryPressureLevel::NoPrefetch);
|
|
}
|
|
void MemoryPressureTests::monitorInjectionAndOneSecondSource() {
|
|
int calls = 0;
|
|
MemoryPressureMonitor monitor(nullptr, [&] {
|
|
++calls;
|
|
return Critical;
|
|
});
|
|
QSignalSpy changed(&monitor, &MemoryPressureMonitor::levelChanged);
|
|
QVERIFY(!monitor.running());
|
|
QCOMPARE(calls, 0);
|
|
monitor.start();
|
|
QCOMPARE(calls, 1);
|
|
QCOMPARE(changed.size(), 1);
|
|
QCOMPARE(qvariant_cast<MemoryPressureLevel>(changed.last().first()), MemoryPressureLevel::NoPrefetch);
|
|
monitor.start();
|
|
QCOMPARE(calls, 1); // Idempotent start must not accelerate escalation.
|
|
QTRY_VERIFY_WITH_TIMEOUT(calls >= 2, 1500);
|
|
monitor.stop();
|
|
QVERIFY(!monitor.running());
|
|
QCOMPARE(monitor.level(), MemoryPressureLevel::Trim);
|
|
monitor.observeSample(Critical);
|
|
monitor.observeSample(Critical);
|
|
QCOMPARE(monitor.level(), MemoryPressureLevel::Stop);
|
|
QCOMPARE(changed.size(), 4);
|
|
monitor.observeSample({});
|
|
QCOMPARE(changed.size(), 4);
|
|
for (int n = 0; n < MemoryPressurePolicy::RecoverySamples; ++n)
|
|
monitor.observeSample(Healthy);
|
|
QCOMPARE(changed.size(), 5);
|
|
QCOMPARE(qvariant_cast<MemoryPressureLevel>(changed.last().first()), MemoryPressureLevel::Normal);
|
|
}
|
|
void MemoryPressureTests::nativeSampleIsConsistent() {
|
|
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
|
|
const auto sample = readSystemMemory();
|
|
QVERIFY(sample.valid);
|
|
QVERIFY(sample.totalBytes > 0);
|
|
QVERIFY(sample.availableBytes <= sample.totalBytes);
|
|
#else
|
|
QVERIFY(!readSystemMemory().valid);
|
|
#endif
|
|
}
|
|
|
|
QTEST_GUILESS_MAIN(MemoryPressureTests)
|
|
#include "test_memory_pressure.moc"
|