cgtop: use Table and fiber (#44272)

No effective change, mostly refactoring.
This commit is contained in:
Yu Watanabe authored and GitHub committed 2026-10-21 03:32:48 +09:00
commit c366d2550d
3 files changed
+590 -288

No files matched your search

+547 -254
View File
@@ -1,8 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <poll.h>
#include <signal.h>
#include <termios.h>
#include <unistd.h>
#include "sd-event.h"
#include "sd-future.h"
#include "sd-json.h"
#include "alloc-util.h"
@@ -11,9 +15,12 @@
#include "cgroup-show.h"
#include "cgroup-util.h"
#include "cpu-set-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "hashmap.h"
#include "io-util.h"
#include "log.h"
#include "main-func.h"
#include "parse-argument.h"
@@ -21,10 +28,13 @@
#include "path-util.h"
#include "process-util.h"
#include "procfs-util.h"
#include "signal-util.h"
#include "sort-util.h"
#include "string-table.h"
#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
#include "utf8.h"
#include "verbs.h"
#include "virt.h"
@@ -68,6 +78,17 @@ typedef enum {
_ORDER_INVALID = -EINVAL,
} Order;
typedef enum {
COLUMN_PATH = ORDER_PATH,
COLUMN_TASKS = ORDER_TASKS,
COLUMN_CPU = ORDER_CPU,
COLUMN_MEMORY = ORDER_MEMORY,
COLUMN_IO_INPUT = ORDER_IO,
COLUMN_IO_OUTPUT = _ORDER_MAX,
_COLUMN_MAX,
_COLUMN_INVALID = -EINVAL,
} Column;
typedef enum {
CPU_PERCENTAGE,
CPU_TIME,
@@ -75,6 +96,33 @@ typedef enum {
_CPU_INVALID = -EINVAL,
} CPUType;
typedef struct Context {
sd_future *fiber;
sd_event_source *signal_sources[3]; /* SIGWINCH, SIGTSTP, SIGCONT */
char *root;
Hashmap *groups_by_path;
/* Running sum of memory.current across depth-1 children, filled in by
* process() during the refresh() walk and consumed by display() for
* the root row when arg_root_memory_mode == ROOT_MEMORY_CGROUP. */
uint64_t root_children_memory_sum;
unsigned iteration;
usec_t last_refresh;
bool immediate_refresh;
char **message;
usec_t message_lifetime;
usec_t next_display;
int poll_fd;
struct termios saved_termios;
bool termios_saved;
} Context;
#define DEFAULT_MAXIMUM_DEPTH 3
static unsigned arg_depth = DEFAULT_MAXIMUM_DEPTH;
@@ -126,31 +174,50 @@ static Group *group_free(Group *g) {
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(group_hash_ops, char, path_hash_func, path_compare, Group, group_free);
static const char *maybe_format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
if (arg_raw) {
(void) snprintf(buf, l, USEC_FMT, t);
return buf;
}
return format_timespan(buf, l, t, accuracy);
static void save_console(Context *c) {
assert(c);
if (c->termios_saved)
return;
if (tcgetattr(STDIN_FILENO, &c->saved_termios) >= 0)
c->termios_saved = true;
}
#define BUFSIZE1 CONST_MAX(FORMAT_TIMESPAN_MAX, DECIMAL_STR_MAX(usec_t))
#define MAYBE_FORMAT_TIMESPAN(t, accuracy) \
maybe_format_timespan((char[BUFSIZE1]){}, BUFSIZE1, t, accuracy)
static int modify_console(Context *c) {
assert(c);
static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) {
if (!is_valid)
return "-";
if (arg_raw) {
(void) snprintf(buf, l, "%" PRIu64, t);
return buf;
}
return format_bytes(buf, l, t);
if (!c->termios_saved)
return 0;
struct termios t = c->saved_termios;
termios_disable_echo(&t);
return RET_NERRNO(tcsetattr(STDIN_FILENO, TCSANOW, &t));
}
#define BUFSIZE2 CONST_MAX(FORMAT_BYTES_MAX, DECIMAL_STR_MAX(uint64_t))
#define MAYBE_FORMAT_BYTES(is_valid, t) \
maybe_format_bytes((char[BUFSIZE2]){}, BUFSIZE2, is_valid, t)
static void restore_console(Context *c) {
assert(c);
if (!c->termios_saved)
return;
(void) tcsetattr(STDIN_FILENO, TCSANOW, &c->saved_termios);
}
static void context_done(Context *c) {
assert(c);
restore_console(c);
/* We live on the stack of the main fiber, but the event loop is not ours: it is the default one,
* which outlives us. Make sure that no handler can be invoked with a dangling pointer to us. */
FOREACH_ELEMENT(s, c->signal_sources)
*s = sd_event_source_disable_unref(*s);
free(c->root);
hashmap_free(c->groups_by_path);
strv_free(c->message);
}
static bool is_root_cgroup(const char *path) {
@@ -598,116 +665,162 @@ static int group_compare(Group * const *a, Group * const *b) {
return path_compare(x->path, y->path);
}
static void display(Hashmap *a, uint64_t root_children_memory_sum) {
static int table_add_bytes(Table *table, bool is_valid, uint64_t t) {
if (!is_valid)
return table_add_cell(table, /* ret_cell= */ NULL, TABLE_EMPTY, /* data= */ NULL);
if (arg_raw)
return table_add_cell(table, /* ret_cell= */ NULL, TABLE_UINT64, &t);
return table_add_cell(table, /* ret_cell= */ NULL, TABLE_SIZE, &t);
}
static int display(Context *c) {
int r;
assert(c);
_cleanup_(table_unrefp) Table *table = table_new(
"CGroup",
arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+",
arg_cpu_type == CPU_PERCENTAGE ? "%CPU" : "CPU Time",
"Memory", "Input/s", "Output/s");
if (!table)
return log_oom();
table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
if (on_tty())
table_set_width(table, columns());
else
table_set_header(table, false);
for (Column col = 0; col < _COLUMN_MAX; col++) {
TableCell *cell = ASSERT_PTR(table_get_cell(table, 0, col));
(void) table_set_uppercase(table, cell, false);
if (col == (Column) arg_order ||
(IN_SET(col, COLUMN_IO_INPUT, COLUMN_IO_OUTPUT) && arg_order == ORDER_IO))
(void) table_set_color(table, cell, ansi_highlight());
switch (col) {
case COLUMN_PATH:
(void) table_set_weight(table, cell, 100000);
(void) table_set_ellipsize_percent(table, cell, 33);
break;
case COLUMN_TASKS:
(void) table_set_minimum_width(table, cell, 7);
(void) table_set_align_percent(table, cell, 100);
(void) table_set_ellipsize_percent(table, cell, 100);
break;
case COLUMN_CPU: {
unsigned n_cpus = 16;
(void) cpus_online(&n_cpus);
(void) table_set_minimum_width(table, cell, DECIMAL_STR_WIDTH(n_cpus) + STRLEN("00.0"));
(void) table_set_align_percent(table, cell, 100);
(void) table_set_ellipsize_percent(table, cell, 100);
break;
}
case COLUMN_MEMORY:
case COLUMN_IO_INPUT:
case COLUMN_IO_OUTPUT:
(void) table_set_minimum_width(table, cell, 8);
(void) table_set_align_percent(table, cell, 100);
(void) table_set_ellipsize_percent(table, cell, 100);
break;
default:
assert_not_reached();
}
}
Group **array = newa(Group*, hashmap_size(c->groups_by_path));
unsigned n = 0;
Group *g;
Group **array;
signed path_columns;
unsigned rows, n = 0, maxtcpu = 0, maxtpath = 3, /* 3 for ellipsize() to work properly */
maxpcpu = 6; /* minimum width for "%CPU" header */
assert(a);
if (!terminal_is_dumb())
fputs(ANSI_HOME_CLEAR, stdout);
array = newa(Group*, hashmap_size(a));
HASHMAP_FOREACH(g, a)
HASHMAP_FOREACH(g, c->groups_by_path)
if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
array[n++] = g;
typesafe_qsort(array, n, group_compare);
/* Find the longest names in one run */
for (unsigned j = 0; j < n; j++) {
maxtcpu = MAX(maxtcpu,
strlen(MAYBE_FORMAT_TIMESPAN((usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0)));
maxtpath = MAX(maxtpath,
strlen(array[j]->path));
}
if (arg_cpu_type == CPU_PERCENTAGE) {
unsigned n_cpus;
if (cpus_online(&n_cpus) >= 0)
maxpcpu = MAX(maxpcpu, DECIMAL_STR_WIDTH(n_cpus) + STRLEN("00.0"));
}
rows = lines();
unsigned rows = lines();
if (rows <= 10)
rows = 10;
if (on_tty()) {
const char *on, *off;
int cpu_len = arg_cpu_type == CPU_PERCENTAGE ? (int) maxpcpu : (int) maxtcpu;
path_columns = columns() - 36 - cpu_len;
if (path_columns < 10)
path_columns = 10;
on = ansi_highlight_underline();
off = ansi_underline();
printf("%s%s%-*s%s %s%7s%s %s%*s%s %s%8s%s %s%8s%s %s%8s%s%s\n",
ansi_underline(),
arg_order == ORDER_PATH ? on : "", path_columns, "CGroup",
arg_order == ORDER_PATH ? off : "",
arg_order == ORDER_TASKS ? on : "",
arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+",
arg_order == ORDER_TASKS ? off : "",
arg_order == ORDER_CPU ? on : "",
cpu_len,
arg_cpu_type == CPU_PERCENTAGE ? "%CPU" : "CPU Time",
arg_order == ORDER_CPU ? off : "",
arg_order == ORDER_MEMORY ? on : "", "Memory",
arg_order == ORDER_MEMORY ? off : "",
arg_order == ORDER_IO ? on : "", "Input/s",
arg_order == ORDER_IO ? off : "",
arg_order == ORDER_IO ? on : "", "Output/s",
arg_order == ORDER_IO ? off : "",
ansi_normal());
} else
path_columns = maxtpath;
/* Keep lines for message and table header. */
unsigned m = strv_length(c->message) + !strv_isempty(c->message) + on_tty() + 1;
for (unsigned j = 0; j < n; j++) {
_cleanup_free_ char *ellipsized = NULL;
const char *path;
if (on_tty() && j + 6 > rows)
if (on_tty() && j + m >= rows)
break;
g = array[j];
path = empty_to_root(g->path);
ellipsized = ellipsize(path, path_columns, 33);
_cleanup_free_ char *escaped = utf8_escape_non_printable(empty_to_root(g->path));
if (!escaped)
return log_oom();
if (is_root_cgroup(g->path) && arg_root_memory_system) {
g->memory = root_children_memory_sum;
g->memory_valid = true;
}
printf("%-*s", path_columns, ellipsized ?: path);
r = table_add_cell(table, /* ret_cell= */ NULL, TABLE_PATH, escaped);
if (r < 0)
return table_log_add_error(r);
if (g->n_tasks_valid)
printf(" %7" PRIu64, g->n_tasks);
r = table_add_cell(table, /* ret_cell= */ NULL, TABLE_UINT64, &g->n_tasks);
else
fputs(" -", stdout);
r = table_add_cell(table, /* ret_cell= */ NULL, TABLE_EMPTY, /* data= */ NULL);
if (r < 0)
return table_log_add_error(r);
if (arg_cpu_type == CPU_PERCENTAGE) {
if (g->cpu_valid)
printf(" %*.1f", (int) maxpcpu, g->cpu_fraction*100);
r = table_add_many(
table,
TABLE_DOUBLE, g->cpu_fraction * 100,
TABLE_SET_DECIMALS, 1);
else
printf(" %*s", (int) maxpcpu, "-");
r = table_add_cell(table, /* ret_cell= */ NULL, TABLE_EMPTY, /* data= */ NULL);
} else
printf(" %*s",
(int) maxtcpu,
MAYBE_FORMAT_TIMESPAN((usec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
r = table_add_many(
table,
arg_raw ? TABLE_UINT64 : TABLE_TIMESPAN,
g->cpu_usage / NSEC_PER_USEC);
if (r < 0)
return table_log_add_error(r);
printf(" %8s", MAYBE_FORMAT_BYTES(g->memory_valid, g->memory));
printf(" %8s", MAYBE_FORMAT_BYTES(g->io_valid, g->io_input_bps));
printf(" %8s", MAYBE_FORMAT_BYTES(g->io_valid, g->io_output_bps));
if (is_root_cgroup(g->path) && arg_root_memory_system) {
g->memory = c->root_children_memory_sum;
g->memory_valid = true;
}
putchar('\n');
r = table_add_bytes(table, g->memory_valid, g->memory);
if (r < 0)
return table_log_add_error(r);
r = table_add_bytes(table, g->io_valid, g->io_input_bps);
if (r < 0)
return table_log_add_error(r);
r = table_add_bytes(table, g->io_valid, g->io_output_bps);
if (r < 0)
return table_log_add_error(r);
}
if (!terminal_is_dumb())
fputs(ANSI_HOME_CLEAR, stdout);
r = table_print(table);
if (r < 0)
return table_log_print_error(r);
STRV_FOREACH(s, c->message) {
fputs("\n", stdout);
fputs(*s, stdout);
}
return 0;
}
static int parse_argv(int argc, char *argv[]) {
@@ -847,6 +960,117 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
static int step(Context *c, usec_t t) {
int r;
assert(c);
if (t >= usec_add(c->last_refresh, arg_delay) || c->immediate_refresh) {
c->root_children_memory_sum = 0;
_cleanup_hashmap_free_ Hashmap *previous = TAKE_PTR(c->groups_by_path);
c->groups_by_path = hashmap_new(&group_hash_ops);
if (!c->groups_by_path)
return log_oom();
r = refresh(c->root, c->groups_by_path, previous, c->iteration++, /* depth= */ 0,
&c->root_children_memory_sum, /* ret= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to refresh: %m");
c->last_refresh = t;
c->immediate_refresh = false;
}
if (c->message_lifetime > 0 && c->message_lifetime <= t) {
c->message = strv_free(c->message);
c->message_lifetime = 0;
}
r = display(c);
if (r < 0)
return r;
c->next_display = MIN(usec_add(c->last_refresh, arg_delay), c->message_lifetime ?: USEC_INFINITY);
if (arg_iterations > 0 && c->iteration >= arg_iterations)
return 0; /* finished */
if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
fputs("\n", stdout);
fflush(stdout);
return 1;
}
static int redisplay(Context *c) {
assert(c);
c->next_display = 0;
/* Only for the event source handlers below, which don't run on a fiber: the main fiber is suspended
* in sd_fiber_ppoll() at this point, wake it up so that it looks at next_display again. The fiber
* sees -EBUSY for such an unrelated wakeup. If it is not suspended (anymore), it looks at
* next_display before it suspends the next time anyway, so a lost wakeup costs nothing. */
return sd_fiber_resume(c->fiber, 0);
}
static int on_sigwinch(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
Context *c = ASSERT_PTR(userdata);
assert(si);
assert(si->ssi_signo == SIGWINCH);
columns_lines_cache_reset(si->ssi_signo);
return redisplay(c);
}
static int on_sigtstp(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
Context *c = ASSERT_PTR(userdata);
int r;
assert(si);
assert(si->ssi_signo == SIGTSTP);
/* restore console setting before raising SIGTSTP */
restore_console(c);
r = sigprocmask_many(SIG_UNBLOCK, /* ret_old_mask= */ NULL, SIGTSTP);
if (r < 0)
return sd_event_exit(sd_event_source_get_event(s), log_error_errno(r, "Faield to unblock SIGTSTP: %m"));
if (raise(SIGTSTP) < 0)
log_debug_errno(errno, "Failed to raise SIGTSTP, ignoring: %m");
r = sigprocmask_many(SIG_BLOCK, /* ret_old_mask= */ NULL, SIGTSTP);
if (r < 0)
return sd_event_exit(sd_event_source_get_event(s), log_error_errno(r, "Faield to block SIGTSTP: %m"));
r = modify_console(c);
if (r < 0)
return sd_event_exit(sd_event_source_get_event(s), log_error_errno(r, "Failed to update terminfo: %m"));
return redisplay(c);
}
static int on_sigcont(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
Context *c = ASSERT_PTR(userdata);
int r;
assert(si);
assert(si->ssi_signo == SIGCONT);
columns_lines_cache_reset(si->ssi_signo);
r = modify_console(c);
if (r < 0)
return sd_event_exit(sd_event_source_get_event(s), log_error_errno(r, "Failed to update terminfo: %m"));
return redisplay(c);
}
static const char* counting_what(void) {
if (arg_count == COUNT_PIDS)
return "tasks";
@@ -856,179 +1080,231 @@ static const char* counting_what(void) {
return "userspace processes (excl. kernel)";
}
static int loop(const char *root) {
_cleanup_hashmap_free_ Hashmap *a = NULL, *b = NULL;
unsigned iteration = 0;
usec_t last_refresh = 0;
bool immediate_refresh = false;
/* Running sum of memory.current across depth-1 children, filled in by
* process() during the refresh() walk and consumed by display() for
* the root row when arg_root_memory_mode == ROOT_MEMORY_CGROUP. */
uint64_t root_children_memory_sum = 0;
_printf_(3, 4)
static int set_message(Context *c, usec_t t, const char *format, ...) {
_cleanup_free_ char *str = NULL;
va_list ap;
int r;
a = hashmap_new(&group_hash_ops);
b = hashmap_new(&group_hash_ops);
if (!a || !b)
assert(c);
assert(format);
va_start(ap, format);
r = vasprintf(&str, format, ap);
va_end(ap);
if (r < 0)
return log_oom();
for (;;) {
usec_t t;
char key;
char **v = strv_split_newlines(str);
if (!v)
return log_oom();
t = now(CLOCK_MONOTONIC);
strv_free_and_replace(c->message, v);
c->message_lifetime = usec_add(now(CLOCK_MONOTONIC), t);
return 0;
}
if (t >= usec_add(last_refresh, arg_delay) || immediate_refresh) {
static int on_key(Context *c, char key) {
int r = 0;
root_children_memory_sum = 0;
assert(c);
r = refresh(root, a, b, iteration++, /* depth= */ 0,
&root_children_memory_sum, /* ret= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to refresh: %m");
/* Returns 0 if the user asked us to quit, and > 0 otherwise. */
hashmap_clear(b);
SWAP_TWO(a, b);
last_refresh = t;
immediate_refresh = false;
}
display(b, root_children_memory_sum);
if (arg_iterations && iteration >= arg_iterations)
return 0;
if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */
fputs("\n", stdout);
if (on_tty()) { /* TTY: Clear any user keystroke */
fputs("\r \r", stdout);
fflush(stdout);
}
if (arg_batch)
(void) usleep_safe(usec_add(usec_sub_unsigned(last_refresh, t), arg_delay));
switch (key) {
case ' ':
c->immediate_refresh = true;
break;
case 'q':
return 0;
case 'p':
arg_order = ORDER_PATH;
break;
case 't':
arg_order = ORDER_TASKS;
break;
case 'c':
arg_order = ORDER_CPU;
break;
case 'm':
arg_order = ORDER_MEMORY;
break;
case 'i':
arg_order = ORDER_IO;
break;
case '%':
arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENTAGE : CPU_TIME;
break;
case '/':
arg_root_memory_system = !arg_root_memory_system;
c->immediate_refresh = true;
break;
case 'k':
arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS;
r = set_message(c, USEC_PER_SEC, "Counting: %s.", counting_what());
break;
case 'P':
arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS;
r = set_message(c, USEC_PER_SEC, "Counting: %s.", counting_what());
break;
case 'r':
if (arg_count == COUNT_PIDS)
r = set_message(c, USEC_PER_SEC, "\aCannot toggle recursive counting, not available in task counting mode.");
else {
r = read_one_char(stdin, &key, usec_add(usec_sub_unsigned(last_refresh, t), arg_delay), /* echo= */ false, /* need_nl= */ NULL);
if (r == -ETIMEDOUT)
continue;
if (r < 0)
return log_error_errno(r, "Couldn't read key: %m");
arg_recursive = !arg_recursive;
r = set_message(c, USEC_PER_SEC, "Recursive process counting: %s", yes_no(arg_recursive));
}
break;
if (on_tty()) { /* TTY: Clear any user keystroke */
fputs("\r \r", stdout);
fflush(stdout);
}
case '+':
arg_delay = usec_add(arg_delay, arg_delay < USEC_PER_SEC ? USEC_PER_MSEC * 250 : USEC_PER_SEC);
r = set_message(c, USEC_PER_SEC, "Increased delay to %s.", FORMAT_TIMESPAN(arg_delay, 0));
break;
if (arg_batch)
continue;
case '-':
if (arg_delay <= USEC_PER_MSEC*500)
arg_delay = USEC_PER_MSEC*250;
else
arg_delay = usec_sub_unsigned(arg_delay, arg_delay < USEC_PER_MSEC * 1250 ? USEC_PER_MSEC * 250 : USEC_PER_SEC);
switch (key) {
r = set_message(c, USEC_PER_SEC, "Decreased delay to %s.", FORMAT_TIMESPAN(arg_delay, 0));
break;
case ' ':
immediate_refresh = true;
break;
case 'q':
return 0;
case 'p':
arg_order = ORDER_PATH;
break;
case 't':
arg_order = ORDER_TASKS;
break;
case 'c':
arg_order = ORDER_CPU;
break;
case 'm':
arg_order = ORDER_MEMORY;
break;
case 'i':
arg_order = ORDER_IO;
break;
case '%':
arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENTAGE : CPU_TIME;
break;
case '/':
arg_root_memory_system = !arg_root_memory_system;
immediate_refresh = true;
break;
case 'k':
arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS;
fprintf(stdout, "\nCounting: %s.", counting_what());
fflush(stdout);
sleep(1);
break;
case 'P':
arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS;
fprintf(stdout, "\nCounting: %s.", counting_what());
fflush(stdout);
sleep(1);
break;
case 'r':
if (arg_count == COUNT_PIDS)
fprintf(stdout, "\n\aCannot toggle recursive counting, not available in task counting mode.");
else {
arg_recursive = !arg_recursive;
fprintf(stdout, "\nRecursive process counting: %s", yes_no(arg_recursive));
}
fflush(stdout);
sleep(1);
break;
case '+':
arg_delay = usec_add(arg_delay, arg_delay < USEC_PER_SEC ? USEC_PER_MSEC * 250 : USEC_PER_SEC);
fprintf(stdout, "\nIncreased delay to %s.", FORMAT_TIMESPAN(arg_delay, 0));
fflush(stdout);
sleep(1);
break;
case '-':
if (arg_delay <= USEC_PER_MSEC*500)
arg_delay = USEC_PER_MSEC*250;
else
arg_delay = usec_sub_unsigned(arg_delay, arg_delay < USEC_PER_MSEC * 1250 ? USEC_PER_MSEC * 250 : USEC_PER_SEC);
fprintf(stdout, "\nDecreased delay to %s.", FORMAT_TIMESPAN(arg_delay, 0));
fflush(stdout);
sleep(1);
break;
case '?':
case 'h':
fprintf(stdout,
case '?':
case 'h':
r = set_message(c, 3 * USEC_PER_SEC,
"\t<%1$sp%2$s> By path; <%1$st%2$s> By tasks/procs; <%1$sc%2$s> By CPU; <%1$sm%2$s> By memory; <%1$si%2$s> By I/O\n"
"\t<%1$s+%2$s> Inc. delay; <%1$s-%2$s> Dec. delay; <%1$s%%%2$s> Toggle time; <%1$sSPACE%2$s> Refresh\n"
"\t<%1$sP%2$s> Toggle count userspace processes; <%1$sk%2$s> Toggle count all processes\n"
"\t<%1$sr%2$s> Count processes recursively; <%1$s/%2$s> Toggle root row (meminfo/cgroup sum); <%1$sq%2$s> Quit",
ansi_highlight(), ansi_normal());
fflush(stdout);
sleep(3);
break;
break;
default:
if (key < ' ')
fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", (unsigned) key);
else
fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
fflush(stdout);
sleep(1);
default:
if (key < ' ')
r = set_message(c, USEC_PER_SEC, "Unknown key '\\x%x'. Ignoring.", (unsigned) key);
else
r = set_message(c, USEC_PER_SEC, "Unknown key '%c'. Ignoring.", key);
}
if (r < 0)
return r;
c->next_display = 0;
return 1;
}
static int loop(Context *c) {
int r;
assert(c);
for (;;) {
usec_t t = now(CLOCK_MONOTONIC);
if (t >= c->next_display) {
r = step(c, t);
if (r <= 0)
return r;
t = now(CLOCK_MONOTONIC);
}
/* This waits for a key or for the next redraw, whichever comes first. A negative fd is
* ignored by poll(), which is how we deal with batch mode and EOF on stdin.
*
* Never pass a zero timeout here: the fiber would not suspend, hence the event loop would
* not run and signals (e.g. SIGINT) might not be processed, if redrawing takes longer than
* the delay. Hence wait at least 1 usec. */
r = fd_wait_for_event(c->poll_fd, POLLIN, MAX(usec_sub_unsigned(c->next_display, t), 1u));
if (r == -EBUSY)
continue; /* A signal handler called redisplay(). */
if (r == -ECANCELED)
return r; /* SIGINT or SIGTERM */
if (r < 0)
return log_error_errno(r, "Failed to wait for stdin: %m");
if (r == 0)
continue; /* Time to redraw. */
/* The fd is ready, hence this does not block. We deliberately don't use sd_fiber_read()
* here, which would flip O_NONBLOCK on the terminal, which is shared with the shell. */
assert(c->poll_fd >= 0);
char key;
ssize_t n = read(c->poll_fd, &key, 1);
if (n < 0) {
if (ERRNO_IS_TRANSIENT(errno))
continue;
return log_error_errno(errno, "Failed to read from stdin: %m");
}
if (n == 0) { /* EOF */
c->poll_fd = -EBADF;
continue;
}
r = on_key(c, key);
if (r <= 0)
return r;
}
}
static int setup_loop(Context *c) {
sd_event *e = ASSERT_PTR(sd_fiber_get_event());
int r;
assert(c);
c->fiber = sd_fiber_get_current();
r = sd_event_set_signal_exit(e, true);
if (r < 0)
return r;
/* on terminal size change */
r = sd_event_add_signal(e, &c->signal_sources[0], SIGWINCH | SD_EVENT_SIGNAL_PROCMASK, on_sigwinch, c);
if (r < 0)
return r;
/* on Ctrl+z */
r = sd_event_add_signal(e, &c->signal_sources[1], SIGTSTP | SD_EVENT_SIGNAL_PROCMASK, on_sigtstp, c);
if (r < 0)
return r;
/* on continue */
r = sd_event_add_signal(e, &c->signal_sources[2], SIGCONT | SD_EVENT_SIGNAL_PROCMASK, on_sigcont, c);
if (r < 0)
return r;
if (c->poll_fd >= 0) {
save_console(c);
r = modify_console(c);
if (r < 0)
return r;
}
return 0;
}
static int run(int argc, char *argv[]) {
_cleanup_free_ char *root = NULL;
CGroupMask mask;
int r;
@@ -1050,17 +1326,34 @@ static int run(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k.");
r = show_cgroup_get_path_and_warn(arg_machine, arg_root, &root);
_cleanup_(context_done) Context c = {
.immediate_refresh = true,
.poll_fd = arg_batch ? -EBADF : STDIN_FILENO,
};
r = show_cgroup_get_path_and_warn(arg_machine, arg_root, &c.root);
if (r < 0)
return log_error_errno(r, "Failed to get root control group path: %m");
log_debug("CGroup path: %s", root);
signal(SIGWINCH, columns_lines_cache_reset);
log_debug("CGroup path: %s", c.root);
if (arg_iterations == UINT_MAX)
arg_iterations = on_tty() ? 0 : 1;
return loop(root);
if (arg_iterations == 1)
return step(&c, /* t= */ 0);
r = setup_loop(&c);
if (r < 0)
return log_error_errno(r, "Failed to initialize event loop: %m");
r = loop(&c);
if (on_tty()) {
fputs("\n", stdout);
fflush(stdout);
}
return r;
}
DEFINE_MAIN_FUNCTION(run);
DEFINE_MAIN_FUNCTION_FIBER(run);
+40 -34
View File
@@ -58,10 +58,11 @@ typedef struct Fiber {
sigjmp_buf context; /* Where to jump to when entering or resuming the fiber. */
sigjmp_buf resume_context; /* Where to jump back to when the fiber yields or completes. */
/* Caller's stack range, recorded by fiber_run() on each entry so the fiber's siglongjmp back
* out (in fiber_swap() or the trampoline's terminate path) can hand AddressSanitizer the
* destination stack info. With ucontext this comes for free via uc_link/uc_stack; sigjmp_buf
* is opaque and doesn't carry it. */
/* Stack range we were entered from, recorded by finish_switch_stack() on each entry (the
* entering side may be the thread's original stack or another fiber's), so the fiber's
* siglongjmp back out (in fiber_swap() or the trampoline's terminate path) can hand
* AddressSanitizer the destination stack info. With ucontext this comes for free via
* uc_link/uc_stack; sigjmp_buf is opaque and doesn't carry it. */
struct iovec resume_stack;
FiberState state;
@@ -101,10 +102,6 @@ static Fiber* fiber_get(sd_future *f) {
return ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f)));
}
static Fiber* fiber_get_maybe(sd_future *f) {
return f ? fiber_get(f) : NULL;
}
static void fiber_set_current(sd_future *f) {
current_fiber = f;
}
@@ -172,24 +169,31 @@ static inline void start_switch_stack(void **fake_stack_save, const struct iovec
#endif
}
static inline void finish_switch_stack(void *fake_stack_save) {
static inline void finish_switch_stack(void *fake_stack_save, struct iovec *ret_old) {
/* ret_old, if not NULL, is set to the stack range we just switched away from, as ASAN knows it.
* Fibers use this to remember where to switch back to. Asking ASAN, rather than deriving the range
* ourselves, also covers the thread's original stack, whose bounds we don't otherwise know. Passing
* NULL/0 as the destination of start_switch_stack() instead makes ASAN forget the bounds of the
* stack we return to, after which it complains in __asan_handle_no_return() on every siglongjmp(). */
#if HAS_FEATURE_ADDRESS_SANITIZER
__sanitizer_finish_switch_fiber(fake_stack_save, NULL, NULL);
const void *bottom_old = NULL;
size_t size_old = 0;
__sanitizer_finish_switch_fiber(fake_stack_save, &bottom_old, &size_old);
if (ret_old)
*ret_old = (struct iovec) {
.iov_base = (void*) bottom_old,
.iov_len = size_old,
};
#else
if (ret_old)
*ret_old = (struct iovec) {};
#endif
}
/* Refresh f->resume_stack from whoever is currently the running fiber, so the next siglongjmp() out
* of f (in the trampoline or fiber_swap()) can hand the right destination stack to ASAN. Must be
* called before fiber_set_current(f) — relies on sd_fiber_get_current() returning the caller. */
static void fiber_set_resume_stack(Fiber *f, Fiber *resume) {
assert(f);
if (resume)
f->resume_stack = fiber_stack_usable(&resume->stack);
else
f->resume_stack = (struct iovec) {};
}
_noreturn_ static void fiber_entry_point(void) {
Fiber *fiber = fiber_get(sd_fiber_get_current());
void *fake_stack_save = NULL;
@@ -197,7 +201,7 @@ _noreturn_ static void fiber_entry_point(void) {
assert(fiber->func);
assert(fiber->state == FIBER_STATE_INITIAL);
finish_switch_stack(NULL);
finish_switch_stack(/* fake_stack_save= */ NULL, &fiber->resume_stack);
/* Capture our resumable point on the fiber's stack, then bounce back to whoever last set
* fiber->resume_context. On bootstrap that's fiber_init(); on every subsequent yield it's
@@ -209,7 +213,7 @@ _noreturn_ static void fiber_entry_point(void) {
}
/* Re-entered for real via fiber_run()'s siglongjmp(fiber->context). */
finish_switch_stack(fake_stack_save);
finish_switch_stack(fake_stack_save, &fiber->resume_stack);
assert(fiber->state == FIBER_STATE_RUNNING);
/* Block scope so the cleanups attached to LOG_SET_PREFIX / LOG_CONTEXT_PUSH_KEY_VALUE fire
@@ -238,7 +242,7 @@ _noreturn_ static void fiber_entry_point(void) {
static int fiber_init(sd_future *f) {
Fiber *fiber = fiber_get(f);
ucontext_t old_uc, uc;
ucontext_t uc;
void *fake_stack_save = NULL;
if (getcontext(&uc) < 0)
@@ -256,18 +260,22 @@ static int fiber_init(sd_future *f) {
makecontext(&uc, fiber_entry_point, /* argc= */ 0);
fiber_set_resume_stack(fiber, fiber_get_maybe(prev));
if (sigsetjmp(fiber->resume_context, /* savemask= */ 0) == 0) {
start_switch_stack(&fake_stack_save, &fiber_stack);
if (swapcontext(&old_uc, &uc) < 0) {
finish_switch_stack(fake_stack_save);
/* We never come back to this context (the trampoline siglongjmps to resume_context),
* so there is nothing to save: use setcontext() rather than swapcontext(). Besides
* being cheaper, this avoids the unconditional "ASan doesn't fully support
* makecontext/swapcontext functions" warning the ASAN swapcontext() interceptor
* prints to stderr on first use, which breaks tests that expect clean stderr. */
if (setcontext(&uc) < 0) {
finish_switch_stack(fake_stack_save, /* ret_old= */ NULL);
fiber_set_current(prev);
return -errno;
}
assert_not_reached(); /* Trampoline siglongjmps back; swapcontext doesn't return. */
assert_not_reached(); /* Trampoline siglongjmps back; setcontext doesn't return. */
}
finish_switch_stack(fake_stack_save);
finish_switch_stack(fake_stack_save, /* ret_old= */ NULL);
fiber_set_current(prev);
return 0;
@@ -337,7 +345,6 @@ static const FiberOps fiber_ops = {
static void fiber_enter(sd_future *f, sd_future *prev, void **fake_stack_save) {
Fiber *fiber = fiber_get(f);
Fiber *prev_fiber = fiber_get_maybe(prev);
fiber_set_current(f);
fiber_swap_log_state(fiber);
@@ -346,13 +353,12 @@ static void fiber_enter(sd_future *f, sd_future *prev, void **fake_stack_save) {
struct iovec fiber_stack = fiber_stack_usable(&fiber->stack);
start_switch_stack(fake_stack_save, &fiber_stack);
fiber_set_resume_stack(fiber, prev_fiber);
}
static void fiber_leave(sd_future *f, sd_future *prev, void *fake_stack_save) {
Fiber *fiber = fiber_get(f);
finish_switch_stack(fake_stack_save);
finish_switch_stack(fake_stack_save, /* ret_old= */ NULL);
if (!prev)
fiber_ops_set(NULL);
fiber_swap_log_state(fiber);
@@ -575,7 +581,7 @@ static int fiber_swap(FiberState state) {
siglongjmp_unchecked(fiber->resume_context, 1);
}
finish_switch_stack(fake_stack_save);
finish_switch_stack(fake_stack_save, &fiber->resume_stack);
}
assert(fiber->state == FIBER_STATE_RUNNING);
+3
View File
@@ -25,6 +25,9 @@ for order in path tasks cpu memory io; do
done
systemd-cgtop -p -t -c -m -i
systemd-cgtop -b -n 3 -d 1ms
printf 'ptcmi%%/kPr+-?xq' | timeout 60 systemd-cgtop -n 0 -d 1ms
(! systemd-cgtop --cpu=foo)
(! systemd-cgtop --order=foo)
(! systemd-cgtop --depth=-1)