]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared: helpers to read pressure stats from cgroups
authorAnita Zhang <the.anitazha@gmail.com>
Sun, 8 Mar 2020 01:58:33 +0000 (17:58 -0800)
committerAnita Zhang <the.anitazha@gmail.com>
Wed, 7 Oct 2020 23:17:24 +0000 (16:17 -0700)
src/shared/meson.build
src/shared/psi-util.c [new file with mode: 0644]
src/shared/psi-util.h [new file with mode: 0644]
src/test/meson.build
src/test/test-psi-util.c [new file with mode: 0644]

index 723e00e4377d06bd4eb00ad8188c7da007d61e07..9ef12959e53282fb9bbf3a3695b3599b41c0ddcd 100644 (file)
@@ -187,6 +187,8 @@ shared_sources = files('''
         pkcs11-util.h
         pretty-print.c
         pretty-print.h
+        psi-util.c
+        psi-util.h
         ptyfwd.c
         ptyfwd.h
         pwquality-util.c
diff --git a/src/shared/psi-util.c b/src/shared/psi-util.c
new file mode 100644 (file)
index 0000000..21e965b
--- /dev/null
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "parse-util.h"
+#include "psi-util.h"
+#include "string-util.h"
+#include "stat-util.h"
+#include "strv.h"
+
+int read_resource_pressure(const char *path, PressureType type, ResourcePressure *ret) {
+        _cleanup_free_ char *line = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        unsigned field_filled = 0;
+        ResourcePressure rp = {};
+        const char *t, *cline;
+        char *word;
+        int r;
+
+        assert(path);
+        assert(IN_SET(type, PRESSURE_TYPE_SOME, PRESSURE_TYPE_FULL));
+        assert(ret);
+
+        if (type == PRESSURE_TYPE_SOME)
+                t = "some";
+        else if (type == PRESSURE_TYPE_FULL)
+                t = "full";
+        else
+                return -EINVAL;
+
+        r = fopen_unlocked(path, "re", &f);
+         if (r < 0)
+                 return r;
+
+        for (;;) {
+                _cleanup_free_ char *l = NULL;
+                char *w;
+
+                r = read_line(f, LONG_LINE_MAX, &l);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                w = first_word(l, t);
+                if (w) {
+                        line = TAKE_PTR(l);
+                        cline = w;
+                        break;
+                }
+        }
+
+        if (!line)
+                return -ENODATA;
+
+        /* extracts either avgX=Y.Z or total=X */
+        while ((r = extract_first_word(&cline, &word, NULL, 0)) > 0) {
+                _cleanup_free_ char *w = word;
+                const char *v;
+
+                if ((v = startswith(w, "avg10="))) {
+                        if (field_filled & (1U << 0))
+                                return -EINVAL;
+
+                        field_filled |= 1U << 0;
+                        r = parse_loadavg_fixed_point(v, &rp.avg10);
+                } else if ((v = startswith(w, "avg60="))) {
+                        if (field_filled & (1U << 1))
+                                return -EINVAL;
+
+                        field_filled |= 1U << 1;
+                        r = parse_loadavg_fixed_point(v, &rp.avg60);
+                } else if ((v = startswith(w, "avg300="))) {
+                        if (field_filled & (1U << 2))
+                                return -EINVAL;
+
+                        field_filled |= 1U << 2;
+                        r = parse_loadavg_fixed_point(v, &rp.avg300);
+                } else if ((v = startswith(w, "total="))) {
+                        if (field_filled & (1U << 3))
+                                return -EINVAL;
+
+                        field_filled |= 1U << 3;
+                        r = safe_atou64(v, &rp.total);
+                } else
+                        continue;
+
+                if (r < 0)
+                        return r;
+        }
+
+        if (r < 0)
+                return r;
+
+        if (field_filled != 15U)
+                return -EINVAL;
+
+        *ret = rp;
+        return 0;
+}
+
+int is_pressure_supported(void) {
+        const char *p;
+
+        FOREACH_STRING(p, "/proc/pressure/cpu", "/proc/pressure/io", "/proc/pressure/memory")
+                if (access(p, F_OK) < 0) {
+                        if (errno == ENOENT)
+                                return 0;
+                        return -errno;
+                }
+
+        return 1;
+}
diff --git a/src/shared/psi-util.h b/src/shared/psi-util.h
new file mode 100644 (file)
index 0000000..9810dbe
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "parse-util.h"
+#include "time-util.h"
+
+typedef enum PressureType {
+        PRESSURE_TYPE_SOME,
+        PRESSURE_TYPE_FULL,
+} PressureType;
+
+/* Averages are stored in fixed-point with 11 bit fractions */
+typedef struct ResourcePressure {
+        loadavg_t avg10;
+        loadavg_t avg60;
+        loadavg_t avg300;
+        usec_t total;
+} ResourcePressure;
+
+/** Upstream 4.20+ format
+ *
+ *  some avg10=0.22 avg60=0.17 avg300=1.11 total=58761459
+ *  full avg10=0.23 avg60=0.16 avg300=1.08 total=58464525
+ */
+int read_resource_pressure(const char *path, PressureType type, ResourcePressure *ret);
+
+/* Was the kernel compiled with CONFIG_PSI=y? 1 if yes, 0 if not, negative on error. */
+int is_pressure_supported(void);
index 9bb349996372f8669f0eb3d7904355aac009a0e8..60dec9512c60748a737d645e958890acb4fa79eb 100644 (file)
@@ -804,6 +804,10 @@ tests += [
         [['src/test/test-local-addresses.c'],
          [],
          []],
+
+        [['src/test/test-psi-util.c'],
+         [],
+         []],
 ]
 
 ############################################################
diff --git a/src/test/test-psi-util.c b/src/test/test-psi-util.c
new file mode 100644 (file)
index 0000000..bde8ef8
--- /dev/null
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <linux/loadavg.h>
+
+#include "alloc-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "psi-util.h"
+#include "tests.h"
+
+static void test_read_mem_pressure(void) {
+        _cleanup_(unlink_tempfilep) char path[] = "/tmp/pressurereadtestXXXXXX";
+        ResourcePressure rp;
+
+        if (geteuid() != 0)
+                return (void) log_tests_skipped("not root");
+
+        assert_se(mkstemp(path));
+
+        assert_se(read_resource_pressure("/verylikelynonexistentpath", PRESSURE_TYPE_SOME, &rp) < 0);
+        assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0);
+
+        assert_se(write_string_file(path, "herpdederp\n", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0);
+
+        /* Pressure file with some invalid values*/
+        assert_se(write_string_file(path, "some avg10=0.22=55 avg60=0.17=8 avg300=1.11=00 total=58761459\n"
+                                          "full avg10=0.23=55 avg60=0.16=8 avg300=1.08=00 total=58464525", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0);
+
+        /* Same pressure valid values as below but with duplicate avg60 field */
+        assert_se(write_string_file(path, "some avg10=0.22 avg60=0.17 avg60=0.18 avg300=1.11 total=58761459\n"
+                                          "full avg10=0.23 avg60=0.16 avg300=1.08 total=58464525", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0);
+
+        assert_se(write_string_file(path, "some avg10=0.22 avg60=0.17 avg300=1.11 total=58761459\n"
+                                          "full avg10=0.23 avg60=0.16 avg300=1.08 total=58464525", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) == 0);
+        assert_se(LOAD_INT(rp.avg10) == 0);
+        assert_se(LOAD_FRAC(rp.avg10) == 22);
+        assert_se(LOAD_INT(rp.avg60) == 0);
+        assert_se(LOAD_FRAC(rp.avg60) == 17);
+        assert_se(LOAD_INT(rp.avg300) == 1);
+        assert_se(LOAD_FRAC(rp.avg300) == 11);
+        assert_se(rp.total == 58761459);
+        assert(read_resource_pressure(path, PRESSURE_TYPE_FULL, &rp) == 0);
+        assert_se(LOAD_INT(rp.avg10) == 0);
+        assert_se(LOAD_FRAC(rp.avg10) == 23);
+        assert_se(LOAD_INT(rp.avg60) == 0);
+        assert_se(LOAD_FRAC(rp.avg60) == 16);
+        assert_se(LOAD_INT(rp.avg300) == 1);
+        assert_se(LOAD_FRAC(rp.avg300) == 8);
+        assert_se(rp.total == 58464525);
+
+        /* Pressure file with extra unsupported fields */
+        assert_se(write_string_file(path, "some avg5=0.55 avg10=0.22 avg60=0.17 avg300=1.11 total=58761459\n"
+                                          "full avg10=0.23 avg60=0.16 avg300=1.08 avg600=2.00 total=58464525", WRITE_STRING_FILE_CREATE) == 0);
+        assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) == 0);
+        assert_se(LOAD_INT(rp.avg10) == 0);
+        assert_se(LOAD_FRAC(rp.avg10) == 22);
+        assert_se(LOAD_INT(rp.avg60) == 0);
+        assert_se(LOAD_FRAC(rp.avg60) == 17);
+        assert_se(LOAD_INT(rp.avg300) == 1);
+        assert_se(LOAD_FRAC(rp.avg300) == 11);
+        assert_se(rp.total == 58761459);
+        assert(read_resource_pressure(path, PRESSURE_TYPE_FULL, &rp) == 0);
+        assert_se(LOAD_INT(rp.avg10) == 0);
+        assert_se(LOAD_FRAC(rp.avg10) == 23);
+        assert_se(LOAD_INT(rp.avg60) == 0);
+        assert_se(LOAD_FRAC(rp.avg60) == 16);
+        assert_se(LOAD_INT(rp.avg300) == 1);
+        assert_se(LOAD_FRAC(rp.avg300) == 8);
+        assert_se(rp.total == 58464525);
+}
+
+int main(void) {
+        test_setup_logging(LOG_DEBUG);
+        test_read_mem_pressure();
+        return 0;
+}