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