Skip to content

Commit 0471440

Browse files
Fan Yuakpm00
authored andcommitted
tools/delaytop: add flexible sorting by delay field
Patch series "tools/delaytop: implement real-time keyboard interaction support", v2. Current Limitations =================== The current delaytop implementation has two main limitations: 1) Static sorting only by CPU delay Forcing users to restart with different parameters to analyze other resource bottlenecks. 2) Memory delay information is always expanded Causing information overload when only high-level memory pressure monitoring is needed. Improvements ============ 1) Implemented dynamic sorting capability - Interactive key 'o' triggers sort mode. - Supports sorting by CPU/IO/Memory/IRQ delays. - Memory subcategories available in verbose mode. * c - CPU delay (default) * i - IO delay * m - Total memory delay * q - IRQ delay * s/r/t/p/w - Memory subcategories (in verbose mode) 2) Added memory display modes - Compact view (default): shows aggregated memory delays. - Verbose view ('M' key): breaks down into memory sub-delays. * SWAP - swapin delays * RCL - freepages reclaim delays * THR - thrashing delays * CMP - compaction delays * WP - write-protect copy delays Practical benefits ================== 1) Dynamic Sorting for Real-Time Bottleneck Detection System administrators can now dynamically change sorting to identify different types of resource bottlenecks without restarting. 2) Enhanced Usability with On-Screen Keybindings More intuitive interactive usage with on-screen keybindings help. Reduced screen clutter when only memory overview is needed. Use Case ======== # ./delaytop System Pressure Information: (avg10/avg60vg300/total) CPU some: 0.0%/ 0.0%/ 0.0%/ 106817(ms) CPU full: 0.0%/ 0.0%/ 0.0%/ 0(ms) Memory full: 0.0%/ 0.0%/ 0.0%/ 0(ms) Memory some: 0.0%/ 0.0%/ 0.0%/ 0(ms) IO full: 0.0%/ 0.0%/ 0.0%/ 2245(ms) IO some: 0.0%/ 0.0%/ 0.0%/ 2791(ms) IRQ full: 0.0%/ 0.0%/ 0.0%/ 0(ms) [o]sort [M]memverbose [q]quit Top 20 processes (sorted by cpu delay): PID TGID COMMAND CPU(ms) IO(ms) IRQ(ms) MEM(ms) ------------------------------------------------------------------------ 110 110 kworker/15:0H-s 27.91 0.00 0.00 0.00 57 57 cpuhp/7 3.18 0.00 0.00 0.00 99 99 cpuhp/14 2.97 0.00 0.00 0.00 51 51 cpuhp/6 0.90 0.00 0.00 0.00 44 44 kworker/4:0H-sy 0.80 0.00 0.00 0.00 76 76 idle_inject/10 0.31 0.00 0.00 0.00 100 100 idle_inject/14 0.30 0.00 0.00 0.00 1309 1309 systemsettings 0.29 0.00 0.00 0.00 60 60 ksoftirqd/7 0.28 0.00 0.00 0.00 45 45 cpuhp/5 0.22 0.00 0.00 0.00 63 63 cpuhp/8 0.20 0.00 0.00 0.00 87 87 cpuhp/12 0.18 0.00 0.00 0.00 93 93 cpuhp/13 0.17 0.00 0.00 0.00 1265 1265 acpid 0.17 0.00 0.00 0.00 1552 1552 sshd 0.17 0.00 0.00 0.00 2584 2584 sddm-helper 0.16 0.00 0.00 0.00 1284 1284 rtkit-daemon 0.15 0.00 0.00 0.00 1326 1326 nde-netfilter 0.14 0.00 0.00 0.00 27 27 cpuhp/2 0.13 0.00 0.00 0.00 631 631 kworker/11:2-rc 0.11 0.00 0.00 0.00 # ./delaytop -M System Pressure Information: (avg10/avg60vg300/total) CPU some: 0.0%/ 0.0%/ 0.0%/ 106827(ms) CPU full: 0.0%/ 0.0%/ 0.0%/ 0(ms) Memory full: 0.0%/ 0.0%/ 0.0%/ 0(ms) Memory some: 0.0%/ 0.0%/ 0.0%/ 0(ms) IO full: 0.0%/ 0.0%/ 0.0%/ 2245(ms) IO some: 0.0%/ 0.0%/ 0.0%/ 2791(ms) IRQ full: 0.0%/ 0.0%/ 0.0%/ 0(ms) [o]sort [M]memverbose [q]quit Top 20 processes (sorted by mem delay): PID TGID COMMAND MEM(ms) SWAP(ms) RCL(ms) THR(ms) CMP(ms) WP(ms) ------------------------------------------------------------------------------------------ 121732 121732 delaytop 0.01 0.00 0.00 0.00 0.00 0.01 95876 95876 top 0.00 0.00 0.00 0.00 0.00 0.00 121641 121641 systemd-userwor 0.00 0.00 0.00 0.00 0.00 0.00 121693 121693 systemd-userwor 0.00 0.00 0.00 0.00 0.00 0.00 121661 121661 systemd-userwor 0.00 0.00 0.00 0.00 0.00 0.00 1 1 systemd 0.00 0.00 0.00 0.00 0.00 0.00 2 2 kthreadd 0.00 0.00 0.00 0.00 0.00 0.00 3 3 pool_workqueue_ 0.00 0.00 0.00 0.00 0.00 0.00 4 4 kworker/R-rcu_g 0.00 0.00 0.00 0.00 0.00 0.00 5 5 kworker/R-rcu_p 0.00 0.00 0.00 0.00 0.00 0.00 6 6 kworker/R-slub_ 0.00 0.00 0.00 0.00 0.00 0.00 7 7 kworker/R-netns 0.00 0.00 0.00 0.00 0.00 0.00 9 9 kworker/0:0H-sy 0.00 0.00 0.00 0.00 0.00 0.00 11 11 kworker/u32:0-n 0.00 0.00 0.00 0.00 0.00 0.00 12 12 kworker/R-mm_pe 0.00 0.00 0.00 0.00 0.00 0.00 13 13 rcu_tasks_kthre 0.00 0.00 0.00 0.00 0.00 0.00 14 14 rcu_tasks_rude_ 0.00 0.00 0.00 0.00 0.00 0.00 15 15 rcu_tasks_trace 0.00 0.00 0.00 0.00 0.00 0.00 16 16 ksoftirqd/0 0.00 0.00 0.00 0.00 0.00 0.00 17 17 rcu_preempt 0.00 0.00 0.00 0.00 0.00 0.00 When psi is not enabled: # ./delaytop System Pressure Information: (avg10/avg60vg300/total) PSI not found: check if psi=1 enabled in cmdline This patch (of 5): The delaytop tool only supported sorting by CPU delay, which limited its usefulness when users needed to identify bottlenecks in other subsystems. Users had no way to sort processes by IO, IRQ, or other delay types to quickly pinpoint specific performance issues. Add -s/--sort option to allow sorting by different delay types. Users can now quickly identify bottlenecks in specific subsystems by sorting processes by the relevant delay metric. Link: https://lkml.kernel.org/r/20250907001101305vrTGnXaRNvtmsGkp-Ljk_@zte.com.cn Link: https://lkml.kernel.org/r/20250907001205573L3XpsQMIQnLgDqiiKYd3H@zte.com.cn Signed-off-by: Fan Yu <fan.yu9@zte.com.cn> Reviewed-by: xu xin <xu.xin16@zte.com.cn> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Wang Yaxin <wang.yaxin@zte.com.cn> Cc: Yang Yang <yang.yang29@zte.com.cn> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent 7b1e502 commit 0471440

