From 9184ca48ea43e009cd6f379f319926109a30926c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 8 Jun 2016 19:25:38 +0200 Subject: util-lib: introduce parse_percent() for parsing percent specifications And port a couple of users over to it. --- src/core/load-fragment.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'src/core') diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 17c72aed88..fe60bee789 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2774,7 +2774,7 @@ int config_parse_cpu_quota( void *userdata) { CGroupContext *c = data; - double percent; + int r; assert(filename); assert(lvalue); @@ -2785,18 +2785,13 @@ int config_parse_cpu_quota( return 0; } - if (!endswith(rvalue, "%")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' not ending in '%%'. Ignoring.", rvalue); + r = parse_percent(rvalue); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "CPU quota '%s' invalid. Ignoring.", rvalue); return 0; } - if (sscanf(rvalue, "%lf%%", &percent) != 1 || percent <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' invalid. Ignoring.", rvalue); - return 0; - } - - c->cpu_quota_per_sec_usec = (usec_t) (percent * USEC_PER_SEC / 100); - + c->cpu_quota_per_sec_usec = ((usec_t) r * USEC_PER_SEC) / 100U; return 0; } -- cgit v1.2.3-54-g00ecf From 875ae5661a011b757f0eaa468dea6ba91cbe5437 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 8 Jun 2016 19:36:09 +0200 Subject: core: optionally, accept a percentage value for MemoryLimit= and related settings If a percentage is used, it is taken relative to the installed RAM size. This should make it easier to write generic unit files that adapt to the local system. --- man/systemd.resource-control.xml | 36 +++++++++++++++++++----------------- src/core/load-fragment.c | 16 +++++++++++++--- 2 files changed, 32 insertions(+), 20 deletions(-) (limited to 'src/core') diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index d4c8fa7091..0551d75026 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -228,9 +228,11 @@ reclaimed as long as memory can be reclaimed from unprotected units. Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is - parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. This controls the - memory.low control group attribute. For details about this control group attribute, see - cgroup-v2.txt. + parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a + percentage value may be specified, which is taken relative to the installed physical memory on the + system. This controls the memory.low control group attribute. For details about this + control group attribute, see cgroup-v2.txt. Implies MemoryAccounting=true. @@ -247,7 +249,9 @@ aggressively in such cases. This is the main mechanism to control memory usage of a unit. Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is - parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the + parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a + percentage value may be specified, which is taken relative to the installed physical memory on the + system. If assigned the special value infinity, no memory limit is applied. This controls the memory.high control group attribute. For details about this control group attribute, see cgroup-v2.txt. @@ -268,8 +272,9 @@ last line of defense. Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is - parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. If assigned the - special value infinity, no memory limit is applied. This controls the + parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively. Alternatively, a + percentage value may be specified, which is taken relative to the installed physical memory on the system. If + assigned the special value infinity, no memory limit is applied. This controls the memory.max control group attribute. For details about this control group attribute, see cgroup-v2.txt. @@ -284,17 +289,14 @@ MemoryLimit=bytes - Specify the limit on maximum memory usage of the - executed processes. The limit specifies how much process and - kernel memory can be used by tasks in this unit. Takes a - memory size in bytes. If the value is suffixed with K, M, G - or T, the specified memory size is parsed as Kilobytes, - Megabytes, Gigabytes, or Terabytes (with the base 1024), - respectively. If assigned the special value - infinity, no memory limit is applied. This - controls the memory.limit_in_bytes - control group attribute. For details about this control - group attribute, see Specify the limit on maximum memory usage of the executed processes. The limit specifies how much + process and kernel memory can be used by tasks in this unit. Takes a memory size in bytes. If the value is + suffixed with K, M, G or T, the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or + Terabytes (with the base 1024), respectively. Alternatively, a percentage value may be specified, which is + taken relative to the installed physical memory on the system. If assigned the special value + infinity, no memory limit is applied. This controls the + memory.limit_in_bytes control group attribute. For details about this control group + attribute, see memory.txt. Implies MemoryAccounting=true. diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index fe60bee789..05852cc7e3 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2812,9 +2812,19 @@ int config_parse_memory_limit( int r; if (!isempty(rvalue) && !streq(rvalue, "infinity")) { - r = parse_size(rvalue, 1024, &bytes); - if (r < 0 || bytes < 1) { - log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue); + + r = parse_percent(rvalue); + if (r < 0) { + r = parse_size(rvalue, 1024, &bytes); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue); + return 0; + } + } else + bytes = (((physical_memory() / page_size()) * (uint64_t) r) / 100) * page_size(); + + if (bytes < 1) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Memory limit '%s' too small. Ignoring.", rvalue); return 0; } } -- cgit v1.2.3-54-g00ecf From cd0a7a8e58cf7e9f273bf446bb50bf982a8cf29c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 8 Jun 2016 20:04:22 +0200 Subject: core: when receiving a memory limit via the bus, refuse 0 When parsing unit files we already refuse unit memory limits of zero, let's also refuse it when the value is set via the bus. --- src/core/dbus-cgroup.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 8525fa1bf1..5b94b5e575 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -641,7 +641,7 @@ int bus_cgroup_set_property( return 1; - } else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) { + } else if (STR_IN_SET(name, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) { const char *path; bool read = true; unsigned n = 0; @@ -835,6 +835,8 @@ int bus_cgroup_set_property( r = sd_bus_message_read(message, "t", &v); if (r < 0) return r; + if (v <= 0) + return sd_bus_error_set_errnof(error, EINVAL, "%s= is too small", name); if (mode != UNIT_CHECK) { if (streq(name, "MemoryLow")) @@ -860,6 +862,8 @@ int bus_cgroup_set_property( r = sd_bus_message_read(message, "t", &limit); if (r < 0) return r; + if (limit <= 0) + return sd_bus_error_set_errnof(error, EINVAL, "%s= is too small", name); if (mode != UNIT_CHECK) { c->memory_limit = limit; -- cgit v1.2.3-54-g00ecf From 799ec13412e2f6649fd69ce4f1ca1674a40039b0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 8 Jun 2016 20:05:14 +0200 Subject: core: make sure to use "infinity" in unit files, not "max" THe latter is a kernelism, we only understand "infinity". --- src/core/dbus-cgroup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 5b94b5e575..fe035109e3 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -849,7 +849,7 @@ int bus_cgroup_set_property( unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); if (v == CGROUP_LIMIT_MAX) - unit_write_drop_in_private_format(u, mode, name, "%s=max", name); + unit_write_drop_in_private_format(u, mode, name, "%s=infinity", name); else unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64, name, v); } -- cgit v1.2.3-54-g00ecf From d8cf2ac79b524d7784bccb428295ebc9c5e8548c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 8 Jun 2016 20:45:32 +0200 Subject: util: introduce physical_memory_scale() to unify how we scale by physical memory The various bits of code did the scaling all different, let's unify this, given that the code is not trivial. --- src/basic/util.c | 27 +++++++++++++++++++++++++++ src/basic/util.h | 1 + src/core/load-fragment.c | 2 +- src/login/logind-user.c | 2 +- src/login/logind.c | 2 +- src/test/test-util.c | 38 +++++++++++++++++++++++++++++++++++++- 6 files changed, 68 insertions(+), 4 deletions(-) (limited to 'src/core') diff --git a/src/basic/util.c b/src/basic/util.c index 88d58cd94a..09d16697b7 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -805,6 +805,33 @@ uint64_t physical_memory(void) { return MIN(mem, lim); } +uint64_t physical_memory_scale(uint64_t v, uint64_t max) { + uint64_t p, m, ps, r; + + assert(max > 0); + + /* Returns the physical memory size, multiplied by v divided by max. Returns UINT64_MAX on overflow. On success + * the result is a multiple of the page size (rounds down). */ + + ps = page_size(); + assert(ps > 0); + + p = physical_memory() / ps; + assert(p > 0); + + m = p * v; + if (m / p != v) + return UINT64_MAX; + + m /= max; + + r = m * ps; + if (r / ps != m) + return UINT64_MAX; + + return r; +} + int update_reboot_parameter_and_warn(const char *param) { int r; diff --git a/src/basic/util.h b/src/basic/util.h index 9e6df19ef1..db105197e8 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -184,6 +184,7 @@ int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int * int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); uint64_t physical_memory(void); +uint64_t physical_memory_scale(uint64_t v, uint64_t max); int update_reboot_parameter_and_warn(const char *param); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 05852cc7e3..58d7275a96 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2821,7 +2821,7 @@ int config_parse_memory_limit( return 0; } } else - bytes = (((physical_memory() / page_size()) * (uint64_t) r) / 100) * page_size(); + bytes = physical_memory_scale(r, 100U); if (bytes < 1) { log_syntax(unit, LOG_ERR, filename, line, 0, "Memory limit '%s' too small. Ignoring.", rvalue); diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 6d0904f5ca..de44d369cf 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -853,7 +853,7 @@ int config_parse_tmpfs_size( /* First, try to parse as percentage */ r = parse_percent(rvalue); if (r > 0 && r < 100) - *sz = PAGE_ALIGN((size_t) ((physical_memory() * (uint64_t) r) / 100U)); + *sz = physical_memory_scale(r, 100U); else { uint64_t k; diff --git a/src/login/logind.c b/src/login/logind.c index caf149cfb7..d01dd110ea 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -61,7 +61,7 @@ static void manager_reset_config(Manager *m) { m->idle_action_usec = 30 * USEC_PER_MINUTE; m->idle_action = HANDLE_IGNORE; - m->runtime_dir_size = PAGE_ALIGN((size_t) (physical_memory() / 10)); /* 10% */ + m->runtime_dir_size = physical_memory_scale(10U, 100U); /* 10% */ m->user_tasks_max = 12288; m->sessions_max = 8192; m->inhibitors_max = 8192; diff --git a/src/test/test-util.c b/src/test/test-util.c index 5b3fbcff53..e177612a9f 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -273,7 +273,42 @@ static void test_physical_memory(void) { assert_se(p < UINT64_MAX); assert_se(p % page_size() == 0); - log_info("Memory: %s", format_bytes(buf, sizeof(buf), p)); + log_info("Memory: %s (%" PRIu64 ")", format_bytes(buf, sizeof(buf), p), p); +} + +static void test_physical_memory_scale(void) { + uint64_t p; + + p = physical_memory(); + + assert_se(physical_memory_scale(0, 100) == 0); + assert_se(physical_memory_scale(100, 100) == p); + + log_info("Memory original: %" PRIu64, physical_memory()); + log_info("Memory scaled by 50%%: %" PRIu64, physical_memory_scale(50, 100)); + log_info("Memory divided by 2: %" PRIu64, physical_memory() / 2); + log_info("Page size: %zu", page_size()); + + /* There might be an uneven number of pages, hence permit these calculations to be half a page off... */ + assert_se(page_size()/2 + physical_memory_scale(50, 100) - p/2 <= page_size()); + assert_se(physical_memory_scale(200, 100) == p*2); + + assert_se(physical_memory_scale(0, 1) == 0); + assert_se(physical_memory_scale(1, 1) == p); + assert_se(physical_memory_scale(2, 1) == p*2); + + assert_se(physical_memory_scale(0, 2) == 0); + + assert_se(page_size()/2 + physical_memory_scale(1, 2) - p/2 <= page_size()); + assert_se(physical_memory_scale(2, 2) == p); + assert_se(physical_memory_scale(4, 2) == p*2); + + assert_se(physical_memory_scale(0, UINT32_MAX) == 0); + assert_se(physical_memory_scale(UINT32_MAX, UINT32_MAX) == p); + + /* overflow */ + assert_se(physical_memory_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX); + } int main(int argc, char *argv[]) { @@ -291,6 +326,7 @@ int main(int argc, char *argv[]) { test_execute_directory(); test_raw_clone(); test_physical_memory(); + test_physical_memory_scale(); return 0; } -- cgit v1.2.3-54-g00ecf From d58d600efdcbefa38247861a1df032d1a8fda606 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 8 Jun 2016 20:52:06 +0200 Subject: systemctl: allow percent-based MemoryLimit= settings via systemctl set-property The unit files already accept relative, percent-based memory limit specification, let's make sure "systemctl set-property" support this too. Since we want the physical memory size of the destination machine to apply we pass the percentage in a new set of properties that only exist for this purpose, and can only be set. --- src/core/dbus-cgroup.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/bus-unit-util.c | 43 +++++++++++++++++++++++++------------- 2 files changed, 80 insertions(+), 15 deletions(-) (limited to 'src/core') diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index fe035109e3..27bbe2d26d 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -856,6 +856,38 @@ int bus_cgroup_set_property( return 1; + } else if (STR_IN_SET(name, "MemoryLowByPhysicalMemory", "MemoryHighByPhysicalMemory", "MemoryMaxByPhysicalMemory")) { + uint32_t raw; + uint64_t v; + + r = sd_bus_message_read(message, "u", &raw); + if (r < 0) + return r; + + v = physical_memory_scale(raw, UINT32_MAX); + if (v <= 0 || v == UINT64_MAX) + return sd_bus_error_set_errnof(error, EINVAL, "%s= is out of range", name); + + if (mode != UNIT_CHECK) { + const char *e; + + /* Chop off suffix */ + assert_se(e = endswith(name, "ByPhysicalMemory")); + name = strndupa(name, e - name); + + if (streq(name, "MemoryLow")) + c->memory_low = v; + else if (streq(name, "MemoryHigh")) + c->memory_high = v; + else + c->memory_max = v; + + unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); + unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu32 "%%", name, (uint32_t) (DIV_ROUND_UP((uint64_t) raw * 100, (uint64_t) UINT32_MAX))); + } + + return 1; + } else if (streq(name, "MemoryLimit")) { uint64_t limit; @@ -877,6 +909,26 @@ int bus_cgroup_set_property( return 1; + } else if (streq(name, "MemoryLimitByPhysicalMemory")) { + uint64_t limit; + uint32_t raw; + + r = sd_bus_message_read(message, "u", &raw); + if (r < 0) + return r; + + limit = physical_memory_scale(raw, UINT32_MAX); + if (limit <= 0 || limit == UINT64_MAX) + return sd_bus_error_set_errnof(error, EINVAL, "%s= is out of range", name); + + if (mode != UNIT_CHECK) { + c->memory_limit = limit; + unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); + unit_write_drop_in_private_format(u, mode, "MemoryLimit", "MemoryLimit=%" PRIu32 "%%", (uint32_t) (DIV_ROUND_UP((uint64_t) raw * 100, (uint64_t) UINT32_MAX))); + } + + return 1; + } else if (streq(name, "DevicePolicy")) { const char *policy; CGroupDevicePolicy p; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 778c79b3cf..6fc201b885 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -120,6 +120,34 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen strcpy(mempcpy(n, field, l - 3), "USec"); r = sd_bus_message_append(m, "sv", n, "t", t); goto finish; + + } else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) { + uint64_t bytes; + + if (isempty(eq) || streq(eq, "infinity")) + bytes = CGROUP_LIMIT_MAX; + else { + r = parse_percent(eq); + if (r >= 0) { + char *n; + + /* When this is a percentage we'll convert this into a relative value in the range + * 0…UINT32_MAX and pass it in the MemoryLowByPhysicalMemory property (and related + * ones). This way the physical memory size can be determined server-side */ + + n = strjoina(field, "ByPhysicalMemory"); + r = sd_bus_message_append(m, "sv", n, "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U)); + goto finish; + + } else { + r = parse_size(eq, 1024, &bytes); + if (r < 0) + return log_error_errno(r, "Failed to parse bytes specification %s", assignment); + } + } + + r = sd_bus_message_append(m, "sv", field, "t", bytes); + goto finish; } r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); @@ -163,21 +191,6 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "v", "b", r); - } else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) { - uint64_t bytes; - - if (isempty(eq) || streq(eq, "infinity")) - bytes = CGROUP_LIMIT_MAX; - else { - r = parse_size(eq, 1024, &bytes); - if (r < 0) { - log_error("Failed to parse bytes specification %s", assignment); - return -EINVAL; - } - } - - r = sd_bus_message_append(m, "v", "t", bytes); - } else if (streq(field, "TasksMax")) { uint64_t n; -- cgit v1.2.3-54-g00ecf From 3f71dec5d7210e3b21e79bde446fc2ba003486f1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 9 Jun 2016 19:53:45 +0200 Subject: unit: properly comment generated comments in unit files Fix-up for 2a9a6f8ac04a69ca36d645f9305a33645f22a22b --- src/core/unit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/unit.c b/src/core/unit.c index e98086a3f6..581962eba6 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -3375,7 +3375,7 @@ int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, co return -EINVAL; wrapped = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\"\n" - "or an equivalent operation. Do not edit.\n", + "# or an equivalent operation. Do not edit.\n", data, "\n"); -- cgit v1.2.3-54-g00ecf