From ebfe4c9d5a75ce5ea9881c55facd5edbdb18ee50 Mon Sep 17 00:00:00 2001 From: Petr Malat Date: Mon, 4 Dec 2023 10:19:16 +0100 Subject: [PATCH] tools: lxc-info: Print memory and cpu utilization on cgroup2 system Emulate the cgroup1 behavior by calculating memory.kmem.usage_in_bytes and memory.usage_in_bytes using information provided in memory.stat and use cpu.stat/usage_usec instead of cpuacct.usage. Signed-off-by: Petr Malat --- src/lxc/tools/lxc_info.c | 172 ++++++++++++++++++++++++++++++++++----- 1 file changed, 153 insertions(+), 19 deletions(-) diff --git a/src/lxc/tools/lxc_info.c b/src/lxc/tools/lxc_info.c index 681b597521..b59dc576a6 100644 --- a/src/lxc/tools/lxc_info.c +++ b/src/lxc/tools/lxc_info.c @@ -16,6 +16,7 @@ #include "arguments.h" #include "log.h" #include "utils.h" +#include "macro.h" lxc_log_define(lxc_info, lxc); @@ -196,14 +197,158 @@ static void print_net_stats(struct lxc_container *c) } } -static void print_stats(struct lxc_container *c) +static int cg2_stat_iter(struct lxc_container *c, const char *file, void *cookie, + int (*iter)(char *key, char *val, void *cookie)) { - int i, ret; + char *fileptr, *lineptr, *line, *metric, *value; char buf[4096]; + int ret; + + ret = c->get_cgroup_item(c, file, buf, sizeof(buf)); + if (ret < 0) + return ret; + + if ((size_t)ret >= sizeof(buf)) { + fprintf(stderr, "Internal buffer too small to read '%s'\n", file); + return -EMSGSIZE; + } + + for (line = strtok_r(buf, "\n", &fileptr); line; line = strtok_r(NULL, "\n", &fileptr)) { + metric = strtok_r(line, " ", &lineptr); + if (!metric) + goto err; + + value = strtok_r(NULL, " ", &lineptr); + if (!value) + goto err; + + ret = iter(metric, value, cookie); + if (ret) + return ret; + } + + return 0; + +err: fprintf(stderr, "Unexpected syntax of memory.stat\n"); + return -EIO; +} + +static int cg1_cpu_usage(struct lxc_container *c, char *usage) +{ + char buf[64]; + int ret; ret = c->get_cgroup_item(c, "cpuacct.usage", buf, sizeof(buf)); if (ret > 0 && (size_t)ret < sizeof(buf)) { str_chomp(buf); + strcpy(usage, buf); + return 0; + } + + return ret; +} + +static int cg2_cpu_usage_iter(char *metric, char *value, void *cookie) +{ + if (strcmp(metric, "usage_usec")) + return 0; + + strcpy(cookie, value); + strcat(cookie, "000"); + return 1; +} + +static int cg2_cpu_usage(struct lxc_container *c, char *usage) +{ + int ret; + + ret = cg2_stat_iter(c, "cpu.stat", usage, cg2_cpu_usage_iter); + return ret == 1 ? 0 : ret ?: -ESRCH; +} + +static int search_array(const char *array[], const char *needle, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) + if (!strcmp(array[i], needle)) + return 1; + return 0; +} + +struct cg2_mem_usage_s { + unsigned long long user, kernel; +}; + +static int cg2_mem_usage_iter(char *metric, char *value, void *cookie) +{ + const char *kernel_fields[] = { "kernel_stack", "pagetables", "sock", "slab" }; + const char *user_fields[] = { "anon", "file" }; + struct cg2_mem_usage_s *usage = cookie; + unsigned long long numvalue; + char *end; + + numvalue = strtoull(value, &end, 10); + if (numvalue == ULLONG_MAX || *end) { + fprintf(stderr, "Unexpected syntax of memory.stat\n"); + return -EIO; + } + + if (search_array(kernel_fields, metric, ARRAY_SIZE(kernel_fields))) + usage->kernel += numvalue; + else if (search_array(user_fields, metric, ARRAY_SIZE(user_fields))) + usage->user += numvalue; + return 0; +} + +static int cg2_mem_usage(struct lxc_container *c, char *user, char *kernel) +{ + struct cg2_mem_usage_s usage = { 0 }; + int ret; + + ret = cg2_stat_iter(c, "memory.stat", &usage, cg2_mem_usage_iter); + if (ret < 0) + return ret; + + sprintf(kernel, "%llu", usage.kernel); + sprintf(user, "%llu", usage.user); + + return 0; + +} + +static int cg1_mem_usage(struct lxc_container *c, char *user, char *kernel) +{ + struct { + const char *file; + char *target; + } lxstat[] = { + { "memory.usage_in_bytes", user }, + { "memory.kmem.usage_in_bytes", kernel }, + }; + int ret, i, match = 0; + char buf[64]; + + for (i = 0; i < (int)ARRAY_SIZE(lxstat); i++) { + ret = c->get_cgroup_item(c, lxstat[i].file, buf, sizeof(buf)); + if (ret > 0 && (size_t)ret < sizeof(buf)) { + str_chomp(buf); + strcpy(lxstat[i].target, buf); + match++; + } + } + + return match == ARRAY_SIZE(lxstat) ? 0 : -ESRCH; +} + +static void print_stats(struct lxc_container *c) +{ + int ret; + char buf[4096]; + char *user = buf; + char *kernel = buf + sizeof(buf) / 2; + + if (!cg1_cpu_usage(c, buf) || !cg2_cpu_usage(c, buf)) { if (humanize) { float seconds = strtof(buf, NULL) / 1000000000.0; printf("%-15s %.2f seconds\n", "CPU use:", seconds); @@ -233,23 +378,12 @@ static void print_stats(struct lxc_container *c) fflush(stdout); } - static const struct { - const char *name; - const char *file; - } lxstat[] = { - { "Memory use:", "memory.usage_in_bytes" }, - { "KMem use:", "memory.kmem.usage_in_bytes" }, - { NULL, NULL }, - }; - - for (i = 0; lxstat[i].name; i++) { - ret = c->get_cgroup_item(c, lxstat[i].file, buf, sizeof(buf)); - if (ret > 0 && (size_t)ret < sizeof(buf)) { - str_chomp(buf); - str_size_humanize(buf, sizeof(buf)); - printf("%-15s %s\n", lxstat[i].name, buf); - fflush(stdout); - } + if (!cg1_mem_usage(c, user, kernel) || !cg2_mem_usage(c, user, kernel)) { + str_size_humanize(user, sizeof(buf) / 2 - 1); + printf("%-15s %s\n", "Memory use:", user); + str_size_humanize(kernel, sizeof(buf) / 2 - 1); + printf("%-15s %s\n", "KMem use:", kernel); + fflush(stdout); } }