]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
irqtop: implement a new utility to display kernel interrupt
authorzhenwei pi <pizhenwei@bytedance.com>
Mon, 11 Nov 2019 12:48:59 +0000 (20:48 +0800)
committerSami Kerola <kerolasa@iki.fi>
Fri, 21 Feb 2020 21:09:13 +0000 (21:09 +0000)
currently, there are usually 40/48/64/96 CPUs on a single server,
and a lot of interrupts are enabled by multi-queues of a NIC.
"/proc/interrupts" is not human readable any more.
'watch -d -n 1 "cat /proc/interrupts"' also can not work well.

so implement irqtop to show the interrupts information, we can
sort the interrupts by count(default), name and /proc/interrupts.

irqtop - IRQ : 49, TOTAL : 2361705032, CPU : 8, ACTIVE CPU : 8
 IRQ        COUNT   DESC
 CAL        21196   Function call interrupts
 LOC        13733   Local timer interrupts
 154         1430   IR-PCI-MSI 32768-edge      i915
 127         1322   IR-PCI-MSI 327680-edge      xhci_hcd
 RES         1224   Rescheduling interrupts
 146          336   IR-PCI-MSI 520192-edge      enp0s31f6
 IWI          135   IRQ work interrupts
 147           48   IR-PCI-MSI 31981569-edge      nvme0q2
 151           42   IR-PCI-MSI 31981573-edge      nvme0q6
 TLB            8   TLB shootdowns
 150            7   IR-PCI-MSI 31981572-edge      nvme0q5
 152            5   IR-PCI-MSI 31981574-edge      nvme0q7
 156            4   IR-PCI-MSI 1572864-edge      iwlwifi
 148            3   IR-PCI-MSI 31981570-edge      nvme0q3
 153            2   IR-PCI-MSI 31981575-edge      nvme0q8
 NMI            2   Non-maskable interrupts
 PMI            2   Performance monitoring interrupts
   0            0   IR-IO-APIC    2-edge      timer
   1            0   IR-IO-APIC    1-edge      i8042
   8            0   IR-IO-APIC    8-edge      rtc0
   9            0   IR-IO-APIC    9-fasteoi   acpi
  12            0   IR-IO-APIC   12-edge      i8042

