chiark / gitweb /
util: simplify column caching logic
[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() - 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         signal(SIGWINCH, columns_cache_reset);
657
658         while (!quit) {
659                 Hashmap *c;
660                 usec_t t;
661                 char key;
662                 char h[FORMAT_TIMESPAN_MAX];
663
664                 t = now(CLOCK_MONOTONIC);
665
666                 if (t >= last_refresh + arg_delay || immediate_refresh) {
667
668                         r = refresh(a, b, iteration++);
669                         if (r < 0)
670                                 goto finish;
671
672                         group_hashmap_clear(b);
673
674                         c = a;
675                         a = b;
676                         b = c;
677
678                         last_refresh = t;
679                         immediate_refresh = false;
680                 }
681
682                 r = display(b);
683                 if (r < 0)
684                         goto finish;
685
686                 if (arg_iterations && iteration >= arg_iterations)
687                         break;
688
689                 if (arg_batch) {
690                         usleep(last_refresh + arg_delay - t);
691                 } else {
692                         r = read_one_char(stdin, &key,
693                                           last_refresh + arg_delay - t, NULL);
694                         if (r == -ETIMEDOUT)
695                                 continue;
696                         if (r < 0) {
697                                 log_error("Couldn't read key: %s", strerror(-r));
698                                 goto finish;
699                         }
700                 }
701
702                 fputs("\r \r", stdout);
703                 fflush(stdout);
704
705                 if (arg_batch)
706                         continue;
707
708                 switch (key) {
709
710                 case ' ':
711                         immediate_refresh = true;
712                         break;
713
714                 case 'q':
715                         quit = true;
716                         break;
717
718                 case 'p':
719                         arg_order = ORDER_PATH;
720                         break;
721
722                 case 't':
723                         arg_order = ORDER_TASKS;
724                         break;
725
726                 case 'c':
727                         arg_order = ORDER_CPU;
728                         break;
729
730                 case 'm':
731                         arg_order = ORDER_MEMORY;
732                         break;
733
734                 case 'i':
735                         arg_order = ORDER_IO;
736                         break;
737
738                 case '+':
739                         if (arg_delay < USEC_PER_SEC)
740                                 arg_delay += USEC_PER_MSEC*250;
741                         else
742                                 arg_delay += USEC_PER_SEC;
743
744                         fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
745                         fflush(stdout);
746                         sleep(1);
747                         break;
748
749                 case '-':
750                         if (arg_delay <= USEC_PER_MSEC*500)
751                                 arg_delay = USEC_PER_MSEC*250;
752                         else if (arg_delay < USEC_PER_MSEC*1250)
753                                 arg_delay -= USEC_PER_MSEC*250;
754                         else
755                                 arg_delay -= USEC_PER_SEC;
756
757                         fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
758                         fflush(stdout);
759                         sleep(1);
760                         break;
761
762                 case '?':
763                 case 'h':
764                         fprintf(stdout,
765                                 "\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"
766                                 "\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");
767                         fflush(stdout);
768                         sleep(3);
769                         break;
770
771                 default:
772                         fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
773                         fflush(stdout);
774                         sleep(1);
775                         break;
776                 }
777         }
778
779         log_info("Exiting.");
780
781         r = 0;
782
783 finish:
784         group_hashmap_free(a);
785         group_hashmap_free(b);
786
787         if (r < 0) {
788                 log_error("Exiting with failure: %s", strerror(-r));
789                 return EXIT_FAILURE;
790         }
791
792         return EXIT_SUCCESS;
793 }