]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/cgtop/cgtop.c
tree-wide: drop license boilerplate
[thirdparty/systemd.git] / src / cgtop / cgtop.c
index c67b328b3882176f1781d8301a453192b9aace54..7fe812d0ffd83b9f79397b5d6faebda8eff2453a 100644 (file)
@@ -1,20 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
   Copyright 2012 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
 #include <alloca.h>
@@ -31,6 +19,7 @@
 #include "alloc-util.h"
 #include "bus-error.h"
 #include "bus-util.h"
+#include "cgroup-show.h"
 #include "cgroup-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
+#include "procfs-util.h"
 #include "stdio-util.h"
+#include "strv.h"
 #include "terminal-util.h"
 #include "unit-name.h"
 #include "util.h"
+#include "virt.h"
 
 typedef struct Group {
         char *path;
@@ -74,6 +66,7 @@ static usec_t arg_delay = 1*USEC_PER_SEC;
 static char* arg_machine = NULL;
 static char* arg_root = NULL;
 static bool arg_recursive = true;
+static bool arg_recursive_unset = false;
 
 static enum {
         COUNT_PIDS,
@@ -102,10 +95,7 @@ static void group_free(Group *g) {
 }
 
 static void group_hashmap_clear(Hashmap *h) {
-        Group *g;
-
-        while ((g = hashmap_steal_first(h)))
-                group_free(g);
+        hashmap_clear_with_destructor(h, group_free);
 }
 
 static void group_hashmap_free(Hashmap *h) {
@@ -117,12 +107,36 @@ static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64
         if (!is_valid)
                 return "-";
         if (arg_raw) {
-                snprintf(buf, l, "%jd", t);
+                snprintf(buf, l, "%" PRIu64, t);
                 return buf;
         }
         return format_bytes(buf, l, t);
 }
 
+static bool is_root_cgroup(const char *path) {
+
+        /* Returns true if the specified path belongs to the root cgroup. The root cgroup is special on cgroupsv2 as it
+         * carries only very few attributes in order not to export multiple truth about system state as most
+         * information is available elsewhere in /proc anyway. We need to be able to deal with that, and need to get
+         * our data from different sources in that case.
+         *
+         * There's one extra complication in all of this, though ðŸ˜£: if the path to the cgroup indicates we are in the
+         * root cgroup this might actually not be the case, because cgroup namespacing might be in effect
+         * (CLONE_NEWCGROUP). Since there's no nice way to distuingish a real cgroup root from a fake namespaced one we
+         * do an explicit container check here, under the assumption that CLONE_NEWCGROUP is generally used when
+         * container managers are used too.
+         *
+         * Note that checking for a container environment is kinda ugly, since in theory people could use cgtop from
+         * inside a container where cgroup namespacing is turned off to watch the host system. However, that's mostly a
+         * theoretic usecase, and if people actually try all they'll lose is accounting for the top-level cgroup. Which
+         * isn't too bad. */
+
+        if (detect_container() > 0)
+                return false;
+
+        return isempty(path) || path_equal(path, "/");
+}
+
 static int process(
                 const char *controller,
                 const char *path,
@@ -132,12 +146,16 @@ static int process(
                 Group **ret) {
 
         Group *g;
-        int r;
+        int r, all_unified;
 
         assert(controller);
         assert(path);
         assert(a);
 
+        all_unified = cg_all_unified();
+        if (all_unified < 0)
+                return all_unified;
+
         g = hashmap_get(a, path);
         if (!g) {
                 g = hashmap_get(b, path);
@@ -166,7 +184,8 @@ static int process(
                 }
         }
 
-        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) && IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) {
+        if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) &&
+            IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) {
                 _cleanup_fclose_ FILE *f = NULL;
                 pid_t pid;
 
@@ -189,43 +208,76 @@ static int process(
                         g->n_tasks_valid = true;
 
         } else if (streq(controller, "pids") && arg_count == COUNT_PIDS) {
-                _cleanup_free_ char *p = NULL, *v = NULL;
 
-                r = cg_get_path(controller, path, "pids.current", &p);
-                if (r < 0)
-                        return r;
+                if (is_root_cgroup(path)) {
+                        r = procfs_tasks_get_current(&g->n_tasks);
+                        if (r < 0)
+                                return r;
+                } else {
+                        _cleanup_free_ char *p = NULL, *v = NULL;
 
-                r = read_one_line_file(p, &v);
-                if (r == -ENOENT)
-                        return 0;
-                if (r < 0)
-                        return r;
+                        r = cg_get_path(controller, path, "pids.current", &p);
+                        if (r < 0)
+                                return r;
 
-                r = safe_atou64(v, &g->n_tasks);
-                if (r < 0)
-                        return r;
+                        r = read_one_line_file(p, &v);
+                        if (r == -ENOENT)
+                                return 0;
+                        if (r < 0)
+                                return r;
+
+                        r = safe_atou64(v, &g->n_tasks);
+                        if (r < 0)
+                                return r;
+                }
 
                 if (g->n_tasks > 0)
                         g->n_tasks_valid = true;
 
-        } else if (streq(controller, "cpuacct") && cg_unified() <= 0) {
+        } else if (STR_IN_SET(controller, "cpu", "cpuacct")) {
                 _cleanup_free_ char *p = NULL, *v = NULL;
                 uint64_t new_usage;
                 nsec_t timestamp;
 
-                r = cg_get_path(controller, path, "cpuacct.usage", &p);
-                if (r < 0)
-                        return r;
+                if (is_root_cgroup(path)) {
+                        r = procfs_cpu_get_usage(&new_usage);
+                        if (r < 0)
+                                return r;
+                } else if (all_unified) {
+                        _cleanup_free_ char *val = NULL;
 
-                r = read_one_line_file(p, &v);
-                if (r == -ENOENT)
-                        return 0;
-                if (r < 0)
-                        return r;
+                        if (!streq(controller, "cpu"))
+                                return 0;
 
-                r = safe_atou64(v, &new_usage);
-                if (r < 0)
-                        return r;
+                        r = cg_get_keyed_attribute("cpu", path, "cpu.stat", STRV_MAKE("usage_usec"), &val);
+                        if (IN_SET(r, -ENOENT, -ENXIO))
+                                return 0;
+                        if (r < 0)
+                                return r;
+
+                        r = safe_atou64(val, &new_usage);
+                        if (r < 0)
+                                return r;
+
+                        new_usage *= NSEC_PER_USEC;
+                } else {
+                        if (!streq(controller, "cpuacct"))
+                                return 0;
+
+                        r = cg_get_path(controller, path, "cpuacct.usage", &p);
+                        if (r < 0)
+                                return r;
+
+                        r = read_one_line_file(p, &v);
+                        if (r == -ENOENT)
+                                return 0;
+                        if (r < 0)
+                                return r;
+
+                        r = safe_atou64(v, &new_usage);
+                        if (r < 0)
+                                return r;
+                }
 
                 timestamp = now_nsec(CLOCK_MONOTONIC);
 
@@ -248,37 +300,43 @@ static int process(
                 g->cpu_iteration = iteration;
 
         } else if (streq(controller, "memory")) {
-                _cleanup_free_ char *p = NULL, *v = NULL;
 
-                if (cg_unified() <= 0)
-                        r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
-                else
-                        r = cg_get_path(controller, path, "memory.current", &p);
-                if (r < 0)
-                        return r;
+                if (is_root_cgroup(path)) {
+                        r = procfs_memory_get_current(&g->memory);
+                        if (r < 0)
+                                return r;
+                } else {
+                        _cleanup_free_ char *p = NULL, *v = NULL;
 
-                r = read_one_line_file(p, &v);
-                if (r == -ENOENT)
-                        return 0;
-                if (r < 0)
-                        return r;
+                        if (all_unified)
+                                r = cg_get_path(controller, path, "memory.current", &p);
+                        else
+                                r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
+                        if (r < 0)
+                                return r;
 
-                r = safe_atou64(v, &g->memory);
-                if (r < 0)
-                        return r;
+                        r = read_one_line_file(p, &v);
+                        if (r == -ENOENT)
+                                return 0;
+                        if (r < 0)
+                                return r;
+
+                        r = safe_atou64(v, &g->memory);
+                        if (r < 0)
+                                return r;
+                }
 
                 if (g->memory > 0)
                         g->memory_valid = true;
 
-        } else if ((streq(controller, "io") && cg_unified() > 0) ||
-                   (streq(controller, "blkio") && cg_unified() <= 0)) {
+        } else if ((streq(controller, "io") && all_unified) ||
+                   (streq(controller, "blkio") && !all_unified)) {
                 _cleanup_fclose_ FILE *f = NULL;
                 _cleanup_free_ char *p = NULL;
-                bool unified = cg_unified() > 0;
                 uint64_t wr = 0, rd = 0;
                 nsec_t timestamp;
 
-                r = cg_get_path(controller, path, unified ? "io.stat" : "blkio.io_service_bytes", &p);
+                r = cg_get_path(controller, path, all_unified ? "io.stat" : "blkio.io_service_bytes", &p);
                 if (r < 0)
                         return r;
 
@@ -301,7 +359,7 @@ static int process(
                         l += strcspn(l, WHITESPACE);
                         l += strspn(l, WHITESPACE);
 
-                        if (unified) {
+                        if (all_unified) {
                                 while (!isempty(l)) {
                                         if (sscanf(l, "rbytes=%" SCNu64, &k))
                                                 rd += k;
@@ -408,7 +466,7 @@ static int refresh_one(
                 if (r == 0)
                         break;
 
-                p = strjoin(path, "/", fn, NULL);
+                p = strjoin(path, "/", fn);
                 if (!p)
                         return -ENOMEM;
 
@@ -447,6 +505,9 @@ static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration)
         assert(a);
 
         r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL);
+        if (r < 0)
+                return r;
+        r = refresh_one("cpu", root, a, b, iteration, 0, NULL);
         if (r < 0)
                 return r;
         r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL);
@@ -468,6 +529,10 @@ static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration)
         return 0;
 }
 
+static const char *empty_to_slash(const char *p) {
+        return isempty(p) ? "/" : p;
+}
+
 static int group_compare(const void*a, const void *b) {
         const Group *x = *(Group**)a, *y = *(Group**)b;
 
@@ -477,9 +542,9 @@ static int group_compare(const void*a, const void *b) {
                  * recursive summing is off, since that is actually
                  * not accumulative for all children. */
 
-                if (path_startswith(y->path, x->path))
+                if (path_startswith(empty_to_slash(y->path), empty_to_slash(x->path)))
                         return -1;
-                if (path_startswith(x->path, y->path))
+                if (path_startswith(empty_to_slash(x->path), empty_to_slash(y->path)))
                         return 1;
         }
 
@@ -628,7 +693,7 @@ static void display(Hashmap *a) {
 
                 g = array[j];
 
-                path = isempty(g->path) ? "/" : g->path;
+                path = empty_to_slash(g->path);
                 ellipsized = ellipsize(path, path_columns, 33);
                 printf("%-*s", path_columns, ellipsized ?: path);
 
@@ -671,6 +736,7 @@ static void help(void) {
                "     --recursive=BOOL Sum up process count recursively\n"
                "  -d --delay=DELAY    Delay between updates\n"
                "  -n --iterations=N   Run for N iterations before exiting\n"
+               "  -1                  Shortcut for --iterations=1\n"
                "  -b --batch          Run in batch mode, accepting no input\n"
                "     --depth=DEPTH    Maximum traversal depth (default: %u)\n"
                "  -M --machine=       Show container\n"
@@ -702,13 +768,12 @@ static int parse_argv(int argc, char *argv[]) {
                 {}
         };
 
-        bool recursive_unset = false;
         int c, r;
 
         assert(argc >= 1);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:1", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -736,17 +801,15 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case ARG_DEPTH:
                         r = safe_atou(optarg, &arg_depth);
-                        if (r < 0) {
-                                log_error("Failed to parse depth parameter.");
-                                return -EINVAL;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse depth parameter: %s", optarg);
 
                         break;
 
                 case 'd':
                         r = parse_sec(optarg, &arg_delay);
                         if (r < 0 || arg_delay <= 0) {
-                                log_error("Failed to parse delay parameter.");
+                                log_error("Failed to parse delay parameter: %s", optarg);
                                 return -EINVAL;
                         }
 
@@ -754,11 +817,13 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case 'n':
                         r = safe_atou(optarg, &arg_iterations);
-                        if (r < 0) {
-                                log_error("Failed to parse iterations parameter.");
-                                return -EINVAL;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse iterations parameter: %s", optarg);
+
+                        break;
 
+                case '1':
+                        arg_iterations = 1;
                         break;
 
                 case 'b':
@@ -816,13 +881,11 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case ARG_RECURSIVE:
                         r = parse_boolean(optarg);
-                        if (r < 0) {
-                                log_error("Failed to parse --recursive= argument: %s", optarg);
-                                return r;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --recursive= argument: %s", optarg);
 
                         arg_recursive = r;
-                        recursive_unset = r == 0;
+                        arg_recursive_unset = r == 0;
                         break;
 
                 case 'M':
@@ -836,22 +899,13 @@ static int parse_argv(int argc, char *argv[]) {
                         assert_not_reached("Unhandled option");
                 }
 
-        if (optind == argc-1) {
-                if (arg_machine) {
-                        log_error("Specifying a control group path together with the -M option is not allowed");
-                        return -EINVAL;
-                }
+        if (optind == argc - 1)
                 arg_root = argv[optind];
-        else if (optind < argc) {
+        else if (optind < argc) {
                 log_error("Too many arguments.");
                 return -EINVAL;
         }
 
-        if (recursive_unset && arg_count == COUNT_PIDS) {
-                log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
-                return -EINVAL;
-        }
-
         return 1;
 }
 
@@ -864,59 +918,6 @@ static const char* counting_what(void) {
                 return "userspace processes (excl. kernel)";
 }
 
-static int get_cgroup_root(char **ret) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_free_ char *unit = NULL, *path = NULL;
-        const char *m;
-        int r;
-
-        if (arg_root) {
-                char *aux;
-
-                aux = strdup(arg_root);
-                if (!aux)
-                        return log_oom();
-
-                *ret = aux;
-                return 0;
-        }
-
-        if (!arg_machine) {
-                r = cg_get_root_path(ret);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to get root control group path: %m");
-
-                return 0;
-        }
-
-        m = strjoina("/run/systemd/machines/", arg_machine);
-        r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to load machine data: %m");
-
-        path = unit_dbus_path_from_name(unit);
-        if (!path)
-                return log_oom();
-
-        r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus);
-        if (r < 0)
-                return log_error_errno(r, "Failed to create bus connection: %m");
-
-        r = sd_bus_get_property_string(
-                        bus,
-                        "org.freedesktop.systemd1",
-                        path,
-                        unit_dbus_interface_from_name(unit),
-                        "ControlGroup",
-                        &error,
-                        ret);
-        if (r < 0)
-                return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r));
-
-        return 0;
-}
-
 int main(int argc, char *argv[]) {
         int r;
         Hashmap *a = NULL, *b = NULL;
@@ -929,6 +930,10 @@ int main(int argc, char *argv[]) {
         log_parse_environment();
         log_open();
 
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                goto finish;
+
         r = cg_mask_supported(&mask);
         if (r < 0) {
                 log_error_errno(r, "Failed to determine supported controllers: %m");
@@ -937,18 +942,20 @@ int main(int argc, char *argv[]) {
 
         arg_count = (mask & CGROUP_MASK_PIDS) ? COUNT_PIDS : COUNT_USERSPACE_PROCESSES;
 
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                goto finish;
+        if (arg_recursive_unset && arg_count == COUNT_PIDS) {
+                log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
+                return -EINVAL;
+        }
 
-        r = get_cgroup_root(&root);
+        r = show_cgroup_get_path_and_warn(arg_machine, arg_root, &root);
         if (r < 0) {
                 log_error_errno(r, "Failed to get root control group path: %m");
                 goto finish;
-        }
+        } else
+                log_debug("Cgroup path: %s", root);
 
-        a = hashmap_new(&string_hash_ops);
-        b = hashmap_new(&string_hash_ops);
+        a = hashmap_new(&path_hash_ops);
+        b = hashmap_new(&path_hash_ops);
         if (!a || !b) {
                 r = log_oom();
                 goto finish;