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