Commit 5e57515d authored by Fan Yu's avatar Fan Yu Committed by Andrew Morton
Browse files

tools/delaytop: add interactive mode with keyboard controls

The original delaytop only supported static output with limited
interaction.  Users had to restart the tool with different command-line
options to change sorting or display modes, which disrupted continuous
monitoring and reduced productivity during performance investigations.

Adds real-time interactive controls through keyboard input:
1) Add interactive menu system with visual prompts
2) Support dynamic sorting changes without restarting
3) Enable toggle of memory verbose mode with 'M' key

The interactive mode transforms delaytop from a static monitoring tool
into a dynamic investigation platform, allowing users to adapt the view in
real-time based on observed performance patterns.

Link: https://lkml.kernel.org/r/20250907001338580EURha20BxWFmBSrUpS8D1@zte.com.cn


Signed-off-by: default avatarFan Yu <fan.yu9@zte.com.cn>
Reviewed-by: default avatarxu xin <xu.xin16@zte.com.cn>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 99d9c55f
Loading
Loading
Loading
Loading
+121 −45
Original line number Diff line number Diff line
@@ -73,8 +73,8 @@
#define PSI_LINE_FORMAT "%-12s %6.1f%%/%6.1f%%/%6.1f%%/%8llu(ms)\n"
#define DELAY_FMT_DEFAULT "%8.2f %8.2f %8.2f %8.2f\n"
#define DELAY_FMT_MEMVERBOSE "%8.2f %8.2f %8.2f %8.2f %8.2f %8.2f\n"
#define SORT_FIELD(name, modes) \
	{#name, \
#define SORT_FIELD(name, cmd, modes) \
	{#name, #cmd, \
	offsetof(struct task_info, name##_delay_total), \
	offsetof(struct task_info, name##_count), \
	modes}
@@ -140,6 +140,7 @@ struct container_stats {
/* Delay field structure */
struct field_desc {
	const char *name;	/* Field name for cmdline argument */
	const char *cmd_char;	/* Interactive command */
	unsigned long total_offset; /* Offset of total delay in task_info */
	unsigned long count_offset; /* Offset of count in task_info */
	size_t supported_modes; /* Supported display modes */
@@ -165,17 +166,18 @@ static int task_count;
static int running = 1;
static struct container_stats container_stats;
static const struct field_desc sort_fields[] = {
	SORT_FIELD(cpu,		MODE_DEFAULT),
	SORT_FIELD(blkio,	MODE_DEFAULT),
	SORT_FIELD(irq,		MODE_DEFAULT),
	SORT_FIELD(mem,		MODE_DEFAULT | MODE_MEMVERBOSE),
	SORT_FIELD(swapin,	MODE_MEMVERBOSE),
	SORT_FIELD(freepages,	MODE_MEMVERBOSE),
	SORT_FIELD(thrashing,	MODE_MEMVERBOSE),
	SORT_FIELD(compact,	MODE_MEMVERBOSE),
	SORT_FIELD(wpcopy,	MODE_MEMVERBOSE),
	SORT_FIELD(cpu,		c,	MODE_DEFAULT),
	SORT_FIELD(blkio,	i,	MODE_DEFAULT),
	SORT_FIELD(irq,		q,	MODE_DEFAULT),
	SORT_FIELD(mem,		m,	MODE_DEFAULT | MODE_MEMVERBOSE),
	SORT_FIELD(swapin,	s,	MODE_MEMVERBOSE),
	SORT_FIELD(freepages,	r,	MODE_MEMVERBOSE),
	SORT_FIELD(thrashing,	t,	MODE_MEMVERBOSE),
	SORT_FIELD(compact,	p,	MODE_MEMVERBOSE),
	SORT_FIELD(wpcopy,	w,	MODE_MEMVERBOSE),
	END_FIELD
};
static int sort_selected;

/* Netlink socket variables */
static int nl_sd = -1;
@@ -197,6 +199,19 @@ static void disable_raw_mode(void)
	tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

/* Find field descriptor by command line */
static const struct field_desc *get_field_by_cmd_char(char ch)
{
	const struct field_desc *field;

	for (field = sort_fields; field->name != NULL; field++) {
		if (field->cmd_char[0] == ch)
			return field;
	}

	return NULL;
}

/* Find field descriptor by name with string comparison */
static const struct field_desc *get_field_by_name(const char *name)
{
@@ -870,6 +885,18 @@ static void display_results(void)
			container_stats.nr_stopped, container_stats.nr_uninterruptible,
			container_stats.nr_io_wait);
	}

	/* Interacive command */
	suc &= BOOL_FPRINT(out, "[o]sort [M]memverbose [q]quit\n");
	if (sort_selected) {
		if (cfg.display_mode == MODE_MEMVERBOSE)
			suc &= BOOL_FPRINT(out,
				"sort selection: [m]MEM [r]RCL [t]THR [p]CMP [w]WP\n");
		else
			suc &= BOOL_FPRINT(out,
				"sort selection: [c]CPU [i]IO [m]MEM [q]IRQ\n");
	}

	/* Task delay output */
	suc &= BOOL_FPRINT(out, "Top %d processes (sorted by %s delay):\n",
			cfg.max_processes, get_name_by_field(cfg.sort_field));
@@ -919,11 +946,78 @@ static void display_results(void)
		perror("Error writing to output");
}

/* Check for keyboard input with timeout based on cfg.delay */
static char check_for_keypress(void)
{
	struct timeval tv = {cfg.delay, 0};
	fd_set readfds;
	char ch = 0;

	FD_ZERO(&readfds);
	FD_SET(STDIN_FILENO, &readfds);
	int r = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);

	if (r > 0 && FD_ISSET(STDIN_FILENO, &readfds)) {
		read(STDIN_FILENO, &ch, 1);
		return ch;
	}

	return 0;
}

#define MAX_MODE_SIZE 2
static void toggle_display_mode(void)
{
	static const size_t modes[MAX_MODE_SIZE] = {MODE_DEFAULT, MODE_MEMVERBOSE};
	static size_t cur_index;

	cur_index = (cur_index + 1) % MAX_MODE_SIZE;
	cfg.display_mode = modes[cur_index];
}

/* Handle keyboard input: sorting selection, mode toggle, or quit */
static void handle_keypress(char ch, int *running)
{
	const struct field_desc *field;

	/* Change sort field */
	if (sort_selected) {
		field = get_field_by_cmd_char(ch);
		if (field && (field->supported_modes & cfg.display_mode))
			cfg.sort_field = field;

		sort_selected = 0;
	/* Handle mode changes or quit */
	} else {
		switch (ch) {
		case 'o':
			sort_selected = 1;
			break;
		case 'M':
			toggle_display_mode();
			for (field = sort_fields; field->name != NULL; field++) {
				if (field->supported_modes & cfg.display_mode) {
					cfg.sort_field = field;
					break;
				}
			}
			break;
		case 'q':
		case 'Q':
			*running = 0;
			break;
		default:
			break;
		}
	}
}

/* Main function */
int main(int argc, char **argv)
{
	const struct field_desc *field;
	int iterations = 0;
	int use_q_quit = 0;
	char keypress;

	/* Parse command line arguments */
	parse_args(argc, argv);
@@ -943,21 +1037,21 @@ int main(int argc, char **argv)
		exit(1);
	}

	if (!cfg.output_one_time) {
		use_q_quit = 1;
	/* Set terminal to non-canonical mode for interaction */
	enable_raw_mode();
		printf("Press 'q' to quit.\n");
		fflush(stdout);
	}

	/* Main loop */
	while (running) {
		/* Exit when sort field do not match display mode */
		/* Auto-switch sort field when not matching display mode */
		if (!(cfg.sort_field->supported_modes & cfg.display_mode)) {
			fprintf(stderr, "Sort field not supported in this mode\n");
			display_available_fields(cfg.display_mode);
			for (field = sort_fields; field->name != NULL; field++) {
				if (field->supported_modes & cfg.display_mode) {
					cfg.sort_field = field;
					printf("Auto-switched sort field to: %s\n", field->name);
					break;
				}
			}
		}

		/* Read PSI statistics */
		read_psi_stats();
@@ -983,31 +1077,13 @@ int main(int argc, char **argv)
		if (cfg.output_one_time)
			break;

		/* Check for 'q' key to quit */
		if (use_q_quit) {
			struct timeval tv = {cfg.delay, 0};
			fd_set readfds;

			FD_ZERO(&readfds);
			FD_SET(STDIN_FILENO, &readfds);
			int r = select(STDIN_FILENO+1, &readfds, NULL, NULL, &tv);

			if (r > 0 && FD_ISSET(STDIN_FILENO, &readfds)) {
				char ch = 0;

				read(STDIN_FILENO, &ch, 1);
				if (ch == 'q' || ch == 'Q') {
					running = 0;
					break;
				}
			}
		} else {
			sleep(cfg.delay);
		}
		/* Keypress for interactive usage */
		keypress = check_for_keypress();
		if (keypress)
			handle_keypress(keypress, &running);
	}

	/* Restore terminal mode */
	if (use_q_quit)
	disable_raw_mode();

	/* Cleanup */