]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/irqtop.c
sys-utils: cleanup license lines, add SPDX
[thirdparty/util-linux.git] / sys-utils / irqtop.c
1 /*
2 * SPDX-License-Identifier: GPL-2.1-or-later
3 *
4 * irqtop.c - utility to display kernel interrupt information.
5 *
6 * Copyright (C) 2019 zhenwei pi <pizhenwei@bytedance.com>
7 * Copyright (C) 2020 Karel Zak <kzak@redhat.com>
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.
13 */
14 #include <ctype.h>
15 #include <errno.h>
16 #include <getopt.h>
17 #include <limits.h>
18 #include <locale.h>
19 #include <signal.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/epoll.h>
24 #include <sys/ioctl.h>
25 #include <sys/select.h>
26 #include <sys/signalfd.h>
27 #include <sys/time.h>
28 #include <sys/timerfd.h>
29 #include <sys/types.h>
30 #include <termios.h>
31 #include <unistd.h>
32
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
50 #include <libsmartcols.h>
51
52 #include "closestream.h"
53 #include "cpuset.h"
54 #include "monotonic.h"
55 #include "pathnames.h"
56 #include "strutils.h"
57 #include "timeutils.h"
58 #include "ttyutils.h"
59 #include "xalloc.h"
60
61 #include "irq-common.h"
62
63 #define MAX_EVENTS 3
64
65 enum irqtop_cpustat_mode {
66 IRQTOP_CPUSTAT_AUTO,
67 IRQTOP_CPUSTAT_ENABLE,
68 IRQTOP_CPUSTAT_DISABLE,
69 };
70
71 /* top control struct */
72 struct irqtop_ctl {
73 WINDOW *win;
74 int cols;
75 int rows;
76 char *hostname;
77
78 struct itimerspec timer;
79 struct irq_stat *prev_stat;
80 size_t setsize;
81 cpu_set_t *cpuset;
82
83 enum irqtop_cpustat_mode cpustat_mode;
84 unsigned int request_exit:1;
85 unsigned int softirq:1;
86 };
87
88 /* user's input parser */
89 static void parse_input(struct irqtop_ctl *ctl, struct irq_output *out, char c)
90 {
91 switch (c) {
92 case 'q':
93 case 'Q':
94 ctl->request_exit = 1;
95 break;
96 default:
97 set_sort_func_by_key(out, c);
98 break;
99 }
100 }
101
102 static int update_screen(struct irqtop_ctl *ctl, struct irq_output *out)
103 {
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;
108
109 /* make irqs table */
110 table = get_scols_table(out, ctl->prev_stat, &stat, ctl->softirq, ctl->setsize,
111 ctl->cpuset);
112 if (!table) {
113 ctl->request_exit = 1;
114 return 1;
115 }
116 scols_table_enable_maxout(table, 1);
117 scols_table_enable_nowrap(table, 1);
118 scols_table_reduce_termwidth(table, 1);
119
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,
123 ctl->cpuset);
124 scols_table_reduce_termwidth(cpus, 1);
125 if (ctl->cpustat_mode == IRQTOP_CPUSTAT_AUTO)
126 scols_table_enable_nowrap(cpus, 1);
127 }
128
129 /* print header */
130 move(0, 0);
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);
134
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 }
141
142 /* print irqs table */
143 scols_print_table_to_string(table, &data0);
144 data = data0;
145
146 p = strchr(data, '\n');
147 if (p) {
148 /* print header in reverse mode */
149 *p = '\0';
150 attron(A_REVERSE);
151 wprintw(ctl->win, "%s\n", data);
152 attroff(A_REVERSE);
153 data = p + 1;
154 }
155
156 wprintw(ctl->win, "%s", data);
157 free(data0);
158
159 /* clean up */
160 scols_unref_table(table);
161 if (ctl->prev_stat)
162 free_irqstat(ctl->prev_stat);
163 ctl->prev_stat = stat;
164 return 0;
165 }
166
167 static int event_loop(struct irqtop_ctl *ctl, struct irq_output *out)
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"));
183
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"));
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)
200 err(EXIT_FAILURE, _("cannot not create signalfd"));
201
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
212 retval |= update_screen(ctl, out);
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 }
227 if (siginfo.ssi_signo == SIGWINCH) {
228 get_terminal_dimension(&ctl->cols, &ctl->rows);
229 #if HAVE_RESIZETERM
230 resizeterm(ctl->rows, ctl->cols);
231 #endif
232 }
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"));
242 parse_input(ctl, out, c);
243 } else
244 abort();
245 retval |= update_screen(ctl, out);
246 refresh();
247 }
248 }
249 return retval;
250 }
251
252 static void __attribute__((__noreturn__)) usage(void)
253 {
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);
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));
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);
278 irq_print_columns(stdout, 0);
279
280 fprintf(stdout, USAGE_MAN_TAIL("irqtop(1)"));
281 exit(EXIT_SUCCESS);
282 }
283
284 static void parse_args( struct irqtop_ctl *ctl,
285 struct irq_output *out,
286 int argc,
287 char **argv)
288 {
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'},
299 {NULL, 0, NULL, 0}
300 };
301 int o;
302
303 while ((o = getopt_long(argc, argv, "c:C:d:o:s:ShV", longopts, NULL)) != -1) {
304 switch (o) {
305 case 'c':
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;
312 else
313 errx(EXIT_FAILURE, _("unsupported mode '%s'"), optarg);
314 break;
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;
330 case 'd':
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 }
339 break;
340 case 's':
341 set_sort_func_by_name(out, optarg);
342 break;
343 case 'o':
344 outarg = optarg;
345 break;
346 case 'S':
347 ctl->softirq = 1;
348 break;
349 case 'V':
350 print_version(EXIT_SUCCESS);
351 case 'h':
352 usage();
353 default:
354 errtryhelp(EXIT_FAILURE);
355 }
356 }
357
358 /* default */
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;
364 }
365
366 /* add -o [+]<list> to putput */
367 if (outarg && string_add_to_idarray(outarg, out->columns,
368 ARRAY_SIZE(out->columns),
369 &out->ncolumns,
370 irq_column_name_to_id) < 0)
371 exit(EXIT_FAILURE);
372 }
373
374 int main(int argc, char **argv)
375 {
376 int is_tty = 0;
377 struct termios saved_tty;
378 struct irq_output out = {
379 .ncolumns = 0
380 };
381 struct irqtop_ctl ctl = {
382 .timer.it_interval = {3, 0},
383 .timer.it_value = {3, 0}
384 };
385
386 setlocale(LC_ALL, "");
387
388 parse_args(&ctl, &out, argc, argv);
389
390 is_tty = isatty(STDIN_FILENO);
391 if (is_tty && tcgetattr(STDIN_FILENO, &saved_tty) == -1)
392 fputs(_("terminal setting retrieval"), stdout);
393
394 ctl.win = initscr();
395 get_terminal_dimension(&ctl.cols, &ctl.rows);
396 #if HAVE_RESIZETERM
397 resizeterm(ctl.rows, ctl.cols);
398 #endif
399 curs_set(0);
400
401 ctl.hostname = xgethostname();
402 event_loop(&ctl, &out);
403
404 free_irqstat(ctl.prev_stat);
405 free(ctl.hostname);
406 cpuset_free(ctl.cpuset);
407
408 if (is_tty)
409 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tty);
410 delwin(ctl.win);
411 endwin();
412
413 return EXIT_SUCCESS;
414 }