1// Copyright 2017 The Fuchsia Authors 2// 3// Use of this source code is governed by a MIT-style 4// license that can be found in the LICENSE file or at 5// https://opensource.org/licenses/MIT 6 7#include <lib/oom.h> 8 9#include <fbl/auto_lock.h> 10#include <fbl/mutex.h> 11#include <kernel/thread.h> 12#include <lib/console.h> 13#include <platform.h> 14#include <pretty/sizes.h> 15#include <vm/pmm.h> 16#include <zircon/errors.h> 17#include <zircon/time.h> 18#include <zircon/types.h> 19 20#include <inttypes.h> 21#include <string.h> 22#include <sys/types.h> 23 24using fbl::AutoLock; 25 26// Guards the oom_* values below. 27static fbl::Mutex oom_mutex; 28 29// Function to call when we hit a low-memory condition. 30static oom_lowmem_callback_t* oom_lowmem_callback TA_GUARDED(oom_mutex); 31 32// The thread, if it's running; nullptr otherwise. 33static thread_t* oom_thread TA_GUARDED(oom_mutex); 34 35// True if the thread should keep running. 36static bool oom_running TA_GUARDED(oom_mutex); 37 38// How long the OOM thread sleeps between checks. 39static uint64_t oom_sleep_duration_ns TA_GUARDED(oom_mutex); 40 41// If the PMM has fewer than this many bytes free, start killing processes. 42static uint64_t oom_redline_bytes TA_GUARDED(oom_mutex); 43 44// True if the thread should print the current free value when it runs. 45static bool oom_printing TA_GUARDED(oom_mutex); 46 47// True if the thread should simulate a low-memory condition on its next loop. 48static bool oom_simulate_lowmem TA_GUARDED(oom_mutex); 49 50static int oom_loop(void* arg) { 51 const size_t total_bytes = pmm_count_total_bytes(); 52 char total_buf[MAX_FORMAT_SIZE_LEN]; 53 format_size_fixed(total_buf, sizeof(total_buf), total_bytes, 'M'); 54 55 size_t last_free_bytes = total_bytes; 56 while (true) { 57 const size_t free_bytes = pmm_count_free_pages() * PAGE_SIZE; 58 59 bool lowmem = false; 60 bool printing = false; 61 size_t shortfall_bytes = 0; 62 oom_lowmem_callback_t* lowmem_callback = nullptr; 63 uint64_t sleep_duration_ns = 0; 64 { 65 AutoLock lock(&oom_mutex); 66 if (!oom_running) { 67 break; 68 } 69 if (oom_simulate_lowmem) { 70 printf("OOM: simulating low-memory situation\n"); 71 } 72 lowmem = free_bytes < oom_redline_bytes || oom_simulate_lowmem; 73 if (lowmem) { 74 shortfall_bytes = 75 oom_simulate_lowmem 76 ? 512 * 1024 77 : oom_redline_bytes - free_bytes; 78 } 79 oom_simulate_lowmem = false; 80 81 printing = 82 lowmem || (oom_printing && free_bytes != last_free_bytes); 83 lowmem_callback = oom_lowmem_callback; 84 DEBUG_ASSERT(lowmem_callback != nullptr); 85 sleep_duration_ns = oom_sleep_duration_ns; 86 } 87 88 if (printing) { 89 char free_buf[MAX_FORMAT_SIZE_LEN]; 90 format_size_fixed(free_buf, sizeof(free_buf), free_bytes, 'M'); 91 92 int64_t free_delta_bytes = free_bytes - last_free_bytes; 93 char delta_sign = '+'; 94 if (free_delta_bytes < 0) { 95 free_delta_bytes *= -1; 96 delta_sign = '-'; 97 } 98 char delta_buf[MAX_FORMAT_SIZE_LEN]; 99 format_size(delta_buf, sizeof(delta_buf), free_delta_bytes); 100 101 printf("OOM: %s free (%c%s) / %s total\n", 102 free_buf, 103 delta_sign, 104 delta_buf, 105 total_buf); 106 } 107 last_free_bytes = free_bytes; 108 109 if (lowmem) { 110 lowmem_callback(shortfall_bytes); 111 } 112 113 thread_sleep_relative(sleep_duration_ns); 114 } 115 116 return 0; 117} 118 119static void start_thread_locked() TA_REQ(oom_mutex) { 120 DEBUG_ASSERT(oom_thread == nullptr); 121 DEBUG_ASSERT(oom_running == false); 122 thread_t* t = thread_create("oom", oom_loop, nullptr, HIGH_PRIORITY); 123 if (t != nullptr) { 124 oom_running = true; 125 oom_thread = t; 126 thread_resume(t); 127 printf("OOM: started thread\n"); 128 } else { 129 printf("OOM: failed to create thread\n"); 130 } 131} 132 133void oom_init(bool enable, uint64_t sleep_duration_ns, size_t redline_bytes, 134 oom_lowmem_callback_t* lowmem_callback) { 135 DEBUG_ASSERT(sleep_duration_ns > 0); 136 DEBUG_ASSERT(redline_bytes > 0); 137 DEBUG_ASSERT(lowmem_callback != nullptr); 138 139 AutoLock lock(&oom_mutex); 140 DEBUG_ASSERT(oom_lowmem_callback == nullptr); 141 oom_lowmem_callback = lowmem_callback; 142 oom_sleep_duration_ns = sleep_duration_ns; 143 oom_redline_bytes = redline_bytes; 144 oom_printing = false; 145 oom_simulate_lowmem = false; 146 if (enable) { 147 start_thread_locked(); 148 } else { 149 printf("OOM: thread disabled\n"); 150 } 151} 152 153static int cmd_oom(int argc, const cmd_args* argv, uint32_t flags) { 154 if (argc < 2) { 155 printf("Not enough arguments:\n"); 156 usage: 157 printf("oom start : ensure that the OOM thread is running\n"); 158 printf("oom stop : ensure that the OOM thread is not running\n"); 159 printf("oom info : dump OOM params/state\n"); 160 printf("oom print : continually print free memory (toggle)\n"); 161 printf("oom lowmem : act as if the redline was just hit (once)\n"); 162 return -1; 163 } 164 165 AutoLock lock(&oom_mutex); 166 if (strcmp(argv[1].str, "start") == 0) { 167 if (!oom_running) { 168 start_thread_locked(); 169 } else { 170 printf("OOM thread already running\n"); 171 } 172 } else if (strcmp(argv[1].str, "stop") == 0) { 173 if (oom_running) { 174 printf("Stopping OOM thread...\n"); 175 oom_running = false; 176 thread_t* t = oom_thread; 177 oom_thread = nullptr; 178 zx_duration_t timeout = zx_duration_mul_int64(oom_sleep_duration_ns, 4); 179 zx_time_t deadline = zx_time_add_duration(current_time(), timeout); 180 lock.release(); 181 zx_status_t s = thread_join(t, nullptr, deadline); 182 if (s == ZX_OK) { 183 printf("OOM thread stopped.\n"); 184 } else { 185 printf("Error stopping OOM thread: %d\n", s); 186 } 187 // We released the mutex; avoid executing any further. 188 return 0; 189 } else { 190 printf("OOM thread already stopped\n"); 191 } 192 } else if (strcmp(argv[1].str, "info") == 0) { 193 printf("OOM info:\n"); 194 printf(" running: %s\n", oom_running ? "true" : "false"); 195 printf(" printing: %s\n", oom_printing ? "true" : "false"); 196 printf(" simulating lowmem: %s\n", 197 oom_simulate_lowmem ? "true" : "false"); 198 199 printf(" sleep duration: %" PRIu64 "ms\n", 200 oom_sleep_duration_ns / 1000000); 201 202 char buf[MAX_FORMAT_SIZE_LEN]; 203 format_size_fixed(buf, sizeof(buf), oom_redline_bytes, 'M'); 204 printf(" redline: %s (%" PRIu64 " bytes)\n", buf, oom_redline_bytes); 205 } else if (strcmp(argv[1].str, "print") == 0) { 206 oom_printing = !oom_printing; 207 printf("OOM print is now %s\n", oom_printing ? "on" : "off"); 208 } else if (strcmp(argv[1].str, "lowmem") == 0) { 209 oom_simulate_lowmem = true; 210 } else { 211 printf("Unrecognized subcommand '%s'\n", argv[1].str); 212 goto usage; 213 } 214 return 0; 215} 216 217STATIC_COMMAND_START 218STATIC_COMMAND("oom", "out-of-memory watcher/killer", &cmd_oom) 219STATIC_COMMAND_END(oom); 220