]> git.ipfire.org Git - people/stevee/pakfire.git/commitdiff
cgroup: Implement reading stats
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 10 Aug 2022 20:56:10 +0000 (20:56 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 10 Aug 2022 20:56:10 +0000 (20:56 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/libpakfire/cgroup.c
src/libpakfire/include/pakfire/cgroup.h
src/libpakfire/jail.c
tests/libpakfire/cgroup.c

index 9a95c444739b751fd6b4843afe5b0719adec8de9..d295738ea0f190a760a3df326dc7439cb8963f79 100644 (file)
@@ -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;
+}
index 80903c97714e6de94d4f3a7f1130d528d6ce7864..716809f083484173fb1c98b2e1484cadad6da75d 100644 (file)
 
 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 */
index 92faad6fc0bb2e75b1e5c0424d60d39a01440173..18bca5e09b41c945902fef09c8f8f697384347b9 100644 (file)
@@ -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);
        }
index 3b43236f1321c90b8091df7e300e7fac92eb7a9b..c3708428192f27c36b00a53ab73052b907bdb9a8 100644 (file)
@@ -21,6 +21,7 @@
 #include <unistd.h>
 
 #include <pakfire/cgroup.h>
+#include <pakfire/jail.h>
 
 #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);
 }