diff options
Diffstat (limited to 'sys-utils/irq-common.c')
-rw-r--r-- | sys-utils/irq-common.c | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/sys-utils/irq-common.c b/sys-utils/irq-common.c new file mode 100644 index 0000000..2d736a0 --- /dev/null +++ b/sys-utils/irq-common.c @@ -0,0 +1,578 @@ +/* + * irq-common.c - functions to display kernel interrupt information. + * + * Copyright (C) 2019 zhenwei pi <pizhenwei@bytedance.com> + * Copyright (C) 2020 Karel Zak <kzak@redhat.com> + * + * 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 <ctype.h> +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libsmartcols.h> + +#include "c.h" +#include "nls.h" +#include "pathnames.h" +#include "strutils.h" +#include "xalloc.h" + +#include "irq-common.h" + +#define IRQ_INFO_LEN 64 + +struct colinfo { + const char *name; + double whint; + int flags; + const char *help; + int json_type; +}; + +static const struct colinfo infos[] = { + [COL_IRQ] = {"IRQ", 0.10, SCOLS_FL_RIGHT, N_("interrupts"), SCOLS_JSON_STRING}, + [COL_TOTAL] = {"TOTAL", 0.10, SCOLS_FL_RIGHT, N_("total count"), SCOLS_JSON_NUMBER}, + [COL_DELTA] = {"DELTA", 0.10, SCOLS_FL_RIGHT, N_("delta count"), SCOLS_JSON_NUMBER}, + [COL_NAME] = {"NAME", 0.70, SCOLS_FL_TRUNC, N_("name"), SCOLS_JSON_STRING}, +}; + +/* make softirq friendly to end-user */ +struct softirq_desc { + char *irq; + char *desc; +} softirq_descs[] = { + { .irq = "HI", .desc = "high priority tasklet softirq" }, + { .irq = "TIMER", .desc = "timer softirq" }, + { .irq = "NET_TX", .desc = "network transmit softirq", }, + { .irq = "NET_RX", .desc = "network receive softirq" }, + { .irq = "BLOCK", .desc = "block device softirq" }, + { .irq = "IRQ_POLL", .desc = "IO poll softirq" }, + { .irq = "TASKLET", .desc = "normal priority tasklet softirq" }, + { .irq = "SCHED", .desc = "schedule softirq" }, + { .irq = "HRTIMER", .desc = "high resolution timer softirq" }, + { .irq = "RCU", .desc = "RCU softirq" }, +}; + +static void get_softirq_desc(struct irq_info *curr) +{ + int i, size = ARRAY_SIZE(softirq_descs); + + for (i = 0; i < size; i++) { + if (!strcmp(curr->irq, softirq_descs[i].irq)) + break; + } + + if (i < size) + curr->name = xstrdup(softirq_descs[i].desc); + else + curr->name = xstrdup(""); +} + +int irq_column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + assert(name); + for (i = 0; i < ARRAY_SIZE(infos); i++) { + const char *cn = infos[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + return -1; +} + +static inline int get_column_id(struct irq_output *out, size_t const num) +{ + assert(num < out->ncolumns); + assert(out->columns[num] < (int)ARRAY_SIZE(infos)); + + return out->columns[num]; +} + +static inline const struct colinfo *get_column_info( + struct irq_output *out, unsigned num) +{ + return &infos[get_column_id(out, num)]; +} + +void irq_print_columns(FILE *f, int nodelta) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(infos); i++) { + if (nodelta && i == COL_DELTA) + continue; + fprintf(f, " %-5s %s\n", infos[i].name, _(infos[i].help)); + } +} + +static struct libscols_table *new_scols_table(struct irq_output *out) +{ + size_t i; + struct libscols_table *table; + + table = scols_new_table(); + if (!table) { + warn(_("failed to initialize output table")); + return NULL; + } + scols_table_enable_json(table, out->json); + scols_table_enable_noheadings(table, out->no_headings); + scols_table_enable_export(table, out->pairs); + + if (out->json) + scols_table_set_name(table, "interrupts"); + + for (i = 0; i < out->ncolumns; i++) { + const struct colinfo *col = get_column_info(out, i); + int flags = col->flags; + struct libscols_column *cl; + + cl = scols_table_new_column(table, col->name, col->whint, flags); + if (cl == NULL) { + warnx(_("failed to initialize output column")); + goto err; + } + if (out->json) + scols_column_set_json_type(cl, col->json_type); + } + + return table; + err: + scols_unref_table(table); + return NULL; +} + +static struct libscols_line *new_scols_line(struct libscols_table *table) +{ + struct libscols_line *line = scols_table_new_line(table, NULL); + if (!line) { + warn(_("failed to add line to output")); + return NULL; + } + return line; +} + +static void add_scols_line(struct irq_output *out, + struct irq_info *info, + struct libscols_table *table) +{ + size_t i; + struct libscols_line *line = new_scols_line(table); + + for (i = 0; i < out->ncolumns; i++) { + char *str = NULL; + + switch (get_column_id(out, i)) { + case COL_IRQ: + xasprintf(&str, "%s", info->irq); + break; + case COL_TOTAL: + xasprintf(&str, "%ld", info->total); + break; + case COL_DELTA: + xasprintf(&str, "%ld", info->delta); + break; + case COL_NAME: + xasprintf(&str, "%s", info->name); + break; + default: + break; + } + + if (str && scols_line_refer_data(line, i, str) != 0) + err_oom(); + } +} + +static char *remove_repeated_spaces(char *str) +{ + char *inp = str, *outp = str; + uint8_t prev_space = 0; + + while (*inp) { + if (isspace(*inp)) { + if (!prev_space) { + *outp++ = ' '; + prev_space = 1; + } + } else { + *outp++ = *inp; + prev_space = 0; + } + ++inp; + } + *outp = '\0'; + return str; +} + +static bool cpu_in_list(int cpu, size_t setsize, cpu_set_t *cpuset) +{ + /* no -C/--cpu-list specified, use all the CPUs */ + if (!cpuset) + return true; + + return CPU_ISSET_S(cpu, setsize, cpuset); +} + +/* + * irqinfo - parse the system's interrupts + */ +static struct irq_stat *get_irqinfo(int softirq, size_t setsize, cpu_set_t *cpuset) +{ + FILE *irqfile; + char *line = NULL, *tmp; + size_t len = 0; + struct irq_stat *stat; + struct irq_info *curr; + + /* NAME + ':' + 11 bytes/cpu + IRQ_NAME_LEN */ + stat = xcalloc(1, sizeof(*stat)); + + stat->irq_info = xmalloc(sizeof(*stat->irq_info) * IRQ_INFO_LEN); + stat->nr_irq_info = IRQ_INFO_LEN; + + if (softirq) + irqfile = fopen(_PATH_PROC_SOFTIRQS, "r"); + else + irqfile = fopen(_PATH_PROC_INTERRUPTS, "r"); + if (!irqfile) { + warn(_("cannot open %s"), _PATH_PROC_INTERRUPTS); + goto free_stat; + } + + /* read header firstly */ + if (getline(&line, &len, irqfile) < 0) { + warn(_("cannot read %s"), _PATH_PROC_INTERRUPTS); + goto close_file; + } + + tmp = line; + while ((tmp = strstr(tmp, "CPU")) != NULL) { + tmp += 3; /* skip this "CPU", find next */ + stat->nr_active_cpu++; + } + + stat->cpus = xcalloc(stat->nr_active_cpu, sizeof(struct irq_cpu)); + + /* parse each line of _PATH_PROC_INTERRUPTS */ + while (getline(&line, &len, irqfile) >= 0) { + unsigned long count; + size_t index; + int length; + + tmp = strchr(line, ':'); + if (!tmp) + continue; + + length = strlen(line); + + curr = stat->irq_info + stat->nr_irq++; + memset(curr, 0, sizeof(*curr)); + *tmp = '\0'; + curr->irq = xstrdup(line); + ltrim_whitespace((unsigned char *)curr->irq); + + tmp += 1; + for (index = 0; (index < stat->nr_active_cpu) && (tmp - line < length); index++) { + struct irq_cpu *cpu = &stat->cpus[index]; + + if (sscanf(tmp, " %10lu", &count) != 1) + continue; + if (cpu_in_list(index, setsize, cpuset)) { + curr->total += count; + cpu->total += count; + stat->total_irq += count; + } + + tmp += 11; + } + + /* softirq always has no desc, add additional desc for softirq */ + if (softirq) + get_softirq_desc(curr); + else { + if (tmp - line < length) { + /* strip all space before desc */ + while (isspace(*tmp)) + tmp++; + tmp = remove_repeated_spaces(tmp); + rtrim_whitespace((unsigned char *)tmp); + curr->name = xstrdup(tmp); + } else /* no irq name string, we have to set '\0' here */ + curr->name = xstrdup(""); + } + + if (stat->nr_irq == stat->nr_irq_info) { + stat->nr_irq_info *= 2; + stat->irq_info = xrealloc(stat->irq_info, + sizeof(*stat->irq_info) * stat->nr_irq_info); + } + } + fclose(irqfile); + free(line); + return stat; + + close_file: + fclose(irqfile); + free_stat: + free(stat->irq_info); + free(stat->cpus); + free(stat); + free(line); + return NULL; +} + +void free_irqstat(struct irq_stat *stat) +{ + size_t i; + + if (!stat) + return; + + for (i = 0; i < stat->nr_irq; i++) { + free(stat->irq_info[i].name); + free(stat->irq_info[i].irq); + } + + free(stat->irq_info); + free(stat->cpus); + free(stat); +} + +static inline int cmp_name(const struct irq_info *a, + const struct irq_info *b) +{ + return strcoll(a->name, b->name); +} + +static inline int cmp_total(const struct irq_info *a, + const struct irq_info *b) +{ + return a->total < b->total; +} + +static inline int cmp_delta(const struct irq_info *a, + const struct irq_info *b) +{ + if (a->delta != b->delta) + return a->delta < b->delta; + return cmp_name(a, b); +} + +static inline int cmp_interrupts(const struct irq_info *a, + const struct irq_info *b) +{ + return strverscmp(a->irq, b->irq); +} + +static void sort_result(struct irq_output *out, + struct irq_info *result, + size_t nmemb) +{ + irq_cmp_t *func = cmp_total; /* default */ + + if (out->sort_cmp_func) + func = out->sort_cmp_func; + + qsort(result, nmemb, sizeof(*result), + (int (*)(const void *, const void *)) func); +} + +void set_sort_func_by_name(struct irq_output *out, const char *name) +{ + if (strcasecmp(name, "IRQ") == 0) + out->sort_cmp_func = cmp_interrupts; + else if (strcasecmp(name, "TOTAL") == 0) + out->sort_cmp_func = cmp_total; + else if (strcasecmp(name, "DELTA") == 0) + out->sort_cmp_func = cmp_delta; + else if (strcasecmp(name, "NAME") == 0) + out->sort_cmp_func = cmp_name; + else + errx(EXIT_FAILURE, _("unsupported column name to sort output")); +} + +void set_sort_func_by_key(struct irq_output *out, char c) +{ + switch (c) { + case 'i': + out->sort_cmp_func = cmp_interrupts; + break; + case 't': + out->sort_cmp_func = cmp_total; + break; + case 'd': + out->sort_cmp_func = cmp_delta; + break; + case 'n': + out->sort_cmp_func = cmp_name; + break; + } +} + +struct libscols_table *get_scols_cpus_table(struct irq_output *out, + struct irq_stat *prev, + struct irq_stat *curr, + size_t setsize, + cpu_set_t *cpuset) +{ + struct libscols_table *table; + struct libscols_column *cl; + struct libscols_line *ln; + char colname[sizeof("cpu") + sizeof(stringify_value(LONG_MAX))]; + size_t i, j; + + if (prev) { + for (i = 0; i < curr->nr_active_cpu; i++) { + struct irq_cpu *pre = &prev->cpus[i]; + struct irq_cpu *cur = &curr->cpus[i]; + + cur->delta = cur->total - pre->total; + } + } + + table = scols_new_table(); + if (!table) { + warn(_("failed to initialize output table")); + return NULL; + } + scols_table_enable_json(table, out->json); + scols_table_enable_noheadings(table, out->no_headings); + scols_table_enable_export(table, out->pairs); + + if (out->json) + scols_table_set_name(table, _("cpu-interrupts")); + else + scols_table_new_column(table, "", 0, SCOLS_FL_RIGHT); + + for (i = 0; i < curr->nr_active_cpu; i++) { + if (!cpu_in_list(i, setsize, cpuset)) + continue; + snprintf(colname, sizeof(colname), "cpu%zu", i); + cl = scols_table_new_column(table, colname, 0, SCOLS_FL_RIGHT); + if (cl == NULL) { + warnx(_("failed to initialize output column")); + goto err; + } + if (out->json) + scols_column_set_json_type(cl, SCOLS_JSON_STRING); + } + + /* per cpu % of total */ + ln = new_scols_line(table); + if (!ln || (!out->json && scols_line_set_data(ln, 0, "%irq:") != 0)) + goto err; + + for (i = 0, j = 0; i < curr->nr_active_cpu; i++) { + struct irq_cpu *cpu = &curr->cpus[i]; + char *str; + + if (!cpu_in_list(i, setsize, cpuset)) + continue; + xasprintf(&str, "%0.1f", (double)((long double) cpu->total / (long double) curr->total_irq * 100.0)); + if (str && scols_line_refer_data(ln, ++j, str) != 0) + goto err; + } + + /* per cpu % of delta */ + ln = new_scols_line(table); + /* xgettext:no-c-format */ + if (!ln || (!out->json && scols_line_set_data(ln, 0, _("%delta:")) != 0)) + goto err; + + for (i = 0, j = 0; i < curr->nr_active_cpu; i++) { + struct irq_cpu *cpu = &curr->cpus[i]; + char *str; + + if (!cpu_in_list(i, setsize, cpuset)) + continue; + if (!curr->delta_irq) + continue; + xasprintf(&str, "%0.1f", (double)((long double) cpu->delta / (long double) curr->delta_irq * 100.0)); + if (str && scols_line_refer_data(ln, ++j, str) != 0) + goto err; + } + + return table; + err: + scols_unref_table(table); + return NULL; +} + +struct libscols_table *get_scols_table(struct irq_output *out, + struct irq_stat *prev, + struct irq_stat **xstat, + int softirq, + size_t setsize, + cpu_set_t *cpuset) +{ + struct libscols_table *table; + struct irq_info *result; + struct irq_stat *stat; + size_t size; + size_t i; + + /* the stats */ + stat = get_irqinfo(softirq, setsize, cpuset); + if (!stat) + return NULL; + + size = sizeof(*stat->irq_info) * stat->nr_irq; + result = xmalloc(size); + memcpy(result, stat->irq_info, size); + + if (prev) { + stat->delta_irq = 0; + for (i = 0; i < stat->nr_irq; i++) { + struct irq_info *cur = &result[i]; + struct irq_info *pre = &prev->irq_info[i]; + + cur->delta = cur->total - pre->total; + stat->delta_irq += cur->delta; + } + } + sort_result(out, result, stat->nr_irq); + + table = new_scols_table(out); + if (!table) { + free(result); + free_irqstat(stat); + return NULL; + } + + for (i = 0; i < stat->nr_irq; i++) + add_scols_line(out, &result[i], table); + + free(result); + + if (xstat) + *xstat = stat; + else + free_irqstat(stat); + + return table; +} |