blob: 3f5f4b764f7e47b93c642ce6bd4f5a955d1e58d3 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/memory_pressure_monitor_mac.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/macros.h"
#include "base/test/metrics/histogram_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace mac {
class TestMemoryPressureMonitor : public MemoryPressureMonitor {
public:
using MemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel;
// A HistogramTester for verifying correct UMA stat generation.
base::HistogramTester tester;
TestMemoryPressureMonitor() { }
// Clears the next run loop update time so that the next pass of the run
// loop checks the memory pressure level immediately. Normally there's a
// 5 second delay between pressure readings.
void ResetRunLoopUpdateTime() { next_run_loop_update_time_ = 0; }
// Sets the last UMA stat report time. Time spent in memory pressure is
// recorded in 5-second "ticks" from the last time statistics were recorded.
void SetLastStatisticReportTime(CFTimeInterval time) {
last_statistic_report_time_ = time;
}
// Sets the raw macOS memory pressure level read by the memory pressure
// monitor.
int macos_pressure_level_for_testing_;
// Exposes the UpdatePressureLevel() method for testing.
void UpdatePressureLevel() { MemoryPressureMonitor::UpdatePressureLevel(); }
// Returns the number of seconds left over from the last UMA tick
// calculation.
int SubTickSeconds() { return subtick_seconds_; }
// Returns the number of seconds per UMA tick.
static int GetSecondsPerUMATick() {
return MemoryPressureMonitor::GetSecondsPerUMATick();
}
private:
DISALLOW_COPY_AND_ASSIGN(TestMemoryPressureMonitor);
int GetMacMemoryPressureLevel() override {
return macos_pressure_level_for_testing_;
}
};
TEST(MacMemoryPressureMonitorTest, MemoryPressureFromMacMemoryPressure) {
EXPECT_EQ(
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
TestMemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
DISPATCH_MEMORYPRESSURE_NORMAL));
EXPECT_EQ(
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
TestMemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
DISPATCH_MEMORYPRESSURE_WARN));
EXPECT_EQ(
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
TestMemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
DISPATCH_MEMORYPRESSURE_CRITICAL));
EXPECT_EQ(
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
TestMemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
0));
EXPECT_EQ(
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
TestMemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
3));
EXPECT_EQ(
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
TestMemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
5));
EXPECT_EQ(
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
TestMemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
-1));
}
TEST(MacMemoryPressureMonitorTest, CurrentMemoryPressure) {
TestMemoryPressureMonitor monitor;
MemoryPressureListener::MemoryPressureLevel memory_pressure =
monitor.GetCurrentPressureLevel();
EXPECT_TRUE(memory_pressure ==
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE ||
memory_pressure ==
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE ||
memory_pressure ==
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
}
TEST(MacMemoryPressureMonitorTest, MemoryPressureConversion) {
TestMemoryPressureMonitor monitor;
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_NORMAL;
monitor.UpdatePressureLevel();
MemoryPressureListener::MemoryPressureLevel memory_pressure =
monitor.GetCurrentPressureLevel();
EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
memory_pressure);
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_WARN;
monitor.UpdatePressureLevel();
memory_pressure = monitor.GetCurrentPressureLevel();
EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
memory_pressure);
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_CRITICAL;
monitor.UpdatePressureLevel();
memory_pressure = monitor.GetCurrentPressureLevel();
EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
memory_pressure);
}
TEST(MacMemoryPressureMonitorTest, MemoryPressureRunLoopChecking) {
TestMemoryPressureMonitor monitor;
// To test grabbing the memory presure at the end of the run loop, we have to
// run the run loop, but to do that the run loop needs a run loop source. Add
// a timer as the source. We know that the exit observer is attached to
// the kMessageLoopExclusiveRunLoopMode mode, so use that mode.
ScopedCFTypeRef<CFRunLoopTimerRef> timer_ref(CFRunLoopTimerCreate(
NULL, CFAbsoluteTimeGetCurrent() + 10, 0, 0, 0, nullptr, nullptr));
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer_ref,
kMessageLoopExclusiveRunLoopMode);
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_WARN;
monitor.ResetRunLoopUpdateTime();
CFRunLoopRunInMode(kMessageLoopExclusiveRunLoopMode, 0, true);
EXPECT_EQ(monitor.GetCurrentPressureLevel(),
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_CRITICAL;
monitor.ResetRunLoopUpdateTime();
CFRunLoopRunInMode(kMessageLoopExclusiveRunLoopMode, 0, true);
EXPECT_EQ(monitor.GetCurrentPressureLevel(),
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_NORMAL;
monitor.ResetRunLoopUpdateTime();
CFRunLoopRunInMode(kMessageLoopExclusiveRunLoopMode, 0, true);
EXPECT_EQ(monitor.GetCurrentPressureLevel(),
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer_ref,
kMessageLoopExclusiveRunLoopMode);
}
TEST(MacMemoryPressureMonitorTest, RecordMemoryPressureStats) {
TestMemoryPressureMonitor monitor;
const char* kHistogram = "Memory.PressureLevel";
CFTimeInterval now = CFAbsoluteTimeGetCurrent();
const int seconds_per_tick =
TestMemoryPressureMonitor::GetSecondsPerUMATick();
// Set the initial pressure level.
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_NORMAL;
// Incur one UMA tick of time (and include one extra second of elapsed time).
monitor.SetLastStatisticReportTime(now - (seconds_per_tick + 1));
monitor.UpdatePressureLevel();
monitor.tester.ExpectTotalCount(kHistogram, 1);
monitor.tester.ExpectBucketCount(kHistogram, 0, 1);
// The report time above included an extra second so there should be 1
// sub-tick second left over.
EXPECT_EQ(1, monitor.SubTickSeconds());
// Simulate sitting in normal pressure for 1 second less than 6 UMA tick
// seconds and then elevating to warning. With the left over sub-tick second
// from above, the total elapsed ticks should be an even 6 UMA ticks.
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_WARN;
monitor.SetLastStatisticReportTime(now - (seconds_per_tick * 6 - 1));
monitor.UpdatePressureLevel();
monitor.tester.ExpectTotalCount(kHistogram, 7);
monitor.tester.ExpectBucketCount(kHistogram, 0, 7);
monitor.tester.ExpectBucketCount(kHistogram, 1, 0);
EXPECT_EQ(0, monitor.SubTickSeconds());
// Simulate sitting in warning pressure for 20 UMA ticks and 2 seconds, and
// then elevating to critical.
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_CRITICAL;
monitor.SetLastStatisticReportTime(now - (20 * seconds_per_tick + 2));
monitor.UpdatePressureLevel();
monitor.tester.ExpectTotalCount(kHistogram, 27);
monitor.tester.ExpectBucketCount(kHistogram, 0, 7);
monitor.tester.ExpectBucketCount(kHistogram, 1, 20);
monitor.tester.ExpectBucketCount(kHistogram, 2, 0);
EXPECT_EQ(2, monitor.SubTickSeconds());
// A quick update while critical - the stats should not budge because less
// than 1 tick of time has elapsed.
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_CRITICAL;
monitor.SetLastStatisticReportTime(now - 1);
monitor.UpdatePressureLevel();
monitor.tester.ExpectTotalCount(kHistogram, 27);
monitor.tester.ExpectBucketCount(kHistogram, 0, 7);
monitor.tester.ExpectBucketCount(kHistogram, 1, 20);
monitor.tester.ExpectBucketCount(kHistogram, 2, 0);
EXPECT_EQ(3, monitor.SubTickSeconds());
// A quick change back to normal. Less than 1 tick of time has elapsed, but
// in this case the pressure level changed, so the critical bucket should
// get another sample (otherwise we could miss quick level changes).
monitor.macos_pressure_level_for_testing_ = DISPATCH_MEMORYPRESSURE_NORMAL;
monitor.SetLastStatisticReportTime(now - 1);
monitor.UpdatePressureLevel();
monitor.tester.ExpectTotalCount(kHistogram, 28);
monitor.tester.ExpectBucketCount(kHistogram, 0, 7);
monitor.tester.ExpectBucketCount(kHistogram, 1, 20);
monitor.tester.ExpectBucketCount(kHistogram, 2, 1);
// When less than 1 tick of time has elapsed but the pressure level changed,
// the subtick remainder gets zeroed out.
EXPECT_EQ(0, monitor.SubTickSeconds());
}
} // namespace mac
} // namespace base