mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
cgtop: use Table and fiber (#44272)
No effective change, mostly refactoring.
This commit is contained in:
3 files changed
+590
-288
No files matched your search
+547
-254
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user