chiark / gitweb /
cgtop: use full terminal width
[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, path_columns, 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         path_columns = columns_uncached() - 42;
450         if (path_columns < 10)
451                 path_columns = 10;
452
453         printf("%s%-*s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
454                arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_ON : "", path_columns, "Path",
455                       arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_OFF : "",
456                arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_ON : "", "Tasks",
457                       arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_OFF : "",
458                arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_ON : "", "%CPU",
459                       arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_OFF : "",
460                arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",
461                       arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
462                arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Input/s",
463                       arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "",
464                arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Output/s",
465                       arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "");
466
467         for (j = 0; j < n; j++) {
468                 char *p;
469                 char m[FORMAT_BYTES_MAX];
470
471                 if (j + 5 > rows)
472                         break;
473
474                 g = array[j];
475
476                 p = ellipsize(g->path, path_columns, 33);
477                 printf("%-*s", path_columns, p ? p : g->path);
478                 free(p);
479
480                 if (g->n_tasks_valid)
481                         printf(" %7u", g->n_tasks);
482                 else
483                         fputs("       -", stdout);
484
485                 if (g->cpu_valid)
486                         printf(" %6.1f", g->cpu_fraction*100);
487                 else
488                         fputs("      -", stdout);
489
490                 if (g->memory_valid)
491                         printf(" %8s", format_bytes(m, sizeof(m), g->memory));
492                 else
493                         fputs("        -", stdout);
494
495                 if (g->io_valid) {
496                         printf(" %8s",
497                                format_bytes(m, sizeof(m), g->io_input_bps));
498                         printf(" %8s",
499                                format_bytes(m, sizeof(m), g->io_output_bps));
500                 } else
501                         fputs("        -        -", stdout);
502
503                 putchar('\n');
504         }
505
506         return 0;
507 }
508
509 static void help(void) {
510
511         printf("%s [OPTIONS...]\n\n"
512                "Show top control groups by their resource usage.\n\n"
513                "  -h --help           Show this help\n"
514                "  -p                  Order by path\n"
515                "  -t                  Order by number of tasks\n"
516                "  -c                  Order by CPU load\n"
517                "  -m                  Order by memory load\n"
518                "  -i                  Order by IO load\n"
519                "  -d --delay=DELAY    Specify delay\n"
520                "  -n --iterations=N   Run for N iterations before exiting\n"
521                "  -b --batch          Run in batch mode, accepting no input\n"
522                "     --depth=DEPTH    Maximum traversal depth (default: 2)\n",
523                program_invocation_short_name);
524 }
525
526 static int parse_argv(int argc, char *argv[]) {
527
528         enum {
529                 ARG_DEPTH = 0x100
530         };
531
532         static const struct option options[] = {
533                 { "help",       no_argument,       NULL, 'h'       },
534                 { "delay",      required_argument, NULL, 'd'       },
535                 { "iterations", required_argument, NULL, 'n'       },
536                 { "batch",      no_argument,       NULL, 'b'       },
537                 { "depth",      required_argument, NULL, ARG_DEPTH },
538                 { NULL,         0,                 NULL, 0         }
539         };
540
541         int c;
542         int r;
543
544         assert(argc >= 1);
545         assert(argv);
546
547         while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0) {
548
549                 switch (c) {
550
551                 case 'h':
552                         help();
553                         return 0;
554
555                 case ARG_DEPTH:
556                         r = safe_atou(optarg, &arg_depth);
557                         if (r < 0) {
558                                 log_error("Failed to parse depth parameter.");
559                                 return -EINVAL;
560                         }
561
562                         break;
563
564                 case 'd':
565                         r = parse_usec(optarg, &arg_delay);
566                         if (r < 0 || arg_delay <= 0) {
567                                 log_error("Failed to parse delay parameter.");
568                                 return -EINVAL;
569                         }
570
571                         break;
572
573                 case 'n':
574                         r = safe_atou(optarg, &arg_iterations);
575                         if (r < 0) {
576                                 log_error("Failed to parse iterations parameter.");
577                                 return -EINVAL;
578                         }
579
580                         break;
581
582                 case 'b':
583                         arg_batch = true;
584                         break;
585
586                 case 'p':
587                         arg_order = ORDER_PATH;
588                         break;
589
590                 case 't':
591                         arg_order = ORDER_TASKS;
592                         break;
593
594                 case 'c':
595                         arg_order = ORDER_CPU;
596                         break;
597
598                 case 'm':
599                         arg_order = ORDER_MEMORY;
600                         break;
601
602                 case 'i':
603                         arg_order = ORDER_IO;
604                         break;
605
606                 case '?':
607                         return -EINVAL;
608
609                 default:
610                         log_error("Unknown option code %c", c);
611                         return -EINVAL;
612                 }
613         }
614
615         if (optind < argc) {
616                 log_error("Too many arguments.");
617                 return -EINVAL;
618         }
619
620         return 1;
621 }
622
623 int main(int argc, char *argv[]) {
624         int r;
625         Hashmap *a = NULL, *b = NULL;
626         unsigned iteration = 0;
627         usec_t last_refresh = 0;
628         bool quit = false, immediate_refresh = false;
629
630         log_parse_environment();
631         log_open();
632
633         r = parse_argv(argc, argv);
634         if (r <= 0)
635                 goto finish;
636
637         a = hashmap_new(string_hash_func, string_compare_func);
638         b = hashmap_new(string_hash_func, string_compare_func);
639         if (!a || !b) {
640                 r = log_oom();
641                 goto finish;
642         }
643
644         while (!quit) {
645                 Hashmap *c;
646                 usec_t t;
647                 char key;
648                 char h[FORMAT_TIMESPAN_MAX];
649
650                 t = now(CLOCK_MONOTONIC);
651
652                 if (t >= last_refresh + arg_delay || immediate_refresh) {
653
654                         r = refresh(a, b, iteration++);
655                         if (r < 0)
656                                 goto finish;
657
658                         group_hashmap_clear(b);
659
660                         c = a;
661                         a = b;
662                         b = c;
663
664                         last_refresh = t;
665                         immediate_refresh = false;
666                 }
667
668                 r = display(b);
669                 if (r < 0)
670                         goto finish;
671
672                 if (arg_iterations && iteration >= arg_iterations)
673                         break;
674
675                 if (arg_batch) {
676                         usleep(last_refresh + arg_delay - t);
677                 } else {
678                         r = read_one_char(stdin, &key,
679                                           last_refresh + arg_delay - t, NULL);
680                         if (r == -ETIMEDOUT)
681                                 continue;
682                         if (r < 0) {
683                                 log_error("Couldn't read key: %s", strerror(-r));
684                                 goto finish;
685                         }
686                 }
687
688                 fputs("\r \r", stdout);
689                 fflush(stdout);
690
691                 if (arg_batch)
692                         continue;
693
694                 switch (key) {
695
696                 case ' ':
697                         immediate_refresh = true;
698                         break;
699
700                 case 'q':
701                         quit = true;
702                         break;
703
704                 case 'p':
705                         arg_order = ORDER_PATH;
706                         break;
707
708                 case 't':
709                         arg_order = ORDER_TASKS;
710                         break;
711
712                 case 'c':
713                         arg_order = ORDER_CPU;
714                         break;
715
716                 case 'm':
717                         arg_order = ORDER_MEMORY;
718                         break;
719
720                 case 'i':
721                         arg_order = ORDER_IO;
722                         break;
723
724                 case '+':
725                         if (arg_delay < USEC_PER_SEC)
726                                 arg_delay += USEC_PER_MSEC*250;
727                         else
728                                 arg_delay += USEC_PER_SEC;
729
730                         fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
731                         fflush(stdout);
732                         sleep(1);
733                         break;
734
735                 case '-':
736                         if (arg_delay <= USEC_PER_MSEC*500)
737                                 arg_delay = USEC_PER_MSEC*250;
738                         else if (arg_delay < USEC_PER_MSEC*1250)
739                                 arg_delay -= USEC_PER_MSEC*250;
740                         else
741                                 arg_delay -= USEC_PER_SEC;
742
743                         fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
744                         fflush(stdout);
745                         sleep(1);
746                         break;
747
748                 case '?':
749                 case 'h':
750                         fprintf(stdout,
751                                 "\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"
752                                 "\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");
753                         fflush(stdout);
754                         sleep(3);
755                         break;
756
757                 default:
758                         fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
759                         fflush(stdout);
760                         sleep(1);
761                         break;
762                 }
763         }
764
765         log_info("Exiting.");
766
767         r = 0;
768
769 finish:
770         group_hashmap_free(a);
771         group_hashmap_free(b);
772
773         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
774 }