From b3bb4fefde2e116b663c05c29ada8496b97c3b0b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sat, 7 Mar 2026 21:25:09 +0100 Subject: [PATCH] test-mempress: Support unprivileged operation --- man/rules/meson.build | 5 +- man/sd_event_add_memory_pressure.xml | 86 ++++- src/basic/psi-util.c | 21 ++ src/basic/psi-util.h | 26 +- src/core/exec-invoke.c | 8 +- src/libsystemd/libsystemd.sym | 3 + src/libsystemd/sd-event/event-source.h | 3 +- src/libsystemd/sd-event/sd-event.c | 334 +++++++++++------- src/systemd/sd-event.h | 3 + src/test/meson.build | 2 +- src/test/{test-mempress.c => test-pressure.c} | 214 +++++++++-- 11 files changed, 523 insertions(+), 182 deletions(-) rename src/test/{test-mempress.c => test-pressure.c} (58%) diff --git a/man/rules/meson.build b/man/rules/meson.build index aa2653ce0d..81e7ef4f88 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -608,7 +608,10 @@ manpages = [ ''], ['sd_event_add_memory_pressure', '3', - ['sd_event_source_set_memory_pressure_period', + ['sd_event_add_cpu_pressure', + 'sd_event_source_set_cpu_pressure_period', + 'sd_event_source_set_cpu_pressure_type', + 'sd_event_source_set_memory_pressure_period', 'sd_event_source_set_memory_pressure_type', 'sd_event_trim_memory'], ''], diff --git a/man/sd_event_add_memory_pressure.xml b/man/sd_event_add_memory_pressure.xml index b112855f06..1e6b734738 100644 --- a/man/sd_event_add_memory_pressure.xml +++ b/man/sd_event_add_memory_pressure.xml @@ -21,7 +21,11 @@ sd_event_source_set_memory_pressure_period sd_event_trim_memory - Add and configure an event source run as result of memory pressure + sd_event_add_cpu_pressure + sd_event_source_set_cpu_pressure_type + sd_event_source_set_cpu_pressure_period + + Add and configure an event source run as result of memory or CPU pressure @@ -51,6 +55,27 @@ uint64_t window_usec + + int sd_event_add_cpu_pressure + sd_event *event + sd_event_source **ret_source + sd_event_handler_t handler + void *userdata + + + + int sd_event_source_set_cpu_pressure_type + sd_event_source *source + const char *type + + + + int sd_event_source_set_cpu_pressure_period + sd_event_source *source + uint64_t threshold_usec + uint64_t window_usec + + int sd_event_trim_memory void @@ -62,12 +87,14 @@ Description sd_event_add_memory_pressure() adds a new event source that is triggered - whenever memory pressure is seen. This functionality is built around the Linux kernel's sd_event_add_cpu_pressure() adds a new event source that is triggered whenever CPU + pressure is seen. This functionality is built around the Linux kernel's Pressure Stall Information (PSI) logic. - Expects an event loop object as first parameter, and returns the allocated event source object in - the second parameter, on success. The handler parameter is a function to call when - memory pressure is seen, or NULL. The handler function will be passed the + Both functions expect an event loop object as first parameter, and return the allocated event source + object in the second parameter, on success. The handler parameter is a function to + call when pressure is seen, or NULL. The handler function will be passed the userdata pointer, which may be chosen freely by the caller. The handler may return negative to signal an error (see below), other return values are ignored. If handler is NULL, a default handler that compacts allocation @@ -83,12 +110,13 @@ sd_event_source_set_enabled3 with SD_EVENT_OFF. - If the second parameter of sd_event_add_memory_pressure() is + If the second parameter of sd_event_add_memory_pressure() or + sd_event_add_cpu_pressure() is NULL no reference to the event source object is returned. In this case, the event source is considered "floating", and will be destroyed implicitly when the event loop itself is destroyed. - The event source will fire according to the following logic: + The memory pressure event source will fire according to the following logic: If the @@ -111,6 +139,13 @@ /proc/pressure/memory is watched instead. + The CPU pressure event source follows the same logic, but uses the + $CPU_PRESSURE_WATCH/$CPU_PRESSURE_WRITE environment variables, + the cpu.pressure cgroup file, and the system-wide PSI interface file + /proc/pressure/cpu instead. Note that /proc/pressure/cpu only + provides the some line, not the full line, so only + some is valid when watching at the system level. + Or in other words: preferably any explicit configuration passed in by an invoking service manager (or similar) is used as notification source, before falling back to local notifications of the service, and finally to global notifications of the system. @@ -143,7 +178,7 @@ The sd_event_source_set_memory_pressure_type() and sd_event_source_set_memory_pressure_period() functions can be used to fine-tune the - PSI parameters for pressure notifications. The former takes either some, + PSI parameters for memory pressure notifications. The former takes either some, full as second parameter, the latter takes threshold and period times in microseconds as parameters. For details about these three parameters see the PSI documentation. Note that these two calls must be invoked immediately after allocating the event source, as they must be configured before @@ -152,6 +187,16 @@ environment variables (or in other words: configuration supplied by a service manager wins over internal settings). + Similarly, sd_event_source_set_cpu_pressure_type() and + sd_event_source_set_cpu_pressure_period() can be used to fine-tune the PSI + parameters for CPU pressure notifications. They work identically to their memory pressure counterparts. + The type parameter takes either some or full, and the period + function takes threshold and period times in microseconds. The same constraints apply: these calls must + be invoked immediately after allocating the event source, and will fail if CPU pressure parameterization + has been passed in via the + $CPU_PRESSURE_WATCH/$CPU_PRESSURE_WRITE environment + variables. + The sd_event_trim_memory() function releases various internal allocation caches maintained by libsystemd and then invokes glibc's malloc_trim3. This @@ -197,8 +242,9 @@ -EHOSTDOWN - The $MEMORY_PRESSURE_WATCH variable has been set to the literal - string /dev/null, in order to explicitly disable memory pressure + The $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH variable has been set to the literal + string /dev/null, in order to explicitly disable pressure handling. @@ -207,7 +253,8 @@ -EBADMSG - The $MEMORY_PRESSURE_WATCH variable has been set to an invalid + The $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH variable has been set to an invalid string, for example a relative rather than an absolute path. @@ -216,7 +263,8 @@ -ENOTTY - The $MEMORY_PRESSURE_WATCH variable points to a regular file + The $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH variable points to a regular file outside of the procfs or cgroupfs file systems. @@ -225,8 +273,9 @@ -EOPNOTSUPP - No configuration via $MEMORY_PRESSURE_WATCH has been specified - and the local kernel does not support the PSI interface. + No configuration via $MEMORY_PRESSURE_WATCH or + $CPU_PRESSURE_WATCH has been specified and the local kernel does not support the + PSI interface. @@ -234,8 +283,10 @@ -EBUSY - This is returned by sd_event_source_set_memory_pressure_type() - and sd_event_source_set_memory_pressure_period() if invoked on event sources + This is returned by sd_event_source_set_memory_pressure_type(), + sd_event_source_set_memory_pressure_period(), + sd_event_source_set_cpu_pressure_type(), + and sd_event_source_set_cpu_pressure_period() if invoked on event sources at a time later than immediately after allocating them. @@ -277,6 +328,9 @@ sd_event_source_set_memory_pressure_type(), sd_event_source_set_memory_pressure_period(), and sd_event_trim_memory() were added in version 254. + sd_event_add_cpu_pressure(), + sd_event_source_set_cpu_pressure_type(), and + sd_event_source_set_cpu_pressure_period() were added in version 261. diff --git a/src/basic/psi-util.c b/src/basic/psi-util.c index df1ccbc1b2..cf05485dc7 100644 --- a/src/basic/psi-util.c +++ b/src/basic/psi-util.c @@ -10,6 +10,7 @@ #include "fileio.h" #include "parse-util.h" #include "psi-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" @@ -104,6 +105,26 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure return 0; } +const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = { + .name = "memory", + .env_watch = "MEMORY_PRESSURE_WATCH", + .env_write = "MEMORY_PRESSURE_WRITE", + }, + [PRESSURE_CPU] = { + .name = "cpu", + .env_watch = "CPU_PRESSURE_WATCH", + .env_write = "CPU_PRESSURE_WRITE", + }, +}; + +static const char* const pressure_resource_table[_PRESSURE_RESOURCE_MAX] = { + [PRESSURE_MEMORY] = "memory", + [PRESSURE_CPU] = "cpu", +}; + +DEFINE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); + int is_pressure_supported(void) { static thread_local int cached = -1; int r; diff --git a/src/basic/psi-util.h b/src/basic/psi-util.h index f5e79960a8..aed74ef742 100644 --- a/src/basic/psi-util.h +++ b/src/basic/psi-util.h @@ -9,6 +9,13 @@ typedef enum PressureType { PRESSURE_TYPE_FULL, } PressureType; +typedef enum PressureResource { + PRESSURE_MEMORY, + PRESSURE_CPU, + _PRESSURE_RESOURCE_MAX, + _PRESSURE_RESOURCE_INVALID = -EINVAL, +} PressureResource; + /* Averages are stored in fixed-point with 11 bit fractions */ typedef struct ResourcePressure { loadavg_t avg10; @@ -27,7 +34,18 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure /* Was the kernel compiled with CONFIG_PSI=y? 1 if yes, 0 if not, negative on error. */ int is_pressure_supported(void); -/* Default parameters for memory pressure watch logic in sd-event and PID 1 */ -#define MEMORY_PRESSURE_DEFAULT_TYPE "some" -#define MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC (200 * USEC_PER_MSEC) -#define MEMORY_PRESSURE_DEFAULT_WINDOW_USEC (2 * USEC_PER_SEC) +/* Metadata for each pressure resource type, for use in sd-event and PID 1 */ +typedef struct PressureResourceInfo { + const char *name; /* "memory", "cpu", "io" */ + const char *env_watch; /* "MEMORY_PRESSURE_WATCH", etc. */ + const char *env_write; /* "MEMORY_PRESSURE_WRITE", etc. */ +} PressureResourceInfo; + +extern const PressureResourceInfo pressure_resource_info[_PRESSURE_RESOURCE_MAX]; + +DECLARE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource); + +/* Default parameters for pressure watch logic in sd-event and PID 1 */ +#define PRESSURE_DEFAULT_TYPE "some" +#define PRESSURE_DEFAULT_THRESHOLD_USEC (200 * USEC_PER_MSEC) +#define PRESSURE_DEFAULT_WINDOW_USEC (2 * USEC_PER_SEC) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index b91a964cdd..7500888c41 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2224,10 +2224,10 @@ static int build_environment( _cleanup_free_ char *b = NULL, *x = NULL; if (asprintf(&b, "%s " USEC_FMT " " USEC_FMT, - MEMORY_PRESSURE_DEFAULT_TYPE, - cgroup_context->memory_pressure_threshold_usec == USEC_INFINITY ? MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC : - CLAMP(cgroup_context->memory_pressure_threshold_usec, 1U, MEMORY_PRESSURE_DEFAULT_WINDOW_USEC), - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC) < 0) + PRESSURE_DEFAULT_TYPE, + cgroup_context->memory_pressure_threshold_usec == USEC_INFINITY ? PRESSURE_DEFAULT_THRESHOLD_USEC : + CLAMP(cgroup_context->memory_pressure_threshold_usec, 1U, PRESSURE_DEFAULT_WINDOW_USEC), + PRESSURE_DEFAULT_WINDOW_USEC) < 0) return -ENOMEM; if (base64mem(b, strlen(b) + 1, &x) < 0) diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 619bcf820c..5f5eca6083 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1096,4 +1096,7 @@ global: sd_varlink_call_and_upgrade; sd_varlink_reply_and_upgrade; sd_varlink_set_sentinel; + sd_event_add_cpu_pressure; + sd_event_source_set_cpu_pressure_type; + sd_event_source_set_cpu_pressure_period; } LIBSYSTEMD_260; diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h index e4dc456fae..c7d5ba166d 100644 --- a/src/libsystemd/sd-event/event-source.h +++ b/src/libsystemd/sd-event/event-source.h @@ -26,6 +26,7 @@ typedef enum EventSourceType { SOURCE_WATCHDOG, SOURCE_INOTIFY, SOURCE_MEMORY_PRESSURE, + SOURCE_CPU_PRESSURE, _SOURCE_EVENT_SOURCE_TYPE_MAX, _SOURCE_EVENT_SOURCE_TYPE_INVALID = -EINVAL, } EventSourceType; @@ -144,7 +145,7 @@ struct sd_event_source { size_t write_buffer_size; uint32_t events, revents; LIST_FIELDS(sd_event_source, write_list); - } memory_pressure; + } pressure; }; }; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 19feff5668..4b539a35cf 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -76,6 +76,7 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] [SOURCE_WATCHDOG] = "watchdog", [SOURCE_INOTIFY] = "inotify", [SOURCE_MEMORY_PRESSURE] = "memory-pressure", + [SOURCE_CPU_PRESSURE] = "cpu-pressure", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); @@ -99,7 +100,8 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); SOURCE_SIGNAL, \ SOURCE_DEFER, \ SOURCE_INOTIFY, \ - SOURCE_MEMORY_PRESSURE) + SOURCE_MEMORY_PRESSURE, \ + SOURCE_CPU_PRESSURE) /* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put(). * Time sources and ratelimited sources can be passed, so effectively this is the same as the @@ -144,8 +146,8 @@ struct sd_event { /* A list of inotify objects that already have events buffered which aren't processed yet */ LIST_HEAD(InotifyData, buffered_inotify_data_list); - /* A list of memory pressure event sources that still need their subscription string written */ - LIST_HEAD(sd_event_source, memory_pressure_write_list); + /* A list of pressure event sources that still need their subscription string written */ + LIST_HEAD(sd_event_source, pressure_write_list); uint64_t origin_id; @@ -564,63 +566,65 @@ static int source_child_pidfd_register(sd_event_source *s, int enabled) { return 0; } -static void source_memory_pressure_unregister(sd_event_source *s) { +#define EVENT_SOURCE_IS_PRESSURE(s) IN_SET((s)->type, SOURCE_MEMORY_PRESSURE, SOURCE_CPU_PRESSURE) + +static void source_pressure_unregister(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); if (event_origin_changed(s->event)) return; - if (!s->memory_pressure.registered) + if (!s->pressure.registered) return; - if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->memory_pressure.fd, NULL) < 0) + if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->pressure.fd, NULL) < 0) log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m", strna(s->description), event_source_type_to_string(s->type)); - s->memory_pressure.registered = false; + s->pressure.registered = false; } -static int source_memory_pressure_register(sd_event_source *s, int enabled) { +static int source_pressure_register(sd_event_source *s, int enabled) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); assert(enabled != SD_EVENT_OFF); struct epoll_event ev = { - .events = s->memory_pressure.write_buffer_size > 0 ? EPOLLOUT : - (s->memory_pressure.events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0)), + .events = s->pressure.write_buffer_size > 0 ? EPOLLOUT : + (s->pressure.events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0)), .data.ptr = s, }; if (epoll_ctl(s->event->epoll_fd, - s->memory_pressure.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, - s->memory_pressure.fd, &ev) < 0) + s->pressure.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, + s->pressure.fd, &ev) < 0) return -errno; - s->memory_pressure.registered = true; + s->pressure.registered = true; return 0; } -static void source_memory_pressure_add_to_write_list(sd_event_source *s) { +static void source_pressure_add_to_write_list(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - if (s->memory_pressure.in_write_list) + if (s->pressure.in_write_list) return; - LIST_PREPEND(memory_pressure.write_list, s->event->memory_pressure_write_list, s); - s->memory_pressure.in_write_list = true; + LIST_PREPEND(pressure.write_list, s->event->pressure_write_list, s); + s->pressure.in_write_list = true; } -static void source_memory_pressure_remove_from_write_list(sd_event_source *s) { +static void source_pressure_remove_from_write_list(sd_event_source *s) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - if (!s->memory_pressure.in_write_list) + if (!s->pressure.in_write_list) return; - LIST_REMOVE(memory_pressure.write_list, s->event->memory_pressure_write_list, s); - s->memory_pressure.in_write_list = false; + LIST_REMOVE(pressure.write_list, s->event->pressure_write_list, s); + s->pressure.in_write_list = false; } static clockid_t event_source_type_to_clock(EventSourceType t) { @@ -1047,8 +1051,9 @@ static void source_disconnect(sd_event_source *s) { } case SOURCE_MEMORY_PRESSURE: - source_memory_pressure_remove_from_write_list(s); - source_memory_pressure_unregister(s); + case SOURCE_CPU_PRESSURE: + source_pressure_remove_from_write_list(s); + source_pressure_unregister(s); break; default: @@ -1111,9 +1116,9 @@ static sd_event_source* source_free(sd_event_source *s) { s->child.pidfd = safe_close(s->child.pidfd); } - if (s->type == SOURCE_MEMORY_PRESSURE) { - s->memory_pressure.fd = safe_close(s->memory_pressure.fd); - s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer); + if (EVENT_SOURCE_IS_PRESSURE(s)) { + s->pressure.fd = safe_close(s->pressure.fd); + s->pressure.write_buffer = mfree(s->pressure.write_buffer); } if (s->destroy_callback) @@ -1191,7 +1196,8 @@ static sd_event_source* source_new(sd_event *e, bool floating, EventSourceType t [SOURCE_POST] = endoffsetof_field(sd_event_source, post), [SOURCE_EXIT] = endoffsetof_field(sd_event_source, exit), [SOURCE_INOTIFY] = endoffsetof_field(sd_event_source, inotify), - [SOURCE_MEMORY_PRESSURE] = endoffsetof_field(sd_event_source, memory_pressure), + [SOURCE_MEMORY_PRESSURE] = endoffsetof_field(sd_event_source, pressure), + [SOURCE_CPU_PRESSURE] = endoffsetof_field(sd_event_source, pressure), }; sd_event_source *s; @@ -1917,17 +1923,21 @@ static int memory_pressure_callback(sd_event_source *s, void *userdata) { return 0; } -_public_ int sd_event_add_memory_pressure( +static int event_add_pressure( sd_event *e, sd_event_source **ret, sd_event_handler_t callback, - void *userdata) { + void *userdata, + EventSourceType type, + sd_event_handler_t default_callback, + PressureResource resource) { _cleanup_free_ char *w = NULL; _cleanup_(source_freep) sd_event_source *s = NULL; _cleanup_close_ int path_fd = -EBADF, fd = -EBADF; _cleanup_free_ void *write_buffer = NULL; - const char *watch, *watch_fallback = NULL, *env; + _cleanup_free_ char *watch_fallback = NULL; + const char *watch, *env; size_t write_buffer_size = 0; struct stat st; uint32_t events; @@ -1939,32 +1949,35 @@ _public_ int sd_event_add_memory_pressure( assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_origin_changed(e), -ECHILD); - if (!callback) - callback = memory_pressure_callback; + assert(resource >= 0 && resource < _PRESSURE_RESOURCE_MAX); + const PressureResourceInfo *info = &pressure_resource_info[resource]; - s = source_new(e, !ret, SOURCE_MEMORY_PRESSURE); + if (!callback) + callback = default_callback; + + s = source_new(e, !ret, type); if (!s) return -ENOMEM; s->wakeup = WAKEUP_EVENT_SOURCE; - s->memory_pressure.callback = callback; + s->pressure.callback = callback; s->userdata = userdata; s->enabled = SD_EVENT_ON; - s->memory_pressure.fd = -EBADF; + s->pressure.fd = -EBADF; - env = secure_getenv("MEMORY_PRESSURE_WATCH"); + env = secure_getenv(info->env_watch); if (env) { if (isempty(env) || path_equal(env, "/dev/null")) return log_debug_errno(SYNTHETIC_ERRNO(EHOSTDOWN), - "Memory pressure logic is explicitly disabled via $MEMORY_PRESSURE_WATCH."); + "Pressure logic is explicitly disabled via $%s.", info->env_watch); if (!path_is_absolute(env) || !path_is_normalized(env)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "$MEMORY_PRESSURE_WATCH set to invalid path: %s", env); + "$%s set to invalid path: %s", info->env_watch, env); watch = env; - env = secure_getenv("MEMORY_PRESSURE_WRITE"); + env = secure_getenv(info->env_write); if (env) { r = unbase64mem(env, &write_buffer, &write_buffer_size); if (r < 0) @@ -1980,8 +1993,8 @@ _public_ int sd_event_add_memory_pressure( if (r == 0) return -EOPNOTSUPP; - /* By default we want to watch memory pressure on the local cgroup, but we'll fall back on - * the system wide pressure if for some reason we cannot (which could be: memory controller + /* By default we want to watch pressure on the local cgroup, but we'll fall back on + * the system wide pressure if for some reason we cannot (which could be: controller * not delegated to us, or PSI simply not available in the kernel). */ _cleanup_free_ char *cg = NULL; @@ -1989,12 +2002,19 @@ _public_ int sd_event_add_memory_pressure( if (r < 0) return r; - w = path_join("/sys/fs/cgroup", cg, "memory.pressure"); + _cleanup_free_ char *cgroup_file = strjoin(info->name, ".pressure"); + if (!cgroup_file) + return -ENOMEM; + + w = path_join("/sys/fs/cgroup", cg, cgroup_file); if (!w) return -ENOMEM; watch = w; - watch_fallback = "/proc/pressure/memory"; + + watch_fallback = strjoin("/proc/pressure/", info->name); + if (!watch_fallback) + return -ENOMEM; /* Android uses three levels in its userspace low memory killer logic: * some 70000 1000000 @@ -2011,9 +2031,9 @@ _public_ int sd_event_add_memory_pressure( * kernel will allow us to do unprivileged, also in the future. */ if (asprintf((char**) &write_buffer, "%s " USEC_FMT " " USEC_FMT, - MEMORY_PRESSURE_DEFAULT_TYPE, - MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC, - MEMORY_PRESSURE_DEFAULT_WINDOW_USEC) < 0) + PRESSURE_DEFAULT_TYPE, + PRESSURE_DEFAULT_THRESHOLD_USEC, + PRESSURE_DEFAULT_WINDOW_USEC) < 0) return -ENOMEM; write_buffer_size = strlen(write_buffer) + 1; @@ -2080,24 +2100,24 @@ _public_ int sd_event_add_memory_pressure( else return -EBADF; - s->memory_pressure.fd = TAKE_FD(fd); - s->memory_pressure.write_buffer = TAKE_PTR(write_buffer); - s->memory_pressure.write_buffer_size = write_buffer_size; - s->memory_pressure.events = events; - s->memory_pressure.locked = locked; + s->pressure.fd = TAKE_FD(fd); + s->pressure.write_buffer = TAKE_PTR(write_buffer); + s->pressure.write_buffer_size = write_buffer_size; + s->pressure.events = events; + s->pressure.locked = locked; /* So here's the thing: if we are talking to PSI we need to write the watch string before adding the * fd to epoll (if we ignore this, then the watch won't work). Hence we'll not actually register the - * fd with the epoll right-away. Instead, we just add the event source to a list of memory pressure - * event sources on which writes must be executed before the first event loop iteration is - * executed. (We could also write the data here, right away, but we want to give the caller the - * freedom to call sd_event_source_set_memory_pressure_type() and - * sd_event_source_set_memory_pressure_rate() before we write it. */ + * fd with the epoll right-away. Instead, we just add the event source to a list of pressure event + * sources on which writes must be executed before the first event loop iteration is executed. (We + * could also write the data here, right away, but we want to give the caller the freedom to call + * sd_event_source_set_{memory,cpu}_pressure_type() and + * sd_event_source_set_{memory,cpu}_pressure_period() before we write it. */ - if (s->memory_pressure.write_buffer_size > 0) - source_memory_pressure_add_to_write_list(s); + if (s->pressure.write_buffer_size > 0) + source_pressure_add_to_write_list(s); else { - r = source_memory_pressure_register(s, s->enabled); + r = source_pressure_register(s, s->enabled); if (r < 0) return r; } @@ -2109,6 +2129,38 @@ _public_ int sd_event_add_memory_pressure( return 0; } +_public_ int sd_event_add_memory_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_MEMORY_PRESSURE, + memory_pressure_callback, + PRESSURE_MEMORY); +} + +static int cpu_pressure_callback(sd_event_source *s, void *userdata) { + assert(s); + + return 0; +} + +_public_ int sd_event_add_cpu_pressure( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + return event_add_pressure( + e, ret, callback, userdata, + SOURCE_CPU_PRESSURE, + cpu_pressure_callback, + PRESSURE_CPU); +} + static void event_free_inotify_data(sd_event *e, InotifyData *d) { assert(e); @@ -2910,7 +2962,8 @@ static int event_source_offline( break; case SOURCE_MEMORY_PRESSURE: - source_memory_pressure_unregister(s); + case SOURCE_CPU_PRESSURE: + source_pressure_unregister(s); break; case SOURCE_TIME_REALTIME: @@ -3001,10 +3054,11 @@ static int event_source_online( break; case SOURCE_MEMORY_PRESSURE: - /* As documented in sd_event_add_memory_pressure(), we can only register the PSI fd with - * epoll after writing the watch string. */ - if (s->memory_pressure.write_buffer_size == 0) { - r = source_memory_pressure_register(s, enabled); + case SOURCE_CPU_PRESSURE: + /* As documented in sd_event_add_{memory,cpu,io}_pressure(), we can only register the PSI fd + * with epoll after writing the watch string. */ + if (s->pressure.write_buffer_size == 0) { + r = source_pressure_register(s, enabled); if (r < 0) return r; } @@ -3986,30 +4040,30 @@ static int process_inotify(sd_event *e) { return done; } -static int process_memory_pressure(sd_event_source *s, uint32_t revents) { +static int process_pressure(sd_event_source *s, uint32_t revents) { assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); if (s->pending) - s->memory_pressure.revents |= revents; + s->pressure.revents |= revents; else - s->memory_pressure.revents = revents; + s->pressure.revents = revents; return source_set_pending(s, true); } -static int source_memory_pressure_write(sd_event_source *s) { +static int source_pressure_write(sd_event_source *s) { ssize_t n; int r; assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); /* once we start writing, the buffer is locked, we allow no further changes. */ - s->memory_pressure.locked = true; + s->pressure.locked = true; - if (s->memory_pressure.write_buffer_size > 0) { - n = write(s->memory_pressure.fd, s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size); + if (s->pressure.write_buffer_size > 0) { + n = write(s->pressure.fd, s->pressure.write_buffer, s->pressure.write_buffer_size); if (n < 0) { if (!ERRNO_IS_TRANSIENT(errno)) { /* If kernel is built with CONFIG_PSI_DEFAULT_DISABLED it will expose PSI @@ -4018,7 +4072,7 @@ static int source_memory_pressure_write(sd_event_source *s) { * so late. Let's make the best of it, and turn off the event source like we * do for failed event source handlers. */ - log_debug_errno(errno, "Writing memory pressure settings to kernel failed, disabling memory pressure event source: %m"); + log_debug_errno(errno, "Writing pressure settings to kernel failed, disabling pressure event source: %m"); assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); return 0; } @@ -4030,41 +4084,41 @@ static int source_memory_pressure_write(sd_event_source *s) { assert(n >= 0); - if ((size_t) n == s->memory_pressure.write_buffer_size) { - s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer); + if ((size_t) n == s->pressure.write_buffer_size) { + s->pressure.write_buffer = mfree(s->pressure.write_buffer); if (n > 0) { - s->memory_pressure.write_buffer_size = 0; + s->pressure.write_buffer_size = 0; /* Update epoll events mask, since we have now written everything and don't care for EPOLLOUT anymore */ - r = source_memory_pressure_register(s, s->enabled); + r = source_pressure_register(s, s->enabled); if (r < 0) return r; } } else if (n > 0) { _cleanup_free_ void *c = NULL; - assert((size_t) n < s->memory_pressure.write_buffer_size); + assert((size_t) n < s->pressure.write_buffer_size); - c = memdup((uint8_t*) s->memory_pressure.write_buffer + n, s->memory_pressure.write_buffer_size - n); + c = memdup((uint8_t*) s->pressure.write_buffer + n, s->pressure.write_buffer_size - n); if (!c) return -ENOMEM; - free_and_replace(s->memory_pressure.write_buffer, c); - s->memory_pressure.write_buffer_size -= n; + free_and_replace(s->pressure.write_buffer, c); + s->pressure.write_buffer_size -= n; return 1; } return 0; } -static int source_memory_pressure_initiate_dispatch(sd_event_source *s) { +static int source_pressure_initiate_dispatch(sd_event_source *s) { int r; assert(s); - assert(s->type == SOURCE_MEMORY_PRESSURE); + assert(EVENT_SOURCE_IS_PRESSURE(s)); - r = source_memory_pressure_write(s); + r = source_pressure_write(s); if (r < 0) return r; if (r > 0) @@ -4072,22 +4126,22 @@ static int source_memory_pressure_initiate_dispatch(sd_event_source *s) { * function. Instead, shortcut it so that we wait for next EPOLLOUT immediately. */ /* No pending incoming IO? Then let's not continue further */ - if ((s->memory_pressure.revents & (EPOLLIN|EPOLLPRI)) == 0) { + if ((s->pressure.revents & (EPOLLIN|EPOLLPRI)) == 0) { /* Treat IO errors on the notifier the same ways errors returned from a callback */ - if ((s->memory_pressure.revents & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) != 0) + if ((s->pressure.revents & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) != 0) return -EIO; return 1; /* leave dispatch, we already processed everything */ } - if (s->memory_pressure.revents & EPOLLIN) { + if (s->pressure.revents & EPOLLIN) { uint8_t pipe_buf[PIPE_BUF]; ssize_t n; /* If the fd is readable, then flush out anything that might be queued */ - n = read(s->memory_pressure.fd, pipe_buf, sizeof(pipe_buf)); + n = read(s->pressure.fd, pipe_buf, sizeof(pipe_buf)); if (n < 0 && !ERRNO_IS_TRANSIENT(errno)) return -errno; } @@ -4158,8 +4212,8 @@ static int source_dispatch(sd_event_source *s) { if (r < 0) return r; - if (s->type == SOURCE_MEMORY_PRESSURE) { - r = source_memory_pressure_initiate_dispatch(s); + if (EVENT_SOURCE_IS_PRESSURE(s)) { + r = source_pressure_initiate_dispatch(s); if (r == -EIO) /* handle EIO errors similar to callback errors */ goto finish; if (r < 0) @@ -4254,7 +4308,8 @@ static int source_dispatch(sd_event_source *s) { } case SOURCE_MEMORY_PRESSURE: - r = s->memory_pressure.callback(s, s->userdata); + case SOURCE_CPU_PRESSURE: + r = s->pressure.callback(s, s->userdata); break; case SOURCE_WATCHDOG: @@ -4422,7 +4477,7 @@ static void event_close_inode_data_fds(sd_event *e) { } } -static int event_memory_pressure_write_list(sd_event *e) { +static int event_pressure_write_list(sd_event *e) { int r; assert(e); @@ -4430,15 +4485,15 @@ static int event_memory_pressure_write_list(sd_event *e) { for (;;) { sd_event_source *s; - s = LIST_POP(memory_pressure.write_list, e->memory_pressure_write_list); + s = LIST_POP(pressure.write_list, e->pressure_write_list); if (!s) break; - assert(s->type == SOURCE_MEMORY_PRESSURE); - assert(s->memory_pressure.write_buffer_size > 0); - s->memory_pressure.in_write_list = false; + assert(EVENT_SOURCE_IS_PRESSURE(s)); + assert(s->pressure.write_buffer_size > 0); + s->pressure.in_write_list = false; - r = source_memory_pressure_write(s); + r = source_pressure_write(s); if (r < 0) return r; } @@ -4499,7 +4554,7 @@ _public_ int sd_event_prepare(sd_event *e) { if (r < 0) return r; - r = event_memory_pressure_write_list(e); + r = event_pressure_write_list(e); if (r < 0) return r; @@ -4668,7 +4723,8 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t break; case SOURCE_MEMORY_PRESSURE: - r = process_memory_pressure(s, i->events); + case SOURCE_CPU_PRESSURE: + r = process_pressure(s, i->events); break; default: @@ -5306,27 +5362,27 @@ _public_ int sd_event_get_exit_on_idle(sd_event *e) { return e->exit_on_idle; } -_public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) { +static int event_source_set_pressure_type(sd_event_source *s, const char *ty) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + assert_return(EVENT_SOURCE_IS_PRESSURE(s), -EDOM); assert_return(ty, -EINVAL); assert_return(!event_origin_changed(s->event), -ECHILD); if (!STR_IN_SET(ty, "some", "full")) return -EINVAL; - if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ + if (s->pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ return -EBUSY; - char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size); + char* space = memchr(s->pressure.write_buffer, ' ', s->pressure.write_buffer_size); if (!space) return -EINVAL; - size_t l = space - (char*) s->memory_pressure.write_buffer; - b = memdup_suffix0(s->memory_pressure.write_buffer, l); + size_t l = space - (char*) s->pressure.write_buffer; + b = memdup_suffix0(s->pressure.write_buffer, l); if (!b) return -ENOMEM; if (!STR_IN_SET(b, "some", "full")) @@ -5335,26 +5391,40 @@ _public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const if (streq(b, ty)) return 0; - size_t nl = strlen(ty) + (s->memory_pressure.write_buffer_size - l); + size_t nl = strlen(ty) + (s->pressure.write_buffer_size - l); w = new(char, nl); if (!w) return -ENOMEM; - memcpy(stpcpy(w, ty), space, (s->memory_pressure.write_buffer_size - l)); + memcpy(stpcpy(w, ty), space, (s->pressure.write_buffer_size - l)); - free_and_replace(s->memory_pressure.write_buffer, w); - s->memory_pressure.write_buffer_size = nl; - s->memory_pressure.locked = false; + free_and_replace(s->pressure.write_buffer, w); + s->pressure.write_buffer_size = nl; + s->pressure.locked = false; return 1; } -_public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { +_public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + +_public_ int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const char *ty) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CPU_PRESSURE, -EDOM); + + return event_source_set_pressure_type(s, ty); +} + +static int event_source_set_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { _cleanup_free_ char *b = NULL; _cleanup_free_ void *w = NULL; assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + assert_return(EVENT_SOURCE_IS_PRESSURE(s), -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); if (threshold_usec <= 0 || threshold_usec >= UINT64_MAX) @@ -5364,15 +5434,15 @@ _public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint if (threshold_usec > window_usec) return -EINVAL; - if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ + if (s->pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */ return -EBUSY; - char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size); + char* space = memchr(s->pressure.write_buffer, ' ', s->pressure.write_buffer_size); if (!space) return -EINVAL; - size_t l = space - (char*) s->memory_pressure.write_buffer; - b = memdup_suffix0(s->memory_pressure.write_buffer, l); + size_t l = space - (char*) s->pressure.write_buffer; + b = memdup_suffix0(s->pressure.write_buffer, l); if (!b) return -ENOMEM; if (!STR_IN_SET(b, "some", "full")) @@ -5386,12 +5456,26 @@ _public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint return -EINVAL; l = strlen(w) + 1; - if (memcmp_nn(s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size, w, l) == 0) + if (memcmp_nn(s->pressure.write_buffer, s->pressure.write_buffer_size, w, l) == 0) return 0; - free_and_replace(s->memory_pressure.write_buffer, w); - s->memory_pressure.write_buffer_size = l; - s->memory_pressure.locked = false; + free_and_replace(s->pressure.write_buffer, w); + s->pressure.write_buffer_size = l; + s->pressure.locked = false; return 1; } + +_public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} + +_public_ int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_CPU_PRESSURE, -EDOM); + + return event_source_set_pressure_period(s, threshold_usec, window_usec); +} diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index f5c79acdfd..71fc950488 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -97,6 +97,7 @@ int sd_event_add_defer(sd_event *e, sd_event_source **ret, sd_event_handler_t ca int sd_event_add_post(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_exit(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_add_memory_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); +int sd_event_add_cpu_pressure(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata); int sd_event_prepare(sd_event *e); int sd_event_wait(sd_event *e, uint64_t timeout); @@ -162,6 +163,8 @@ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret); int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret); int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty); int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); +int sd_event_source_set_cpu_pressure_type(sd_event_source *s, const char *ty); +int sd_event_source_set_cpu_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret); int sd_event_source_get_floating(sd_event_source *s); diff --git a/src/test/meson.build b/src/test/meson.build index aa6e5b97f4..fbb730ab5e 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -368,7 +368,7 @@ executables += [ 'dependencies' : libm, }, test_template + { - 'sources' : files('test-mempress.c'), + 'sources' : files('test-pressure.c'), 'dependencies' : threads, }, test_template + { diff --git a/src/test/test-mempress.c b/src/test/test-pressure.c similarity index 58% rename from src/test/test-mempress.c rename to src/test/test-pressure.c index f8e58bf1e4..44ff810753 100644 --- a/src/test/test-mempress.c +++ b/src/test/test-pressure.c @@ -28,6 +28,8 @@ #include "tmpfile-util.h" #include "unit-def.h" +/* Shared infrastructure for fake pressure tests */ + struct fake_pressure_context { int fifo_fd; int socket_fd; @@ -62,7 +64,7 @@ static int fake_pressure_callback(sd_event_source *s, void *userdata) { *value *= d[0]; - log_notice("memory pressure event: %s", d); + log_notice("pressure event: %s", d); if (*value == 7 * 'f' * 's') ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); @@ -70,7 +72,12 @@ static int fake_pressure_callback(sd_event_source *s, void *userdata) { return 0; } -TEST(fake_pressure) { +typedef int (*event_add_pressure_t)(sd_event *, sd_event_source **, sd_event_handler_t, void *); + +static void test_fake_pressure( + const char *resource, + event_add_pressure_t add_pressure) { + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *ef = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; @@ -79,6 +86,12 @@ TEST(fake_pressure) { pthread_t th; int value = 7; + _cleanup_free_ char *resource_upper = ASSERT_NOT_NULL(strdup(resource)); + ascii_strupper(resource_upper); + + _cleanup_free_ char *env_watch = ASSERT_NOT_NULL(strjoin(resource_upper, "_PRESSURE_WATCH")), + *env_write = ASSERT_NOT_NULL(strjoin(resource_upper, "_PRESSURE_WRITE")); + ASSERT_OK(sd_event_default(&e)); ASSERT_OK(mkdtemp_malloc(NULL, &tmp)); @@ -106,16 +119,16 @@ TEST(fake_pressure) { ASSERT_EQ(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)), 0); - ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WATCH", j, /* override= */ true)); - ASSERT_OK_ERRNO(unsetenv("MEMORY_PRESSURE_WRITE")); + ASSERT_OK_ERRNO(setenv(env_watch, j, /* override= */ true)); + ASSERT_OK_ERRNO(unsetenv(env_write)); - ASSERT_OK(sd_event_add_memory_pressure(e, &es, fake_pressure_callback, &value)); + ASSERT_OK(add_pressure(e, &es, fake_pressure_callback, &value)); ASSERT_OK(sd_event_source_set_description(es, "fifo event source")); - ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WATCH", k, /* override= */ true)); - ASSERT_OK_ERRNO(setenv("MEMORY_PRESSURE_WRITE", "aGVsbG8K", /* override= */ true)); + ASSERT_OK_ERRNO(setenv(env_watch, k, /* override= */ true)); + ASSERT_OK_ERRNO(setenv(env_write, "aGVsbG8K", /* override= */ true)); - ASSERT_OK(sd_event_add_memory_pressure(e, &ef, fake_pressure_callback, &value)); + ASSERT_OK(add_pressure(e, &ef, fake_pressure_callback, &value)); ASSERT_OK(sd_event_source_set_description(ef, "socket event source")); ASSERT_OK(sd_event_loop(e)); @@ -125,18 +138,52 @@ TEST(fake_pressure) { ASSERT_EQ(pthread_join(th, NULL), 0); } +static int fake_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_memory_pressure(e, ret, callback, userdata); +} + +TEST(fake_memory_pressure) { + test_fake_pressure("memory", fake_pressure_wrapper); +} + +static int fake_cpu_pressure_wrapper(sd_event *e, sd_event_source **ret, sd_event_handler_t callback, void *userdata) { + return sd_event_add_cpu_pressure(e, ret, callback, userdata); +} + +TEST(fake_cpu_pressure) { + test_fake_pressure("cpu", fake_cpu_pressure_wrapper); +} + +/* Shared infrastructure for real pressure tests */ + struct real_pressure_context { sd_event_source *pid; }; -static int real_pressure_callback(sd_event_source *s, void *userdata) { +static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { + ASSERT_NOT_NULL(s); + ASSERT_NOT_NULL(si); + + log_notice("child dead"); + + ASSERT_EQ(si->si_signo, SIGCHLD); + ASSERT_EQ(si->si_status, SIGKILL); + ASSERT_EQ(si->si_code, CLD_KILLED); + + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 31)); + return 0; +} + +/* Memory pressure real test */ + +static int real_memory_pressure_callback(sd_event_source *s, void *userdata) { struct real_pressure_context *c = ASSERT_PTR(userdata); const char *d; ASSERT_NOT_NULL(s); ASSERT_OK(sd_event_source_get_description(s, &d)); - log_notice("real_memory pressure event: %s", d); + log_notice("real memory pressure event: %s", d); sd_event_trim_memory(); @@ -174,21 +221,7 @@ _noreturn_ static void real_pressure_eat_memory(int pipe_fd) { } } -static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { - ASSERT_NOT_NULL(s); - ASSERT_NOT_NULL(si); - - log_notice("child dead"); - - ASSERT_EQ(si->si_signo, SIGCHLD); - ASSERT_EQ(si->si_status, SIGKILL); - ASSERT_EQ(si->si_code, CLD_KILLED); - - ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 31)); - return 0; -} - -TEST(real_pressure) { +TEST(real_memory_pressure) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; @@ -200,9 +233,12 @@ TEST(real_pressure) { const char *object; int r; - r = sd_bus_open_system(&bus); + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); if (r < 0) - return (void) log_tests_skipped_errno(r, "can't connect to system bus"); + return (void) log_tests_skipped_errno(r, "can't connect to bus"); ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); @@ -245,7 +281,7 @@ TEST(real_pressure) { .pid = cs, }; - r = sd_event_add_memory_pressure(e, &es, real_pressure_callback, &context); + r = sd_event_add_memory_pressure(e, &es, real_memory_pressure_callback, &context); if (r < 0) return (void) log_tests_skipped_errno(r, "can't allocate memory pressure fd"); @@ -255,8 +291,9 @@ TEST(real_pressure) { ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "full")); ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_type(es, "some")); ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_type(es, "some")); - ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC)); - ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC)); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); _cleanup_free_ char *uo = NULL; @@ -298,6 +335,123 @@ TEST(real_pressure) { ASSERT_EQ(ex, 31); } +/* CPU pressure real test */ + +static int real_cpu_pressure_callback(sd_event_source *s, void *userdata) { + struct real_pressure_context *c = ASSERT_PTR(userdata); + const char *d; + + ASSERT_NOT_NULL(s); + ASSERT_OK(sd_event_source_get_description(s, &d)); + + log_notice("real cpu pressure event: %s", d); + + ASSERT_NOT_NULL(c->pid); + ASSERT_OK(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0)); + c->pid = NULL; + + return 0; +} + +_noreturn_ static void real_pressure_eat_cpu(int pipe_fd) { + char x; + ASSERT_EQ(read(pipe_fd, &x, 1), 1); /* Wait for the GO! */ + + /* Busy-loop to generate CPU pressure */ + for (;;) + __asm__ volatile("" ::: "memory"); /* Prevent optimization */ +} + +TEST(real_cpu_pressure) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *scope = NULL; + const char *object; + int r; + + if (getuid() == 0) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't connect to bus"); + + ASSERT_OK(bus_wait_for_jobs_new(bus, &w)); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit")); + ASSERT_OK(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64())); + ASSERT_OK(sd_bus_message_append(m, "ss", scope, "fail")); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0)); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "CPUAccounting", "b", true)); + ASSERT_OK(sd_bus_message_close_container(m)); + ASSERT_OK(sd_bus_message_append(m, "a(sa(sv))", 0)); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't issue transient unit call"); + + ASSERT_OK(sd_bus_message_read(reply, "o", &object)); + + ASSERT_OK(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL)); + + ASSERT_OK(sd_event_default(&e)); + + ASSERT_OK_ERRNO(pipe2(pipe_fd, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork("(eat-cpu)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pidref); + ASSERT_OK(r); + if (r == 0) { + real_pressure_eat_cpu(pipe_fd[0]); + _exit(EXIT_SUCCESS); + } + + ASSERT_OK(event_add_child_pidref(e, &cs, &pidref, WEXITED, real_pressure_child_callback, NULL)); + ASSERT_OK(sd_event_source_set_child_process_own(cs, true)); + + ASSERT_OK_ERRNO(unsetenv("CPU_PRESSURE_WATCH")); + ASSERT_OK_ERRNO(unsetenv("CPU_PRESSURE_WRITE")); + + struct real_pressure_context context = { + .pid = cs, + }; + + r = sd_event_add_cpu_pressure(e, &es, real_cpu_pressure_callback, &context); + if (r < 0) + return (void) log_tests_skipped_errno(r, "can't allocate cpu pressure fd"); + + ASSERT_OK(sd_event_source_set_description(es, "real pressure event source")); + ASSERT_OK_ZERO(sd_event_source_set_cpu_pressure_type(es, "some")); + /* Unprivileged writes require a minimum of 2s otherwise the kernel will refuse the write. */ + ASSERT_OK_POSITIVE(sd_event_source_set_cpu_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK_ZERO(sd_event_source_set_cpu_pressure_period(es, 70 * USEC_PER_MSEC, 2 * USEC_PER_SEC)); + ASSERT_OK(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT)); + + m = sd_bus_message_unref(m); + + ASSERT_OK(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties")); + ASSERT_OK(sd_bus_message_append(m, "sb", scope, true)); + ASSERT_OK(sd_bus_message_open_container(m, 'a', "(sv)")); + ASSERT_OK(sd_bus_message_append(m, "(sv)", "CPUQuotaPerSecUSec", "t", (uint64_t) 1000)); /* 0.1% CPU */ + ASSERT_OK(sd_bus_message_close_container(m)); + + ASSERT_OK(sd_bus_call(bus, m, 0, NULL, NULL)); + + /* Now start eating CPU */ + ASSERT_EQ(write(pipe_fd[1], &(const char) { 'x' }, 1), 1); + + ASSERT_OK(sd_event_loop(e)); + int ex = 0; + ASSERT_OK(sd_event_get_exit_code(e, &ex)); + ASSERT_EQ(ex, 31); +} + static int outro(void) { hashmap_trim_pools(); return 0;