+/* 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>
#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;
static bool arg_raw = false;
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,
}
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) {
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,
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);
}
}
- 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;
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);
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;
l += strcspn(l, WHITESPACE);
l += strspn(l, WHITESPACE);
- if (unified) {
+ if (all_unified) {
while (!isempty(l)) {
if (sscanf(l, "rbytes=%" SCNu64, &k))
rd += k;
if (r == 0)
break;
- p = strjoin(path, "/", fn, NULL);
+ p = strjoin(path, "/", fn);
if (!p)
return -ENOMEM;
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);
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;
* 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;
}
assert(a);
- if (on_tty())
+ if (!terminal_is_dumb())
fputs(ANSI_HOME_CLEAR, stdout);
array = alloca(sizeof(Group*) * hashmap_size(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);
}
static void help(void) {
- printf("%s [OPTIONS...]\n\n"
+ printf("%s [OPTIONS...] [CGROUP]\n\n"
"Show top control groups by their resource usage.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --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"
{}
};
- 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) {
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;
}
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':
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':
assert_not_reached("Unhandled option");
}
- if (optind < argc) {
+ if (optind == argc - 1)
+ arg_root = argv[optind];
+ 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;
}
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_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;
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");
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;