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