+/* 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 "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 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) {
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,
}
}
- 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, "cpu") || streq(controller, "cpuacct")) {
+ } else if (STR_IN_SET(controller, "cpu", "cpuacct")) {
_cleanup_free_ char *p = NULL, *v = NULL;
uint64_t new_usage;
nsec_t timestamp;
- if (all_unified) {
- const char *keys[] = { "usage_usec", NULL };
+ 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;
if (!streq(controller, "cpu"))
return 0;
- r = cg_get_keyed_attribute("cpu", path, "cpu.stat", keys, &val);
- if (r == -ENOENT)
+ 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;
g->cpu_iteration = iteration;
} else if (streq(controller, "memory")) {
- _cleanup_free_ char *p = NULL, *v = NULL;
- 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;
+ 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;
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;
}
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);
" --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"
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;
arg_recursive_unset = r == 0;
} 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;