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