From: zhenwei pi Date: Mon, 11 Nov 2019 12:48:59 +0000 (+0800) Subject: irqtop: implement a new utility to display kernel interrupt X-Git-Tag: v2.36-rc1~196^2~52 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ae0dfe14124ab5b3bb999fd909dde5437c1c7335;p=thirdparty%2Futil-linux.git irqtop: implement a new utility to display kernel interrupt 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 --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..f020d4e33a --- /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 index 0000000000..be96b58652 --- /dev/null +++ b/irqtop.c @@ -0,0 +1,479 @@ +/* + * irqtop.c - utility to display kernel interrupt information. + * + * zhenwei pi + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define IRQTOP_VERSION "Version 0.1" +#define IRQTOP_AUTHOR "zhenwei pi" +#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 delay updates\n", out); + fputs(" -o, --once only display average irq once, then exit\n", out); + fputs(" -s, --sort 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; +}