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