''],
['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'],
''],
<refname>sd_event_source_set_memory_pressure_period</refname>
<refname>sd_event_trim_memory</refname>
- <refpurpose>Add and configure an event source run as result of memory pressure</refpurpose>
+ <refname>sd_event_add_cpu_pressure</refname>
+ <refname>sd_event_source_set_cpu_pressure_type</refname>
+ <refname>sd_event_source_set_cpu_pressure_period</refname>
+
+ <refpurpose>Add and configure an event source run as result of memory or CPU pressure</refpurpose>
</refnamediv>
<refsynopsisdiv>
<paramdef>uint64_t <parameter>window_usec</parameter></paramdef>
</funcprototype>
+ <funcprototype>
+ <funcdef>int <function>sd_event_add_cpu_pressure</function></funcdef>
+ <paramdef>sd_event *<parameter>event</parameter></paramdef>
+ <paramdef>sd_event_source **<parameter>ret_source</parameter></paramdef>
+ <paramdef>sd_event_handler_t <parameter>handler</parameter></paramdef>
+ <paramdef>void *<parameter>userdata</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_event_source_set_cpu_pressure_type</function></funcdef>
+ <paramdef>sd_event_source *<parameter>source</parameter></paramdef>
+ <paramdef>const char *<parameter>type</parameter></paramdef>
+ </funcprototype>
+
+ <funcprototype>
+ <funcdef>int <function>sd_event_source_set_cpu_pressure_period</function></funcdef>
+ <paramdef>sd_event_source *<parameter>source</parameter></paramdef>
+ <paramdef>uint64_t <parameter>threshold_usec</parameter></paramdef>
+ <paramdef>uint64_t <parameter>window_usec</parameter></paramdef>
+ </funcprototype>
+
<funcprototype>
<funcdef>int <function>sd_event_trim_memory</function></funcdef>
<paramdef>void</paramdef>
<title>Description</title>
<para><function>sd_event_add_memory_pressure()</function> adds a new event source that is triggered
- whenever memory pressure is seen. This functionality is built around the Linux kernel's <ulink
+ whenever memory pressure is seen. Similarly,
+ <function>sd_event_add_cpu_pressure()</function> adds a new event source that is triggered whenever CPU
+ pressure is seen. This functionality is built around the Linux kernel's <ulink
url="https://docs.kernel.org/accounting/psi.html">Pressure Stall Information (PSI)</ulink> logic.</para>
- <para>Expects an event loop object as first parameter, and returns the allocated event source object in
- the second parameter, on success. The <parameter>handler</parameter> parameter is a function to call when
- memory pressure is seen, or <constant>NULL</constant>. The handler function will be passed the
+ <para>Both functions expect an event loop object as first parameter, and return the allocated event source
+ object in the second parameter, on success. The <parameter>handler</parameter> parameter is a function to
+ call when pressure is seen, or <constant>NULL</constant>. The handler function will be passed the
<parameter>userdata</parameter> 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
<parameter>handler</parameter> is <constant>NULL</constant>, a default handler that compacts allocation
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>
with <constant>SD_EVENT_OFF</constant>.</para>
- <para>If the second parameter of <function>sd_event_add_memory_pressure()</function> is
+ <para>If the second parameter of <function>sd_event_add_memory_pressure()</function> or
+ <function>sd_event_add_cpu_pressure()</function> is
<constant>NULL</constant> 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.</para>
- <para>The event source will fire according to the following logic:</para>
+ <para>The memory pressure event source will fire according to the following logic:</para>
<orderedlist>
<listitem><para>If the
<filename>/proc/pressure/memory</filename> is watched instead.</para></listitem>
</orderedlist>
+ <para>The CPU pressure event source follows the same logic, but uses the
+ <varname>$CPU_PRESSURE_WATCH</varname>/<varname>$CPU_PRESSURE_WRITE</varname> environment variables,
+ the <filename>cpu.pressure</filename> cgroup file, and the system-wide PSI interface file
+ <filename>/proc/pressure/cpu</filename> instead. Note that <filename>/proc/pressure/cpu</filename> only
+ provides the <literal>some</literal> line, not the <literal>full</literal> line, so only
+ <literal>some</literal> is valid when watching at the system level.</para>
+
<para>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.</para>
<para>The <function>sd_event_source_set_memory_pressure_type()</function> and
<function>sd_event_source_set_memory_pressure_period()</function> functions can be used to fine-tune the
- PSI parameters for pressure notifications. The former takes either <literal>some</literal>,
+ PSI parameters for memory pressure notifications. The former takes either <literal>some</literal>,
<literal>full</literal> 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
environment variables (or in other words: configuration supplied by a service manager wins over internal
settings).</para>
+ <para>Similarly, <function>sd_event_source_set_cpu_pressure_type()</function> and
+ <function>sd_event_source_set_cpu_pressure_period()</function> 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 <literal>some</literal> or <literal>full</literal>, 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
+ <varname>$CPU_PRESSURE_WATCH</varname>/<varname>$CPU_PRESSURE_WRITE</varname> environment
+ variables.</para>
+
<para>The <function>sd_event_trim_memory()</function> function releases various internal allocation
caches maintained by <filename>libsystemd</filename> and then invokes glibc's <citerefentry
project='man-pages'><refentrytitle>malloc_trim</refentrytitle><manvolnum>3</manvolnum></citerefentry>. This
<varlistentry>
<term><constant>-EHOSTDOWN</constant></term>
- <listitem><para>The <varname>$MEMORY_PRESSURE_WATCH</varname> variable has been set to the literal
- string <filename>/dev/null</filename>, in order to explicitly disable memory pressure
+ <listitem><para>The <varname>$MEMORY_PRESSURE_WATCH</varname> or
+ <varname>$CPU_PRESSURE_WATCH</varname> variable has been set to the literal
+ string <filename>/dev/null</filename>, in order to explicitly disable pressure
handling.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
<varlistentry>
<term><constant>-EBADMSG</constant></term>
- <listitem><para>The <varname>$MEMORY_PRESSURE_WATCH</varname> variable has been set to an invalid
+ <listitem><para>The <varname>$MEMORY_PRESSURE_WATCH</varname> or
+ <varname>$CPU_PRESSURE_WATCH</varname> variable has been set to an invalid
string, for example a relative rather than an absolute path.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
<varlistentry>
<term><constant>-ENOTTY</constant></term>
- <listitem><para>The <varname>$MEMORY_PRESSURE_WATCH</varname> variable points to a regular file
+ <listitem><para>The <varname>$MEMORY_PRESSURE_WATCH</varname> or
+ <varname>$CPU_PRESSURE_WATCH</varname> variable points to a regular file
outside of the procfs or cgroupfs file systems.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
<varlistentry>
<term><constant>-EOPNOTSUPP</constant></term>
- <listitem><para>No configuration via <varname>$MEMORY_PRESSURE_WATCH</varname> has been specified
- and the local kernel does not support the PSI interface.</para>
+ <listitem><para>No configuration via <varname>$MEMORY_PRESSURE_WATCH</varname> or
+ <varname>$CPU_PRESSURE_WATCH</varname> has been specified and the local kernel does not support the
+ PSI interface.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
<varlistentry>
<term><constant>-EBUSY</constant></term>
- <listitem><para>This is returned by <function>sd_event_source_set_memory_pressure_type()</function>
- and <function>sd_event_source_set_memory_pressure_period()</function> if invoked on event sources
+ <listitem><para>This is returned by <function>sd_event_source_set_memory_pressure_type()</function>,
+ <function>sd_event_source_set_memory_pressure_period()</function>,
+ <function>sd_event_source_set_cpu_pressure_type()</function>,
+ and <function>sd_event_source_set_cpu_pressure_period()</function> if invoked on event sources
at a time later than immediately after allocating them.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
<function>sd_event_source_set_memory_pressure_type()</function>,
<function>sd_event_source_set_memory_pressure_period()</function>, and
<function>sd_event_trim_memory()</function> were added in version 254.</para>
+ <para><function>sd_event_add_cpu_pressure()</function>,
+ <function>sd_event_source_set_cpu_pressure_type()</function>, and
+ <function>sd_event_source_set_cpu_pressure_period()</function> were added in version 261.</para>
</refsect1>
<refsect1>
#include "fileio.h"
#include "parse-util.h"
#include "psi-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
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;
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;
/* 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)
_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)
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;
SOURCE_WATCHDOG,
SOURCE_INOTIFY,
SOURCE_MEMORY_PRESSURE,
+ SOURCE_CPU_PRESSURE,
_SOURCE_EVENT_SOURCE_TYPE_MAX,
_SOURCE_EVENT_SOURCE_TYPE_INVALID = -EINVAL,
} EventSourceType;
size_t write_buffer_size;
uint32_t events, revents;
LIST_FIELDS(sd_event_source, write_list);
- } memory_pressure;
+ } pressure;
};
};
[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);
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
/* 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;
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) {
}
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:
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)
[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;
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;
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(!event_origin_changed(e), -ECHILD);
+ assert(resource >= 0 && resource < _PRESSURE_RESOURCE_MAX);
+ const PressureResourceInfo *info = &pressure_resource_info[resource];
+
if (!callback)
- callback = memory_pressure_callback;
+ callback = default_callback;
- s = source_new(e, !ret, SOURCE_MEMORY_PRESSURE);
+ 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)
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;
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
* 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;
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. */
-
- if (s->memory_pressure.write_buffer_size > 0)
- source_memory_pressure_add_to_write_list(s);
+ * 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->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;
}
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);
break;
case SOURCE_MEMORY_PRESSURE:
- source_memory_pressure_unregister(s);
+ case SOURCE_CPU_PRESSURE:
+ source_pressure_unregister(s);
break;
case SOURCE_TIME_REALTIME:
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;
}
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
* 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;
}
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)
* 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;
}
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)
}
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:
}
}
-static int event_memory_pressure_write_list(sd_event *e) {
+static int event_pressure_write_list(sd_event *e) {
int r;
assert(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;
}
if (r < 0)
return r;
- r = event_memory_pressure_write_list(e);
+ r = event_pressure_write_list(e);
if (r < 0)
return r;
break;
case SOURCE_MEMORY_PRESSURE:
- r = process_memory_pressure(s, i->events);
+ case SOURCE_CPU_PRESSURE:
+ r = process_pressure(s, i->events);
break;
default:
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"))
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)
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"))
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);
+}
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);
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);
'dependencies' : libm,
},
test_template + {
- 'sources' : files('test-mempress.c'),
+ 'sources' : files('test-pressure.c'),
'dependencies' : threads,
},
test_template + {
#include "tmpfile-util.h"
#include "unit-def.h"
+/* Shared infrastructure for fake pressure tests */
+
struct fake_pressure_context {
int fifo_fd;
int socket_fd;
*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));
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;
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));
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));
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();
}
}
-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;
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));
.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");
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;
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;