| // 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 <CoreFoundation/CoreFoundation.h> |
| |
| #include <dlfcn.h> |
| #include <sys/sysctl.h> |
| |
| #include <cmath> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/mac/mac_util.h" |
| #include "starboard/types.h" |
| |
| // Redeclare for partial 10.9 availability. |
| DISPATCH_EXPORT const struct dispatch_source_type_s |
| _dispatch_source_type_memorypressure; |
| |
| namespace { |
| static const int kUMATickSize = 5; |
| } // namespace |
| |
| namespace base { |
| namespace mac { |
| |
| MemoryPressureListener::MemoryPressureLevel |
| 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: |
| return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE; |
| case DISPATCH_MEMORYPRESSURE_CRITICAL: |
| return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL; |
| } |
| 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, |
| 0, |
| DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL | |
| DISPATCH_MEMORYPRESSURE_NORMAL, |
| dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))), |
| dispatch_callback_( |
| base::Bind(&MemoryPressureListener::NotifyMemoryPressure)), |
| last_statistic_report_time_(CFAbsoluteTimeGetCurrent()), |
| last_pressure_level_(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE), |
| 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() { |
| // 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_); |
| } |
| } |
| |
| 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_level, &length, nullptr, 0); |
| |
| return mac_memory_pressure_level; |
| } |
| |
| 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 time_since_last_report = now - last_statistic_report_time_; |
| last_statistic_report_time_ = now; |
| |
| double accumulated_time = time_since_last_report + subtick_seconds_; |
| int ticks_to_report = static_cast<int>(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 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; |
| subtick_seconds_ = 0; |
| } |
| |
| // 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 - subtick_seconds_; |
| } |
| } |
| |
| // Static. |
| int MemoryPressureMonitor::GetSecondsPerUMATick() { |
| return kUMATickSize; |
| } |
| |
| MemoryPressureListener::MemoryPressureLevel |
| MemoryPressureMonitor::GetCurrentPressureLevel() { |
| return last_pressure_level_; |
| } |
| |
| void MemoryPressureMonitor::OnMemoryPressureChanged( |
| dispatch_source_s* event_source, |
| const MemoryPressureMonitor::DispatchCallback& dispatch_callback) { |
| // The OS has sent a notification that the memory pressure level has changed. |
| // Go through the normal memory pressure level checking mechanism so that |
| // last_pressure_level_ and UMA get updated to the current value. |
| UpdatePressureLevel(); |
| |
| // Run the callback that's waiting on memory pressure change notifications. |
| // The convention is to not send notifiations on memory pressure returning to |
| // normal. |
| if (last_pressure_level_ != |
| MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) |
| dispatch_callback.Run(last_pressure_level_); |
| } |
| |
| void MemoryPressureMonitor::SetDispatchCallback( |
| const DispatchCallback& callback) { |
| dispatch_callback_ = callback; |
| } |
| |
| } // namespace mac |
| } // namespace base |