(root)/
util-linux-2.39/
sys-utils/
irq-common.c
       1  /*
       2   * irq-common.c - functions 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  
      22  #include <ctype.h>
      23  #include <errno.h>
      24  #include <limits.h>
      25  #include <locale.h>
      26  #include <stdbool.h>
      27  #include <stdio.h>
      28  #include <stdlib.h>
      29  #include <string.h>
      30  #include <sys/types.h>
      31  #include <unistd.h>
      32  
      33  #include <libsmartcols.h>
      34  
      35  #include "c.h"
      36  #include "nls.h"
      37  #include "pathnames.h"
      38  #include "strutils.h"
      39  #include "xalloc.h"
      40  
      41  #include "irq-common.h"
      42  
      43  #define IRQ_INFO_LEN	64
      44  
      45  struct colinfo {
      46  	const char *name;
      47  	double whint;
      48  	int flags;
      49  	const char *help;
      50  	int json_type;
      51  };
      52  
      53  static const struct colinfo infos[] = {
      54  	[COL_IRQ]   = {"IRQ",   0.10, SCOLS_FL_RIGHT, N_("interrupts"),  SCOLS_JSON_STRING},
      55  	[COL_TOTAL] = {"TOTAL", 0.10, SCOLS_FL_RIGHT, N_("total count"), SCOLS_JSON_NUMBER},
      56  	[COL_DELTA] = {"DELTA", 0.10, SCOLS_FL_RIGHT, N_("delta count"), SCOLS_JSON_NUMBER},
      57  	[COL_NAME]  = {"NAME",  0.70, SCOLS_FL_TRUNC, N_("name"),        SCOLS_JSON_STRING},
      58  };
      59  
      60  /* make softirq friendly to end-user */
      61  struct softirq_desc {
      62  	char *irq;
      63  	char *desc;
      64  } softirq_descs[] = {
      65  	{ .irq = "HI", .desc = "high priority tasklet softirq" },
      66  	{ .irq = "TIMER", .desc = "timer softirq" },
      67  	{ .irq = "NET_TX", .desc = "network transmit softirq", },
      68  	{ .irq = "NET_RX", .desc = "network receive softirq" },
      69  	{ .irq = "BLOCK", .desc = "block device softirq" },
      70  	{ .irq = "IRQ_POLL", .desc = "IO poll softirq" },
      71  	{ .irq = "TASKLET", .desc = "normal priority tasklet softirq" },
      72  	{ .irq = "SCHED", .desc = "schedule softirq" },
      73  	{ .irq = "HRTIMER", .desc = "high resolution timer softirq" },
      74  	{ .irq = "RCU", .desc = "RCU softirq" },
      75  };
      76  
      77  static void get_softirq_desc(struct irq_info *curr)
      78  {
      79  	int i, size = ARRAY_SIZE(softirq_descs);
      80  
      81  	for (i = 0; i < size; i++) {
      82  		if (!strcmp(curr->irq, softirq_descs[i].irq))
      83  			break;
      84  	}
      85  
      86  	if (i < size)
      87  		curr->name = xstrdup(softirq_descs[i].desc);
      88  	else
      89  		curr->name = xstrdup("");
      90  }
      91  
      92  int irq_column_name_to_id(const char *name, size_t namesz)
      93  {
      94  	size_t i;
      95  
      96  	assert(name);
      97  	for (i = 0; i < ARRAY_SIZE(infos); i++) {
      98  		const char *cn = infos[i].name;
      99  
     100  		if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
     101  			return i;
     102  	}
     103  	warnx(_("unknown column: %s"), name);
     104  	return -1;
     105  }
     106  
     107  static inline int get_column_id(struct irq_output *out, size_t const num)
     108  {
     109  	assert(num < out->ncolumns);
     110  	assert(out->columns[num] < (int)ARRAY_SIZE(infos));
     111  
     112  	return out->columns[num];
     113  }
     114  
     115  static inline const struct colinfo *get_column_info(
     116  			struct irq_output *out, unsigned num)
     117  {
     118  	return &infos[get_column_id(out, num)];
     119  }
     120  
     121  void irq_print_columns(FILE *f, int nodelta)
     122  {
     123  	size_t i;
     124  
     125  	for (i = 0; i < ARRAY_SIZE(infos); i++) {
     126  		if (nodelta && i == COL_DELTA)
     127  			continue;
     128  		fprintf(f, "  %-5s  %s\n", infos[i].name, _(infos[i].help));
     129  	}
     130  }
     131  
     132  static struct libscols_table *new_scols_table(struct irq_output *out)
     133  {
     134  	size_t i;
     135  	struct libscols_table *table;
     136  
     137  	table = scols_new_table();
     138  	if (!table) {
     139  		warn(_("failed to initialize output table"));
     140  		return NULL;
     141  	}
     142  	scols_table_enable_json(table, out->json);
     143  	scols_table_enable_noheadings(table, out->no_headings);
     144  	scols_table_enable_export(table, out->pairs);
     145  
     146  	if (out->json)
     147  		scols_table_set_name(table, "interrupts");
     148  
     149  	for (i = 0; i < out->ncolumns; i++) {
     150  		const struct colinfo *col = get_column_info(out, i);
     151  		int flags = col->flags;
     152  		struct libscols_column *cl;
     153  
     154  		cl = scols_table_new_column(table, col->name, col->whint, flags);
     155  		if (cl == NULL) {
     156  			warnx(_("failed to initialize output column"));
     157  			goto err;
     158  		}
     159  		if (out->json)
     160  			scols_column_set_json_type(cl, col->json_type);
     161  	}
     162  
     163  	return table;
     164   err:
     165  	scols_unref_table(table);
     166  	return NULL;
     167  }
     168  
     169  static struct libscols_line *new_scols_line(struct libscols_table *table)
     170  {
     171  	struct libscols_line *line = scols_table_new_line(table, NULL);
     172  	if (!line) {
     173  		warn(_("failed to add line to output"));
     174  		return NULL;
     175  	}
     176  	return line;
     177  }
     178  
     179  static void add_scols_line(struct irq_output *out,
     180  			   struct irq_info *info,
     181  			   struct libscols_table *table)
     182  {
     183  	size_t i;
     184  	struct libscols_line *line = new_scols_line(table);
     185  
     186  	for (i = 0; i < out->ncolumns; i++) {
     187  		char *str = NULL;
     188  
     189  		switch (get_column_id(out, i)) {
     190  		case COL_IRQ:
     191  			xasprintf(&str, "%s", info->irq);
     192  			break;
     193  		case COL_TOTAL:
     194  			xasprintf(&str, "%ld", info->total);
     195  			break;
     196  		case COL_DELTA:
     197  			xasprintf(&str, "%ld", info->delta);
     198  			break;
     199  		case COL_NAME:
     200  			xasprintf(&str, "%s", info->name);
     201  			break;
     202  		default:
     203  			break;
     204  		}
     205  
     206  		if (str && scols_line_refer_data(line, i, str) != 0)
     207  			err_oom();
     208  	}
     209  }
     210  
     211  static char *remove_repeated_spaces(char *str)
     212  {
     213  	char *inp = str, *outp = str;
     214  	uint8_t prev_space = 0;
     215  
     216  	while (*inp) {
     217  		if (isspace(*inp)) {
     218  			if (!prev_space) {
     219  				*outp++ = ' ';
     220  				prev_space = 1;
     221  			}
     222  		} else {
     223  			*outp++ = *inp;
     224  			prev_space = 0;
     225  		}
     226  		++inp;
     227  	}
     228  	*outp = '\0';
     229  	return str;
     230  }
     231  
     232  static bool cpu_in_list(int cpu, size_t setsize, cpu_set_t *cpuset)
     233  {
     234  	/* no -C/--cpu-list specified, use all the CPUs */
     235  	if (!cpuset)
     236  		return true;
     237  
     238  	return CPU_ISSET_S(cpu, setsize, cpuset);
     239  }
     240  
     241  /*
     242   * irqinfo - parse the system's interrupts
     243   */
     244  static struct irq_stat *get_irqinfo(int softirq, size_t setsize, cpu_set_t *cpuset)
     245  {
     246  	FILE *irqfile;
     247  	char *line = NULL, *tmp;
     248  	size_t len = 0;
     249  	struct irq_stat *stat;
     250  	struct irq_info *curr;
     251  
     252  	/* NAME + ':' + 11 bytes/cpu + IRQ_NAME_LEN */
     253  	stat = xcalloc(1, sizeof(*stat));
     254  
     255  	stat->irq_info = xmalloc(sizeof(*stat->irq_info) * IRQ_INFO_LEN);
     256  	stat->nr_irq_info = IRQ_INFO_LEN;
     257  
     258  	if (softirq)
     259  		irqfile = fopen(_PATH_PROC_SOFTIRQS, "r");
     260  	else
     261  		irqfile = fopen(_PATH_PROC_INTERRUPTS, "r");
     262  	if (!irqfile) {
     263  		warn(_("cannot open %s"), _PATH_PROC_INTERRUPTS);
     264  		goto free_stat;
     265  	}
     266  
     267  	/* read header firstly */
     268  	if (getline(&line, &len, irqfile) < 0) {
     269  		warn(_("cannot read %s"), _PATH_PROC_INTERRUPTS);
     270  		goto close_file;
     271  	}
     272  
     273  	tmp = line;
     274  	while ((tmp = strstr(tmp, "CPU")) != NULL) {
     275  		tmp += 3;	/* skip this "CPU", find next */
     276  		stat->nr_active_cpu++;
     277  	}
     278  
     279  	stat->cpus =  xcalloc(stat->nr_active_cpu, sizeof(struct irq_cpu));
     280  
     281  	/* parse each line of _PATH_PROC_INTERRUPTS */
     282  	while (getline(&line, &len, irqfile) >= 0) {
     283  		unsigned long count;
     284  		size_t index;
     285  		int length;
     286  
     287  		tmp = strchr(line, ':');
     288  		if (!tmp)
     289  			continue;
     290  
     291  		length = strlen(line);
     292  
     293  		curr = stat->irq_info + stat->nr_irq++;
     294  		memset(curr, 0, sizeof(*curr));
     295  		*tmp = '\0';
     296  		curr->irq = xstrdup(line);
     297  		ltrim_whitespace((unsigned char *)curr->irq);
     298  
     299  		tmp += 1;
     300  		for (index = 0; (index < stat->nr_active_cpu) && (tmp - line < length); index++) {
     301  			struct irq_cpu *cpu = &stat->cpus[index];
     302  
     303  			if (sscanf(tmp, " %10lu", &count) != 1)
     304  				continue;
     305  			if (cpu_in_list(index, setsize, cpuset)) {
     306  				curr->total += count;
     307  				cpu->total += count;
     308  				stat->total_irq += count;
     309  			}
     310  
     311  			tmp += 11;
     312  		}
     313  
     314  		/* softirq always has no desc, add additional desc for softirq */
     315  		if (softirq)
     316  			get_softirq_desc(curr);
     317  		else {
     318  			if (tmp - line < length) {
     319  				/* strip all space before desc */
     320  				while (isspace(*tmp))
     321  					tmp++;
     322  				tmp = remove_repeated_spaces(tmp);
     323  				rtrim_whitespace((unsigned char *)tmp);
     324  				curr->name = xstrdup(tmp);
     325  			} else /* no irq name string, we have to set '\0' here */
     326  				curr->name = xstrdup("");
     327  		}
     328  
     329  		if (stat->nr_irq == stat->nr_irq_info) {
     330  			stat->nr_irq_info *= 2;
     331  			stat->irq_info = xrealloc(stat->irq_info,
     332  						  sizeof(*stat->irq_info) * stat->nr_irq_info);
     333  		}
     334  	}
     335  	fclose(irqfile);
     336  	free(line);
     337  	return stat;
     338  
     339   close_file:
     340  	fclose(irqfile);
     341   free_stat:
     342  	free(stat->irq_info);
     343  	free(stat->cpus);
     344  	free(stat);
     345  	free(line);
     346  	return NULL;
     347  }
     348  
     349  void free_irqstat(struct irq_stat *stat)
     350  {
     351  	size_t i;
     352  
     353  	if (!stat)
     354  		return;
     355  
     356  	for (i = 0; i < stat->nr_irq; i++) {
     357  		free(stat->irq_info[i].name);
     358  		free(stat->irq_info[i].irq);
     359  	}
     360  
     361  	free(stat->irq_info);
     362  	free(stat->cpus);
     363  	free(stat);
     364  }
     365  
     366  static inline int cmp_name(const struct irq_info *a,
     367  		     const struct irq_info *b)
     368  {
     369  	return strcoll(a->name, b->name);
     370  }
     371  
     372  static inline int cmp_total(const struct irq_info *a,
     373  		      const struct irq_info *b)
     374  {
     375  	return a->total < b->total;
     376  }
     377  
     378  static inline int cmp_delta(const struct irq_info *a,
     379  		      const struct irq_info *b)
     380  {
     381  	if (a->delta != b->delta)
     382  		return a->delta < b->delta;
     383  	return cmp_name(a, b);
     384  }
     385  
     386  static inline int cmp_interrupts(const struct irq_info *a,
     387  			   const struct irq_info *b)
     388  {
     389  	return strverscmp(a->irq, b->irq);
     390  }
     391  
     392  static void sort_result(struct irq_output *out,
     393  			struct irq_info *result,
     394  			size_t nmemb)
     395  {
     396  	irq_cmp_t *func = cmp_total;	/* default */
     397  
     398  	if (out->sort_cmp_func)
     399  		func = out->sort_cmp_func;
     400  
     401  	qsort(result, nmemb, sizeof(*result),
     402  			(int (*)(const void *, const void *)) func);
     403  }
     404  
     405  void set_sort_func_by_name(struct irq_output *out, const char *name)
     406  {
     407  	if (strcasecmp(name, "IRQ") == 0)
     408  		out->sort_cmp_func = cmp_interrupts;
     409  	else if (strcasecmp(name, "TOTAL") == 0)
     410  		out->sort_cmp_func = cmp_total;
     411  	else if (strcasecmp(name, "DELTA") == 0)
     412  		out->sort_cmp_func = cmp_delta;
     413  	else if (strcasecmp(name, "NAME") == 0)
     414  		out->sort_cmp_func = cmp_name;
     415  	else
     416  		errx(EXIT_FAILURE, _("unsupported column name to sort output"));
     417  }
     418  
     419  void set_sort_func_by_key(struct irq_output *out, char c)
     420  {
     421  	switch (c) {
     422  	case 'i':
     423  		out->sort_cmp_func = cmp_interrupts;
     424  		break;
     425  	case 't':
     426  		out->sort_cmp_func = cmp_total;
     427  		break;
     428  	case 'd':
     429  		out->sort_cmp_func = cmp_delta;
     430  		break;
     431  	case 'n':
     432  		out->sort_cmp_func = cmp_name;
     433  		break;
     434  	}
     435  }
     436  
     437  struct libscols_table *get_scols_cpus_table(struct irq_output *out,
     438  					struct irq_stat *prev,
     439  					struct irq_stat *curr,
     440  					size_t setsize,
     441  					cpu_set_t *cpuset)
     442  {
     443  	struct libscols_table *table;
     444  	struct libscols_column *cl;
     445  	struct libscols_line *ln;
     446  	char colname[sizeof("cpu") + sizeof(stringify_value(LONG_MAX))];
     447  	size_t i, j;
     448  
     449  	if (prev) {
     450  		for (i = 0; i < curr->nr_active_cpu; i++) {
     451  			struct irq_cpu *pre = &prev->cpus[i];
     452  			struct irq_cpu *cur = &curr->cpus[i];
     453  
     454  			cur->delta = cur->total - pre->total;
     455  		}
     456  	}
     457  
     458  	table = scols_new_table();
     459  	if (!table) {
     460  		warn(_("failed to initialize output table"));
     461  		return NULL;
     462  	}
     463  	scols_table_enable_json(table, out->json);
     464  	scols_table_enable_noheadings(table, out->no_headings);
     465  	scols_table_enable_export(table, out->pairs);
     466  
     467  	if (out->json)
     468  		scols_table_set_name(table, _("cpu-interrupts"));
     469  	else
     470  		scols_table_new_column(table, "", 0, SCOLS_FL_RIGHT);
     471  
     472  	for (i = 0; i < curr->nr_active_cpu; i++) {
     473  		if (!cpu_in_list(i, setsize, cpuset))
     474  			continue;
     475  		snprintf(colname, sizeof(colname), "cpu%zu", i);
     476  		cl = scols_table_new_column(table, colname, 0, SCOLS_FL_RIGHT);
     477  		if (cl == NULL) {
     478  			warnx(_("failed to initialize output column"));
     479  			goto err;
     480  		}
     481  		if (out->json)
     482  			scols_column_set_json_type(cl, SCOLS_JSON_STRING);
     483  	}
     484  
     485  	/* per cpu % of total */
     486  	ln = new_scols_line(table);
     487  	if (!ln || (!out->json && scols_line_set_data(ln, 0, "%irq:") != 0))
     488  		goto err;
     489  
     490  	for (i = 0, j = 0; i < curr->nr_active_cpu; i++) {
     491  		struct irq_cpu *cpu = &curr->cpus[i];
     492  		char *str;
     493  
     494  		if (!cpu_in_list(i, setsize, cpuset))
     495  			continue;
     496  		xasprintf(&str, "%0.1f", (double)((long double) cpu->total / (long double) curr->total_irq * 100.0));
     497  		if (str && scols_line_refer_data(ln, ++j, str) != 0)
     498  			goto err;
     499  	}
     500  
     501  	/* per cpu % of delta */
     502  	ln = new_scols_line(table);
     503  	/* xgettext:no-c-format */
     504  	if (!ln || (!out->json && scols_line_set_data(ln, 0, _("%delta:")) != 0))
     505  		goto err;
     506  
     507  	for (i = 0, j = 0; i < curr->nr_active_cpu; i++) {
     508  		struct irq_cpu *cpu = &curr->cpus[i];
     509  		char *str;
     510  
     511  		if (!cpu_in_list(i, setsize, cpuset))
     512  			continue;
     513  		if (!curr->delta_irq)
     514  			continue;
     515  		xasprintf(&str, "%0.1f", (double)((long double) cpu->delta / (long double) curr->delta_irq * 100.0));
     516  		if (str && scols_line_refer_data(ln, ++j, str) != 0)
     517  			goto err;
     518  	}
     519  
     520  	return table;
     521   err:
     522  	scols_unref_table(table);
     523  	return NULL;
     524  }
     525  
     526  struct libscols_table *get_scols_table(struct irq_output *out,
     527  					      struct irq_stat *prev,
     528  					      struct irq_stat **xstat,
     529  					      int softirq,
     530  					      size_t setsize,
     531  					      cpu_set_t *cpuset)
     532  {
     533  	struct libscols_table *table;
     534  	struct irq_info *result;
     535  	struct irq_stat *stat;
     536  	size_t size;
     537  	size_t i;
     538  
     539  	/* the stats */
     540  	stat = get_irqinfo(softirq, setsize, cpuset);
     541  	if (!stat)
     542  		return NULL;
     543  
     544  	size = sizeof(*stat->irq_info) * stat->nr_irq;
     545  	result = xmalloc(size);
     546  	memcpy(result, stat->irq_info, size);
     547  
     548  	if (prev) {
     549  		stat->delta_irq = 0;
     550  		for (i = 0; i < stat->nr_irq; i++) {
     551  			struct irq_info *cur = &result[i];
     552  			struct irq_info *pre = &prev->irq_info[i];
     553  
     554  			cur->delta = cur->total - pre->total;
     555  			stat->delta_irq += cur->delta;
     556  		}
     557  	}
     558  	sort_result(out, result, stat->nr_irq);
     559  
     560  	table = new_scols_table(out);
     561  	if (!table) {
     562  		free(result);
     563  		free_irqstat(stat);
     564  		return NULL;
     565  	}
     566  
     567  	for (i = 0; i < stat->nr_irq; i++)
     568  		add_scols_line(out, &result[i], table);
     569  
     570  	free(result);
     571  
     572  	if (xstat)
     573  		*xstat = stat;
     574  	else
     575  		free_irqstat(stat);
     576  
     577  	return table;
     578  }