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