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