blob: ffbaf152ef4402ab60b64358a2075eb2c504944b [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/oom.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <kernel/thread.h>
#include <lib/console.h>
#include <platform.h>
#include <pretty/sizes.h>
#include <vm/pmm.h>
#include <zircon/errors.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <inttypes.h>
#include <string.h>
#include <sys/types.h>
using fbl::AutoLock;
// Guards the oom_* values below.
static fbl::Mutex oom_mutex;
// Function to call when we hit a low-memory condition.
static oom_lowmem_callback_t* oom_lowmem_callback TA_GUARDED(oom_mutex);
// The thread, if it's running; nullptr otherwise.
static thread_t* oom_thread TA_GUARDED(oom_mutex);
// True if the thread should keep running.
static bool oom_running TA_GUARDED(oom_mutex);
// How long the OOM thread sleeps between checks.
static uint64_t oom_sleep_duration_ns TA_GUARDED(oom_mutex);
// If the PMM has fewer than this many bytes free, start killing processes.
static uint64_t oom_redline_bytes TA_GUARDED(oom_mutex);
// True if the thread should print the current free value when it runs.
static bool oom_printing TA_GUARDED(oom_mutex);
// True if the thread should simulate a low-memory condition on its next loop.
static bool oom_simulate_lowmem TA_GUARDED(oom_mutex);
static int oom_loop(void* arg) {
const size_t total_bytes = pmm_count_total_bytes();
char total_buf[MAX_FORMAT_SIZE_LEN];
format_size_fixed(total_buf, sizeof(total_buf), total_bytes, 'M');
size_t last_free_bytes = total_bytes;
while (true) {
const size_t free_bytes = pmm_count_free_pages() * PAGE_SIZE;
bool lowmem = false;
bool printing = false;
size_t shortfall_bytes = 0;
oom_lowmem_callback_t* lowmem_callback = nullptr;
uint64_t sleep_duration_ns = 0;
{
AutoLock lock(&oom_mutex);
if (!oom_running) {
break;
}
if (oom_simulate_lowmem) {
printf("OOM: simulating low-memory situation\n");
}
lowmem = free_bytes < oom_redline_bytes || oom_simulate_lowmem;
if (lowmem) {
shortfall_bytes =
oom_simulate_lowmem
? 512 * 1024
: oom_redline_bytes - free_bytes;
}
oom_simulate_lowmem = false;
printing =
lowmem || (oom_printing && free_bytes != last_free_bytes);
lowmem_callback = oom_lowmem_callback;
DEBUG_ASSERT(lowmem_callback != nullptr);
sleep_duration_ns = oom_sleep_duration_ns;
}
if (printing) {
char free_buf[MAX_FORMAT_SIZE_LEN];
format_size_fixed(free_buf, sizeof(free_buf), free_bytes, 'M');
int64_t free_delta_bytes = free_bytes - last_free_bytes;
char delta_sign = '+';
if (free_delta_bytes < 0) {
free_delta_bytes *= -1;
delta_sign = '-';
}
char delta_buf[MAX_FORMAT_SIZE_LEN];
format_size(delta_buf, sizeof(delta_buf), free_delta_bytes);
printf("OOM: %s free (%c%s) / %s total\n",
free_buf,
delta_sign,
delta_buf,
total_buf);
}
last_free_bytes = free_bytes;
if (lowmem) {
lowmem_callback(shortfall_bytes);
}
thread_sleep_relative(sleep_duration_ns);
}
return 0;
}
static void start_thread_locked() TA_REQ(oom_mutex) {
DEBUG_ASSERT(oom_thread == nullptr);
DEBUG_ASSERT(oom_running == false);
thread_t* t = thread_create("oom", oom_loop, nullptr, HIGH_PRIORITY);
if (t != nullptr) {
oom_running = true;
oom_thread = t;
thread_resume(t);
printf("OOM: started thread\n");
} else {
printf("OOM: failed to create thread\n");
}
}
void oom_init(bool enable, uint64_t sleep_duration_ns, size_t redline_bytes,
oom_lowmem_callback_t* lowmem_callback) {
DEBUG_ASSERT(sleep_duration_ns > 0);
DEBUG_ASSERT(redline_bytes > 0);
DEBUG_ASSERT(lowmem_callback != nullptr);
AutoLock lock(&oom_mutex);
DEBUG_ASSERT(oom_lowmem_callback == nullptr);
oom_lowmem_callback = lowmem_callback;
oom_sleep_duration_ns = sleep_duration_ns;
oom_redline_bytes = redline_bytes;
oom_printing = false;
oom_simulate_lowmem = false;
if (enable) {
start_thread_locked();
} else {
printf("OOM: thread disabled\n");
}
}
static int cmd_oom(int argc, const cmd_args* argv, uint32_t flags) {
if (argc < 2) {
printf("Not enough arguments:\n");
usage:
printf("oom start : ensure that the OOM thread is running\n");
printf("oom stop : ensure that the OOM thread is not running\n");
printf("oom info : dump OOM params/state\n");
printf("oom print : continually print free memory (toggle)\n");
printf("oom lowmem : act as if the redline was just hit (once)\n");
return -1;
}
AutoLock lock(&oom_mutex);
if (strcmp(argv[1].str, "start") == 0) {
if (!oom_running) {
start_thread_locked();
} else {
printf("OOM thread already running\n");
}
} else if (strcmp(argv[1].str, "stop") == 0) {
if (oom_running) {
printf("Stopping OOM thread...\n");
oom_running = false;
thread_t* t = oom_thread;
oom_thread = nullptr;
zx_duration_t timeout = zx_duration_mul_int64(oom_sleep_duration_ns, 4);
zx_time_t deadline = zx_time_add_duration(current_time(), timeout);
lock.release();
zx_status_t s = thread_join(t, nullptr, deadline);
if (s == ZX_OK) {
printf("OOM thread stopped.\n");
} else {
printf("Error stopping OOM thread: %d\n", s);
}
// We released the mutex; avoid executing any further.
return 0;
} else {
printf("OOM thread already stopped\n");
}
} else if (strcmp(argv[1].str, "info") == 0) {
printf("OOM info:\n");
printf(" running: %s\n", oom_running ? "true" : "false");
printf(" printing: %s\n", oom_printing ? "true" : "false");
printf(" simulating lowmem: %s\n",
oom_simulate_lowmem ? "true" : "false");
printf(" sleep duration: %" PRIu64 "ms\n",
oom_sleep_duration_ns / 1000000);
char buf[MAX_FORMAT_SIZE_LEN];
format_size_fixed(buf, sizeof(buf), oom_redline_bytes, 'M');
printf(" redline: %s (%" PRIu64 " bytes)\n", buf, oom_redline_bytes);
} else if (strcmp(argv[1].str, "print") == 0) {
oom_printing = !oom_printing;
printf("OOM print is now %s\n", oom_printing ? "on" : "off");
} else if (strcmp(argv[1].str, "lowmem") == 0) {
oom_simulate_lowmem = true;
} else {
printf("Unrecognized subcommand '%s'\n", argv[1].str);
goto usage;
}
return 0;
}
STATIC_COMMAND_START
STATIC_COMMAND("oom", "out-of-memory watcher/killer", &cmd_oom)
STATIC_COMMAND_END(oom);