]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/irq-common.c
f36ed693c8cbe59113897b8bf728548a2dfdc9a3
[thirdparty/util-linux.git] / 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 = xreallocarray(stat->irq_info, stat->nr_irq_info,
332 sizeof(*stat->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_ulong_descending(unsigned long a,
373 unsigned long b)
374 {
375 if (a == b)
376 return 0;
377 if (a < b)
378 return 1;
379 else
380 return -1;
381 }
382
383 static inline int cmp_total(const struct irq_info *a,
384 const struct irq_info *b)
385 {
386 int cmp = cmp_ulong_descending(a->total, b->total);
387 return cmp ? cmp : cmp_name(a, b);
388 }
389
390 static inline int cmp_delta(const struct irq_info *a,
391 const struct irq_info *b)
392 {
393 int cmp = cmp_ulong_descending(a->delta, b->delta);
394 return cmp ? cmp : cmp_name(a, b);
395 }
396
397 static inline int cmp_interrupts(const struct irq_info *a,
398 const struct irq_info *b)
399 {
400 return strverscmp(a->irq, b->irq);
401 }
402
403 static void sort_result(struct irq_output *out,
404 struct irq_info *result,
405 size_t nmemb)
406 {
407 irq_cmp_t *func = cmp_total; /* default */
408
409 if (out->sort_cmp_func)
410 func = out->sort_cmp_func;
411
412 qsort(result, nmemb, sizeof(*result),
413 (int (*)(const void *, const void *)) func);
414 }
415
416 void set_sort_func_by_name(struct irq_output *out, const char *name)
417 {
418 if (strcasecmp(name, "IRQ") == 0)
419 out->sort_cmp_func = cmp_interrupts;
420 else if (strcasecmp(name, "TOTAL") == 0)
421 out->sort_cmp_func = cmp_total;
422 else if (strcasecmp(name, "DELTA") == 0)
423 out->sort_cmp_func = cmp_delta;
424 else if (strcasecmp(name, "NAME") == 0)
425 out->sort_cmp_func = cmp_name;
426 else
427 errx(EXIT_FAILURE, _("unsupported column name to sort output"));
428 }
429
430 void set_sort_func_by_key(struct irq_output *out, char c)
431 {
432 switch (c) {
433 case 'i':
434 out->sort_cmp_func = cmp_interrupts;
435 break;
436 case 't':
437 out->sort_cmp_func = cmp_total;
438 break;
439 case 'd':
440 out->sort_cmp_func = cmp_delta;
441 break;
442 case 'n':
443 out->sort_cmp_func = cmp_name;
444 break;
445 }
446 }
447
448 struct libscols_table *get_scols_cpus_table(struct irq_output *out,
449 struct irq_stat *prev,
450 struct irq_stat *curr,
451 size_t setsize,
452 cpu_set_t *cpuset)
453 {
454 struct libscols_table *table;
455 struct libscols_column *cl;
456 struct libscols_line *ln;
457 char colname[sizeof("cpu") + sizeof(stringify_value(LONG_MAX))];
458 size_t i, j;
459
460 if (prev) {
461 for (i = 0; i < curr->nr_active_cpu; i++) {
462 struct irq_cpu *pre = &prev->cpus[i];
463 struct irq_cpu *cur = &curr->cpus[i];
464
465 cur->delta = cur->total - pre->total;
466 }
467 }
468
469 table = scols_new_table();
470 if (!table) {
471 warn(_("failed to initialize output table"));
472 return NULL;
473 }
474 scols_table_enable_json(table, out->json);
475 scols_table_enable_noheadings(table, out->no_headings);
476 scols_table_enable_export(table, out->pairs);
477
478 if (out->json)
479 scols_table_set_name(table, _("cpu-interrupts"));
480 else
481 scols_table_new_column(table, "", 0, SCOLS_FL_RIGHT);
482
483 for (i = 0; i < curr->nr_active_cpu; i++) {
484 if (!cpu_in_list(i, setsize, cpuset))
485 continue;
486 snprintf(colname, sizeof(colname), "cpu%zu", i);
487 cl = scols_table_new_column(table, colname, 0, SCOLS_FL_RIGHT);
488 if (cl == NULL) {
489 warnx(_("failed to initialize output column"));
490 goto err;
491 }
492 if (out->json)
493 scols_column_set_json_type(cl, SCOLS_JSON_STRING);
494 }
495
496 /* per cpu % of total */
497 ln = new_scols_line(table);
498 if (!ln || (!out->json && scols_line_set_data(ln, 0, "%irq:") != 0))
499 goto err;
500
501 for (i = 0, j = 0; i < curr->nr_active_cpu; i++) {
502 struct irq_cpu *cpu = &curr->cpus[i];
503 char *str;
504
505 if (!cpu_in_list(i, setsize, cpuset))
506 continue;
507 xasprintf(&str, "%0.1f", (double)((long double) cpu->total / (long double) curr->total_irq * 100.0));
508 if (str && scols_line_refer_data(ln, ++j, str) != 0)
509 goto err;
510 }
511
512 /* per cpu % of delta */
513 ln = new_scols_line(table);
514 /* xgettext:no-c-format */
515 if (!ln || (!out->json && scols_line_set_data(ln, 0, _("%delta:")) != 0))
516 goto err;
517
518 for (i = 0, j = 0; i < curr->nr_active_cpu; i++) {
519 struct irq_cpu *cpu = &curr->cpus[i];
520 char *str;
521
522 if (!cpu_in_list(i, setsize, cpuset))
523 continue;
524 if (!curr->delta_irq)
525 continue;
526 xasprintf(&str, "%0.1f", (double)((long double) cpu->delta / (long double) curr->delta_irq * 100.0));
527 if (str && scols_line_refer_data(ln, ++j, str) != 0)
528 goto err;
529 }
530
531 return table;
532 err:
533 scols_unref_table(table);
534 return NULL;
535 }
536
537 struct libscols_table *get_scols_table(struct irq_output *out,
538 struct irq_stat *prev,
539 struct irq_stat **xstat,
540 int softirq,
541 size_t setsize,
542 cpu_set_t *cpuset)
543 {
544 struct libscols_table *table;
545 struct irq_info *result;
546 struct irq_stat *stat;
547 size_t size;
548 size_t i;
549
550 /* the stats */
551 stat = get_irqinfo(softirq, setsize, cpuset);
552 if (!stat)
553 return NULL;
554
555 size = sizeof(*stat->irq_info) * stat->nr_irq;
556 result = xmalloc(size);
557 memcpy(result, stat->irq_info, size);
558
559 if (prev) {
560 stat->delta_irq = 0;
561 for (i = 0; i < stat->nr_irq; i++) {
562 struct irq_info *cur = &result[i];
563 struct irq_info *pre = &prev->irq_info[i];
564
565 cur->delta = cur->total - pre->total;
566 stat->delta_irq += cur->delta;
567 }
568 }
569 sort_result(out, result, stat->nr_irq);
570
571 table = new_scols_table(out);
572 if (!table) {
573 free(result);
574 free_irqstat(stat);
575 return NULL;
576 }
577
578 for (i = 0; i < stat->nr_irq; i++)
579 add_scols_line(out, &result[i], table);
580
581 free(result);
582
583 if (xstat)
584 *xstat = stat;
585 else
586 free_irqstat(stat);
587
588 return table;
589 }