]>
Commit | Line | Data |
---|---|---|
ae0dfe14 | 1 | /* |
9abd5e4b KZ |
2 | * SPDX-License-Identifier: GPL-2.1-or-later |
3 | * | |
ae0dfe14 | 4 | * irqtop.c - utility to display kernel interrupt information. |
5 | * | |
94e7e258 KZ |
6 | * Copyright (C) 2019 zhenwei pi <pizhenwei@bytedance.com> |
7 | * Copyright (C) 2020 Karel Zak <kzak@redhat.com> | |
ae0dfe14 | 8 | * |
9 | * This library is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU Lesser General Public | |
11 | * License as published by the Free Software Foundation; either | |
12 | * version 2.1 of the License, or (at your option) any later version. | |
ae0dfe14 | 13 | */ |
57e3dc06 SK |
14 | #include <ctype.h> |
15 | #include <errno.h> | |
16 | #include <getopt.h> | |
ae0dfe14 | 17 | #include <limits.h> |
18 | #include <locale.h> | |
57e3dc06 | 19 | #include <signal.h> |
ae0dfe14 | 20 | #include <stdio.h> |
57e3dc06 | 21 | #include <stdlib.h> |
ae0dfe14 | 22 | #include <string.h> |
fa8c5cd5 | 23 | #include <sys/epoll.h> |
ae0dfe14 | 24 | #include <sys/ioctl.h> |
ae0dfe14 | 25 | #include <sys/select.h> |
fa8c5cd5 | 26 | #include <sys/signalfd.h> |
ae0dfe14 | 27 | #include <sys/time.h> |
fa8c5cd5 | 28 | #include <sys/timerfd.h> |
ae0dfe14 | 29 | #include <sys/types.h> |
57e3dc06 | 30 | #include <termios.h> |
ae0dfe14 | 31 | #include <unistd.h> |
ae0dfe14 | 32 | |
57e3dc06 SK |
33 | #ifdef HAVE_SLCURSES_H |
34 | # include <slcurses.h> | |
35 | #elif defined(HAVE_SLANG_SLCURSES_H) | |
36 | # include <slang/slcurses.h> | |
37 | #elif defined(HAVE_NCURSESW_NCURSES_H) && defined(HAVE_WIDECHAR) | |
38 | # include <ncursesw/ncurses.h> | |
39 | #elif defined(HAVE_NCURSES_H) | |
40 | # include <ncurses.h> | |
41 | #elif defined(HAVE_NCURSES_NCURSES_H) | |
42 | # include <ncurses/ncurses.h> | |
43 | #endif | |
44 | ||
45 | #ifdef HAVE_WIDECHAR | |
46 | # include <wctype.h> | |
47 | # include <wchar.h> | |
48 | #endif | |
49 | ||
8d8cef80 SK |
50 | #include <libsmartcols.h> |
51 | ||
57e3dc06 | 52 | #include "closestream.h" |
4b2fadb1 | 53 | #include "cpuset.h" |
465e279a | 54 | #include "monotonic.h" |
57e3dc06 | 55 | #include "pathnames.h" |
4e55ffbd | 56 | #include "strutils.h" |
a163d853 | 57 | #include "timeutils.h" |
a11660d1 | 58 | #include "ttyutils.h" |
9aea73b3 | 59 | #include "xalloc.h" |
57e3dc06 | 60 | |
94e7e258 | 61 | #include "irq-common.h" |
d7f9cdf5 | 62 | |
94e7e258 | 63 | #define MAX_EVENTS 3 |
c6ccf2ec | 64 | |
232e85af KZ |
65 | enum irqtop_cpustat_mode { |
66 | IRQTOP_CPUSTAT_AUTO, | |
67 | IRQTOP_CPUSTAT_ENABLE, | |
68 | IRQTOP_CPUSTAT_DISABLE, | |
17f7caa4 | 69 | }; |
70 | ||
02f2919e | 71 | /* top control struct */ |
a937ed0f | 72 | struct irqtop_ctl { |
9c0740fc KZ |
73 | WINDOW *win; |
74 | int cols; | |
75 | int rows; | |
76 | char *hostname; | |
77 | ||
fa8c5cd5 | 78 | struct itimerspec timer; |
9c0740fc | 79 | struct irq_stat *prev_stat; |
4b2fadb1 | 80 | size_t setsize; |
81 | cpu_set_t *cpuset; | |
487c7466 | 82 | |
232e85af | 83 | enum irqtop_cpustat_mode cpustat_mode; |
94e7e258 | 84 | unsigned int request_exit:1; |
b6ce063b | 85 | unsigned int softirq:1; |
a937ed0f | 86 | }; |
ae0dfe14 | 87 | |
02f2919e | 88 | /* user's input parser */ |
c6ccf2ec | 89 | static void parse_input(struct irqtop_ctl *ctl, struct irq_output *out, char c) |
ae0dfe14 | 90 | { |
57e3dc06 | 91 | switch (c) { |
ae0dfe14 | 92 | case 'q': |
93 | case 'Q': | |
4e55ffbd | 94 | ctl->request_exit = 1; |
ae0dfe14 | 95 | break; |
44e39c99 KZ |
96 | default: |
97 | set_sort_func_by_key(out, c); | |
98 | break; | |
ae0dfe14 | 99 | } |
100 | } | |
101 | ||
9c0740fc | 102 | static int update_screen(struct irqtop_ctl *ctl, struct irq_output *out) |
ae0dfe14 | 103 | { |
17f7caa4 | 104 | struct libscols_table *table, *cpus = NULL; |
29135b23 | 105 | struct irq_stat *stat; |
ec95b436 | 106 | time_t now = time(NULL); |
1b889dcc | 107 | char timestr[64], *data, *data0, *p; |
29135b23 | 108 | |
a23aecc1 | 109 | /* make irqs table */ |
4b2fadb1 | 110 | table = get_scols_table(out, ctl->prev_stat, &stat, ctl->softirq, ctl->setsize, |
111 | ctl->cpuset); | |
212ca752 | 112 | if (!table) { |
29135b23 SK |
113 | ctl->request_exit = 1; |
114 | return 1; | |
115 | } | |
1b889dcc KZ |
116 | scols_table_enable_maxout(table, 1); |
117 | scols_table_enable_nowrap(table, 1); | |
118 | scols_table_reduce_termwidth(table, 1); | |
119 | ||
a23aecc1 | 120 | /* make cpus table */ |
232e85af | 121 | if (ctl->cpustat_mode != IRQTOP_CPUSTAT_DISABLE) { |
4b2fadb1 | 122 | cpus = get_scols_cpus_table(out, ctl->prev_stat, stat, ctl->setsize, |
123 | ctl->cpuset); | |
17f7caa4 | 124 | scols_table_reduce_termwidth(cpus, 1); |
232e85af | 125 | if (ctl->cpustat_mode == IRQTOP_CPUSTAT_AUTO) |
17f7caa4 | 126 | scols_table_enable_nowrap(cpus, 1); |
127 | } | |
a23aecc1 KZ |
128 | |
129 | /* print header */ | |
ec95b436 KZ |
130 | move(0, 0); |
131 | strtime_iso(&now, ISO_TIMESTAMP, timestr, sizeof(timestr)); | |
9c0740fc | 132 | wprintw(ctl->win, _("irqtop | total: %ld delta: %ld | %s | %s\n\n"), |
68afc56b | 133 | stat->total_irq, stat->delta_irq, ctl->hostname, timestr); |
8d8cef80 | 134 | |
17f7caa4 | 135 | /* print cpus table or not by -c option */ |
136 | if (cpus) { | |
137 | scols_print_table_to_string(cpus, &data); | |
138 | wprintw(ctl->win, "%s\n\n", data); | |
139 | free(data); | |
140 | } | |
a23aecc1 KZ |
141 | |
142 | /* print irqs table */ | |
1b889dcc KZ |
143 | scols_print_table_to_string(table, &data0); |
144 | data = data0; | |
145 | ||
1b889dcc KZ |
146 | p = strchr(data, '\n'); |
147 | if (p) { | |
a23aecc1 | 148 | /* print header in reverse mode */ |
1b889dcc KZ |
149 | *p = '\0'; |
150 | attron(A_REVERSE); | |
151 | wprintw(ctl->win, "%s\n", data); | |
152 | attroff(A_REVERSE); | |
153 | data = p + 1; | |
154 | } | |
155 | ||
9c0740fc | 156 | wprintw(ctl->win, "%s", data); |
1b889dcc | 157 | free(data0); |
ec95b436 | 158 | |
daee2720 | 159 | /* clean up */ |
212ca752 | 160 | scols_unref_table(table); |
68afc56b | 161 | if (ctl->prev_stat) |
77f57b90 | 162 | free_irqstat(ctl->prev_stat); |
68afc56b | 163 | ctl->prev_stat = stat; |
29135b23 SK |
164 | return 0; |
165 | } | |
166 | ||
9c0740fc | 167 | static int event_loop(struct irqtop_ctl *ctl, struct irq_output *out) |
fa8c5cd5 SK |
168 | { |
169 | int efd, sfd, tfd; | |
170 | sigset_t sigmask; | |
171 | struct signalfd_siginfo siginfo; | |
172 | struct epoll_event ev, events[MAX_EVENTS]; | |
173 | long int nr; | |
174 | uint64_t unused; | |
175 | int retval = 0; | |
176 | ||
177 | efd = epoll_create1(0); | |
178 | ||
179 | if ((tfd = timerfd_create(CLOCK_MONOTONIC, 0)) < 0) | |
180 | err(EXIT_FAILURE, _("cannot not create timerfd")); | |
181 | if (timerfd_settime(tfd, 0, &ctl->timer, NULL) != 0) | |
182 | err(EXIT_FAILURE, _("cannot set timerfd")); | |
642f6cb0 | 183 | |
fa8c5cd5 SK |
184 | ev.events = EPOLLIN; |
185 | ev.data.fd = tfd; | |
186 | if (epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev) != 0) | |
187 | err(EXIT_FAILURE, _("epoll_ctl failed")); | |
188 | ||
189 | if (sigfillset(&sigmask) != 0) | |
190 | err(EXIT_FAILURE, _("sigfillset failed")); | |
191 | if (sigprocmask(SIG_BLOCK, &sigmask, NULL) != 0) | |
192 | err(EXIT_FAILURE, _("sigprocmask failed")); | |
642f6cb0 KZ |
193 | |
194 | sigaddset(&sigmask, SIGWINCH); | |
195 | sigaddset(&sigmask, SIGTERM); | |
196 | sigaddset(&sigmask, SIGINT); | |
197 | sigaddset(&sigmask, SIGQUIT); | |
198 | ||
199 | if ((sfd = signalfd(-1, &sigmask, SFD_CLOEXEC)) < 0) | |
fa8c5cd5 | 200 | err(EXIT_FAILURE, _("cannot not create signalfd")); |
642f6cb0 | 201 | |
fa8c5cd5 SK |
202 | ev.events = EPOLLIN; |
203 | ev.data.fd = sfd; | |
204 | if (epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &ev) != 0) | |
205 | err(EXIT_FAILURE, _("epoll_ctl failed")); | |
206 | ||
207 | ev.events = EPOLLIN; | |
208 | ev.data.fd = STDIN_FILENO; | |
209 | if (epoll_ctl(efd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) != 0) | |
210 | err(EXIT_FAILURE, _("epoll_ctl failed")); | |
211 | ||
9c0740fc | 212 | retval |= update_screen(ctl, out); |
fa8c5cd5 SK |
213 | refresh(); |
214 | ||
215 | while (!ctl->request_exit) { | |
216 | const ssize_t nr_events = epoll_wait(efd, events, MAX_EVENTS, -1); | |
217 | ||
218 | for (nr = 0; nr < nr_events; nr++) { | |
219 | if (events[nr].data.fd == tfd) { | |
220 | if (read(tfd, &unused, sizeof(unused)) < 0) | |
221 | warn(_("read failed")); | |
222 | } else if (events[nr].data.fd == sfd) { | |
223 | if (read(sfd, &siginfo, sizeof(siginfo)) < 0) { | |
224 | warn(_("read failed")); | |
225 | continue; | |
226 | } | |
642f6cb0 | 227 | if (siginfo.ssi_signo == SIGWINCH) { |
fa8c5cd5 | 228 | get_terminal_dimension(&ctl->cols, &ctl->rows); |
5868026e | 229 | #if HAVE_RESIZETERM |
642f6cb0 | 230 | resizeterm(ctl->rows, ctl->cols); |
5868026e | 231 | #endif |
642f6cb0 | 232 | } |
fa8c5cd5 SK |
233 | else { |
234 | ctl->request_exit = 1; | |
235 | break; | |
236 | } | |
237 | } else if (events[nr].data.fd == STDIN_FILENO) { | |
238 | char c; | |
239 | ||
240 | if (read(STDIN_FILENO, &c, 1) != 1) | |
241 | warn(_("read failed")); | |
c6ccf2ec | 242 | parse_input(ctl, out, c); |
fa8c5cd5 SK |
243 | } else |
244 | abort(); | |
9c0740fc | 245 | retval |= update_screen(ctl, out); |
fa8c5cd5 SK |
246 | refresh(); |
247 | } | |
248 | } | |
249 | return retval; | |
250 | } | |
251 | ||
bd83424b KZ |
252 | static void __attribute__((__noreturn__)) usage(void) |
253 | { | |
bd83424b KZ |
254 | fputs(USAGE_HEADER, stdout); |
255 | printf(_(" %s [options]\n"), program_invocation_short_name); | |
256 | fputs(USAGE_SEPARATOR, stdout); | |
257 | ||
258 | puts(_("Interactive utility to display kernel interrupt information.")); | |
259 | ||
260 | fputs(USAGE_OPTIONS, stdout); | |
17f7caa4 | 261 | fputs(_(" -c, --cpu-stat <mode> show per-cpu stat (auto, enable, disable)\n"), stdout); |
4b2fadb1 | 262 | fputs(_(" -C, --cpu-list <list> specify cpus in list format\n"), stdout); |
bd83424b | 263 | fputs(_(" -d, --delay <secs> delay updates\n"), stdout); |
44e39c99 KZ |
264 | fputs(_(" -o, --output <list> define which output columns to use\n"), stdout); |
265 | fputs(_(" -s, --sort <column> specify sort column\n"), stdout); | |
b6ce063b | 266 | fputs(_(" -S, --softirq show softirqs instead of interrupts\n"), stdout); |
bd83424b | 267 | fputs(USAGE_SEPARATOR, stdout); |
bad4c729 | 268 | fprintf(stdout, USAGE_HELP_OPTIONS(22)); |
bd83424b KZ |
269 | |
270 | fputs(_("\nThe following interactive key commands are valid:\n"), stdout); | |
271 | fputs(_(" i sort by IRQ\n"), stdout); | |
272 | fputs(_(" t sort by TOTAL\n"), stdout); | |
273 | fputs(_(" d sort by DELTA\n"), stdout); | |
274 | fputs(_(" n sort by NAME\n"), stdout); | |
275 | fputs(_(" q Q quit program\n"), stdout); | |
276 | ||
277 | fputs(USAGE_COLUMNS, stdout); | |
a0f62b0b | 278 | irq_print_columns(stdout, 0); |
bd83424b | 279 | |
bad4c729 | 280 | fprintf(stdout, USAGE_MAN_TAIL("irqtop(1)")); |
bd83424b KZ |
281 | exit(EXIT_SUCCESS); |
282 | } | |
283 | ||
c6ccf2ec KZ |
284 | static void parse_args( struct irqtop_ctl *ctl, |
285 | struct irq_output *out, | |
286 | int argc, | |
287 | char **argv) | |
29135b23 | 288 | { |
487c7466 | 289 | const char *outarg = NULL; |
ae0dfe14 | 290 | static const struct option longopts[] = { |
0037eb44 | 291 | {"cpu-stat", required_argument, NULL, 'c'}, |
4b2fadb1 | 292 | {"cpu-list", required_argument, NULL, 'C'}, |
57e3dc06 SK |
293 | {"delay", required_argument, NULL, 'd'}, |
294 | {"sort", required_argument, NULL, 's'}, | |
487c7466 | 295 | {"output", required_argument, NULL, 'o'}, |
b6ce063b | 296 | {"softirq", no_argument, NULL, 'S'}, |
57e3dc06 SK |
297 | {"help", no_argument, NULL, 'h'}, |
298 | {"version", no_argument, NULL, 'V'}, | |
299 | {NULL, 0, NULL, 0} | |
ae0dfe14 | 300 | }; |
29135b23 | 301 | int o; |
ae0dfe14 | 302 | |
4b2fadb1 | 303 | while ((o = getopt_long(argc, argv, "c:C:d:o:s:ShV", longopts, NULL)) != -1) { |
ae0dfe14 | 304 | switch (o) { |
17f7caa4 | 305 | case 'c': |
306 | if (!strcmp(optarg, "auto")) | |
232e85af | 307 | ctl->cpustat_mode = IRQTOP_CPUSTAT_AUTO; |
17f7caa4 | 308 | else if (!strcmp(optarg, "enable")) |
232e85af | 309 | ctl->cpustat_mode = IRQTOP_CPUSTAT_ENABLE; |
17f7caa4 | 310 | else if (!strcmp(optarg, "disable")) |
232e85af | 311 | ctl->cpustat_mode = IRQTOP_CPUSTAT_DISABLE; |
17f7caa4 | 312 | else |
313 | errx(EXIT_FAILURE, _("unsupported mode '%s'"), optarg); | |
314 | break; | |
4b2fadb1 | 315 | case 'C': |
316 | { | |
317 | int ncpus = get_max_number_of_cpus(); | |
318 | if (ncpus <= 0) | |
319 | errx(EXIT_FAILURE, _("cannot determine NR_CPUS; aborting")); | |
320 | ||
321 | ctl->cpuset = cpuset_alloc(ncpus, &ctl->setsize, NULL); | |
322 | if (!ctl->cpuset) | |
323 | err(EXIT_FAILURE, _("cpuset_alloc failed")); | |
324 | ||
325 | if (cpulist_parse(optarg, ctl->cpuset, ctl->setsize, 0)) | |
326 | errx(EXIT_FAILURE, _("failed to parse CPU list: %s"), | |
327 | optarg); | |
328 | } | |
329 | break; | |
ae0dfe14 | 330 | case 'd': |
fa8c5cd5 SK |
331 | { |
332 | struct timeval delay; | |
333 | ||
334 | strtotimeval_or_err(optarg, &delay, | |
335 | _("failed to parse delay argument")); | |
336 | TIMEVAL_TO_TIMESPEC(&delay, &ctl->timer.it_interval); | |
337 | ctl->timer.it_value = ctl->timer.it_interval; | |
338 | } | |
ae0dfe14 | 339 | break; |
340 | case 's': | |
44e39c99 | 341 | set_sort_func_by_name(out, optarg); |
ae0dfe14 | 342 | break; |
487c7466 KZ |
343 | case 'o': |
344 | outarg = optarg; | |
8d8cef80 | 345 | break; |
b6ce063b | 346 | case 'S': |
347 | ctl->softirq = 1; | |
348 | break; | |
ae0dfe14 | 349 | case 'V': |
57e3dc06 | 350 | print_version(EXIT_SUCCESS); |
ae0dfe14 | 351 | case 'h': |
57e3dc06 | 352 | usage(); |
ae0dfe14 | 353 | default: |
29135b23 | 354 | errtryhelp(EXIT_FAILURE); |
ae0dfe14 | 355 | } |
356 | } | |
487c7466 KZ |
357 | |
358 | /* default */ | |
c6ccf2ec KZ |
359 | if (!out->ncolumns) { |
360 | out->columns[out->ncolumns++] = COL_IRQ; | |
361 | out->columns[out->ncolumns++] = COL_TOTAL; | |
94e7e258 | 362 | out->columns[out->ncolumns++] = COL_DELTA; |
b2a49bc2 | 363 | out->columns[out->ncolumns++] = COL_NAME; |
487c7466 KZ |
364 | } |
365 | ||
366 | /* add -o [+]<list> to putput */ | |
c6ccf2ec KZ |
367 | if (outarg && string_add_to_idarray(outarg, out->columns, |
368 | ARRAY_SIZE(out->columns), | |
94e7e258 KZ |
369 | &out->ncolumns, |
370 | irq_column_name_to_id) < 0) | |
487c7466 | 371 | exit(EXIT_FAILURE); |
29135b23 SK |
372 | } |
373 | ||
374 | int main(int argc, char **argv) | |
375 | { | |
642f6cb0 | 376 | int is_tty = 0; |
29135b23 | 377 | struct termios saved_tty; |
9c0740fc | 378 | struct irq_output out = { |
44e39c99 | 379 | .ncolumns = 0 |
9c0740fc | 380 | }; |
fa8c5cd5 SK |
381 | struct irqtop_ctl ctl = { |
382 | .timer.it_interval = {3, 0}, | |
383 | .timer.it_value = {3, 0} | |
384 | }; | |
29135b23 SK |
385 | |
386 | setlocale(LC_ALL, ""); | |
29135b23 | 387 | |
c6ccf2ec | 388 | parse_args(&ctl, &out, argc, argv); |
ae0dfe14 | 389 | |
94e7e258 KZ |
390 | is_tty = isatty(STDIN_FILENO); |
391 | if (is_tty && tcgetattr(STDIN_FILENO, &saved_tty) == -1) | |
392 | fputs(_("terminal setting retrieval"), stdout); | |
642f6cb0 | 393 | |
9c0740fc | 394 | ctl.win = initscr(); |
94e7e258 | 395 | get_terminal_dimension(&ctl.cols, &ctl.rows); |
5868026e | 396 | #if HAVE_RESIZETERM |
94e7e258 | 397 | resizeterm(ctl.rows, ctl.cols); |
5868026e | 398 | #endif |
94e7e258 | 399 | curs_set(0); |
642f6cb0 | 400 | |
94e7e258 | 401 | ctl.hostname = xgethostname(); |
9c0740fc | 402 | event_loop(&ctl, &out); |
ae0dfe14 | 403 | |
77f57b90 | 404 | free_irqstat(ctl.prev_stat); |
94e7e258 | 405 | free(ctl.hostname); |
4b2fadb1 | 406 | cpuset_free(ctl.cpuset); |
94e7e258 KZ |
407 | |
408 | if (is_tty) | |
409 | tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tty); | |
9c0740fc | 410 | delwin(ctl.win); |
94e7e258 | 411 | endwin(); |
ae0dfe14 | 412 | |
e925cf37 | 413 | return EXIT_SUCCESS; |
ae0dfe14 | 414 | } |