]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
util: Add virProcessGetStat
authorMartin Kletzander <mkletzan@redhat.com>
Thu, 18 Nov 2021 15:13:52 +0000 (16:13 +0100)
committerMartin Kletzander <mkletzan@redhat.com>
Tue, 23 Nov 2021 15:43:08 +0000 (16:43 +0100)
This reads and separates all fields from /proc/<pid>/stat or
/proc/<pid>/task/<tid>/stat as there are easy mistakes to be done in the
implementation.  Some tests are added to show it works correctly.  No number
parsing is done as it would be unused for most of the fields most, if not all,
of the time.  No struct is used for the result as the length can vary (new
fields can be added in the future).

Signed-off-by: Martin Kletzander <mkletzan@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
src/libvirt_private.syms
src/util/virprocess.c
src/util/virprocess.h
tests/meson.build
tests/virprocessstatdata/complex/stat [new file with mode: 0644]
tests/virprocessstatdata/simple/stat [new file with mode: 0644]
tests/virprocessstattest.c [new file with mode: 0644]

index c11be4eafa195c9d19281c8a44b156cb336facf6..95bf1db39d6462b35a59875bab164d252cc61612 100644 (file)
@@ -3100,6 +3100,7 @@ virProcessGetMaxMemLock;
 virProcessGetNamespaces;
 virProcessGetPids;
 virProcessGetStartTime;
+virProcessGetStat;
 virProcessGroupGet;
 virProcessGroupKill;
 virProcessKill;
index 81de90200e7cfa982f303391d576e20de9ac91f0..1d5ef048b2d5448212a17808a5898085905e2b8a 100644 (file)
@@ -1721,3 +1721,80 @@ virProcessSetScheduler(pid_t pid G_GNUC_UNUSED,
 }
 
 #endif /* !WITH_SCHED_SETSCHEDULER */
+
+/*
+ * Get all stat fields for a process based on pid and tid:
+ * - pid == 0 && tid == 0 => /proc/self/stat
+ * - pid != 0 && tid == 0 => /proc/<pid>/stat
+ * - pid == 0 && tid != 0 => /proc/self/task/<tid>/stat
+ * - pid != 0 && tid != 0 => /proc/<pid>/task/<tid>/stat
+ * and return them as array of strings.
+ */
+GStrv
+virProcessGetStat(pid_t pid,
+                  pid_t tid)
+{
+    int len = 10 * 1024;  /* 10kB ought to be enough for everyone */
+    g_autofree char *buf = NULL;
+    g_autofree char *path = NULL;
+    GStrv rest = NULL;
+    GStrv ret = NULL;
+    char *comm = NULL;
+    char *rparen = NULL;
+    size_t nrest = 0;
+
+    if (pid) {
+        if (tid)
+            path = g_strdup_printf("/proc/%d/task/%d/stat", (int)pid, (int)tid);
+        else
+            path = g_strdup_printf("/proc/%d/stat", (int)pid);
+    } else {
+        if (tid)
+            path = g_strdup_printf("/proc/self/task/%d/stat", (int)tid);
+        else
+            path = g_strdup("/proc/self/stat");
+    }
+
+    len = virFileReadAllQuiet(path, len, &buf);
+    if (len < 0)
+        return NULL;
+
+    /* eliminate trailing spaces */
+    while (len > 0 && g_ascii_isspace(buf[--len]))
+           buf[len] = '\0';
+
+    /* Find end of the first field */
+    if (!(comm = strchr(buf, ' ')))
+        return NULL;
+    *comm = '\0';
+
+    /* Check start of the second field (filename of the executable, in
+     * parentheses) */
+    comm++;
+    if (*comm != '(')
+        return NULL;
+    comm++;
+
+    /* Check end of the second field (last closing parenthesis) */
+    rparen = strrchr(comm, ')');
+    if (!rparen)
+        return NULL;
+    *rparen = '\0';
+
+    /* We need to check that the next char is not '\0', but why not just opt in
+     * for the safer way of checking whether it is ' ' (space) instead */
+    if (rparen[1] != ' ')
+        return NULL;
+
+    rest = g_strsplit(rparen + 2, " ", 0);
+    nrest = g_strv_length(rest);
+    ret = g_new0(char *, nrest + 3);
+    ret[0] = g_strdup(buf);
+    ret[1] = g_strdup(comm);
+    memcpy(ret + 2, rest, nrest * sizeof(char *));
+
+    /* Do not use g_strfreev() as individual elements they were moved to @ret. */
+    VIR_FREE(rest);
+
+    return ret;
+}
index 9910331a0caa5407bdf92ad86cd5aaa89c701e53..82b7403964997a531e04714bb77f68283371d117 100644 (file)
@@ -117,6 +117,72 @@ int virProcessSetupPrivateMountNS(void);
 int virProcessSetScheduler(pid_t pid,
                            virProcessSchedPolicy policy,
                            int priority);