1 file changed

Lines changed: 121 additions & 32 deletions

File tree

tools/accounting/delaytop.c

Lines changed: 121 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <linux/genetlink.h>
4343
#include <linux/taskstats.h>
4444
#include <linux/cgroupstats.h>
45+
#include <stddef.h>
4546

4647
#define PSI_CPU_SOME "/proc/pressure/cpu"
4748
#define PSI_CPU_FULL "/proc/pressure/cpu"
@@ -61,24 +62,19 @@
6162
#define TASK_COMM_LEN 16
6263
#define MAX_MSG_SIZE 1024
6364
#define MAX_TASKS 1000
65+
#define MAX_BUF_LEN 256
6466
#define SET_TASK_STAT(task_count, field) tasks[task_count].field = stats.field
6567
#define BOOL_FPRINT(stream, fmt, ...) \
6668
({ \
6769
int ret = fprintf(stream, fmt, ##__VA_ARGS__); \
6870
ret >= 0; \
6971
})
7072
#define PSI_LINE_FORMAT "%-12s %6.1f%%/%6.1f%%/%6.1f%%/%8llu(ms)\n"
71-
72-
/* Program settings structure */
73-
struct config {
74-
int delay; /* Update interval in seconds */
75-
int iterations; /* Number of iterations, 0 == infinite */
76-
int max_processes; /* Maximum number of processes to show */
77-
char sort_field; /* Field to sort by */
78-
int output_one_time; /* Output once and exit */
79-
int monitor_pid; /* Monitor specific PID */
80-
char *container_path; /* Path to container cgroup */
81-
};
73+
#define SORT_FIELD(name) \
74+
{#name, \
75+
offsetof(struct task_info, name##_delay_total), \
76+
offsetof(struct task_info, name##_count)}
77+
#define END_FIELD {NULL, 0, 0}
8278

8379
/* PSI statistics structure */
8480
struct psi_stats {
@@ -130,13 +126,42 @@ struct container_stats {
130126
int nr_io_wait; /* Number of processes in IO wait */
131127
};
132128

129+
/* Delay field structure */
130+
struct field_desc {
131+
const char *name; /* Field name for cmdline argument */
132+
unsigned long total_offset; /* Offset of total delay in task_info */
133+
unsigned long count_offset; /* Offset of count in task_info */
134+
};
135+
136+
/* Program settings structure */
137+
struct config {
138+
int delay; /* Update interval in seconds */
139+
int iterations; /* Number of iterations, 0 == infinite */
140+
int max_processes; /* Maximum number of processes to show */
141+
int output_one_time; /* Output once and exit */
142+
int monitor_pid; /* Monitor specific PID */
143+
char *container_path; /* Path to container cgroup */
144+
const struct field_desc *sort_field; /* Current sort field */
145+
};
146+
133147
/* Global variables */
134148
static struct config cfg;
135149
static struct psi_stats psi;
136150
static struct task_info tasks[MAX_TASKS];
137151
static int task_count;
138152
static int running = 1;
139153
static struct container_stats container_stats;
154+
static const struct field_desc sort_fields[] = {
155+
SORT_FIELD(cpu),
156+
SORT_FIELD(blkio),
157+
SORT_FIELD(irq),
158+
SORT_FIELD(swapin),
159+
SORT_FIELD(freepages),
160+
SORT_FIELD(thrashing),
161+
SORT_FIELD(compact),
162+
SORT_FIELD(wpcopy),
163+
END_FIELD
164+
};
140165

141166
/* Netlink socket variables */
142167
static int nl_sd = -1;
@@ -158,32 +183,75 @@ static void disable_raw_mode(void)
158183
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
159184
}
160185

186+
/* Find field descriptor by name with string comparison */
187+
static const struct field_desc *get_field_by_name(const char *name)
188+
{
189+
const struct field_desc *field;
190+
size_t field_len;
191+
192+
for (field = sort_fields; field->name != NULL; field++) {
193+
field_len = strlen(field->name);
194+
if (field_len != strlen(name))
195+
continue;
196+
if (strncmp(field->name, name, field_len) == 0)
197+
return field;
198+
}
199+
200+
return NULL;
201+
}
202+
203+
/* Find display name for a field descriptor */
204+
static const char *get_name_by_field(const struct field_desc *field)
205+
{
206+
return field ? field->name : "UNKNOWN";
207+
}
208+
209+
/* Generate string of available field names */
210+
static void display_available_fields(void)
211+
{
212+
const struct field_desc *field;
213+
char buf[MAX_BUF_LEN];
214+
215+
buf[0] = '\0';
216+
217+
for (field = sort_fields; field->name != NULL; field++) {
218+
strncat(buf, "|", MAX_BUF_LEN - strlen(buf) - 1);
219+
strncat(buf, field->name, MAX_BUF_LEN - strlen(buf) - 1);
220+
buf[MAX_BUF_LEN - 1] = '\0';
221+
}
222+
223+
fprintf(stderr, "Available fields: %s\n", buf);
224+
}
225+
161226
/* Display usage information and command line options */
162227
static void usage(void)
163228
{
164229
printf("Usage: delaytop [Options]\n"
165230
"Options:\n"
166-
" -h, --help Show this help message and exit\n"
167-
" -d, --delay=SECONDS Set refresh interval (default: 2 seconds, min: 1)\n"
168-
" -n, --iterations=COUNT Set number of updates (default: 0 = infinite)\n"
169-
" -P, --processes=NUMBER Set maximum number of processes to show (default: 20, max: 1000)\n"
170-
" -o, --once Display once and exit\n"
171-
" -p, --pid=PID Monitor only the specified PID\n"
172-
" -C, --container=PATH Monitor the container at specified cgroup path\n");
231+
" -h, --help Show this help message and exit\n"
232+
" -d, --delay=SECONDS Set refresh interval (default: 2 seconds, min: 1)\n"
233+
" -n, --iterations=COUNT Set number of updates (default: 0 = infinite)\n"
234+
" -P, --processes=NUMBER Set maximum number of processes to show (default: 20, max: 1000)\n"
235+
" -o, --once Display once and exit\n"
236+
" -p, --pid=PID Monitor only the specified PID\n"
237+
" -C, --container=PATH Monitor the container at specified cgroup path\n"
238+
" -s, --sort=FIELD Sort by delay field (default: cpu)\n");
173239
exit(0);
174240
}
175241

176242
/* Parse command line arguments and set configuration */
177243
static void parse_args(int argc, char **argv)
178244
{
179245
int c;
246+
const struct field_desc *field;
180247
struct option long_options[] = {
181248
{"help", no_argument, 0, 'h'},
182249
{"delay", required_argument, 0, 'd'},
183250
{"iterations", required_argument, 0, 'n'},
184251
{"pid", required_argument, 0, 'p'},
185252
{"once", no_argument, 0, 'o'},
186253
{"processes", required_argument, 0, 'P'},
254+
{"sort", required_argument, 0, 's'},
187255
{"container", required_argument, 0, 'C'},
188256
{0, 0, 0, 0}
189257
};
@@ -192,15 +260,15 @@ static void parse_args(int argc, char **argv)
192260
cfg.delay = 2;
193261
cfg.iterations = 0;
194262
cfg.max_processes = 20;
195-
cfg.sort_field = 'c'; /* Default sort by CPU delay */
263+
cfg.sort_field = &sort_fields[0]; /* Default sorted by CPU delay */
196264
cfg.output_one_time = 0;
197265
cfg.monitor_pid = 0; /* 0 means monitor all PIDs */
198266
cfg.container_path = NULL;
199267

200268
while (1) {
201269
int option_index = 0;
202270

203-
c = getopt_long(argc, argv, "hd:n:p:oP:C:", long_options, &option_index);
271+
c = getopt_long(argc, argv, "hd:n:p:oP:C:s:", long_options, &option_index);
204272
if (c == -1)
205273
break;
206274

@@ -247,6 +315,22 @@ static void parse_args(int argc, char **argv)
247315
case 'C':
248316
cfg.container_path = strdup(optarg);
249317
break;
318+
case 's':
319+
if (strlen(optarg) == 0) {
320+
fprintf(stderr, "Error: empty sort field\n");
321+
exit(1);
322+
}
323+
324+
field = get_field_by_name(optarg);
325+
/* Show available fields if invalid option provided */
326+
if (!field) {
327+
fprintf(stderr, "Error: invalid sort field '%s'\n", optarg);
328+
display_available_fields();
329+
exit(1);
330+
}
331+
332+
cfg.sort_field = field;
333+
break;
250334
default:
251335
fprintf(stderr, "Try 'delaytop --help' for more information.\n");
252336
exit(1);
@@ -587,19 +671,23 @@ static int compare_tasks(const void *a, const void *b)
587671
{
588672
const struct task_info *t1 = (const struct task_info *)a;
589673
const struct task_info *t2 = (const struct task_info *)b;
674+
unsigned long long total1;
675+
unsigned long long total2;
676+
unsigned long count1;
677+
unsigned long count2;
590678
double avg1, avg2;
591679

592-
switch (cfg.sort_field) {
593-
case 'c': /* CPU */
594-
avg1 = average_ms(t1->cpu_delay_total, t1->cpu_count);
595-
avg2 = average_ms(t2->cpu_delay_total, t2->cpu_count);
596-
if (avg1 != avg2)
597-
return avg2 > avg1 ? 1 : -1;
598-
return t2->cpu_delay_total > t1->cpu_delay_total ? 1 : -1;
680+
total1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->total_offset);
681+
total2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->total_offset);
682+
count1 = *(unsigned long *)((char *)t1 + cfg.sort_field->count_offset);
683+
count2 = *(unsigned long *)((char *)t2 + cfg.sort_field->count_offset);
599684

600-
default:
601-
return t2->cpu_delay_total > t1->cpu_delay_total ? 1 : -1;
602-
}
685+
avg1 = average_ms(total1, count1);
686+
avg2 = average_ms(total2, count2);
687+
if (avg1 != avg2)
688+
return avg2 > avg1 ? 1 : -1;
689+
690+
return 0;
603691
}
604692

605693
/* Sort tasks by selected field */
@@ -738,8 +826,9 @@ static void display_results(void)
738826
container_stats.nr_stopped, container_stats.nr_uninterruptible,
739827
container_stats.nr_io_wait);
740828
}
741-
suc &= BOOL_FPRINT(out, "Top %d processes (sorted by CPU delay):\n",
742-
cfg.max_processes);
829+
/* Task delay output */
830+
suc &= BOOL_FPRINT(out, "Top %d processes (sorted by %s delay):\n",
831+
cfg.max_processes, get_name_by_field(cfg.sort_field));
743832
suc &= BOOL_FPRINT(out, "%5s %5s %-17s", "PID", "TGID", "COMMAND");
744833
suc &= BOOL_FPRINT(out, "%7s %7s %7s %7s %7s %7s %7s %7s\n",
745834
"CPU(ms)", "IO(ms)", "SWAP(ms)", "RCL(ms)",

0 commit comments

Comments
 (0)