(root)/
util-linux-2.39/
sys-utils/
irqtop.c
       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  }