2 * SPDX-License-Identifier: LGPL-2.1-or-later
4 * irqtop.c - utility to display kernel interrupt information.
6 * Copyright (C) 2019 zhenwei pi <pizhenwei@bytedance.com>
7 * Copyright (C) 2020 Karel Zak <kzak@redhat.com>
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.
23 #include <sys/epoll.h>
24 #include <sys/ioctl.h>
25 #include <sys/select.h>
26 #include <sys/signalfd.h>
28 #include <sys/timerfd.h>
29 #include <sys/types.h>
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)
41 #elif defined(HAVE_NCURSES_NCURSES_H)
42 # include <ncurses/ncurses.h>
50 #include <libsmartcols.h>
52 #include "closestream.h"
54 #include "monotonic.h"
55 #include "pathnames.h"
57 #include "timeutils.h"
61 #include "irq-common.h"
65 enum irqtop_cpustat_mode
{
67 IRQTOP_CPUSTAT_ENABLE
,
68 IRQTOP_CPUSTAT_DISABLE
,
71 /* top control struct */
78 struct itimerspec timer
;
79 struct irq_stat
*prev_stat
;
83 enum irqtop_cpustat_mode cpustat_mode
;
84 unsigned int request_exit
:1;
85 unsigned int softirq
:1;
88 /* user's input parser */
89 static void parse_input(struct irqtop_ctl
*ctl
, struct irq_output
*out
, char c
)
94 ctl
->request_exit
= 1;
97 set_sort_func_by_key(out
, c
);
102 static int update_screen(struct irqtop_ctl
*ctl
, struct irq_output
*out
)
104 struct libscols_table
*table
, *cpus
= NULL
;
105 struct irq_stat
*stat
;
106 time_t now
= time(NULL
);
107 char timestr
[64], *data
, *data0
, *p
;
109 /* make irqs table */
110 table
= get_scols_table(out
, ctl
->prev_stat
, &stat
, ctl
->softirq
, ctl
->setsize
,
113 ctl
->request_exit
= 1;
116 scols_table_enable_maxout(table
, 1);
117 scols_table_enable_nowrap(table
, 1);
118 scols_table_reduce_termwidth(table
, 1);
120 /* make cpus table */
121 if (ctl
->cpustat_mode
!= IRQTOP_CPUSTAT_DISABLE
) {
122 cpus
= get_scols_cpus_table(out
, ctl
->prev_stat
, stat
, ctl
->setsize
,
124 scols_table_reduce_termwidth(cpus
, 1);
125 if (ctl
->cpustat_mode
== IRQTOP_CPUSTAT_AUTO
)
126 scols_table_enable_nowrap(cpus
, 1);
131 strtime_iso(&now
, ISO_TIMESTAMP
, timestr
, sizeof(timestr
));
132 wprintw(ctl
->win
, _("irqtop | total: %ld delta: %ld | %s | %s\n\n"),
133 stat
->total_irq
, stat
->delta_irq
, ctl
->hostname
, timestr
);
135 /* print cpus table or not by -c option */
137 scols_print_table_to_string(cpus
, &data
);
138 wprintw(ctl
->win
, "%s\n\n", data
);
142 /* print irqs table */
143 scols_print_table_to_string(table
, &data0
);
146 p
= strchr(data
, '\n');
148 /* print header in reverse mode */
151 wprintw(ctl
->win
, "%s\n", data
);
156 wprintw(ctl
->win
, "%s", data
);
160 scols_unref_table(table
);
162 free_irqstat(ctl
->prev_stat
);
163 ctl
->prev_stat
= stat
;
167 static int event_loop(struct irqtop_ctl
*ctl
, struct irq_output
*out
)
171 struct signalfd_siginfo siginfo
;
172 struct epoll_event ev
, events
[MAX_EVENTS
];
177 efd
= epoll_create1(0);
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"));
186 if (epoll_ctl(efd
, EPOLL_CTL_ADD
, tfd
, &ev
) != 0)
187 err(EXIT_FAILURE
, _("epoll_ctl failed"));
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"));
194 sigaddset(&sigmask
, SIGWINCH
);
195 sigaddset(&sigmask
, SIGTERM
);
196 sigaddset(&sigmask
, SIGINT
);
197 sigaddset(&sigmask
, SIGQUIT
);
199 if ((sfd
= signalfd(-1, &sigmask
, SFD_CLOEXEC
)) < 0)
200 err(EXIT_FAILURE
, _("cannot not create signalfd"));
204 if (epoll_ctl(efd
, EPOLL_CTL_ADD
, sfd
, &ev
) != 0)
205 err(EXIT_FAILURE
, _("epoll_ctl failed"));
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"));
212 retval
|= update_screen(ctl
, out
);
215 while (!ctl
->request_exit
) {
216 const ssize_t nr_events
= epoll_wait(efd
, events
, MAX_EVENTS
, -1);
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"));
227 if (siginfo
.ssi_signo
== SIGWINCH
) {
228 get_terminal_dimension(&ctl
->cols
, &ctl
->rows
);
230 resizeterm(ctl
->rows
, ctl
->cols
);
234 ctl
->request_exit
= 1;
237 } else if (events
[nr
].data
.fd
== STDIN_FILENO
) {
240 if (read(STDIN_FILENO
, &c
, 1) != 1)
241 warn(_("read failed"));
242 parse_input(ctl
, out
, c
);
245 retval
|= update_screen(ctl
, out
);
252 static void __attribute__((__noreturn__
)) usage(void)
254 fputs(USAGE_HEADER
, stdout
);
255 printf(_(" %s [options]\n"), program_invocation_short_name
);
256 fputs(USAGE_SEPARATOR
, stdout
);
258 puts(_("Interactive utility to display kernel interrupt information."));
260 fputs(USAGE_OPTIONS
, stdout
);
261 fputs(_(" -c, --cpu-stat <mode> show per-cpu stat (auto, enable, disable)\n"), stdout
);
262 fputs(_(" -C, --cpu-list <list> specify cpus in list format\n"), stdout
);
263 fputs(_(" -d, --delay <secs> delay updates\n"), stdout
);
264 fputs(_(" -o, --output <list> define which output columns to use\n"), stdout
);
265 fputs(_(" -s, --sort <column> specify sort column\n"), stdout
);
266 fputs(_(" -S, --softirq show softirqs instead of interrupts\n"), stdout
);
267 fputs(USAGE_SEPARATOR
, stdout
);
268 fprintf(stdout
, USAGE_HELP_OPTIONS(22));
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
);
277 fputs(USAGE_COLUMNS
, stdout
);
278 irq_print_columns(stdout
, 0);
280 fprintf(stdout
, USAGE_MAN_TAIL("irqtop(1)"));
284 static void parse_args( struct irqtop_ctl
*ctl
,
285 struct irq_output
*out
,
289 const char *outarg
= NULL
;
290 static const struct option longopts
[] = {
291 {"cpu-stat", required_argument
, NULL
, 'c'},
292 {"cpu-list", required_argument
, NULL
, 'C'},
293 {"delay", required_argument
, NULL
, 'd'},
294 {"sort", required_argument
, NULL
, 's'},
295 {"output", required_argument
, NULL
, 'o'},
296 {"softirq", no_argument
, NULL
, 'S'},
297 {"help", no_argument
, NULL
, 'h'},
298 {"version", no_argument
, NULL
, 'V'},
303 while ((o
= getopt_long(argc
, argv
, "c:C:d:o:s:ShV", longopts
, NULL
)) != -1) {
306 if (!strcmp(optarg
, "auto"))
307 ctl
->cpustat_mode
= IRQTOP_CPUSTAT_AUTO
;
308 else if (!strcmp(optarg
, "enable"))
309 ctl
->cpustat_mode
= IRQTOP_CPUSTAT_ENABLE
;
310 else if (!strcmp(optarg
, "disable"))
311 ctl
->cpustat_mode
= IRQTOP_CPUSTAT_DISABLE
;
313 errx(EXIT_FAILURE
, _("unsupported mode '%s'"), optarg
);
317 int ncpus
= get_max_number_of_cpus();
319 errx(EXIT_FAILURE
, _("cannot determine NR_CPUS; aborting"));
321 ctl
->cpuset
= cpuset_alloc(ncpus
, &ctl
->setsize
, NULL
);
323 err(EXIT_FAILURE
, _("cpuset_alloc failed"));
325 if (cpulist_parse(optarg
, ctl
->cpuset
, ctl
->setsize
, 0))
326 errx(EXIT_FAILURE
, _("failed to parse CPU list: %s"),
332 struct timeval delay
;
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
;
341 set_sort_func_by_name(out
, optarg
);
350 print_version(EXIT_SUCCESS
);
354 errtryhelp(EXIT_FAILURE
);
359 if (!out
->ncolumns
) {
360 out
->columns
[out
->ncolumns
++] = COL_IRQ
;
361 out
->columns
[out
->ncolumns
++] = COL_TOTAL
;
362 out
->columns
[out
->ncolumns
++] = COL_DELTA
;
363 out
->columns
[out
->ncolumns
++] = COL_NAME
;
366 /* add -o [+]<list> to putput */
367 if (outarg
&& string_add_to_idarray(outarg
, out
->columns
,
368 ARRAY_SIZE(out
->columns
),
370 irq_column_name_to_id
) < 0)
374 int main(int argc
, char **argv
)
377 struct termios saved_tty
;
378 struct irq_output out
= {
381 struct irqtop_ctl ctl
= {
382 .timer
.it_interval
= {3, 0},
383 .timer
.it_value
= {3, 0}
386 setlocale(LC_ALL
, "");
388 parse_args(&ctl
, &out
, argc
, argv
);
390 is_tty
= isatty(STDIN_FILENO
);
391 if (is_tty
&& tcgetattr(STDIN_FILENO
, &saved_tty
) == -1)
392 fputs(_("terminal setting retrieval"), stdout
);
395 get_terminal_dimension(&ctl
.cols
, &ctl
.rows
);
397 resizeterm(ctl
.rows
, ctl
.cols
);
401 ctl
.hostname
= xgethostname();
402 event_loop(&ctl
, &out
);
404 free_irqstat(ctl
.prev_stat
);
406 cpuset_free(ctl
.cpuset
);
409 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &saved_tty
);