From: Michael Tremer Date: Wed, 10 Aug 2022 20:56:10 +0000 (+0000) Subject: cgroup: Implement reading stats X-Git-Tag: 0.9.28~526 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b7cf275cc6a5811d3f1af6330cb60ed284f2189;p=pakfire.git cgroup: Implement reading stats Signed-off-by: Michael Tremer --- diff --git a/src/libpakfire/cgroup.c b/src/libpakfire/cgroup.c index 9a95c4447..d295738ea 100644 --- a/src/libpakfire/cgroup.c +++ b/src/libpakfire/cgroup.c @@ -718,3 +718,220 @@ int pakfire_cgroup_set_pid_limit(struct pakfire_cgroup* cgroup, size_t limit) { return r; } + +// Stats + +static int __pakfire_cgroup_read_stats_line(struct pakfire_cgroup* cgroup, + int (*callback)(struct pakfire_cgroup* cgroup, const char* key, unsigned long val, void* data), + void* data, char* line) { + char* p = NULL; + + DEBUG(cgroup->pakfire, "Parsing line: %s\n", line); + + char key[NAME_MAX]; + unsigned long val = 0; + + // Number of the field + int i = 0; + + char* elem = strtok_r(line, " ", &p); + while (elem) { + switch (i++) { + // First field is the key + case 0: + // Copy the key + pakfire_string_set(key, elem); + break; + + // The second field is some value + case 1: + val = strtoul(elem, NULL, 10); + break; + + // Ignore the rest + default: + DEBUG(cgroup->pakfire, "%s: Unknown value in cgroup stats (%d): %s\n", + pakfire_cgroup_name(cgroup), i, elem); + break; + } + + elem = strtok_r(NULL, " ", &p); + } + + // Check if we parsed both fields + if (i < 2) { + ERROR(cgroup->pakfire, "Could not parse line\n"); + return 1; + } + + // Call the callback + return callback(cgroup, key, val, data); +} + +static int __pakfire_cgroup_read_stats(struct pakfire_cgroup* cgroup, const char* path, + int (*callback)(struct pakfire_cgroup* cgroup, const char* key, unsigned long val, void* data), + void* data) { + char* p = NULL; + int r; + + char buffer[BUFFER_SIZE]; + + DEBUG(cgroup->pakfire, "%s: Reading stats from %s\n", pakfire_cgroup_name(cgroup), path); + + // Open the file + r = pakfire_cgroup_read(cgroup, path, buffer, sizeof(buffer)); + if (r < 0) + goto ERROR; + + char* line = strtok_r(buffer, "\n", &p); + while (line) { + // Parse the line + r = __pakfire_cgroup_read_stats_line(cgroup, callback, data, line); + if (r) + goto ERROR; + + // Move to the next line + line = strtok_r(NULL, "\n", &p); + } + +ERROR: + return r; +} + +struct pakfire_cgroup_stat_entry { + const char* key; + unsigned long* val; +}; + +static int __pakfire_cgroup_parse_cpu_stats(struct pakfire_cgroup* cgroup, + const char* key, unsigned long val, void* data) { + struct pakfire_cgroup_cpu_stats* stats = (struct pakfire_cgroup_cpu_stats*)data; + + const struct pakfire_cgroup_stat_entry entries[] = { + { "system_usec", &stats->system_usec }, + { "usage_usec", &stats->usage_usec }, + { "user_usec", &stats->user_usec }, + { NULL, NULL }, + }; + // Find and store value + for (const struct pakfire_cgroup_stat_entry* entry = entries; entry->key; entry++) { + if (strcmp(entry->key, key) == 0) { + *entry->val = val; + return 0; + } + } + + DEBUG(cgroup->pakfire, "Unknown key for CPU stats: %s = %ld\n", key, val); + + return 0; +} + +static int __pakfire_cgroup_parse_memory_stats(struct pakfire_cgroup* cgroup, + const char* key, unsigned long val, void* data) { + struct pakfire_cgroup_memory_stats* stats = (struct pakfire_cgroup_memory_stats*)data; + + const struct pakfire_cgroup_stat_entry entries[] = { + { "anon", &stats->anon }, + { "file", &stats->file }, + { "kernel", &stats->kernel }, + { "kernel_stack", &stats->kernel_stack }, + { "pagetables", &stats->pagetables }, + { "percpu", &stats->percpu }, + { "sock", &stats->sock }, + { "vmalloc", &stats->vmalloc }, + { "shmem", &stats->shmem }, + { "zswap", &stats->zswap }, + { "zswapped", &stats->zswapped }, + { "file_mapped", &stats->file_mapped }, + { "file_dirty", &stats->file_dirty }, + { "file_writeback", &stats->file_writeback }, + { "swapcached", &stats->swapcached }, + { "anon_thp", &stats->anon_thp }, + { "file_thp", &stats->file_thp }, + { "shmem_thp", &stats->shmem_thp }, + { "inactive_anon", &stats->inactive_anon }, + { "active_anon", &stats->active_anon }, + { "inactive_file", &stats->inactive_file }, + { "active_file", &stats->active_file }, + { "unevictable", &stats->unevictable }, + { "slab_reclaimable", &stats->slab_reclaimable }, + { "slab_unreclaimable", &stats->slab_unreclaimable }, + { "slab", &stats->slab }, + { "workingset_refault_anon", &stats->workingset_refault_anon }, + { "workingset_refault_file", &stats->workingset_refault_file }, + { "workingset_activate_anon", &stats->workingset_activate_anon }, + { "workingset_activate_file", &stats->workingset_activate_file }, + { "workingset_restore_anon", &stats->workingset_restore_anon }, + { "workingset_restore_file", &stats->workingset_restore_file }, + { "workingset_nodereclaim", &stats->workingset_nodereclaim }, + { "pgfault", &stats->pgfault }, + { "pgmajfault", &stats->pgmajfault }, + { "pgrefill", &stats->pgrefill }, + { "pgscan", &stats->pgscan }, + { "pgsteal", &stats->pgsteal }, + { "pgactivate", &stats->pgactivate }, + { "pgdeactivate", &stats->pgdeactivate }, + { "pglazyfree", &stats->pglazyfree }, + { "pglazyfreed", &stats->pglazyfreed }, + { "thp_fault_alloc", &stats->thp_fault_alloc }, + { "thp_collapse_alloc", &stats->thp_collapse_alloc }, + { NULL, NULL }, + }; + + // Find and store value + for (const struct pakfire_cgroup_stat_entry* entry = entries; entry->key; entry++) { + if (strcmp(entry->key, key) == 0) { + *entry->val = val; + return 0; + } + } + + // Log any unknown keys + DEBUG(cgroup->pakfire, "Unknown key for memory stats: %s = %ld\n", key, val); + + return 0; +} + +int pakfire_cgroup_stat(struct pakfire_cgroup* cgroup, + struct pakfire_cgroup_stats* stats) { + int r; + + // Check input + if (!stats) { + errno = EINVAL; + return 1; + } + + // Read CPU stats + r = __pakfire_cgroup_read_stats(cgroup, "cpu.stat", + __pakfire_cgroup_parse_cpu_stats, &stats->cpu); + if (r) + goto ERROR; + + // Read memory stats + r = __pakfire_cgroup_read_stats(cgroup, "memory.stat", + __pakfire_cgroup_parse_memory_stats, &stats->memory); + if (r) + goto ERROR; + +ERROR: + if (r) + ERROR(cgroup->pakfire, "%s: Could not read cgroup stats: %m\n", + pakfire_cgroup_name(cgroup)); + + return r; +} + +int pakfire_cgroup_stat_dump(struct pakfire_cgroup* cgroup, + const struct pakfire_cgroup_stats* stats) { + // Check input + if (!stats) { + errno = EINVAL; + return 1; + } + + DEBUG(cgroup->pakfire, "%s: Total CPU time usage: %lu\n", + pakfire_cgroup_name(cgroup), stats->cpu.usage_usec); + + return 0; +} diff --git a/src/libpakfire/include/pakfire/cgroup.h b/src/libpakfire/include/pakfire/cgroup.h index 80903c977..716809f08 100644 --- a/src/libpakfire/include/pakfire/cgroup.h +++ b/src/libpakfire/include/pakfire/cgroup.h @@ -27,6 +27,159 @@ struct pakfire_cgroup; +struct pakfire_cgroup_stats { + // CPU + struct pakfire_cgroup_cpu_stats { + unsigned long system_usec; + unsigned long usage_usec; + unsigned long user_usec; + + unsigned long nr_periods; + unsigned long nr_throttled; + unsigned long throttled_usec; + unsigned long nr_bursts; + unsigned long burst_usec; + } cpu; + + // Memory + struct pakfire_cgroup_memory_stats { + // Amount of memory used in anonymous mappings + unsigned long anon; + + // Amount of memory used to cache filesystem data + unsigned long file; + + // Amount of total kernel memory + unsigned long kernel; + + // Amount of memory allocated to kernel stacks + unsigned long kernel_stack; + + // Amount of memory allocated for page tables + unsigned long pagetables; + + // Amount of memory used for storing per-cpu kernel data structures + unsigned long percpu; + + // Amount of memory used in network transmission buffers + unsigned long sock; + + // Amount of memory used for vmap backed memory + unsigned long vmalloc; + + // Amount of cached filesystem data that is swap-backed, + // such as tmpfs, shm segments, shared anonymous mmap()s + unsigned long shmem; + + // Amount of memory consumed by the zswap compression backend + unsigned long zswap; + + // Amount of application memory swapped out to zswap + unsigned long zswapped; + + // Amount of cached filesystem data mapped with mmap() + unsigned long file_mapped; + + // Amount of cached filesystem data that was modified but + // not yet written back to disk + unsigned long file_dirty; + + // Amount of cached filesystem data that was modified and + // is currently being written back to disk + unsigned long file_writeback; + + // Amount of swap cached in memory. The swapcache is accounted + // against both memory and swap usage. + unsigned long swapcached; + + // Amount of memory used in anonymous mappings backed by + // transparent hugepages + unsigned long anon_thp; + + // Amount of cached filesystem data backed by transparent hugepages + unsigned long file_thp; + + // Amount of shm, tmpfs, shared anonymous mmap()s backed by + // transparent hugepages + unsigned long shmem_thp; + + // Amount of memory, swap-backed and filesystem-backed, + // on the internal memory management lists used by the + // page reclaim algorithm. + unsigned long inactive_anon; + unsigned long active_anon; + unsigned long inactive_file; + unsigned long active_file; + unsigned long unevictable; + + // Part of "slab" that might be reclaimed, such as dentries and inodes + unsigned long slab_reclaimable; + + // Part of "slab" that cannot be reclaimed on memory pressure + unsigned long slab_unreclaimable; + + // Amount of memory used for storing in-kernel data structures + unsigned long slab; + + // Number of refaults of previously evicted anonymous pages + unsigned long workingset_refault_anon; + + // Number of refaults of previously evicted file pages + unsigned long workingset_refault_file; + + // Number of refaulted anonymous pages that were immediately activated + unsigned long workingset_activate_anon; + + // Number of refaulted file pages that were immediately activated + unsigned long workingset_activate_file; + + // Number of restored anonymous pages which have been detected as + // an active workingset before they got reclaimed. + unsigned long workingset_restore_anon; + + // Number of restored file pages which have been detected as an + // active workingset before they got reclaimed. + unsigned long workingset_restore_file; + + // Number of times a shadow node has been reclaimed + unsigned long workingset_nodereclaim; + + // Total number of page faults incurred + unsigned long pgfault; + + // Number of major page faults incurred + unsigned long pgmajfault; + + // Amount of scanned pages (in an active LRU list) + unsigned long pgrefill; + + // Amount of scanned pages (in an inactive LRU list) + unsigned long pgscan; + + // Amount of reclaimed pages + unsigned long pgsteal; + + // Amount of pages moved to the active LRU list + unsigned long pgactivate; + + // Amount of pages moved to the inactive LRU list + unsigned long pgdeactivate; + + // Amount of pages postponed to be freed under memory pressure + unsigned long pglazyfree; + + // Amount of reclaimed lazyfree pages + unsigned long pglazyfreed; + + // Number of transparent hugepages which were allocated to satisfy a page fault + unsigned long thp_fault_alloc; + + // Number of transparent hugepages which were allocated to allow + // collapsing an existing range of pages. + unsigned long thp_collapse_alloc; + } memory; +}; + enum pakfire_cgroup_flags { PAKFIRE_CGROUP_ENABLE_ACCOUNTING = (1 << 0), }; @@ -52,6 +205,12 @@ int pakfire_cgroup_set_memory_limit(struct pakfire_cgroup* cgroup, size_t mem); // PIDs int pakfire_cgroup_set_pid_limit(struct pakfire_cgroup* cgroup, size_t limit); +// Stats +int pakfire_cgroup_stat(struct pakfire_cgroup* cgroup, + struct pakfire_cgroup_stats* stats); +int pakfire_cgroup_stat_dump(struct pakfire_cgroup* cgroup, + const struct pakfire_cgroup_stats* stats); + #endif /* PAKFIRE_PRIVATE */ #endif /* PAKFIRE_CGROUP_H */ diff --git a/src/libpakfire/jail.c b/src/libpakfire/jail.c index 92faad6fc..18bca5e09 100644 --- a/src/libpakfire/jail.c +++ b/src/libpakfire/jail.c @@ -122,6 +122,7 @@ struct pakfire_jail_exec { } buffers; struct pakfire_cgroup* cgroup; + struct pakfire_cgroup_stats cgroup_stats; }; static int clone3(struct clone_args* args, size_t size) { @@ -1366,6 +1367,14 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[]) { ERROR: // Destroy the temporary cgroup (if any) if (ctx.cgroup) { + // Read cgroup stats + r = pakfire_cgroup_stat(ctx.cgroup, &ctx.cgroup_stats); + if (r) { + ERROR(jail->pakfire, "Could not read cgroup stats: %m\n"); + } else { + pakfire_cgroup_stat_dump(ctx.cgroup, &ctx.cgroup_stats); + } + pakfire_cgroup_destroy(ctx.cgroup); pakfire_cgroup_unref(ctx.cgroup); } diff --git a/tests/libpakfire/cgroup.c b/tests/libpakfire/cgroup.c index 3b43236f1..c37084281 100644 --- a/tests/libpakfire/cgroup.c +++ b/tests/libpakfire/cgroup.c @@ -21,6 +21,7 @@ #include #include +#include #include "../testsuite.h" @@ -44,8 +45,64 @@ FAIL: return r; } +static int test_stats(const struct test* t) { + struct pakfire_cgroup* cgroup = NULL; + struct pakfire_jail* jail = NULL; + int r = EXIT_FAILURE; + + struct pakfire_cgroup_stats stats; + + const char* argv[] = { + "/command", "sleep", "1", NULL, + }; + + // Open the cgroup + ASSERT_SUCCESS(pakfire_cgroup_open(&cgroup, t->pakfire, "pakfire-test", + PAKFIRE_CGROUP_ENABLE_ACCOUNTING)); + +#if 0 + // Enable CPU & Memory controllers + ASSERT_SUCCESS(pakfire_cgroup_enable_controllers(cgroup, + PAKFIRE_CGROUP_CONTROLLER_CPU|PAKFIRE_CGROUP_CONTROLLER_MEMORY)); +#endif + + // Create a new jail + ASSERT_SUCCESS(pakfire_jail_create(&jail, t->pakfire, 0)); + + // Connect jail to the cgroup + ASSERT_SUCCESS(pakfire_jail_set_cgroup(jail, cgroup)); + + // Run a few things + for (unsigned int i = 0; i < 3; i++) { + ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, NULL)); + } + + // Try reading the stats into some invalid space + ASSERT_ERRNO(pakfire_cgroup_stat(cgroup, NULL), EINVAL); + + // Read the stats + ASSERT_SUCCESS(pakfire_cgroup_stat(cgroup, &stats)); + + // We must have had some CPU usage + ASSERT(stats.cpu.usage_usec > 0); + + // Success + r = EXIT_SUCCESS; + +FAIL: + if (cgroup) { + pakfire_cgroup_destroy(cgroup); + pakfire_cgroup_unref(cgroup); + } + if (jail) + pakfire_jail_unref(jail); + + return r; +} + int main(int argc, const char* argv[]) { testsuite_add_test(test_create_and_destroy); + testsuite_add_test(test_stats); return testsuite_run(argc, argv); }