+
+GStrv virProcessGetStat(pid_t pid, pid_t tid);
+
+/* These constants are modelled after proc(5) */
+enum {
+    VIR_PROCESS_STAT_PID,
+    VIR_PROCESS_STAT_COMM,
+    VIR_PROCESS_STAT_STATE,
+    VIR_PROCESS_STAT_PPID,
+    VIR_PROCESS_STAT_PGRP,
+    VIR_PROCESS_STAT_SESSION,
+    VIR_PROCESS_STAT_TTY_NR,
+    VIR_PROCESS_STAT_TPGID,
+    VIR_PROCESS_STAT_FLAGS,
+    VIR_PROCESS_STAT_MINFLT,
+    VIR_PROCESS_STAT_CMINFLT,
+    VIR_PROCESS_STAT_MAJFLT,
+    VIR_PROCESS_STAT_CMAJFLT,
+    VIR_PROCESS_STAT_UTIME,
+    VIR_PROCESS_STAT_STIME,
+    VIR_PROCESS_STAT_CUTIME,
+    VIR_PROCESS_STAT_CSTIME,
+    VIR_PROCESS_STAT_PRIORITY,
+    VIR_PROCESS_STAT_NICE,
+    VIR_PROCESS_STAT_NUM_THREADS,
+    VIR_PROCESS_STAT_ITREALVALUE,
+    VIR_PROCESS_STAT_STARTTIME,
+    VIR_PROCESS_STAT_VSIZE,
+    VIR_PROCESS_STAT_RSS,
+    VIR_PROCESS_STAT_RSSLIM,
+    VIR_PROCESS_STAT_STARTCODE,
+    VIR_PROCESS_STAT_ENDCODE,
+    VIR_PROCESS_STAT_STARTSTACK,
+    VIR_PROCESS_STAT_KSTKESP,
+    VIR_PROCESS_STAT_KSTKEIP,
+    VIR_PROCESS_STAT_SIGNAL,
+    VIR_PROCESS_STAT_BLOCKED,
+    VIR_PROCESS_STAT_SIGIGNORE,
+    VIR_PROCESS_STAT_SIGCATCH,
+    VIR_PROCESS_STAT_WCHAN,
+    VIR_PROCESS_STAT_NSWAP,
+    VIR_PROCESS_STAT_CNSWAP,
+    VIR_PROCESS_STAT_EXIT_SIGNAL,
+    VIR_PROCESS_STAT_PROCESSOR,
+    VIR_PROCESS_STAT_RT_PRIORITY,
+    VIR_PROCESS_STAT_POLICY,
+    VIR_PROCESS_STAT_DELAYACCT_BLKIO_TICKS,
+    VIR_PROCESS_STAT_GUEST_TIME,
+    VIR_PROCESS_STAT_CGUEST_TIME,
+    VIR_PROCESS_STAT_START_DATA,
+    VIR_PROCESS_STAT_END_DATA,
+    VIR_PROCESS_STAT_START_BRK,
+    VIR_PROCESS_STAT_ARG_START,
+    VIR_PROCESS_STAT_ARG_END,
+    VIR_PROCESS_STAT_ENV_START,
+    VIR_PROCESS_STAT_ENV_END,
+    VIR_PROCESS_STAT_EXIT_CODE,
+};
+
+/*
+ * At the time of writing there are 52 values reported in /proc/.../stat, the
+ * line below checks that the last one has the right value, increase accordingly
+ * based on proc(5) whenever adding new fields.
+*/
+G_STATIC_ASSERT(VIR_PROCESS_STAT_EXIT_CODE == 51);
+
 typedef enum {
     VIR_PROCESS_NAMESPACE_MNT = (1 << 1),
     VIR_PROCESS_NAMESPACE_IPC = (1 << 2),
index 1948c07ae385558e67c63db1a14ee71fafd307be..f75c24872086b99033b6f64a146af4b2fef467f9 100644 (file)
@@ -347,6 +347,7 @@ if host_machine.system() == 'linux'
     { 'name': 'scsihosttest' },
     { 'name': 'vircaps2xmltest', 'link_whole': [ test_file_wrapper_lib ] },
     { 'name': 'virnetdevbandwidthtest' },
+    { 'name': 'virprocessstattest', 'link_whole': [ test_file_wrapper_lib ] },
     { 'name': 'virresctrltest', 'link_whole': [ test_file_wrapper_lib ] },
     { 'name': 'virscsitest' },
     { 'name': 'virusbtest' },
diff --git a/tests/virprocessstatdata/complex/stat b/tests/virprocessstatdata/complex/stat
new file mode 100644 (file)
index 0000000..df0ecc1
--- /dev/null
@@ -0,0 +1,2 @@
+1 (this) is ( a         weird )
+)( (command ( ) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
diff --git a/tests/virprocessstatdata/simple/stat b/tests/virprocessstatdata/simple/stat
new file mode 100644 (file)
index 0000000..97d6873
--- /dev/null
@@ -0,0 +1 @@
+1 (command) 3 4 5
diff --git a/tests/virprocessstattest.c b/tests/virprocessstattest.c
new file mode 100644 (file)
index 0000000..8fb82c4
--- /dev/null
@@ -0,0 +1,88 @@
+#include <config.h>
+
+#include "testutils.h"
+#include "virfilewrapper.h"
+#include "virprocess.h"
+
+
+struct testData {
+    const char *filename;
+    const char *command;
+    size_t count;
+    bool self;
+};
+
+
+static int
+test_virProcessGetStat(const void *opaque)
+{
+    struct testData *data = (struct testData *) opaque;
+    g_autofree char *data_dir = NULL;
+    g_auto(GStrv) proc_stat = NULL;
+    size_t len = 0;
+    id_t id = data->self ? 0 : -1;
+    const char *command = NULL;
+
+    data_dir = g_strdup_printf("%s/virprocessstatdata/%s/",
+                               abs_srcdir, data->filename);
+
+    /* We are using predictable id of -1 because this case we will clearly see
+     * that the test failed in case of virFileWrapper failure */
+    if (id)
+        virFileWrapperAddPrefix("/proc/-1/task/-1/", data_dir);
+    else
+        virFileWrapperAddPrefix("/proc/self/", data_dir);
+
+    proc_stat = virProcessGetStat(id, id);
+
+    virFileWrapperClearPrefixes();
+
+    if (!proc_stat) {
+        fprintf(stderr, "Could not get process stats\n");
+        return -1;
+    }
+
+    len = g_strv_length(proc_stat);
+    if (data->count != len) {
+        fprintf(stderr, "Count incorrect, expected %zu, got %zu\n",
+                data->count, len);
+        return -1;
+    }
+
+    command = proc_stat[VIR_PROCESS_STAT_COMM];
+    if (!STREQ_NULLABLE(data->command, command)) {
+        fprintf(stderr, "Command incorrect, expected %s, got %s\n",
+                data->command, command);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+mymain(void)
+{
+    struct testData data = {0};
+    int ret = 0;
+
+#define DO_TEST(_filename, _command, _count, _self) \
+    do { \
+        data = (struct testData){ \
+            .filename = _filename, \
+            .command = _command, \
+            .count = _count, \
+            .self = _self, \
+        }; \
+        if (virTestRun("Reading process stat: " _filename, \
+                       test_virProcessGetStat, &data) < 0) \
+            ret = -1; \
+    } while (0)
+
+    DO_TEST("simple", "command", 5, true);
+    DO_TEST("complex", "this) is ( a \t weird )\n)( (command ( ", 100, false);
+
+    return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+VIR_TEST_MAIN(mymain)