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
24 using fbl::AutoLock;
25
26 // Guards the oom_* values below.
27 static fbl::Mutex oom_mutex;
28
29 // Function to call when we hit a low-memory condition.
30 static oom_lowmem_callback_t* oom_lowmem_callback TA_GUARDED(oom_mutex);
31
32 // The thread, if it's running; nullptr otherwise.
33 static thread_t* oom_thread TA_GUARDED(oom_mutex);
34
35 // True if the thread should keep running.
36 static bool oom_running TA_GUARDED(oom_mutex);
37
38 // How long the OOM thread sleeps between checks.
39 static 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.
42 static uint64_t oom_redline_bytes TA_GUARDED(oom_mutex);
43
44 // True if the thread should print the current free value when it runs.
45 static bool oom_printing TA_GUARDED(oom_mutex);
46
47 // True if the thread should simulate a low-memory condition on its next loop.
48 static bool oom_simulate_lowmem TA_GUARDED(oom_mutex);
49
oom_loop(void * arg)50 static 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
start_thread_locked()119 static 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
oom_init(bool enable,uint64_t sleep_duration_ns,size_t redline_bytes,oom_lowmem_callback_t * lowmem_callback)133 void 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
cmd_oom(int argc,const cmd_args * argv,uint32_t flags)153 static 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
217 STATIC_COMMAND_START
218 STATIC_COMMAND("oom", "out-of-memory watcher/killer", &cmd_oom)
219 STATIC_COMMAND_END(oom);
220