}
void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
+ _cleanup_free_ char *disable_controllers_str = NULL;
CGroupIODeviceLimit *il;
CGroupIODeviceWeight *iw;
CGroupIODeviceLatency *l;
prefix = strempty(prefix);
+ (void) cg_mask_to_string(c->disable_controllers, &disable_controllers_str);
+
fprintf(f,
"%sCPUAccounting=%s\n"
"%sIOAccounting=%s\n"
"%sStartupIOWeight=%" PRIu64 "\n"
"%sBlockIOWeight=%" PRIu64 "\n"
"%sStartupBlockIOWeight=%" PRIu64 "\n"
+ "%sDefaultMemoryMin=%" PRIu64 "\n"
+ "%sDefaultMemoryLow=%" PRIu64 "\n"
"%sMemoryMin=%" PRIu64 "\n"
"%sMemoryLow=%" PRIu64 "\n"
"%sMemoryHigh=%" PRIu64 "\n"
"%sMemoryLimit=%" PRIu64 "\n"
"%sTasksMax=%" PRIu64 "\n"
"%sDevicePolicy=%s\n"
+ "%sDisableControllers=%s\n"
"%sDelegate=%s\n",
prefix, yes_no(c->cpu_accounting),
prefix, yes_no(c->io_accounting),
prefix, c->startup_io_weight,
prefix, c->blockio_weight,
prefix, c->startup_blockio_weight,
+ prefix, c->default_memory_min,
+ prefix, c->default_memory_low,
prefix, c->memory_min,
prefix, c->memory_low,
prefix, c->memory_high,
prefix, c->memory_limit,
prefix, c->tasks_max,
prefix, cgroup_device_policy_to_string(c->device_policy),
+ prefix, strnull(disable_controllers_str),
prefix, yes_no(c->delegate));
if (c->delegate) {
return 0;
}
+#define UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(entry) \
+ uint64_t unit_get_ancestor_##entry(Unit *u) { \
+ CGroupContext *c; \
+ \
+ /* 1. Is entry set in this unit? If so, use that. \
+ * 2. Is the default for this entry set in any \
+ * ancestor? If so, use that. \
+ * 3. Otherwise, return CGROUP_LIMIT_MIN. */ \
+ \
+ assert(u); \
+ \
+ c = unit_get_cgroup_context(u); \
+ \
+ if (c->entry##_set) \
+ return c->entry; \
+ \
+ while (UNIT_ISSET(u->slice)) { \
+ u = UNIT_DEREF(u->slice); \
+ c = unit_get_cgroup_context(u); \
+ \
+ if (c->default_##entry##_set) \
+ return c->default_##entry; \
+ } \
+ \
+ /* We've reached the root, but nobody had default for \
+ * this entry set, so set it to the kernel default. */ \
+ return CGROUP_LIMIT_MIN; \
+}
+
+UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(memory_low);
+UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(memory_min);
+
static void cgroup_xattr_apply(Unit *u) {
char ids[SD_ID128_STRING_MAX];
int r;
(void) set_attribute_and_warn(u, "blkio", "blkio.throttle.write_bps_device", buf);
}
-static bool cgroup_context_has_unified_memory_config(CGroupContext *c) {
- return c->memory_min > 0 || c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || c->memory_swap_max != CGROUP_LIMIT_MAX;
+static bool unit_has_unified_memory_config(Unit *u) {
+ CGroupContext *c;
+
+ assert(u);
+
+ c = unit_get_cgroup_context(u);
+ assert(c);
+
+ return c->memory_min > 0 || unit_get_ancestor_memory_low(u) > 0 ||
+ c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX ||
+ c->memory_swap_max != CGROUP_LIMIT_MAX;
}
static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) {
if (cg_all_unified() > 0) {
uint64_t max, swap_max = CGROUP_LIMIT_MAX;
- if (cgroup_context_has_unified_memory_config(c)) {
+ if (unit_has_unified_memory_config(u)) {
max = c->memory_max;
swap_max = c->memory_swap_max;
} else {
}
cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min);
- cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low);
+ cgroup_apply_unified_memory_limit(u, "memory.low", unit_get_ancestor_memory_low(u));
cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high);
cgroup_apply_unified_memory_limit(u, "memory.max", max);
cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max);
char buf[DECIMAL_STR_MAX(uint64_t) + 1];
uint64_t val;
- if (cgroup_context_has_unified_memory_config(c)) {
+ if (unit_has_unified_memory_config(u)) {
val = c->memory_max;
log_cgroup_compat(u, "Applying MemoryMax=%" PRIi64 " as MemoryLimit=", val);
} else
return false;
}
-static CGroupMask cgroup_context_get_mask(CGroupContext *c) {
+static CGroupMask unit_get_cgroup_mask(Unit *u) {
CGroupMask mask = 0;
+ CGroupContext *c;
+
+ assert(u);
+
+ c = unit_get_cgroup_context(u);
/* Figure out which controllers we need, based on the cgroup context object */
if (c->memory_accounting ||
c->memory_limit != CGROUP_LIMIT_MAX ||
- cgroup_context_has_unified_memory_config(c))
+ unit_has_unified_memory_config(u))
mask |= CGROUP_MASK_MEMORY;
if (c->device_allow ||
if (!c)
return 0;
- return (cgroup_context_get_mask(c) | unit_get_bpf_mask(u) | unit_get_delegate_mask(u)) & ~unit_get_ancestor_disable_mask(u);
+ return (unit_get_cgroup_mask(u) | unit_get_bpf_mask(u) | unit_get_delegate_mask(u)) & ~unit_get_ancestor_disable_mask(u);
}
CGroupMask unit_get_delegate_mask(Unit *u) {
return r;
}
+ 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=",
+ [CGROUP_IO_WRITE_BYTES] = "wbytes=",
+ [CGROUP_IO_READ_OPERATIONS] = "rios=",
+ [CGROUP_IO_WRITE_OPERATIONS] = "wios=",
+ };
+ uint64_t acc[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {};
+ _cleanup_free_ char *path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(u);
+
+ if (!u->cgroup_path)
+ return -ENODATA;
+
+ if (unit_has_host_root_cgroup(u))
+ return -ENODATA; /* TODO: return useful data for the top-level cgroup */
+
+ r = cg_all_unified();
+ if (r < 0)
+ return r;
+ if (r == 0) /* TODO: support cgroupv1 */
+ return -ENODATA;
+
+ if (!FLAGS_SET(u->cgroup_realized_mask, CGROUP_MASK_IO))
+ return -ENODATA;
+
+ r = cg_get_path("io", u->cgroup_path, "io.stat", &path);
+ if (r < 0)
+ return r;
+
+ f = fopen(path, "re");
+ if (!f)
+ return -errno;
+
+ for (;;) {
+ _cleanup_free_ char *line = NULL;
+ const char *p;
+
+ r = read_line(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ p = line;
+ p += strcspn(p, WHITESPACE); /* Skip over device major/minor */
+ p += strspn(p, WHITESPACE); /* Skip over following whitespace */
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) {
+ const char *x;
+
+ x = startswith(word, field_names[i]);
+ if (x) {
+ uint64_t w;
+
+ r = safe_atou64(x, &w);
+ if (r < 0)
+ return r;
+
+ /* Sum up the stats of all devices */
+ acc[i] += w;
+ break;
+ }
+ }
+ }
+ }
+
+ memcpy(ret, acc, sizeof(acc));
+ return 0;
+ }
+
+ int unit_get_io_accounting(
+ Unit *u,
+ CGroupIOAccountingMetric metric,
+ bool allow_cache,
+ uint64_t *ret) {
+
+ uint64_t raw[_CGROUP_IO_ACCOUNTING_METRIC_MAX];
+ int r;
+
+ /* Retrieve an IO account parameter. This will subtract the counter when the unit was started. */
+
+ if (!UNIT_CGROUP_BOOL(u, io_accounting))
+ return -ENODATA;
+
+ if (allow_cache && u->io_accounting_last[metric] != UINT64_MAX)
+ goto done;
+
+ r = unit_get_io_accounting_raw(u, raw);
+ if (r == -ENODATA && u->io_accounting_last[metric] != UINT64_MAX)
+ goto done;
+ if (r < 0)
+ return r;
+
+ for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) {
+ /* Saturated subtraction */
+ if (raw[i] > u->io_accounting_base[i])
+ u->io_accounting_last[i] = raw[i] - u->io_accounting_base[i];
+ else
+ u->io_accounting_last[i] = 0;
+ }
+
+ done:
+ if (ret)
+ *ret = u->io_accounting_last[metric];
+
+ return 0;
+ }
+
int unit_reset_cpu_accounting(Unit *u) {
- nsec_t ns;
int r;
assert(u);
u->cpu_usage_last = NSEC_INFINITY;
- r = unit_get_cpu_usage_raw(u, &ns);
+ r = unit_get_cpu_usage_raw(u, &u->cpu_usage_base);
if (r < 0) {
u->cpu_usage_base = 0;
return r;
}
- u->cpu_usage_base = ns;
return 0;
}
return r < 0 ? r : q;
}
+ int unit_reset_io_accounting(Unit *u) {
+ int r;
+
+ assert(u);
+
+ for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++)
+ u->io_accounting_last[i] = UINT64_MAX;
+
+ r = unit_get_io_accounting_raw(u, u->io_accounting_base);
+ if (r < 0) {
+ zero(u->io_accounting_base);
+ return r;
+ }
+
+ return 0;
+ }
+
+ int unit_reset_accounting(Unit *u) {
+ int r, q, v;
+
+ assert(u);
+
+ r = unit_reset_cpu_accounting(u);
+ q = unit_reset_io_accounting(u);
+ v = unit_reset_ip_accounting(u);
+
+ return r < 0 ? r : q < 0 ? q : v;
+ }
+
void unit_invalidate_cgroup(Unit *u, CGroupMask m) {
assert(u);
LIST_HEAD(CGroupIODeviceLimit, io_device_limits);
LIST_HEAD(CGroupIODeviceLatency, io_device_latencies);
+ uint64_t default_memory_min;
+ uint64_t default_memory_low;
uint64_t memory_min;
uint64_t memory_low;
uint64_t memory_high;
uint64_t memory_max;
uint64_t memory_swap_max;
+ bool default_memory_min_set;
+ bool default_memory_low_set;
+ bool memory_min_set;
+ bool memory_low_set;
+
LIST_HEAD(IPAddressAccessItem, ip_address_allow);
LIST_HEAD(IPAddressAccessItem, ip_address_deny);
_CGROUP_IP_ACCOUNTING_METRIC_INVALID = -1,
} CGroupIPAccountingMetric;
+ /* Used when querying IO accounting data */
+ typedef enum CGroupIOAccountingMetric {
+ CGROUP_IO_READ_BYTES,
+ CGROUP_IO_WRITE_BYTES,
+ CGROUP_IO_READ_OPERATIONS,
+ CGROUP_IO_WRITE_OPERATIONS,
+ _CGROUP_IO_ACCOUNTING_METRIC_MAX,
+ _CGROUP_IO_ACCOUNTING_METRIC_INVALID = -1,
+ } CGroupIOAccountingMetric;
+
typedef struct Unit Unit;
typedef struct Manager Manager;
Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid);
Unit* manager_get_unit_by_pid(Manager *m, pid_t pid);
+uint64_t unit_get_ancestor_memory_min(Unit *u);
+uint64_t unit_get_ancestor_memory_low(Unit *u);
+
int unit_search_main_pid(Unit *u, pid_t *ret);
int unit_watch_all_pids(Unit *u);
int unit_get_memory_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_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_reset_cpu_accounting(Unit *u);
int unit_reset_ip_accounting(Unit *u);
+ int unit_reset_io_accounting(Unit *u);
+ int unit_reset_accounting(Unit *u);
#define UNIT_CGROUP_BOOL(u, name) \
({ \
arg_with_timer = true;
break;
- case ARG_ON_CALENDAR:
+ case ARG_ON_CALENDAR: {
+ _cleanup_(calendar_spec_freep) CalendarSpec *cs = NULL;
+ usec_t next, curr;
+
+ /* Let's make sure the given calendar event is not in the past */
+ curr = now(CLOCK_REALTIME);
+ r = calendar_spec_from_string(optarg, &cs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse calendar event specification");
+ r = calendar_spec_next_usec(cs, curr, &next);
+ if (r < 0) {
+ /* The calendar event is in the past - in such case
+ * don't add an OnCalendar property and execute
+ * the command immediately instead */
+ log_warning("Specified calendar event is in the past, executing immediately");
+ break;
+ }
+
r = add_timer_property("OnCalendar", optarg);
if (r < 0)
return r;
arg_with_timer = true;
break;
+ }
case ARG_ON_TIMEZONE_CHANGE:
r = add_timer_property("OnTimezoneChange", "yes");
uint64_t cpu_usage_nsec;
uint64_t ip_ingress_bytes;
uint64_t ip_egress_bytes;
+ uint64_t io_read_bytes;
+ uint64_t io_write_bytes;
uint32_t exit_code;
uint32_t exit_status;
} RunContext;
{ "CPUUsageNSec", "t", NULL, offsetof(RunContext, cpu_usage_nsec) },
{ "IPIngressBytes", "t", NULL, offsetof(RunContext, ip_ingress_bytes) },
{ "IPEgressBytes", "t", NULL, offsetof(RunContext, ip_egress_bytes) },
+ { "IOReadBytes", "t", NULL, offsetof(RunContext, io_read_bytes) },
+ { "IOWriteBytes", "t", NULL, offsetof(RunContext, io_write_bytes) },
{}
};
.cpu_usage_nsec = NSEC_INFINITY,
.ip_ingress_bytes = UINT64_MAX,
.ip_egress_bytes = UINT64_MAX,
+ .io_read_bytes = UINT64_MAX,
+ .io_write_bytes = UINT64_MAX,
.inactive_exit_usec = USEC_INFINITY,
.inactive_enter_usec = USEC_INFINITY,
};
char bytes[FORMAT_BYTES_MAX];
log_info("IP traffic sent: %s", format_bytes(bytes, sizeof(bytes), c.ip_egress_bytes));
}
+ if (c.io_read_bytes != UINT64_MAX) {
+ char bytes[FORMAT_BYTES_MAX];
+ log_info("IO bytes read: %s", format_bytes(bytes, sizeof(bytes), c.io_read_bytes));
+ }
+ if (c.io_write_bytes != UINT64_MAX) {
+ char bytes[FORMAT_BYTES_MAX];
+ log_info("IO bytes written: %s", format_bytes(bytes, sizeof(bytes), c.io_write_bytes));
+ }
}
/* Try to propagate the service's return value */
uint64_t cpu_usage_nsec;
uint64_t tasks_current;
uint64_t tasks_max;
-
uint64_t ip_ingress_bytes;
uint64_t ip_egress_bytes;
+ uint64_t io_read_bytes;
+ uint64_t io_write_bytes;
+ uint64_t default_memory_min;
+ uint64_t default_memory_low;
+
LIST_HEAD(ExecStatusInfo, exec);
} UnitStatusInfo;
format_bytes(buf_out, sizeof(buf_out), i->ip_egress_bytes));
}
+ if (i->io_read_bytes != UINT64_MAX && i->io_write_bytes != UINT64_MAX) {
+ char buf_in[FORMAT_BYTES_MAX], buf_out[FORMAT_BYTES_MAX];
+
+ printf(" IO: %s read, %s written\n",
+ format_bytes(buf_in, sizeof(buf_in), i->io_read_bytes),
+ format_bytes(buf_out, sizeof(buf_out), i->io_write_bytes));
+ }
+
if (i->tasks_current != (uint64_t) -1) {
printf(" Tasks: %" PRIu64, i->tasks_current);
{ "Where", "s", NULL, offsetof(UnitStatusInfo, where) },
{ "What", "s", NULL, offsetof(UnitStatusInfo, what) },
{ "MemoryCurrent", "t", NULL, offsetof(UnitStatusInfo, memory_current) },
+ { "DefaultMemoryMin", "t", NULL, offsetof(UnitStatusInfo, default_memory_min) },
+ { "DefaultMemoryLow", "t", NULL, offsetof(UnitStatusInfo, default_memory_low) },
{ "MemoryMin", "t", NULL, offsetof(UnitStatusInfo, memory_min) },
{ "MemoryLow", "t", NULL, offsetof(UnitStatusInfo, memory_low) },
{ "MemoryHigh", "t", NULL, offsetof(UnitStatusInfo, memory_high) },
{ "TasksMax", "t", NULL, offsetof(UnitStatusInfo, tasks_max) },
{ "IPIngressBytes", "t", NULL, offsetof(UnitStatusInfo, ip_ingress_bytes) },
{ "IPEgressBytes", "t", NULL, offsetof(UnitStatusInfo, ip_egress_bytes) },
+ { "IOReadBytes", "t", NULL, offsetof(UnitStatusInfo, io_read_bytes) },
+ { "IOWriteBytes", "t", NULL, offsetof(UnitStatusInfo, io_write_bytes) },
{ "ExecStartPre", "a(sasbttttuii)", map_exec, 0 },
{ "ExecStart", "a(sasbttttuii)", map_exec, 0 },
{ "ExecStartPost", "a(sasbttttuii)", map_exec, 0 },
.tasks_max = (uint64_t) -1,
.ip_ingress_bytes = (uint64_t) -1,
.ip_egress_bytes = (uint64_t) -1,
+ .io_read_bytes = UINT64_MAX,
+ .io_write_bytes = UINT64_MAX,
};
char **pp;
int r;