| // 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); |