test on 4.14 & 4.19, work fine. test on bare metal & kvm virtual
machine, work fine. hot-plug/hot-unplug virtual NIC during running
irqtop, work fine.

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
Makefile [new file with mode: 0644]
irqtop.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..f020d4e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+IRQTOP=irqtop
+
+all :
+       gcc *.c -O2 -g -o $(IRQTOP) -I. -lncurses
+
+install : all
+       @cp $(IRQTOP) /usr/bin
+       @chmod +s /usr/bin/$(IRQTOP)
+
+clean:
+       rm -rf $(IRQTOP)
diff --git a/irqtop.c b/irqtop.c
new file mode 100644 (file)
index 0000000..be96b58
--- /dev/null
+++ b/irqtop.c
@@ -0,0 +1,479 @@
+/*
+ * irqtop.c - utility to display kernel interrupt information.
+ *
+ * zhenwei pi <pizhenwei@bytedance.com>
+ *
+ * Copyright (C) 2019 zhenwei pi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include <limits.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <ncurses.h>
+#include <termios.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define IRQTOP_VERSION                 "Version 0.1"
+#define IRQTOP_AUTHOR          "zhenwei pi<pizhenwei@bytedance.com>"
+#define DEF_SORT_FUNC          sort_count
+#define IRQ_NAME_LEN           4
+#define IRQ_DESC_LEN           64
+#define IRQ_INFO_LEN           64
+#define INTERRUPTS_FILE                "/proc/interrupts"
+#define MIN(x,y)                       ((x) > (y) ? (y) : (x))
+#define RESERVE_ROWS           (1 + 1 + 1)     /* summary + header + last row */
+#define print_line(fmt, ...) if (run_once) printf(fmt, __VA_ARGS__); \
+                                                               else printw(fmt, __VA_ARGS__)
+
+struct irq_info {
+       char irq[IRQ_NAME_LEN+1];       /* name of this irq */
+       char desc[IRQ_DESC_LEN+1];      /* description of this irq */
+       unsigned long count;            /* count of this irq for all cpu(s) */
+};
+
+struct irq_stat {
+       unsigned int nr_irq;            /* number of irq vector */
+       unsigned int nr_irq_info;               /* number of irq info */
+       struct irq_info *irq_info;              /* array of irq_info */
+       long nr_online_cpu;             /* number of online cpu */
+       long nr_active_cpu;             /* number of active cpu */
+       unsigned long total_irq;        /* total irqs */
+};
+
+static int run_once;
+static unsigned short cols, rows;
+static struct termios saved_tty;
+static long delay = 3;
+static int (*sort_func)(const struct irq_info *, const struct irq_info *);
+static long smp_num_cpus;
+static char *program;
+
+/*
+ * irqinfo - parse the system's interrupts
+ */
+static struct irq_stat *get_irqinfo()
+{
+       FILE *irqfile;
+       char *buffer, *tmp;
+       long bufferlen;
+       struct irq_stat *stat;
+       struct irq_info *curr;
+       int ret = -1;
+
+       /* NAME + ':' + 11 bytes/cpu + IRQ_DESC_LEN */
+       bufferlen = IRQ_NAME_LEN + 1 + smp_num_cpus * 11 + IRQ_DESC_LEN;
+       buffer = malloc(bufferlen);
+       if (!buffer)
+               goto out;
+       
+       stat = calloc(1, sizeof(*stat));
+       if (!stat)
+               goto free_buf;
+
+       stat->irq_info = malloc(sizeof(*stat->irq_info) * IRQ_INFO_LEN);
+       if (!stat->irq_info)
+               goto free_stat;
+       stat->nr_irq_info = IRQ_INFO_LEN;
+
+       irqfile = fopen(INTERRUPTS_FILE, "r");
+       if (!irqfile) {
+               perror("fopen " INTERRUPTS_FILE);
+               ret = 1;
+               goto free_stat;
+       }
+
+       /* read header firstly */
+       if (!fgets(buffer, bufferlen, irqfile)) {
+               fprintf(stderr, "cannot read from irqinfo\n");
+               ret = 1;
+               goto close_file;
+       }
+
+       stat->nr_online_cpu = smp_num_cpus;
+       tmp = buffer;
+       while ((tmp = strstr(tmp, "CPU")) != NULL) {
+               tmp += 3;       /* skip this "CPU", find next */
+               stat->nr_active_cpu++;
+       }
+
+       /* parse each line of INTERRUPTS_FILE */
+       while (fgets(buffer, bufferlen, irqfile)) {
+               unsigned long count;
+               int index, length;
+               char *colon;
+
+               tmp = strchr(buffer, ':');
+               if (!tmp)
+                       continue;
+
+               length = strlen(buffer);
+               if (length < IRQ_NAME_LEN + 1 || tmp - buffer > IRQ_NAME_LEN)
+                       continue;
+
+               curr = stat->irq_info + stat->nr_irq++;
+               memset(curr, 0x00, sizeof(*curr));
+               memcpy(curr->irq, buffer, tmp - buffer);
+
+               tmp += 1;
+               for (index = 0; (index < stat->nr_active_cpu) &&
+                               (tmp - buffer < length); index++) {
+                       sscanf(tmp, " %10lu", &count);
+                       curr->count += count;
+                       stat->total_irq += count;
+                       tmp += 11;
+               }
+
+               if (tmp - buffer < length) {
+                       /* strip all space before desc */
+                       while(*tmp == ' ')
+                               tmp++;
+                       strcpy(curr->desc, tmp);
+               } else {
+                       /* no desc string at all, we have to set '\n' here */
+                       curr->desc[0] = '\n';
+               }
+
+               if (stat->nr_irq == stat->nr_irq_info) {
+                       stat->nr_irq_info *= 2;
+                       stat->irq_info = realloc(stat->irq_info,
+                                       sizeof(*stat->irq_info) * stat->nr_irq_info);
+               }
+       }
+
+       return stat;
+
+close_file:
+       fclose(irqfile);
+free_stat:
+       if (stat)
+               free(stat->irq_info);
+       free(stat);
+free_buf:
+       free(buffer);
+out:
+       return NULL;
+}
+
+static void put_irqinfo(struct irq_stat *stat)
+{
+       if (stat)
+               free(stat->irq_info);
+       free(stat);
+}
+
+static int sort_name(const struct irq_info *a, const struct irq_info *b)
+{
+       return (strcmp(a->irq, b->irq) > 0) ? 1 : 0;
+}
+
+static int sort_count(const struct irq_info *a, const struct irq_info *b)
+{
+       return a->count < b->count;
+}
+
+static int sort_interrupts(const struct irq_info *a, const struct irq_info *b)
+{
+       return 0;
+}
+
+static void sort_result(struct irq_info *result, size_t nmemb)
+{
+       qsort(result, nmemb, sizeof(*result),
+               (int (*)(const void *, const void *))sort_func);
+}
+
+static void term_size(int unusused __attribute__ ((__unused__)))
+{
+       struct winsize ws;
+
+       if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) && ws.ws_row > 10) {
+               cols = ws.ws_col;
+               rows = ws.ws_row;
+       } else {
+               cols = 80;
+               rows = 24;
+       }
+       if (run_once)
+               rows = USHRT_MAX;
+}
+
+static int uptime(double *uptime_secs, double *idle_secs)
+{
+       double up, idle;
+       FILE *f;
+       char buf[64];
+
+       f = fopen("/proc/uptime", "r");
+       if (!f)
+               return errno;
+       
+       if (!fgets(buf, sizeof(buf), f)) {
+               fclose(f);
+               return errno;
+       }
+       
+       if (sscanf(buf, "%lf %lf", &up, &idle) < 2) {
+               fclose(f);
+               return errno;
+       }
+
+       if (uptime_secs)
+               *uptime_secs = up;
+
+       if (idle_secs)
+               *idle_secs = idle;
+
+       fclose(f);
+       return 0;
+}
+
+static void sigint_handler(int unused __attribute__ ((__unused__)))
+{
+       delay = 0;
+}
+
+static void __attribute__((__noreturn__)) usage(FILE *out, char *msg)
+{
+       if (!msg)
+               fputs("msg", out);
+       fputs("Usage:\n", out);
+       fprintf(out, "  %s [options]\n", program);
+       fputs("Options:", out);
+       fputs(" -d, --delay <secs>  delay updates\n", out);
+       fputs(" -o, --once          only display average irq once, then exit\n", out);
+       fputs(" -s, --sort <char>   specify sort criteria by character (see below)\n", out);
+
+       fputs("\nThe following are valid sort criteria:\n", out);
+       fputs(" c: sort by increase count of each interrupt\n", out);
+       fputs(" i: sort by default interrupts from proc interrupt\n", out);
+       fputs(" n: sort by name\n", out);
+       fputs("Contact:\n", out);
+       fprintf(out, "  %s\n", IRQTOP_AUTHOR);
+
+       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static void * set_sort_func(char key)
+{
+       switch (key) {
+       case 'c':
+               return (void *) sort_count;
+       case 'i':
+               return (void *) sort_interrupts;
+       case 'n':
+               return (void *) sort_name;
+       default:
+               return (void *) DEF_SORT_FUNC;
+       }
+}
+
+static void parse_input(char c)
+{
+       switch(c) {
+       case 'c':
+               sort_func = sort_count;
+               break;
+       case 'i':
+               sort_func = sort_interrupts;
+               break;
+       case 'n':
+               sort_func = sort_name;
+               break;
+       case 'q':
+       case 'Q':
+               delay = 0;
+               break;
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       int is_tty, o;
+       unsigned short old_rows;
+       struct irq_stat *stat, *last_stat = NULL;
+       double uptime_secs;
+       int retval = EXIT_SUCCESS;
+
+       static const struct option longopts[] = {
+               { "delay",      required_argument, NULL, 'd' },
+               { "sort",       required_argument, NULL, 's' },
+               { "once",       no_argument,       NULL, 'o' },
+               { "help",       no_argument,       NULL, 'h' },
+               { "version",    no_argument,   NULL, 'V' },
+               {  NULL, 0, NULL, 0 }
+       };
+
+       setlocale (LC_ALL, "");
+       program = argv[0];
+       sort_func = DEF_SORT_FUNC;
+
+       while ((o = getopt_long(argc, argv, "d:os:hV", longopts, NULL)) != -1) {
+               switch (o) {
+               case 'd':
+                       errno = 0;
+                       delay = atol(optarg);
+                       if (delay < 1)
+                               usage(stderr, "delay must be positive integer\n");
+                       break;
+               case 's':
+                       sort_func = (int (*)(const struct irq_info*,
+                               const struct irq_info *)) set_sort_func(optarg[0]);
+                       break;
+               case 'o':
+                       run_once = 1;
+                       delay = 0;
+                       break;
+               case 'V':
+                       printf("%s\n", IRQTOP_VERSION);
+                       return EXIT_SUCCESS;
+               case 'h':
+                       usage(stdout, NULL);
+               default:
+                       usage(stderr, NULL);
+               }
+       }
+
+       is_tty = isatty(STDIN_FILENO);
+       if (is_tty && tcgetattr(STDIN_FILENO, &saved_tty) == -1)
+               fputs("terminal setting retrieval", stdout);
+
+       old_rows = rows;
+       term_size(0);
+       if (!run_once) {
+               initscr();
+               resizeterm(rows, cols);
+               signal(SIGWINCH, term_size);
+       }
+       signal(SIGINT, sigint_handler);
+
+       smp_num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+       uptime(&uptime_secs, NULL);
+
+       do {
+               struct timeval tv;
+               struct irq_info *result, *curr;
+               size_t size;
+               fd_set readfds;
+               char c;
+               int i;
+
+               stat = get_irqinfo();
+               if (!stat) {
+                       retval = EXIT_FAILURE;
+                       break;
+               }
+
+               if (!run_once && old_rows != rows) {
+                       resizeterm(rows, cols);
+                       old_rows = rows;
+               }
+
+               move(0, 0);
+
+               /* summary stat */
+               print_line("irqtop - IRQ : %d, TOTAL : %ld, CPU : %ld, "
+                       "ACTIVE CPU : %ld\n", stat->nr_irq, stat->total_irq,
+                       stat->nr_online_cpu, stat->nr_active_cpu);
+
+               /* header */
+               attron(A_REVERSE);
+               print_line("%-80s\n", " IRQ        COUNT   DESC ");
+               attroff(A_REVERSE);
+
+               size = sizeof(*stat->irq_info) * stat->nr_irq;
+               result = malloc(size);
+               memcpy(result, stat->irq_info, size);
+               if (!last_stat) {
+                       for (i = 0; i < stat->nr_irq; i++) {
+                               curr = result + i;
+                               curr->count /= uptime_secs;
+                       }
+                       last_stat = stat;
+               } else {
+                       int i, j;
+
+                       for (i = 0; i < stat->nr_irq; i++) {
+                               struct irq_info *found = NULL;
+                               unsigned long diff = 0;
+
+                               curr = result + i;
+                               for (j = 0; j < last_stat->nr_irq; j++) {
+                                       struct irq_info *prev = last_stat->irq_info + j;
+
+                                       if (!strcmp(curr->irq, prev->irq))
+                                               found = prev;
+                               }
+
+                               if (found && curr->count >= found->count)
+                                       diff = curr->count - found->count;
+                               else
+                                       diff = curr->count;
+
+                               curr->count = diff;
+                       }
+                       put_irqinfo(last_stat);
+
+                       last_stat = stat;
+               }
+
+               /* okay, sort and show the result */
+               sort_result(result, stat->nr_irq);
+               for (i = 0; i < MIN(rows - RESERVE_ROWS, stat->nr_irq); i++) {
+                       curr = result + i;
+                       print_line("%4s   %10ld   %s", curr->irq, curr->count,
+                                       curr->desc);
+               }
+               free(result);
+
+               if (run_once) {
+                       break;
+               } else {
+                       refresh();
+                       FD_ZERO(&readfds);
+                       FD_SET(STDIN_FILENO, &readfds);
+                       tv.tv_sec = delay;
+                       tv.tv_usec = 0;
+                       if (select(STDOUT_FILENO, &readfds, NULL, NULL, &tv) > 0) {
+                               if (read(STDIN_FILENO, &c, 1) != 1)
+                                       break;
+                               parse_input(c);
+                       }
+               }
+       } while (delay);
+
+       put_irqinfo(last_stat);
+
+       if (is_tty)
+               tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tty);
+
+       if (!run_once)
+               endwin();
+
+       return retval;
+}