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