Bug 1234176 - Introduce a dynamic trigger to send memory pressure events before background applications are reaped by the LMK. r=dhylands

This commit is contained in:
Gabriele Svelto 2015-12-29 11:55:41 +01:00
parent 17ddccd53b
commit 0abc5c5e24
2 changed files with 138 additions and 56 deletions

View File

@ -779,7 +779,12 @@ pref("hal.gonk.COMPOSITOR.nice", -4);
// this too high, then we'll send out a memory pressure event every Z seconds
// (see below), even while we have processes that we would happily kill in
// order to free up memory.
pref("hal.processPriorityManager.gonk.notifyLowMemUnderKB", 14336);
pref("gonk.notifyHardLowMemUnderKB", 14336);
// Fire a memory pressure event when the system has less than Xmb of memory
// remaining and then switch to the hard trigger, see above. This should be
// placed above the BACKGROUND priority class.
pref("gonk.notifySoftLowMemUnderKB", 43008);
// We wait this long before polling the memory-pressure fd after seeing one
// memory pressure event. (When we're not under memory pressure, we sit

View File

@ -4,6 +4,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <android/log.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/sysinfo.h>
#include "GonkMemoryPressureMonitoring.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/FileUtils.h"
@ -14,11 +20,8 @@
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsMemoryPressure.h"
#include "nsPrintfCString.h"
#include "nsThreadUtils.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <android/log.h>
#define LOG(args...) \
__android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
@ -67,7 +70,12 @@ class MemoryPressureWatcher final
public:
MemoryPressureWatcher()
: mMonitor("MemoryPressureWatcher")
, mLowMemTriggerKB(0)
, mPageSize(0)
, mShuttingDown(false)
, mTriggerFd(-1)
, mShutdownPipeRead(-1)
, mShutdownPipeWrite(-1)
{
}
@ -81,17 +89,13 @@ public:
// The observer service holds us alive.
os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false);
// While we're under memory pressure, we periodically read()
// notify_trigger_active to try and see when we're no longer under memory
// pressure. mPollMS indicates how many milliseconds we wait between those
// read()s.
mPollMS = Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS",
/* default */ 5000);
// Initialize the internal state
mPageSize = sysconf(_SC_PAGESIZE);
ReadPrefs();
nsresult rv = OpenFiles();
NS_ENSURE_SUCCESS(rv, rv);
SetLowMemTrigger(mSoftLowMemTriggerKB);
int pipes[2];
NS_ENSURE_STATE(!pipe(pipes));
mShutdownPipeRead = pipes[0];
mShutdownPipeWrite = pipes[1];
return NS_OK;
}
@ -125,28 +129,31 @@ public:
}
#endif
int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
O_RDONLY | O_CLOEXEC);
NS_ENSURE_STATE(lowMemFd != -1);
ScopedClose autoClose(lowMemFd);
nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr);
int triggerResetTimeout = -1;
bool memoryPressure;
nsresult rv = CheckForMemoryPressure(&memoryPressure);
NS_ENSURE_SUCCESS(rv, rv);
while (true) {
// Wait for a notification on lowMemFd or for data to be written to
// mShutdownPipeWrite. (poll(lowMemFd, POLLPRI) blocks until we're under
// memory pressure.)
// Wait for a notification on mTriggerFd or for data to be written to
// mShutdownPipeWrite. (poll(mTriggerFd, POLLPRI) blocks until we're
// under memory pressure or until we time out, the time out is used
// to adjust the trigger level after a memory pressure event.)
struct pollfd pollfds[2];
pollfds[0].fd = lowMemFd;
pollfds[0].fd = mTriggerFd;
pollfds[0].events = POLLPRI;
pollfds[1].fd = mShutdownPipeRead;
pollfds[1].events = POLLIN;
int pollRv;
do {
pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1);
} while (pollRv == -1 && errno == EINTR);
int pollRv = MOZ_TEMP_FAILURE_RETRY(
poll(pollfds, ArrayLength(pollfds), triggerResetTimeout)
);
if (pollRv == 0) {
// Timed out, adjust the trigger and update the timeout.
triggerResetTimeout = AdjustTrigger(triggerResetTimeout);
continue;
}
if (pollfds[1].revents) {
// Something was written to our shutdown pipe; we're outta here.
@ -161,23 +168,24 @@ public:
return NS_ERROR_FAILURE;
}
// POLLPRI on lowMemFd indicates that we're in a low-memory situation. We
// could read lowMemFd to double-check, but we've observed that the read
// sometimes completes after the memory-pressure event is over, so let's
// just believe the result of poll().
// We use low-memory-no-forward because each process has its own watcher
// and thus there is no need for the main process to forward this event.
// POLLPRI on mTriggerFd indicates that we're in a low-memory situation.
// We could read lowMemFd to double-check, but we've observed that the
// read sometimes completes after the memory-pressure event is over, so
// let's just believe the result of poll().
rv = DispatchMemoryPressure(MemPressure_New);
NS_ENSURE_SUCCESS(rv, rv);
// Manually check lowMemFd until we observe that memory pressure is over.
// We won't fire any more low-memory events until we observe that
// Move to the hard level if we're on the soft one.
if (mLowMemTriggerKB > mHardLowMemTriggerKB) {
SetLowMemTrigger(mHardLowMemTriggerKB);
}
// Manually check mTriggerFd until we observe that memory pressure is
// over. We won't fire any more low-memory events until we observe that
// we're no longer under pressure. Instead, we fire low-memory-ongoing
// events, which cause processes to keep flushing caches but will not
// trigger expensive GCs and other attempts to save memory that are
// likely futile at this point.
bool memoryPressure;
do {
{
MonitorAutoLock lock(mMonitor);
@ -197,7 +205,7 @@ public:
}
LOG("Checking to see if memory pressure is over.");
rv = CheckForMemoryPressure(lowMemFd, &memoryPressure);
rv = CheckForMemoryPressure(&memoryPressure);
NS_ENSURE_SUCCESS(rv, rv);
if (memoryPressure) {
@ -207,6 +215,11 @@ public:
}
} while (false);
if (XRE_IsParentProcess()) {
// The main process will try to adjust the trigger.
triggerResetTimeout = mPollMS * 2;
}
LOG("Memory pressure is over.");
}
@ -217,36 +230,95 @@ protected:
~MemoryPressureWatcher() {}
private:
void ReadPrefs() {
// While we're under memory pressure, we periodically read()
// notify_trigger_active to try and see when we're no longer under memory
// pressure. mPollMS indicates how many milliseconds we wait between those
// read()s.
Preferences::AddUintVarCache(&mPollMS,
"gonk.systemMemoryPressureRecoveryPollMS", /* default */ 5000);
// We have two values for the notify trigger, a soft one which is triggered
// before we start killing background applications and an hard one which is
// after we've killed background applications but before we start killing
// foreground ones.
Preferences::AddUintVarCache(&mSoftLowMemTriggerKB,
"gonk.notifySoftLowMemUnderKB", /* default */ 43008);
Preferences::AddUintVarCache(&mHardLowMemTriggerKB,
"gonk.notifyHardLowMemUnderKB", /* default */ 14336);
}
nsresult OpenFiles() {
mTriggerFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
O_RDONLY | O_CLOEXEC);
NS_ENSURE_STATE(mTriggerFd != -1);
int pipes[2];
NS_ENSURE_STATE(!pipe(pipes));
mShutdownPipeRead = pipes[0];
mShutdownPipeWrite = pipes[1];
return NS_OK;
}
/**
* Read from aLowMemFd, which we assume corresponds to the
* notify_trigger_active sysfs node, and determine whether we're currently
* under memory pressure.
* Set the low memory trigger to the specified value, this can be done by
* the main process alone.
*/
void SetLowMemTrigger(uint32_t aValue) {
if (XRE_IsParentProcess()) {
nsPrintfCString str("%ld", (aValue * 1024) / mPageSize);
if (WriteSysFile("/sys/module/lowmemorykiller/parameters/notify_trigger",
str.get())) {
mLowMemTriggerKB = aValue;
}
}
}
/**
* Read from the trigger file descriptor and determine whether we're
* currently under memory pressure.
*
* We don't expect this method to block.
*/
nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut)
nsresult CheckForMemoryPressure(bool* aOut)
{
if (aOut) {
*aOut = false;
}
*aOut = false;
lseek(aLowMemFd, 0, SEEK_SET);
lseek(mTriggerFd, 0, SEEK_SET);
char buf[2];
int nread;
do {
nread = read(aLowMemFd, buf, sizeof(buf));
} while(nread == -1 && errno == EINTR);
int nread = MOZ_TEMP_FAILURE_RETRY(read(mTriggerFd, buf, sizeof(buf)));
NS_ENSURE_STATE(nread == 2);
// The notify_trigger_active sysfs node should contain either "0\n" or
// "1\n". The latter indicates memory pressure.
if (aOut) {
*aOut = buf[0] == '1' && buf[1] == '\n';
}
*aOut = (buf[0] == '1');
return NS_OK;
}
int AdjustTrigger(int timeout)
{
if (!XRE_IsParentProcess()) {
return -1; // Only the main process can adjust the trigger.
}
struct sysinfo info;
int rv = sysinfo(&info);
if (rv < 0) {
return -1; // Without system information we're blind, bail out.
}
size_t freeMemory = (info.freeram * info.mem_unit) / 1024;
if (freeMemory > mSoftLowMemTriggerKB) {
SetLowMemTrigger(mSoftLowMemTriggerKB);
return -1; // Trigger adjusted, wait indefinitely.
}
// Wait again but double the duration, max once per day.
return std::min(86400000, timeout * 2);
}
/**
* Dispatch the specified memory pressure event unless a high-priority
* process is present. If a high-priority process is present then it's likely
@ -263,9 +335,14 @@ private:
}
Monitor mMonitor;
uint32_t mPollMS;
uint32_t mPollMS; // Ongoing pressure poll delay
uint32_t mSoftLowMemTriggerKB; // Soft memory pressure level
uint32_t mHardLowMemTriggerKB; // Hard memory pressure level
uint32_t mLowMemTriggerKB; // Current value of the trigger
size_t mPageSize;
bool mShuttingDown;
ScopedClose mTriggerFd;
ScopedClose mShutdownPipeRead;
ScopedClose mShutdownPipeWrite;
};