summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLuca Boccassi <luca.boccassi@microsoft.com>2021-11-17 10:00:12 +0000
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2021-12-01 09:53:18 +0100
commit81513b382b24a7f3602987f71042d075ca27d1a5 (patch)
treef5e91164c685134f8ecaf7b175365b1b97a8c70f /src
parent99f8a6d7f58c9edb00b3d214b685987444dc3931 (diff)
downloadsystemd-81513b382b24a7f3602987f71042d075ca27d1a5.tar.gz
core: add Condition[Memory/CPU/IO]Pressure
By default checks PSI on /proc/pressure, and causes a unit to be skipped if the threshold is above the given configuration for the avg300 measurement. Also allow to pass a custom timespan, and a particular slice unit to check under. Fixes #20139
Diffstat (limited to 'src')
-rw-r--r--src/core/load-fragment-gperf.gperf.in6
-rw-r--r--src/shared/condition.c136
-rw-r--r--src/shared/condition.h3
-rw-r--r--src/test/test-condition.c155
4 files changed, 300 insertions, 0 deletions
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 67cb2d70a2..6baa3b3b74 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -341,6 +341,9 @@ Unit.ConditionUser, config_parse_unit_condition_string,
Unit.ConditionGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, conditions)
Unit.ConditionControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, conditions)
Unit.ConditionOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, conditions)
+Unit.ConditionMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, conditions)
+Unit.ConditionCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, conditions)
+Unit.ConditionIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, conditions)
Unit.AssertPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, asserts)
Unit.AssertPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, asserts)
Unit.AssertPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, asserts)
@@ -368,6 +371,9 @@ Unit.AssertUser, config_parse_unit_condition_string,
Unit.AssertGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, asserts)
Unit.AssertControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, asserts)
Unit.AssertOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, asserts)
+Unit.AssertMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, asserts)
+Unit.AssertCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, asserts)
+Unit.AssertIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, asserts)
Unit.CollectMode, config_parse_collect_mode, 0, offsetof(Unit, collect_mode)
Service.PIDFile, config_parse_pid_file, 0, offsetof(Service, pid_file)
Service.ExecCondition, config_parse_exec, SERVICE_EXEC_CONDITION, offsetof(Service, exec_command)
diff --git a/src/shared/condition.c b/src/shared/condition.c
index dae75a5bf5..63bdc38a49 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -39,8 +39,10 @@
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
+#include "percent-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
+#include "psi-util.h"
#include "selinux-util.h"
#include "smack-util.h"
#include "stat-util.h"
@@ -962,6 +964,131 @@ static int condition_test_file_is_executable(Condition *c, char **env) {
(st.st_mode & 0111));
}
+static int condition_test_psi(Condition *c, char **env) {
+ _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL, *fourth = NULL, *pressure_path = NULL;
+ const char *p, *value, *pressure_type;
+ loadavg_t *current, limit;
+ ResourcePressure pressure;
+ int r;
+
+ assert(c);
+ assert(c->parameter);
+ assert(IN_SET(c->type, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE));
+
+ if (!is_pressure_supported()) {
+ log_debug("Pressure Stall Information (PSI) is not supported, skipping.");
+ return 1;
+ }
+
+ pressure_type = c->type == CONDITION_MEMORY_PRESSURE ? "memory" :
+ c->type == CONDITION_CPU_PRESSURE ? "cpu" :
+ "io";
+
+ p = c->parameter;
+ r = extract_many_words(&p, ":", 0, &first, &second, NULL);
+ if (r <= 0)
+ return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+ /* If only one parameter is passed, then we look at the global system pressure rather than a specific cgroup. */
+ if (r == 1) {
+ pressure_path = path_join("/proc/pressure", pressure_type);
+ if (!pressure_path)
+ return log_oom();
+
+ value = first;
+ } else {
+ const char *controller = strjoina(pressure_type, ".pressure");
+ _cleanup_free_ char *slice_path = NULL;
+ CGroupMask mask, required_mask;
+ char *slice;
+
+ required_mask = c->type == CONDITION_MEMORY_PRESSURE ? CGROUP_MASK_MEMORY :
+ c->type == CONDITION_CPU_PRESSURE ? CGROUP_MASK_CPU :
+ CGROUP_MASK_IO;
+
+ slice = strstrip(first);
+ if (!slice)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+
+ r = cg_all_unified();
+ if (r < 0)
+ return log_debug_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m");
+ if (r == 0) {
+ log_debug("PSI condition check requires the unified cgroups hierarchy, skipping.");
+ return 1;
+ }
+
+ r = cg_mask_supported(&mask);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to get supported cgroup controllers: %m");
+
+ if (!FLAGS_SET(mask, required_mask)) {
+ log_debug("Cgroup %s controller not available, skipping PSI condition check.", pressure_type);
+ return 1;
+ }
+
+ r = cg_slice_to_path(slice, &slice_path);
+ if (r < 0)
+ return log_debug_errno(r, "Cannot determine slice \"%s\" cgroup path: %m", slice);
+
+ r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, slice_path, controller, &pressure_path);
+ if (r < 0)
+ return log_debug_errno(r, "Error getting cgroup pressure path from %s: %m", slice_path);
+
+ value = second;
+ }
+
+ /* If a value including a specific timespan (in the intervals allowed by the kernel),
+ * parse it, otherwise we assume just a plain percentage that will be checked if it is
+ * smaller or equal to the current pressure average over 5 minutes. */
+ r = extract_many_words(&value, "/", 0, &third, &fourth, NULL);
+ if (r <= 0)
+ return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+ if (r == 1)
+ current = &pressure.avg300;
+ else {
+ const char *timespan;
+
+ timespan = skip_leading_chars(fourth, NULL);
+ if (!timespan)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+
+ if (startswith(timespan, "10sec"))
+ current = &pressure.avg10;
+ else if (startswith(timespan, "1min"))
+ current = &pressure.avg60;
+ else if (startswith(timespan, "5min"))
+ current = &pressure.avg300;
+ else
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+ }
+
+ value = strstrip(third);
+ if (!value)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+
+ r = parse_permyriad(value);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse permyriad: %s", c->parameter);
+
+ r = store_loadavg_fixed_point(r / 100LU, r % 100LU, &limit);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse loadavg: %s", c->parameter);
+
+ r = read_resource_pressure(pressure_path, PRESSURE_TYPE_FULL, &pressure);
+ if (r == -ENODATA) /* cpu.pressure 'full' was added recently, fall back to 'some'. */
+ r = read_resource_pressure(pressure_path, PRESSURE_TYPE_SOME, &pressure);
+ if (r == -ENOENT) {
+ /* We already checked that /proc/pressure exists, so this means we were given a cgroup
+ * that doesn't exist or doesn't exist any longer. */
+ log_debug("\"%s\" not found, skipping PSI check.", pressure_path);
+ return 1;
+ }
+ if (r < 0)
+ return log_debug_errno(r, "Error parsing pressure from %s: %m", pressure_path);
+
+ return *current <= limit;
+}
+
int condition_test(Condition *c, char **env) {
static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c, char **env) = {
@@ -994,6 +1121,9 @@ int condition_test(Condition *c, char **env) {
[CONDITION_ENVIRONMENT] = condition_test_environment,
[CONDITION_CPU_FEATURE] = condition_test_cpufeature,
[CONDITION_OS_RELEASE] = condition_test_osrelease,
+ [CONDITION_MEMORY_PRESSURE] = condition_test_psi,
+ [CONDITION_CPU_PRESSURE] = condition_test_psi,
+ [CONDITION_IO_PRESSURE] = condition_test_psi,
};
int r, b;
@@ -1119,6 +1249,9 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_ENVIRONMENT] = "ConditionEnvironment",
[CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
[CONDITION_OS_RELEASE] = "ConditionOSRelease",
+ [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure",
+ [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure",
+ [CONDITION_IO_PRESSURE] = "ConditionIOPressure",
};
DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
@@ -1153,6 +1286,9 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_ENVIRONMENT] = "AssertEnvironment",
[CONDITION_CPU_FEATURE] = "AssertCPUFeature",
[CONDITION_OS_RELEASE] = "AssertOSRelease",
+ [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure",
+ [CONDITION_CPU_PRESSURE] = "AssertCPUPressure",
+ [CONDITION_IO_PRESSURE] = "AssertIOPressure",
};
DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
diff --git a/src/shared/condition.h b/src/shared/condition.h
index 3a5420c402..2bbb7fa7f4 100644
--- a/src/shared/condition.h
+++ b/src/shared/condition.h
@@ -22,6 +22,9 @@ typedef enum ConditionType {
CONDITION_ENVIRONMENT,
CONDITION_CPU_FEATURE,
CONDITION_OS_RELEASE,
+ CONDITION_MEMORY_PRESSURE,
+ CONDITION_CPU_PRESSURE,
+ CONDITION_IO_PRESSURE,
CONDITION_NEEDS_UPDATE,
CONDITION_FIRST_BOOT,
diff --git a/src/test/test-condition.c b/src/test/test-condition.c
index fff697d5a4..4b22784d17 100644
--- a/src/test/test-condition.c
+++ b/src/test/test-condition.c
@@ -25,6 +25,7 @@
#include "nulstr-util.h"
#include "os-util.h"
#include "process-util.h"
+#include "psi-util.h"
#include "selinux-util.h"
#include "set.h"
#include "smack-util.h"
@@ -1031,4 +1032,158 @@ TEST(condition_test_os_release) {
condition_free(condition);
}
+TEST(condition_test_psi) {
+ Condition *condition;
+ CGroupMask mask;
+ int r;
+
+ if (!is_pressure_supported())
+ return (void) log_notice("Pressure Stall Information (PSI) is not supported, skipping %s", __func__);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, "", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "sbarabau", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, "10%sbarabau", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "10% sbarabau", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "-10", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "10%/10min", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "10min/10%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "10% 5min", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "/5min", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_IO_PRESSURE, "10s / ", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, "100%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, "0%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, "0.0%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "100%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "0%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "0.0%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "0.01%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "0.0%/10sec", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "100.0% / 1min", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_IO_PRESSURE, "50.0% / 1min", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ r = cg_all_unified();
+ if (r < 0)
+ return (void) log_notice("Failed to determine whether the unified cgroups hierarchy is used, skipping %s", __func__);
+ if (r == 0)
+ return (void) log_notice("Requires the unified cgroups hierarchy, skipping %s", __func__);
+
+ if (cg_mask_supported(&mask) < 0)
+ return (void) log_notice("Failed to get supported cgroup controllers, skipping %s", __func__);
+
+ if (!FLAGS_SET(mask, CGROUP_MASK_MEMORY))
+ return (void) log_notice("Requires the cgroup memory controller, skipping %s", __func__);
+
+ if (!FLAGS_SET(mask, CGROUP_MASK_CPU))
+ return (void) log_notice("Requires the cgroup CPU controller, skipping %s", __func__);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, " : / ", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) < 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "hopefullythisisnotarealone.slice:100% / 10sec", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) > 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_PRESSURE, "-.slice:100.0% / 1min", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, "-.slice:0.0%/5min", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_MEMORY_PRESSURE, "-.slice:100.0%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_IO_PRESSURE, "-.slice:0.0%", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) >= 0);
+ condition_free(condition);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);