core: add MemoryAvailable unit property

Try to infer the unused memory that a unit can claim before the
memory.max limit is reached, including any limit set on any parent
slice above the unit itself.
This commit is contained in:
Luca Boccassi 2021-05-26 19:16:48 +01:00
parent d477a094e8
commit 93ff34e44a
6 changed files with 135 additions and 1 deletions

View file

@ -2401,6 +2401,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...; readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...; readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...]; readonly ay EffectiveCPUs = [...];
@ -3504,6 +3506,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/> <variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/> <variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -4063,6 +4067,11 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<varname>MountImages</varname> <varname>MountImages</varname>
<varname>ExtensionImages</varname> <varname>ExtensionImages</varname>
see systemd.exec(5) for their meaning.</para> see systemd.exec(5) for their meaning.</para>
<para><varname>MemoryAvailable</varname> indicates how much unused memory is available to the unit before
the <literal>MemoryMax</literal> or <literal>MemoryHigh</literal> (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.</para>
</refsect2> </refsect2>
</refsect1> </refsect1>
@ -4196,6 +4205,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...; readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...; readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...]; readonly ay EffectiveCPUs = [...];
@ -5321,6 +5332,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/> <variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/> <variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -5915,6 +5928,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...; readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...; readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...]; readonly ay EffectiveCPUs = [...];
@ -6886,6 +6901,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/> <variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/> <variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -7601,6 +7618,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...; readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...; readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...]; readonly ay EffectiveCPUs = [...];
@ -8544,6 +8563,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/> <variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/> <variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -9112,6 +9133,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...; readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...; readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...]; readonly ay EffectiveCPUs = [...];
@ -9403,6 +9426,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/> <variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/> <variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>
@ -9571,6 +9596,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryCurrent = ...; readonly t MemoryCurrent = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t MemoryAvailable = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t CPUUsageNSec = ...; readonly t CPUUsageNSec = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay EffectiveCPUs = [...]; readonly ay EffectiveCPUs = [...];
@ -9904,6 +9931,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
<variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/> <variablelist class="dbus-property" generated="True" extra-ref="MemoryCurrent"/>
<variablelist class="dbus-property" generated="True" extra-ref="MemoryAvailable"/>
<variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/> <variablelist class="dbus-property" generated="True" extra-ref="CPUUsageNSec"/>
<variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/> <variablelist class="dbus-property" generated="True" extra-ref="EffectiveCPUs"/>

View file

@ -3402,6 +3402,77 @@ int manager_notify_cgroup_empty(Manager *m, const char *cgroup) {
return 1; 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 unit_get_memory_current(Unit *u, uint64_t *ret) {
int r; int r;

View file

@ -282,6 +282,7 @@ int unit_watch_all_pids(Unit *u);
int unit_synthesize_cgroup_empty_event(Unit *u); int unit_synthesize_cgroup_empty_event(Unit *u);
int unit_get_memory_current(Unit *u, uint64_t *ret); 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_tasks_current(Unit *u, uint64_t *ret);
int unit_get_cpu_usage(Unit *u, nsec_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); int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret);

View file

@ -1097,6 +1097,30 @@ static int property_get_current_memory(
return sd_bus_message_append(reply, "t", sz); 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( static int property_get_current_tasks(
sd_bus *bus, sd_bus *bus,
const char *path, 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("Slice", "s", property_get_slice, 0, 0),
SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 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("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("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0),
SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0), SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0),
SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0), SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0),

View file

@ -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]"); 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) || (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) ||
(startswith(name, "Limit") && u == UINT64_MAX) || (startswith(name, "Limit") && u == UINT64_MAX) ||
(startswith(name, "DefaultLimit") && u == UINT64_MAX)) (startswith(name, "DefaultLimit") && u == UINT64_MAX))

View file

@ -247,6 +247,7 @@ typedef struct UnitStatusInfo {
uint64_t memory_max; uint64_t memory_max;
uint64_t memory_swap_max; uint64_t memory_swap_max;
uint64_t memory_limit; uint64_t memory_limit;
uint64_t memory_available;
uint64_t cpu_usage_nsec; uint64_t cpu_usage_nsec;
uint64_t tasks_current; uint64_t tasks_current;
uint64_t tasks_max; uint64_t tasks_max;
@ -682,6 +683,7 @@ static void print_status_info(
if (i->memory_min > 0 || i->memory_low > 0 || if (i->memory_min > 0 || i->memory_low > 0 ||
i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX || i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX ||
i->memory_swap_max != CGROUP_LIMIT_MAX || i->memory_swap_max != CGROUP_LIMIT_MAX ||
i->memory_available != CGROUP_LIMIT_MAX ||
i->memory_limit != CGROUP_LIMIT_MAX) { i->memory_limit != CGROUP_LIMIT_MAX) {
const char *prefix = ""; const char *prefix = "";
@ -710,6 +712,10 @@ static void print_status_info(
printf("%slimit: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_limit)); printf("%slimit: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_limit));
prefix = " "; prefix = " ";
} }
if (i->memory_available != CGROUP_LIMIT_MAX) {
printf("%savailable: %s", prefix, format_bytes(buf, sizeof(buf), i->memory_available));
prefix = " ";
}
printf(")"); printf(")");
} }
printf("\n"); printf("\n");
@ -1827,6 +1833,7 @@ static int show_one(
{ "Where", "s", NULL, offsetof(UnitStatusInfo, where) }, { "Where", "s", NULL, offsetof(UnitStatusInfo, where) },
{ "What", "s", NULL, offsetof(UnitStatusInfo, what) }, { "What", "s", NULL, offsetof(UnitStatusInfo, what) },
{ "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) }, { "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) },
{ "MemoryAvailable", "t", NULL, offsetof(UnitStatusInfo, memory_available) },
{ "DefaultMemoryMin", "t", NULL, offsetof(UnitStatusInfo, default_memory_min) }, { "DefaultMemoryMin", "t", NULL, offsetof(UnitStatusInfo, default_memory_min) },
{ "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) }, { "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) },
{ "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) }, { "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) },
@ -1869,6 +1876,7 @@ static int show_one(
.memory_max = CGROUP_LIMIT_MAX, .memory_max = CGROUP_LIMIT_MAX,
.memory_swap_max = CGROUP_LIMIT_MAX, .memory_swap_max = CGROUP_LIMIT_MAX,
.memory_limit = UINT64_MAX, .memory_limit = UINT64_MAX,
.memory_available = CGROUP_LIMIT_MAX,
.cpu_usage_nsec = UINT64_MAX, .cpu_usage_nsec = UINT64_MAX,
.tasks_current = UINT64_MAX, .tasks_current = UINT64_MAX,
.tasks_max = UINT64_MAX, .tasks_max = UINT64_MAX,