diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index d55f3c8f9bd..1edaf157b9e 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2401,6 +2401,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -3504,6 +3506,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4063,6 +4067,11 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { MountImages ExtensionImages see systemd.exec(5) for their meaning. + + MemoryAvailable indicates how much unused memory is available to the unit before + the MemoryMax or MemoryHigh (whichever is lower) limit set by the cgroup + memory controller is reached. It will take into consideration limits on all parent slices, other than the + limits set on the unit itself. @@ -4196,6 +4205,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -5321,6 +5332,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -5915,6 +5928,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -6886,6 +6901,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -7601,6 +7618,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -8544,6 +8563,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -9112,6 +9133,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -9403,6 +9426,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -9571,6 +9596,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t MemoryCurrent = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t MemoryAvailable = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t CPUUsageNSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay EffectiveCPUs = [...]; @@ -9904,6 +9931,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 6a46e14556d..7fde1efce42 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -3402,6 +3402,77 @@ int manager_notify_cgroup_empty(Manager *m, const char *cgroup) { return 1; } +int unit_get_memory_available(Unit *u, uint64_t *ret) { + uint64_t unit_current, available = UINT64_MAX; + CGroupContext *unit_context; + const char *memory_file; + int r; + + assert(u); + assert(ret); + + /* If data from cgroups can be accessed, try to find out how much more memory a unit can + * claim before hitting the configured cgroup limits (if any). Consider both MemoryHigh + * and MemoryMax, and also any slice the unit might be nested below. */ + + if (!UNIT_CGROUP_BOOL(u, memory_accounting)) + return -ENODATA; + + if (!u->cgroup_path) + return -ENODATA; + + /* The root cgroup doesn't expose this information */ + if (unit_has_host_root_cgroup(u)) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) + return -ENODATA; + + r = cg_all_unified(); + if (r < 0) + return r; + memory_file = r > 0 ? "memory.current" : "memory.usage_in_bytes"; + + r = cg_get_attribute_as_uint64("memory", u->cgroup_path, memory_file, &unit_current); + if (r < 0) + return r; + + assert_se(unit_context = unit_get_cgroup_context(u)); + + if (unit_context->memory_max != UINT64_MAX || unit_context->memory_high != UINT64_MAX) + available = LESS_BY(MIN(unit_context->memory_max, unit_context->memory_high), unit_current); + + for (Unit *slice = UNIT_GET_SLICE(u); slice; slice = UNIT_GET_SLICE(slice)) { + uint64_t slice_current, slice_available = UINT64_MAX; + CGroupContext *slice_context; + + /* No point in continuing if we can't go any lower */ + if (available == 0) + break; + + if (!slice->cgroup_path) + continue; + + slice_context = unit_get_cgroup_context(slice); + if (!slice_context) + continue; + + if (slice_context->memory_max == UINT64_MAX && slice_context->memory_high == UINT64_MAX) + continue; + + r = cg_get_attribute_as_uint64("memory", slice->cgroup_path, memory_file, &slice_current); + if (r < 0) + continue; + + slice_available = LESS_BY(MIN(slice_context->memory_max, slice_context->memory_high), slice_current); + available = MIN(slice_available, available); + } + + *ret = available; + + return 0; +} + int unit_get_memory_current(Unit *u, uint64_t *ret) { int r; diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 1ad5dd38389..e6790eb0e83 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -282,6 +282,7 @@ int unit_watch_all_pids(Unit *u); int unit_synthesize_cgroup_empty_event(Unit *u); int unit_get_memory_current(Unit *u, uint64_t *ret); +int unit_get_memory_available(Unit *u, uint64_t *ret); int unit_get_tasks_current(Unit *u, uint64_t *ret); int unit_get_cpu_usage(Unit *u, nsec_t *ret); int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 6c04c6e5db8..aa10939a041 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -1097,6 +1097,30 @@ static int property_get_current_memory( return sd_bus_message_append(reply, "t", sz); } +static int property_get_available_memory( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t sz = UINT64_MAX; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_memory_available(u, &sz); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get total available memory from cgroup: %m"); + + return sd_bus_message_append(reply, "t", sz); +} + static int property_get_current_tasks( sd_bus *bus, const char *path, @@ -1541,6 +1565,7 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = { SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0), SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0), SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0), + SD_BUS_PROPERTY("MemoryAvailable", "t", property_get_available_memory, 0, 0), SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0), SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0), SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0), diff --git a/src/shared/bus-print-properties.c b/src/shared/bus-print-properties.c index cbd5fb087e0..b45921943a8 100644 --- a/src/shared/bus-print-properties.c +++ b/src/shared/bus-print-properties.c @@ -165,7 +165,7 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b bus_print_property_value(name, expected_value, flags, "[not set]"); - else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || + else if ((STR_IN_SET(name, "DefaultMemoryLow", "DefaultMemoryMin", "MemoryLow", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryLimit", "MemoryAvailable") && u == CGROUP_LIMIT_MAX) || (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) || (startswith(name, "Limit") && u == UINT64_MAX) || (startswith(name, "DefaultLimit") && u == UINT64_MAX)) diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index 3686ac3c768..d4d5a2b427f 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -247,6 +247,7 @@ typedef struct UnitStatusInfo { uint64_t memory_max; uint64_t memory_swap_max; uint64_t memory_limit; + uint64_t memory_available; uint64_t cpu_usage_nsec; uint64_t tasks_current; uint64_t tasks_max; @@ -682,6 +683,7 @@ static void print_status_info( if (i->memory_min > 0 || i->memory_low > 0 || i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX || i->memory_swap_max != CGROUP_LIMIT_MAX || + i->memory_available != CGROUP_LIMIT_MAX || i->memory_limit != CGROUP_LIMIT_MAX) { const char *prefix = ""; @@ -710,6 +712,10 @@ static void print_status_info( printf("%slimit: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_limit)); prefix = " "; } + if (i->memory_available != CGROUP_LIMIT_MAX) { + printf("%savailable: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_available)); + prefix = " "; + } printf(")"); } printf("\n"); @@ -1827,6 +1833,7 @@ static int show_one( { "Where", "s", NULL, offsetof(UnitStatusInfo, where) }, { "What", "s", NULL, offsetof(UnitStatusInfo, what) }, { "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) }, + { "MemoryAvailable", "t", NULL, offsetof(UnitStatusInfo, memory_available) }, { "DefaultMemoryMin", "t", NULL, offsetof(UnitStatusInfo, default_memory_min) }, { "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) }, { "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) }, @@ -1869,6 +1876,7 @@ static int show_one( .memory_max = CGROUP_LIMIT_MAX, .memory_swap_max = CGROUP_LIMIT_MAX, .memory_limit = UINT64_MAX, + .memory_available = CGROUP_LIMIT_MAX, .cpu_usage_nsec = UINT64_MAX, .tasks_current = UINT64_MAX, .tasks_max = UINT64_MAX,