diff --git a/meson.build b/meson.build index ecb8426ba6..435c0bbc98 100644 --- a/meson.build +++ b/meson.build @@ -2671,6 +2671,16 @@ if conf.get('ENABLE_OOMD') == 1 install_rpath : rootlibexecdir, install : true, install_dir : rootlibexecdir) + + public_programs += executable( + 'oomctl', + oomctl_sources, + include_directories : includes, + link_with : [libshared], + dependencies : [], + install_rpath : rootlibexecdir, + install : true, + install_dir : rootbindir) endif if conf.get('ENABLE_BINFMT') == 1 diff --git a/src/oom/meson.build b/src/oom/meson.build index 92c47e04c4..aa85dab947 100644 --- a/src/oom/meson.build +++ b/src/oom/meson.build @@ -1,6 +1,8 @@ # SPDX-License-Identifier: LGPL-2.1+ systemd_oomd_sources = files(''' + oomd-manager-bus.c + oomd-manager-bus.h oomd-manager.c oomd-manager.h oomd-util.c @@ -8,6 +10,10 @@ systemd_oomd_sources = files(''' oomd.c '''.split()) +oomctl_sources = files(''' + oomctl.c +'''.split()) + if conf.get('ENABLE_OOMD') == 1 tests += [ [['src/oom/test-oomd-util.c', @@ -17,6 +23,9 @@ if conf.get('ENABLE_OOMD') == 1 []] ] + install_data('org.freedesktop.oom1.conf', + install_dir : dbuspolicydir) + install_data('oomd.conf', install_dir : pkgsysconfdir) endif diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c new file mode 100644 index 0000000000..01e43d3560 --- /dev/null +++ b/src/oom/oomctl.c @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include + +#include "bus-error.h" +#include "copy.h" +#include "main-func.h" +#include "pretty-print.h" +#include "terminal-util.h" +#include "verbs.h" + +static PagerFlags arg_pager_flags = 0; + +static int help(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *link = NULL; + int r; + + (void) pager_open(arg_pager_flags); + + r = terminal_urlify_man("oomctl", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] COMMAND ...\n\n" + "%2$sManage or inspect the userspace OOM killer.%3$s\n" + "\n%4$sCommands:%5$s\n" + " dump Output the current state of systemd-oomd\n" + "\n%4$sOptions:%5$s\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + "\nSee the %6$s for details.\n" + , program_invocation_short_name + , ansi_highlight(), ansi_normal() + , ansi_underline(), ansi_normal() + , link + ); + + return 0; +} + +static int dump_state(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int fd = -1; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + + (void) pager_open(arg_pager_flags); + + r = sd_bus_call_method( + bus, + "org.freedesktop.oom1", + "/org/freedesktop/oom1", + "org.freedesktop.oom1.Manager", + "DumpByFileDescriptor", + &error, + &reply, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump context: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "h", &fd); + if (r < 0) + return bus_log_parse_error(r); + + fflush(stdout); + return copy_bytes(fd, STDOUT_FILENO, (uint64_t) -1, 0); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Invalid option passed."); + } + + return 1; +} + +static int run(int argc, char* argv[]) { + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "dump", VERB_ANY, 1, VERB_DEFAULT, dump_state }, + {} + }; + + int r; + + log_show_color(true); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/oom/oomd-manager-bus.c b/src/oom/oomd-manager-bus.c new file mode 100644 index 0000000000..67c5fbf92f --- /dev/null +++ b/src/oom/oomd-manager-bus.c @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "bus-common-errors.h" +#include "bus-polkit.h" +#include "fd-util.h" +#include "oomd-manager-bus.h" +#include "oomd-manager.h" +#include "user-util.h" + +static int bus_method_dump_by_fd(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *dump = NULL; + _cleanup_close_ int fd = -1; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = manager_get_dump_string(m, &dump); + if (r < 0) + return r; + + fd = acquire_data_fd(dump, strlen(dump), 0); + if (fd < 0) + return fd; + + return sd_bus_reply_method_return(message, "h", fd); +} + +const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("DumpByFileDescriptor", NULL, "h", bus_method_dump_by_fd, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; diff --git a/src/oom/oomd-manager-bus.h b/src/oom/oomd-manager-bus.h new file mode 100644 index 0000000000..60ccf3b373 --- /dev/null +++ b/src/oom/oomd-manager-bus.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sd-bus.h" + +typedef struct Manager Manager; + +extern const sd_bus_vtable manager_vtable[]; diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index b66ac85cc0..49b57a86a4 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ +#include "bus-log-control-api.h" +#include "bus-util.h" +#include "bus-polkit.h" #include "cgroup-util.h" #include "fd-util.h" #include "fileio.h" +#include "oomd-manager-bus.h" #include "oomd-manager.h" #include "path-util.h" @@ -396,6 +400,9 @@ void manager_free(Manager *m) { sd_event_source_unref(m->cgroup_context_event_source); sd_event_unref(m->event); + bus_verify_polkit_async_registry_free(m->polkit_registry); + sd_bus_flush_close_unref(m->bus); + hashmap_free(m->monitored_swap_cgroup_contexts); hashmap_free(m->monitored_mem_pressure_cgroup_contexts); @@ -438,6 +445,35 @@ int manager_new(Manager **ret) { return 0; } +static int manager_connect_bus(Manager *m) { + int r; + + assert(m); + assert(!m->bus); + + r = bus_open_system_watch_bind_with_description(&m->bus, "bus-api-oom"); + if (r < 0) + return log_error_errno(r, "Failed to connect to bus: %m"); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/oom1", "org.freedesktop.oom1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to add manager object vtable: %m"); + + r = bus_log_control_api_register(m->bus); + if (r < 0) + return r; + + r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.oom1", 0, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + return 0; +} + int manager_start(Manager *m, bool dry_run, int swap_used_limit, int mem_pressure_limit) { unsigned long l; int r; @@ -454,6 +490,10 @@ int manager_start(Manager *m, bool dry_run, int swap_used_limit, int mem_pressur if (r < 0) return r; + r = manager_connect_bus(m); + if (r < 0) + return r; + r = acquire_managed_oom_connect(m); if (r < 0) return r; @@ -464,3 +504,46 @@ int manager_start(Manager *m, bool dry_run, int swap_used_limit, int mem_pressur return 0; } + +int manager_get_dump_string(Manager *m, char **ret) { + _cleanup_free_ char *dump = NULL; + _cleanup_fclose_ FILE *f = NULL; + OomdCGroupContext *c; + size_t size; + char *key; + int r; + + assert(m); + assert(ret); + + f = open_memstream_unlocked(&dump, &size); + if (!f) + return -errno; + + fprintf(f, + "Dry Run: %s\n" + "Swap Used Limit: %u%%\n" + "Default Memory Pressure Limit: %lu%%\n" + "System Context:\n", + yes_no(m->dry_run), + m->swap_used_limit, + LOAD_INT(m->default_mem_pressure_limit)); + oomd_dump_system_context(&m->system_context, f, "\t"); + + fprintf(f, "Swap Monitored CGroups:\n"); + HASHMAP_FOREACH_KEY(c, key, m->monitored_swap_cgroup_contexts) + oomd_dump_swap_cgroup_context(c, f, "\t"); + + fprintf(f, "Memory Pressure Monitored CGroups:\n"); + HASHMAP_FOREACH_KEY(c, key, m->monitored_mem_pressure_cgroup_contexts) + oomd_dump_memory_pressure_cgroup_context(c, f, "\t"); + + r = fflush_and_check(f); + if (r < 0) + return r; + + f = safe_fclose(f); + + *ret = TAKE_PTR(dump); + return 0; +} diff --git a/src/oom/oomd-manager.h b/src/oom/oomd-manager.h index f546ecf4e7..b5c249799b 100644 --- a/src/oom/oomd-manager.h +++ b/src/oom/oomd-manager.h @@ -25,8 +25,11 @@ typedef struct Manager Manager; struct Manager { + sd_bus *bus; sd_event *event; + Hashmap *polkit_registry; + bool dry_run; unsigned swap_used_limit; loadavg_t default_mem_pressure_limit; @@ -51,3 +54,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); int manager_new(Manager **ret); int manager_start(Manager *m, bool dry_run, int swap_used_limit, int mem_pressure_limit); + +int manager_get_dump_string(Manager *m, char **ret); + +CONFIG_PARSER_PROTOTYPE(config_parse_oomd_default); diff --git a/src/oom/oomd-util.c b/src/oom/oomd-util.c index 4018c70b8e..6cd4ba4f93 100644 --- a/src/oom/oomd-util.c +++ b/src/oom/oomd-util.c @@ -385,3 +385,67 @@ int oomd_insert_cgroup_context(Hashmap *old_h, Hashmap *new_h, const char *path) return 0; } + +void oomd_dump_swap_cgroup_context(const OomdCGroupContext *ctx, FILE *f, const char *prefix) { + char swap[FORMAT_BYTES_MAX]; + + assert(ctx); + assert(f); + + if (!empty_or_root(ctx->path)) + fprintf(f, + "%sPath: %s\n" + "%s\tSwap Usage: %s\n", + strempty(prefix), ctx->path, + strempty(prefix), format_bytes(swap, sizeof(swap), ctx->swap_usage)); + else + fprintf(f, + "%sPath: %s\n" + "%s\tSwap Usage: (see System Context)\n", + strempty(prefix), ctx->path, + strempty(prefix)); +} + +void oomd_dump_memory_pressure_cgroup_context(const OomdCGroupContext *ctx, FILE *f, const char *prefix) { + char tbuf[FORMAT_TIMESPAN_MAX], mem_use[FORMAT_BYTES_MAX]; + char mem_min[FORMAT_BYTES_MAX], mem_low[FORMAT_BYTES_MAX]; + + assert(ctx); + assert(f); + + fprintf(f, + "%sPath: %s\n" + "%s\tMemory Pressure Limit: %lu%%\n" + "%s\tPressure: Avg10: %lu.%02lu Avg60: %lu.%02lu Avg300: %lu.%02lu Total: %s\n" + "%s\tCurrent Memory Usage: %s\n", + strempty(prefix), ctx->path, + strempty(prefix), LOAD_INT(ctx->mem_pressure_limit), + strempty(prefix), + LOAD_INT(ctx->memory_pressure.avg10), LOAD_FRAC(ctx->memory_pressure.avg10), + LOAD_INT(ctx->memory_pressure.avg60), LOAD_FRAC(ctx->memory_pressure.avg60), + LOAD_INT(ctx->memory_pressure.avg300), LOAD_FRAC(ctx->memory_pressure.avg300), + format_timespan(tbuf, sizeof(tbuf), ctx->memory_pressure.total, USEC_PER_SEC), + strempty(prefix), format_bytes(mem_use, sizeof(mem_use), ctx->current_memory_usage)); + + if (!empty_or_root(ctx->path)) + fprintf(f, + "%s\tMemory Min: %s\n" + "%s\tMemory Low: %s\n" + "%s\tPgscan: %" PRIu64 "\n", + strempty(prefix), format_bytes_cgroup_protection(mem_min, sizeof(mem_min), ctx->memory_min), + strempty(prefix), format_bytes_cgroup_protection(mem_low, sizeof(mem_low), ctx->memory_low), + strempty(prefix), ctx->pgscan); +} + +void oomd_dump_system_context(const OomdSystemContext *ctx, FILE *f, const char *prefix) { + char used[FORMAT_BYTES_MAX], total[FORMAT_BYTES_MAX]; + + assert(ctx); + assert(f); + + fprintf(f, + "%sSwap: Used: %s Total: %s\n", + strempty(prefix), + format_bytes(used, sizeof(used), ctx->swap_used), + format_bytes(total, sizeof(total), ctx->swap_total)); +} diff --git a/src/oom/oomd-util.h b/src/oom/oomd-util.h index 6d34d91cc2..cfd717a018 100644 --- a/src/oom/oomd-util.h +++ b/src/oom/oomd-util.h @@ -106,3 +106,7 @@ int oomd_system_context_acquire(const char *proc_swaps_path, OomdSystemContext * * `old_h` is used to get data used to calculate prior interval information. `old_h` can be NULL in which case there * was no prior data to reference. */ int oomd_insert_cgroup_context(Hashmap *old_h, Hashmap *new_h, const char *path); + +void oomd_dump_swap_cgroup_context(const OomdCGroupContext *ctx, FILE *f, const char *prefix); +void oomd_dump_memory_pressure_cgroup_context(const OomdCGroupContext *ctx, FILE *f, const char *prefix); +void oomd_dump_system_context(const OomdSystemContext *ctx, FILE *f, const char *prefix); diff --git a/src/oom/org.freedesktop.oom1.conf b/src/oom/org.freedesktop.oom1.conf new file mode 100644 index 0000000000..48b526f0aa --- /dev/null +++ b/src/oom/org.freedesktop.oom1.conf @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +