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;
+}
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),
};
// 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 */
#include <unistd.h>
#include <pakfire/cgroup.h>
+#include <pakfire/jail.h>
#include "../testsuite.h"
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);
}