555 lines
13 KiB
C
555 lines
13 KiB
C
/*
|
|
* 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 <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;
|
|
}
|
|
|
|
/*
|
|
* irqinfo - parse the system's interrupts
|
|
*/
|
|
static struct irq_stat *get_irqinfo(int softirq)
|
|
{
|
|
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;
|
|
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 (strcmp(a->name, b->name) > 0) ? 1 : 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return a->delta < b->delta;
|
|
}
|
|
|
|
static inline int cmp_interrupts(const struct irq_info *a,
|
|
const struct irq_info *b)
|
|
{
|
|
return (strverscmp(a->irq, b->irq) > 0) ? 1 : 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct libscols_table *table;
|
|
struct libscols_column *cl;
|
|
struct libscols_line *ln;
|
|
char colname[sizeof("cpu") + sizeof(stringify_value(LONG_MAX))];
|
|
size_t i;
|
|
|
|
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++) {
|
|
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; i < curr->nr_active_cpu; i++) {
|
|
struct irq_cpu *cpu = &curr->cpus[i];
|
|
char *str;
|
|
|
|
xasprintf(&str, "%0.1f", (double)((long double) cpu->total / (long double) curr->total_irq * 100.0));
|
|
if (str && scols_line_refer_data(ln, i + 1, 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; i < curr->nr_active_cpu; i++) {
|
|
struct irq_cpu *cpu = &curr->cpus[i];
|
|
char *str;
|
|
|
|
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, i + 1, 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)
|
|
{
|
|
struct libscols_table *table;
|
|
struct irq_info *result;
|
|
struct irq_stat *stat;
|
|
size_t size;
|
|
size_t i;
|
|
|
|
/* the stats */
|
|
stat = get_irqinfo(softirq);
|
|
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;
|
|
}
|