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