Chromium Code Reviews| Index: base/memory/memory_pressure_monitor_mac.cc |
| diff --git a/base/memory/memory_pressure_monitor_mac.cc b/base/memory/memory_pressure_monitor_mac.cc |
| index 391589021bd3cd6988fa10d53cf36535301e3b9e..e6da63829c82b1d03b08288172d3f20094cd6908 100644 |
| --- a/base/memory/memory_pressure_monitor_mac.cc |
| +++ b/base/memory/memory_pressure_monitor_mac.cc |
| @@ -4,6 +4,8 @@ |
| #include "base/memory/memory_pressure_monitor_mac.h" |
| +#include <CoreFoundation/CoreFoundation.h> |
| + |
| #include <dlfcn.h> |
| #include <stddef.h> |
| #include <sys/sysctl.h> |
| @@ -26,9 +28,9 @@ namespace base { |
| namespace mac { |
| MemoryPressureListener::MemoryPressureLevel |
| -MemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressure( |
| - int mac_memory_pressure) { |
| - switch (mac_memory_pressure) { |
| +MemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel( |
| + int mac_memory_pressure_level) { |
| + switch (mac_memory_pressure_level) { |
| case DISPATCH_MEMORYPRESSURE_NORMAL: |
| return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; |
| case DISPATCH_MEMORYPRESSURE_WARN: |
| @@ -39,6 +41,13 @@ MemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressure( |
| return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; |
| } |
| +void MemoryPressureMonitor::OnRunLoopExit(CFRunLoopObserverRef observer, |
| + CFRunLoopActivity activity, |
| + void* info) { |
| + MemoryPressureMonitor* self = static_cast<MemoryPressureMonitor*>(info); |
| + self->UpdatePressureLevelOnRunLoopExit(); |
| +} |
| + |
| MemoryPressureMonitor::MemoryPressureMonitor() |
| : memory_level_event_source_(dispatch_source_create( |
| DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, |
| @@ -48,75 +57,138 @@ MemoryPressureMonitor::MemoryPressureMonitor() |
| dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))), |
| dispatch_callback_( |
| base::Bind(&MemoryPressureListener::NotifyMemoryPressure)), |
| - last_statistic_report_(CFAbsoluteTimeGetCurrent()), |
| + last_statistic_report_time_(CFAbsoluteTimeGetCurrent()), |
| last_pressure_level_(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE), |
| - reporting_error_(0) { |
| - if (memory_level_event_source_.get() != nullptr) { |
| + subtick_seconds_(0) { |
| + // Attach an event handler to the memory pressure event source. |
| + if (memory_level_event_source_.get()) { |
| dispatch_source_set_event_handler(memory_level_event_source_, ^{ |
| OnMemoryPressureChanged(memory_level_event_source_.get(), |
| dispatch_callback_); |
| }); |
| + |
| + // Start monitoring the event source. |
| dispatch_resume(memory_level_event_source_); |
| } |
| + |
| + // Create a CFRunLoopObserver to check the memory pressure at the end of |
| + // every pass through the event loop (modulo kUMATickSize). |
| + CFRunLoopObserverContext observer_context = {0, this, NULL, NULL, NULL}; |
| + |
| + exit_observer_.reset( |
| + CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, true, 0, |
| + OnRunLoopExit, &observer_context)); |
| + |
| + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); |
| + CFRunLoopAddObserver(run_loop, exit_observer_, kCFRunLoopCommonModes); |
| + CFRunLoopAddObserver(run_loop, exit_observer_, |
| + kMessageLoopExclusiveRunLoopMode); |
| } |
| MemoryPressureMonitor::~MemoryPressureMonitor() { |
| - if (memory_level_event_source_.get() != nullptr) |
| + // Detach from the run loop. |
| + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); |
| + CFRunLoopRemoveObserver(run_loop, exit_observer_, kCFRunLoopCommonModes); |
| + CFRunLoopRemoveObserver(run_loop, exit_observer_, |
| + kMessageLoopExclusiveRunLoopMode); |
| + |
| + // Remove the memory pressure event source. |
| + if (memory_level_event_source_.get()) { |
| dispatch_source_cancel(memory_level_event_source_); |
| + } |
| } |
| -MemoryPressureListener::MemoryPressureLevel |
| -MemoryPressureMonitor::GetCurrentPressureLevel() { |
| - int mac_memory_pressure; |
| +int MemoryPressureMonitor::GetMacMemoryPressureLevel() { |
| + // Get the raw memory pressure level from macOS. |
| + int mac_memory_pressure_level; |
| size_t length = sizeof(int); |
| - sysctlbyname("kern.memorystatus_vm_pressure_level", &mac_memory_pressure, |
| - &length, nullptr, 0); |
| - MemoryPressureListener::MemoryPressureLevel memory_pressure_level = |
| - MemoryPressureLevelForMacMemoryPressure(mac_memory_pressure); |
| - bool pressure_level_changed = false; |
| - if (last_pressure_level_ != memory_pressure_level) { |
| - pressure_level_changed = true; |
| - } |
| - SendStatisticsIfNecessary(pressure_level_changed); |
| - last_pressure_level_ = memory_pressure_level; |
| - return memory_pressure_level; |
| -} |
| + sysctlbyname("kern.memorystatus_vm_pressure_level", |
| + &mac_memory_pressure_level, &length, nullptr, 0); |
| -void MemoryPressureMonitor::OnMemoryPressureChanged( |
| - dispatch_source_s* event_source, |
| - const MemoryPressureMonitor::DispatchCallback& dispatch_callback) { |
| - int mac_memory_pressure = dispatch_source_get_data(event_source); |
| - MemoryPressureListener::MemoryPressureLevel memory_pressure_level = |
| - MemoryPressureLevelForMacMemoryPressure(mac_memory_pressure); |
| - bool pressure_level_changed = false; |
| - if (last_pressure_level_ != memory_pressure_level) { |
| - pressure_level_changed = true; |
| - } |
| - SendStatisticsIfNecessary(pressure_level_changed); |
| - last_pressure_level_ = memory_pressure_level; |
| - if (memory_pressure_level != |
| - MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) |
| - dispatch_callback.Run(memory_pressure_level); |
| + return mac_memory_pressure_level; |
| } |
| -void MemoryPressureMonitor::SendStatisticsIfNecessary( |
| - bool pressure_level_changed) { |
| +void MemoryPressureMonitor::UpdatePressureLevel() { |
| + // Get the current macOS pressure level and convert to the corresponding |
| + // Chrome pressure level. |
| + int mac_memory_pressure_level = GetMacMemoryPressureLevel(); |
| + MemoryPressureListener::MemoryPressureLevel new_pressure_level = |
| + MemoryPressureLevelForMacMemoryPressureLevel(mac_memory_pressure_level); |
| + |
| + // Compute the number of "ticks" spent at |last_pressure_level_| (since the |
| + // last report sent to UMA). |
| CFTimeInterval now = CFAbsoluteTimeGetCurrent(); |
| - CFTimeInterval since_last_report = now - last_statistic_report_; |
| - last_statistic_report_ = now; |
| + CFTimeInterval time_since_last_report = now - last_statistic_report_time_; |
| + last_statistic_report_time_ = now; |
| - double accumulated_time = since_last_report + reporting_error_; |
| + double accumulated_time = time_since_last_report + subtick_seconds_; |
| int ticks_to_report = static_cast<int>(accumulated_time / kUMATickSize); |
| - reporting_error_ = std::fmod(accumulated_time, kUMATickSize); |
| + // Save for later the seconds that didn't make it into a full tick. |
| + subtick_seconds_ = std::fmod(accumulated_time, kUMATickSize); |
| - // Round up on change to ensure we capture it |
| + // Round the tick count up on a pressure level change to ensure we capture it. |
| + bool pressure_level_changed = (new_pressure_level != last_pressure_level_); |
| if (pressure_level_changed && ticks_to_report < 1) { |
| ticks_to_report = 1; |
| - reporting_error_ = 0; |
| + subtick_seconds_ = 0; |
| } |
| - if (ticks_to_report >= 1) |
| + // Send elapsed ticks to UMA. |
| + if (ticks_to_report >= 1) { |
| RecordMemoryPressure(last_pressure_level_, ticks_to_report); |
| + } |
| + |
| + // Save the now-current memory pressure level. |
| + last_pressure_level_ = new_pressure_level; |
| +} |
| + |
| +void MemoryPressureMonitor::UpdatePressureLevelOnRunLoopExit() { |
| + // Wait until it's time to check the pressure level. |
| + CFTimeInterval now = CFAbsoluteTimeGetCurrent(); |
| + if (now >= next_run_loop_update_time_) { |
| + UpdatePressureLevel(); |
| + |
| + // Update again in kUMATickSize seconds. We can update at any frequency, |
| + // but because we're only checking memory pressure levels for UMA there's |
| + // no need to update more frequently than we're keeping statistics on. |
| + next_run_loop_update_time_ = now + kUMATickSize; |
|
lgrey
2017/02/22 21:33:40
Should we account for the error? (`now + kUMATickS
shrike
2017/02/22 23:56:25
OK.
|
| + } |
| +} |
| + |
| +// Static. |
| +int MemoryPressureMonitor::GetSecondsPerUMATick() { |
| + return kUMATickSize; |
| +} |
| + |
| +MemoryPressureListener::MemoryPressureLevel |
| +MemoryPressureMonitor::GetCurrentPressureLevel() { |
| + UpdatePressureLevel(); |
| + return last_pressure_level_; |
| +} |
| + |
| +void MemoryPressureMonitor::OnMemoryPressureChanged( |
| + dispatch_source_s* event_source, |
| + const MemoryPressureMonitor::DispatchCallback& dispatch_callback) { |
| + // Get the Chrome-equvialent memory pressure level. |
| + int mac_memory_pressure_level = dispatch_source_get_data(event_source); |
| + MemoryPressureListener::MemoryPressureLevel memory_pressure_level = |
| + MemoryPressureLevelForMacMemoryPressureLevel(mac_memory_pressure_level); |
| + |
| + // Run the callback that's waiting on memory pressure change notifications. |
| + // Note that we don't bother with updating |last_pressure_level_| or |
| + // causing memory pressure stats to be sent to UMA. Memory pressure change |
| + // notifications are delayed on the Mac, so the current actual memory pressure |
| + // level may be different than the incoming pressure level from the event. |
| + // |
| + // In general we don't want to take action (such as freeing memory) on |
| + // memory pressure change events, but that's how the current system is |
| + // designed. Given that it's incorrect to act on either stale or current |
| + // pressure level info, it's not clear which level is better to send. For |
| + // now stick with how it's been implemented to date, which is to send the |
| + // stale value. |
| + if (memory_pressure_level != |
| + MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) |
| + dispatch_callback.Run(memory_pressure_level); |
| } |
| void MemoryPressureMonitor::SetDispatchCallback( |