#include "common/memory_pressure.h" #include #include #include 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::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("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::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::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(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(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"