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