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