cgroup: Add EffectiveMemoryMax=, EffectiveMemoryHigh= and EffectiveTasksMax= properties
Users become perplexed when they run their workload in a unit with no explicit limits configured (moreover, listing the limit property would even show it's infinity) but they experience unexpected resource limitation. The memory and pid limits come as the most visible, therefore add new unit read-only properties: - EffectiveMemoryMax=, - EffectiveMemoryHigh=, - EffectiveTasksMax=. These properties represent the most stringent limit systemd is aware of for the given unit -- and that is typically(*) the effective value. Implement the properties by simply traversing all parents in the leaf-slice tree and picking the minimum value. Note that effective limits are thus defined even for units that don't enable explicit accounting (because of the hierarchy). (*) The evasive case is when systemd runs in a cgroupns and cannot reason about outer setup. Complete solution would need kernel support.
This commit is contained in:
@@ -4247,6 +4247,46 @@ int unit_get_ip_accounting(
|
||||
return r;
|
||||
}
|
||||
|
||||
static uint64_t unit_get_effective_limit_one(Unit *u, CGroupLimitType type) {
|
||||
CGroupContext *cc;
|
||||
|
||||
assert(u);
|
||||
assert(UNIT_HAS_CGROUP_CONTEXT(u));
|
||||
|
||||
cc = unit_get_cgroup_context(u);
|
||||
switch (type) {
|
||||
/* Note: on legacy/hybrid hierarchies memory_max stays CGROUP_LIMIT_MAX unless configured
|
||||
* explicitly. Effective value of MemoryLimit= (cgroup v1) is not implemented. */
|
||||
case CGROUP_LIMIT_MEMORY_MAX:
|
||||
return cc->memory_max;
|
||||
case CGROUP_LIMIT_MEMORY_HIGH:
|
||||
return cc->memory_high;
|
||||
case CGROUP_LIMIT_TASKS_MAX:
|
||||
return cgroup_tasks_max_resolve(&cc->tasks_max);
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
int unit_get_effective_limit(Unit *u, CGroupLimitType type, uint64_t *ret) {
|
||||
uint64_t infimum;
|
||||
|
||||
assert(u);
|
||||
assert(ret);
|
||||
assert(type >= 0);
|
||||
assert(type < _CGROUP_LIMIT_TYPE_MAX);
|
||||
|
||||
if (!UNIT_HAS_CGROUP_CONTEXT(u))
|
||||
return -EINVAL;
|
||||
|
||||
infimum = unit_get_effective_limit_one(u, type);
|
||||
for (Unit *slice = UNIT_GET_SLICE(u); slice; slice = UNIT_GET_SLICE(slice))
|
||||
infimum = MIN(infimum, unit_get_effective_limit_one(slice, type));
|
||||
|
||||
*ret = infimum;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unit_get_io_accounting_raw(Unit *u, uint64_t ret[static _CGROUP_IO_ACCOUNTING_METRIC_MAX]) {
|
||||
static const char *const field_names[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {
|
||||
[CGROUP_IO_READ_BYTES] = "rbytes=",
|
||||
@@ -4667,3 +4707,11 @@ static const char* const cgroup_memory_accounting_metric_table[_CGROUP_MEMORY_AC
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(cgroup_memory_accounting_metric, CGroupMemoryAccountingMetric);
|
||||
|
||||
static const char *const cgroup_limit_type_table[_CGROUP_LIMIT_TYPE_MAX] = {
|
||||
[CGROUP_LIMIT_MEMORY_MAX] = "EffectiveMemoryMax",
|
||||
[CGROUP_LIMIT_MEMORY_HIGH] = "EffectiveMemoryHigh",
|
||||
[CGROUP_LIMIT_TASKS_MAX] = "EffectiveTasksMax",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(cgroup_limit_type, CGroupLimitType);
|
||||
|
||||
@@ -276,6 +276,15 @@ typedef enum CGroupMemoryAccountingMetric {
|
||||
_CGROUP_MEMORY_ACCOUNTING_METRIC_INVALID = -EINVAL,
|
||||
} CGroupMemoryAccountingMetric;
|
||||
|
||||
/* Used for limits whose value sets have infimum */
|
||||
typedef enum CGroupLimitType {
|
||||
CGROUP_LIMIT_MEMORY_MAX,
|
||||
CGROUP_LIMIT_MEMORY_HIGH,
|
||||
CGROUP_LIMIT_TASKS_MAX,
|
||||
_CGROUP_LIMIT_TYPE_MAX,
|
||||
_CGROUP_LIMIT_INVALID = -EINVAL,
|
||||
} CGroupLimitType;
|
||||
|
||||
typedef struct Unit Unit;
|
||||
typedef struct Manager Manager;
|
||||
typedef enum ManagerState ManagerState;
|
||||
@@ -374,6 +383,7 @@ 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);
|
||||
int unit_get_ip_accounting(Unit *u, CGroupIPAccountingMetric metric, uint64_t *ret);
|
||||
int unit_get_effective_limit(Unit *u, CGroupLimitType type, uint64_t *ret);
|
||||
|
||||
int unit_reset_cpu_accounting(Unit *u);
|
||||
void unit_reset_memory_accounting_last(Unit *u);
|
||||
@@ -425,5 +435,8 @@ CGroupIPAccountingMetric cgroup_ip_accounting_metric_from_string(const char *s)
|
||||
const char* cgroup_io_accounting_metric_to_string(CGroupIOAccountingMetric m) _const_;
|
||||
CGroupIOAccountingMetric cgroup_io_accounting_metric_from_string(const char *s) _pure_;
|
||||
|
||||
const char* cgroup_limit_type_to_string(CGroupLimitType m) _const_;
|
||||
CGroupLimitType cgroup_limit_type_from_string(const char *s) _pure_;
|
||||
|
||||
const char* cgroup_memory_accounting_metric_to_string(CGroupMemoryAccountingMetric m) _const_;
|
||||
CGroupMemoryAccountingMetric cgroup_memory_accounting_metric_from_string(const char *s) _pure_;
|
||||
|
||||
@@ -1442,6 +1442,28 @@ static int property_get_io_counter(
|
||||
return sd_bus_message_append(reply, "t", value);
|
||||
}
|
||||
|
||||
static int property_get_effective_limit(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
uint64_t value = CGROUP_LIMIT_MAX;
|
||||
Unit *u = ASSERT_PTR(userdata);
|
||||
ssize_t type;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(property);
|
||||
|
||||
assert_se((type = cgroup_limit_type_from_string(property)) >= 0);
|
||||
(void) unit_get_effective_limit(u, type, &value);
|
||||
return sd_bus_message_append(reply, "t", value);
|
||||
}
|
||||
|
||||
int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
||||
_cleanup_set_free_ Set *pids = NULL;
|
||||
@@ -1563,10 +1585,13 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = {
|
||||
SD_BUS_PROPERTY("MemorySwapPeak", "t", property_get_memory_accounting, 0, 0),
|
||||
SD_BUS_PROPERTY("MemoryZSwapCurrent", "t", property_get_memory_accounting, 0, 0),
|
||||
SD_BUS_PROPERTY("MemoryAvailable", "t", property_get_available_memory, 0, 0),
|
||||
SD_BUS_PROPERTY("EffectiveMemoryMax", "t", property_get_effective_limit, 0, 0),
|
||||
SD_BUS_PROPERTY("EffectiveMemoryHigh", "t", property_get_effective_limit, 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),
|
||||
SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0),
|
||||
SD_BUS_PROPERTY("EffectiveTasksMax", "t", property_get_effective_limit, 0, 0),
|
||||
SD_BUS_PROPERTY("IPIngressBytes", "t", property_get_ip_counter, 0, 0),
|
||||
SD_BUS_PROPERTY("IPIngressPackets", "t", property_get_ip_counter, 0, 0),
|
||||
SD_BUS_PROPERTY("IPEgressBytes", "t", property_get_ip_counter, 0, 0),
|
||||
|
||||
@@ -164,9 +164,11 @@ 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 ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") &&
|
||||
else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin",
|
||||
"MemoryHigh", "MemoryMax",
|
||||
"MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") &&
|
||||
u == CGROUP_LIMIT_MAX) ||
|
||||
(STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) ||
|
||||
(endswith(name, "TasksMax") && u == UINT64_MAX) ||
|
||||
(startswith(name, "Limit") && u == UINT64_MAX) ||
|
||||
(startswith(name, "DefaultLimit") && u == UINT64_MAX))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user