chiark / gitweb /
man: document systemd-cat(1)
[elogind.git] / src / cgtop.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <alloca.h>
27 #include <getopt.h>
28
29 #include "util.h"
30 #include "hashmap.h"
31 #include "cgroup-util.h"
32
33 typedef struct Group {
34         char *path;
35
36         bool n_tasks_valid:1;
37         bool cpu_valid:1;
38         bool memory_valid:1;
39         bool io_valid:1;
40
41         unsigned n_tasks;
42
43         unsigned cpu_iteration;
44         uint64_t cpu_usage;
45         struct timespec cpu_timestamp;
46         double cpu_fraction;
47
48         uint64_t memory;
49
50         unsigned io_iteration;
51         uint64_t io_input, io_output;
52         struct timespec io_timestamp;
53         uint64_t io_input_bps, io_output_bps;
54 } Group;
55
56 static unsigned arg_depth = 2;
57 static usec_t arg_delay = 1*USEC_PER_SEC;
58
59 static enum {
60         ORDER_PATH,
61         ORDER_TASKS,
62         ORDER_CPU,
63         ORDER_MEMORY,
64         ORDER_IO
65 } arg_order = ORDER_CPU;
66
67 static void group_free(Group *g) {
68         assert(g);
69
70         free(g->path);
71         free(g);
72 }
73
74 static void group_hashmap_clear(Hashmap *h) {
75         Group *g;
76
77         while ((g = hashmap_steal_first(h)))
78                 group_free(g);
79 }
80
81 static void group_hashmap_free(Hashmap *h) {
82         group_hashmap_clear(h);
83         hashmap_free(h);
84 }
85
86 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
87         Group *g;
88         int r;
89         FILE *f;
90         pid_t pid;
91         unsigned n;
92
93         assert(controller);
94         assert(path);
95         assert(a);
96
97         g = hashmap_get(a, path);
98         if (!g) {
99                 g = hashmap_get(b, path);
100                 if (!g) {
101                         g = new0(Group, 1);
102                         if (!g)
103                                 return -ENOMEM;
104
105                         g->path = strdup(path);
106                         if (!g->path) {
107                                 group_free(g);
108                                 return -ENOMEM;
109                         }
110
111                         r = hashmap_put(a, g->path, g);
112                         if (r < 0) {
113                                 group_free(g);
114                                 return r;
115                         }
116                 } else {
117                         assert_se(hashmap_move_one(a, b, path) == 0);
118                         g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
119                 }
120         }
121
122         /* Regardless which controller, let's find the maximum number
123          * of processes in any of it */
124
125         r = cg_enumerate_tasks(controller, path, &f);
126         if (r < 0)
127                 return r;
128
129         n = 0;
130         while (cg_read_pid(f, &pid) > 0)
131                 n++;
132         fclose(f);
133
134         if (n > 0) {
135                 if (g->n_tasks_valid)
136                         g->n_tasks = MAX(g->n_tasks, n);
137                 else
138                         g->n_tasks = n;
139
140                 g->n_tasks_valid = true;
141         }
142
143         if (streq(controller, "cpuacct")) {
144                 uint64_t new_usage;
145                 char *p, *v;
146                 struct timespec ts;
147
148                 r = cg_get_path(controller, path, "cpuacct.usage", &p);
149                 if (r < 0)
150                         return r;
151
152                 r = read_one_line_file(p, &v);
153                 free(p);
154                 if (r < 0)
155                         return r;
156
157                 r = safe_atou64(v, &new_usage);
158                 free(v);
159                 if (r < 0)
160                         return r;
161
162                 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
163
164                 if (g->cpu_iteration == iteration - 1) {
165                         uint64_t x, y;
166
167                         x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
168                                 ((uint64_t) g->cpu_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->cpu_timestamp.tv_nsec);
169
170                         y = new_usage - g->cpu_usage;
171
172                         if (y > 0) {
173                                 g->cpu_fraction = (double) y / (double) x;
174                                 g->cpu_valid = true;
175                         }
176                 }
177
178                 g->cpu_usage = new_usage;
179                 g->cpu_timestamp = ts;
180                 g->cpu_iteration = iteration;
181
182         } else if (streq(controller, "memory")) {
183                 char *p, *v;
184
185                 r = cg_get_path(controller, path, "memory.usage_in_bytes", &p);
186                 if (r < 0)
187                         return r;
188
189                 r = read_one_line_file(p, &v);
190                 free(p);
191                 if (r < 0)
192                         return r;
193
194                 r = safe_atou64(v, &g->memory);
195                 free(v);
196                 if (r < 0)
197                         return r;
198
199                 if (g->memory > 0)
200                         g->memory_valid = true;
201
202         } else if (streq(controller, "blkio")) {
203                 char *p;
204                 uint64_t wr = 0, rd = 0;
205                 struct timespec ts;
206
207                 r = cg_get_path(controller, path, "blkio.io_service_bytes", &p);
208                 if (r < 0)
209                         return r;
210
211                 f = fopen(p, "re");
212                 free(p);
213
214                 if (!f)
215                         return -errno;
216
217                 for (;;) {
218                         char line[LINE_MAX], *l;
219                         uint64_t k, *q;
220
221                         if (!fgets(line, sizeof(line), f))
222                                 break;
223
224                         l = strstrip(line);
225                         l += strcspn(l, WHITESPACE);
226                         l += strspn(l, WHITESPACE);
227
228                         if (first_word(l, "Read")) {
229                                 l += 4;
230                                 q = &rd;
231                         } else if (first_word(l, "Write")) {
232                                 l += 5;
233                                 q = &wr;
234                         } else
235                                 continue;
236
237                         l += strspn(l, WHITESPACE);
238                         r = safe_atou64(l, &k);
239                         if (r < 0)
240                                 continue;
241
242                         *q += k;
243                 }
244
245                 fclose(f);
246
247                 assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
248
249                 if (g->io_iteration == iteration - 1) {
250                         uint64_t x, yr, yw;
251
252                         x = ((uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec) -
253                                 ((uint64_t) g->io_timestamp.tv_sec * 1000000000ULL + (uint64_t) g->io_timestamp.tv_nsec);
254
255                         yr = rd - g->io_input;
256                         yw = wr - g->io_output;
257
258                         if (yr > 0 || yw > 0) {
259                                 g->io_input_bps = (yr * 1000000000ULL) / x;
260                                 g->io_output_bps = (yw * 1000000000ULL) / x;
261                                 g->io_valid = true;
262
263                         }
264                 }
265
266                 g->io_input = rd;
267                 g->io_output = wr;
268                 g->io_timestamp = ts;
269                 g->io_iteration = iteration;
270         }
271
272         return 0;
273 }
274
275 static int refresh_one(
276                 const char *controller,
277                 const char *path,
278                 Hashmap *a,
279                 Hashmap *b,
280                 unsigned iteration,
281                 unsigned depth) {
282
283         DIR *d = NULL;
284         int r;
285
286         assert(controller);
287         assert(path);
288         assert(a);
289
290         if (depth > arg_depth)
291                 return 0;
292
293         r = process(controller, path, a, b, iteration);
294         if (r < 0)
295                 return r;
296
297         r = cg_enumerate_subgroups(controller, path, &d);
298         if (r < 0) {
299                 if (r == ENOENT)
300                         return 0;
301
302                 return r;
303         }
304
305         for (;;) {
306                 char *fn, *p;
307
308                 r = cg_read_subgroup(d, &fn);
309                 if (r <= 0)
310                         goto finish;
311
312                 p = join(path, "/", fn, NULL);
313                 free(fn);
314
315                 if (!p) {
316                         r = -ENOMEM;
317                         goto finish;
318                 }
319
320                 path_kill_slashes(p);
321
322                 r = refresh_one(controller, p, a, b, iteration, depth + 1);
323                 free(p);
324
325                 if (r < 0)
326                         goto finish;
327         }
328
329 finish:
330         if (d)
331                 closedir(d);
332
333         return r;
334 }
335
336 static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
337         int r;
338
339         assert(a);
340
341         r = refresh_one("name=systemd", "/", a, b, iteration, 0);
342         if (r < 0)
343                 return r;
344
345         r = refresh_one("cpuacct", "/", a, b, iteration, 0);
346         if (r < 0)
347                 return r;
348
349         r = refresh_one("memory", "/", a, b, iteration, 0);
350         if (r < 0)
351                 return r;
352
353         return refresh_one("blkio", "/", a, b, iteration, 0);
354 }
355
356 static int group_compare(const void*a, const void *b) {
357         const Group *x = *(Group**)a, *y = *(Group**)b;
358
359         if (path_startswith(y->path, x->path))
360                 return -1;
361         if (path_startswith(x->path, y->path))
362                 return 1;
363
364         if (arg_order == ORDER_CPU) {
365                 if (x->cpu_valid && y->cpu_valid) {
366
367                         if (x->cpu_fraction > y->cpu_fraction)
368                                 return -1;
369                         else if (x->cpu_fraction < y->cpu_fraction)
370                                 return 1;
371                 } else if (x->cpu_valid)
372                         return -1;
373                 else if (y->cpu_valid)
374                         return 1;
375         }
376
377         if (arg_order == ORDER_TASKS) {
378
379                 if (x->n_tasks_valid && y->n_tasks_valid) {
380                         if (x->n_tasks > y->n_tasks)
381                                 return -1;
382                         else if (x->n_tasks < y->n_tasks)
383                                 return 1;
384                 } else if (x->n_tasks_valid)
385                         return -1;
386                 else if (y->n_tasks_valid)
387                         return 1;
388         }
389
390         if (arg_order == ORDER_MEMORY) {
391                 if (x->memory_valid && y->memory_valid) {
392                         if (x->memory > y->memory)
393                                 return -1;
394                         else if (x->memory < y->memory)
395                                 return 1;
396                 } else if (x->memory_valid)
397                         return -1;
398                 else if (y->memory_valid)
399                         return 1;
400         }
401
402         if (arg_order == ORDER_IO) {
403                 if (x->io_valid && y->io_valid) {
404                         if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
405                                 return -1;
406                         else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
407                                 return 1;
408                 } else if (x->io_valid)
409                         return -1;
410                 else if (y->io_valid)
411                         return 1;
412         }
413
414         return strcmp(x->path, y->path);
415 }
416
417 static int display(Hashmap *a) {
418         Iterator i;
419         Group *g;
420         Group **array;
421         unsigned rows, n = 0, j;
422
423         assert(a);
424
425         /* Set cursor to top left corner and clear screen */
426         fputs("\033[H"
427               "\033[2J", stdout);
428
429         array = alloca(sizeof(Group*) * hashmap_size(a));
430
431         HASHMAP_FOREACH(g, a, i)
432                 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
433                         array[n++] = g;
434
435         qsort(array, n, sizeof(Group*), group_compare);
436
437         rows = fd_lines(STDOUT_FILENO);
438         if (rows <= 0)
439                 rows = 25;
440
441         printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
442                arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_ON : "", "Path",     arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_OFF : "",
443                arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_ON : "", "Tasks",    arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_OFF : "",
444                arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_ON : "", "%CPU",     arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_OFF : "",
445                arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",   arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
446                arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Input/s",  arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "",
447                arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "");
448
449         for (j = 0; j < n; j++) {
450                 char *p;
451                 char m[FORMAT_BYTES_MAX];
452
453                 if (j + 5 > rows)
454                         break;
455
456                 g = array[j];
457
458                 p = ellipsize(g->path, 37, 33);
459                 printf("%-37s", p ? p : g->path);
460                 free(p);
461
462                 if (g->n_tasks_valid)
463                         printf(" %7u", g->n_tasks);
464                 else
465                         fputs("       -", stdout);
466
467                 if (g->cpu_valid)
468                         printf(" %6.1f", g->cpu_fraction*100);
469                 else
470                         fputs("      -", stdout);
471
472                 if (g->memory_valid)
473                         printf(" %8s", format_bytes(m, sizeof(m), g->memory));
474                 else
475                         fputs("        -", stdout);
476
477                 if (g->io_valid) {
478                         printf(" %8s",
479                                format_bytes(m, sizeof(m), g->io_input_bps));
480                         printf(" %8s",
481                                format_bytes(m, sizeof(m), g->io_output_bps));
482                 } else
483                         fputs("        -        -", stdout);
484
485                 putchar('\n');
486         }
487
488         return 0;
489 }
490
491 static void help(void) {
492
493         printf("%s [OPTIONS...]\n\n"
494                "Show top control groups by their resource usage.\n\n"
495                "  -h --help           Show this help\n"
496                "  -p                  Order by path\n"
497                "  -t                  Order by number of tasks\n"
498                "  -c                  Order by CPU load\n"
499                "  -m                  Order by memory load\n"
500                "  -i                  Order by IO load\n"
501                "  -d --delay=DELAY    Specify delay\n"
502                "     --depth=DEPTH    Maximum traversal depth (default: 2)\n",
503                program_invocation_short_name);
504 }
505
506 static int parse_argv(int argc, char *argv[]) {
507
508         enum {
509                 ARG_DEPTH = 0x100
510         };
511
512         static const struct option options[] = {
513                 { "help",  no_argument,       NULL, 'h'       },
514                 { "delay", required_argument, NULL, 'd'       },
515                 { "depth", required_argument, NULL, ARG_DEPTH },
516                 { NULL,    0,                 NULL, 0         }
517         };
518
519         int c;
520         int r;
521
522         assert(argc >= 1);
523         assert(argv);
524
525         while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) {
526
527                 switch (c) {
528
529                 case 'h':
530                         help();
531                         return 0;
532
533                 case ARG_DEPTH:
534                         r = safe_atou(optarg, &arg_depth);
535                         if (r < 0) {
536                                 log_error("Failed to parse depth parameter.");
537                                 return -EINVAL;
538                         }
539
540                         break;
541
542                 case 'd':
543                         r = parse_usec(optarg, &arg_delay);
544                         if (r < 0 || arg_delay <= 0) {
545                                 log_error("Failed to parse delay parameter.");
546                                 return -EINVAL;
547                         }
548
549                         break;
550
551                 case 'p':
552                         arg_order = ORDER_PATH;
553                         break;
554
555                 case 't':
556                         arg_order = ORDER_TASKS;
557                         break;
558
559                 case 'c':
560                         arg_order = ORDER_CPU;
561                         break;
562
563                 case 'm':
564                         arg_order = ORDER_MEMORY;
565                         break;
566
567                 case 'i':
568                         arg_order = ORDER_IO;
569                         break;
570
571                 case '?':
572                         return -EINVAL;
573
574                 default:
575                         log_error("Unknown option code %c", c);
576                         return -EINVAL;
577                 }
578         }
579
580         if (optind < argc) {
581                 log_error("Too many arguments.");
582                 return -EINVAL;
583         }
584
585         return 1;
586 }
587
588 int main(int argc, char *argv[]) {
589         int r;
590         Hashmap *a = NULL, *b = NULL;
591         unsigned iteration = 0;
592         usec_t last_refresh = 0;
593         bool quit = false, immediate_refresh = false;
594
595         log_parse_environment();
596         log_open();
597
598         r = parse_argv(argc, argv);
599         if (r <= 0)
600                 goto finish;
601
602         a = hashmap_new(string_hash_func, string_compare_func);
603         b = hashmap_new(string_hash_func, string_compare_func);
604         if (!a || !b) {
605                 log_error("Out of memory");
606                 r = -ENOMEM;
607                 goto finish;
608         }
609
610         while (!quit) {
611                 Hashmap *c;
612                 usec_t t;
613                 char key;
614                 char h[FORMAT_TIMESPAN_MAX];
615
616                 t = now(CLOCK_MONOTONIC);
617
618                 if (t >= last_refresh + arg_delay || immediate_refresh) {
619
620                         r = refresh(a, b, iteration++);
621                         if (r < 0)
622                                 goto finish;
623
624                         group_hashmap_clear(b);
625
626                         c = a;
627                         a = b;
628                         b = c;
629
630                         last_refresh = t;
631                         immediate_refresh = false;
632                 }
633
634                 r = display(b);
635                 if (r < 0)
636                         goto finish;
637
638                 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
639                 if (r == -ETIMEDOUT)
640                         continue;
641                 if (r < 0) {
642                         log_error("Couldn't read key: %s", strerror(-r));
643                         goto finish;
644                 }
645
646                 fputs("\r \r", stdout);
647                 fflush(stdout);
648
649                 switch (key) {
650
651                 case ' ':
652                         immediate_refresh = true;
653                         break;
654
655                 case 'q':
656                         quit = true;
657                         break;
658
659                 case 'p':
660                         arg_order = ORDER_PATH;
661                         break;
662
663                 case 't':
664                         arg_order = ORDER_TASKS;
665                         break;
666
667                 case 'c':
668                         arg_order = ORDER_CPU;
669                         break;
670
671                 case 'm':
672                         arg_order = ORDER_MEMORY;
673                         break;
674
675                 case 'i':
676                         arg_order = ORDER_IO;
677                         break;
678
679                 case '+':
680                         if (arg_delay < USEC_PER_SEC)
681                                 arg_delay += USEC_PER_MSEC*250;
682                         else
683                                 arg_delay += USEC_PER_SEC;
684
685                         fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
686                         fflush(stdout);
687                         sleep(1);
688                         break;
689
690                 case '-':
691                         if (arg_delay <= USEC_PER_MSEC*500)
692                                 arg_delay = USEC_PER_MSEC*250;
693                         else if (arg_delay < USEC_PER_MSEC*1250)
694                                 arg_delay -= USEC_PER_MSEC*250;
695                         else
696                                 arg_delay -= USEC_PER_SEC;
697
698                         fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
699                         fflush(stdout);
700                         sleep(1);
701                         break;
702
703                 case '?':
704                 case 'h':
705                         fprintf(stdout,
706                                 "\t<" ANSI_HIGHLIGHT_ON "P" ANSI_HIGHLIGHT_OFF "> By path; <" ANSI_HIGHLIGHT_ON "T" ANSI_HIGHLIGHT_OFF "> By tasks; <" ANSI_HIGHLIGHT_ON "C" ANSI_HIGHLIGHT_OFF "> By CPU; <" ANSI_HIGHLIGHT_ON "M" ANSI_HIGHLIGHT_OFF "> By memory; <" ANSI_HIGHLIGHT_ON "I" ANSI_HIGHLIGHT_OFF "> By I/O\n"
707                                 "\t<" ANSI_HIGHLIGHT_ON "Q" ANSI_HIGHLIGHT_OFF "> Quit; <" ANSI_HIGHLIGHT_ON "+" ANSI_HIGHLIGHT_OFF "> Increase delay; <" ANSI_HIGHLIGHT_ON "-" ANSI_HIGHLIGHT_OFF "> Decrease delay; <" ANSI_HIGHLIGHT_ON "SPACE" ANSI_HIGHLIGHT_OFF "> Refresh");
708                         fflush(stdout);
709                         sleep(3);
710                         break;
711
712                 default:
713                         fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
714                         fflush(stdout);
715                         sleep(1);
716                         break;
717                 }
718         }
719
720         log_info("Exiting.");
721
722         r = 0;
723
724 finish:
725         group_hashmap_free(a);
726         group_hashmap_free(b);
727
728         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
729 }