chiark / gitweb /
util: split-out path-util.[ch]
[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 = 2;
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 = join(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                 return r;
345
346         r = refresh_one("cpuacct", "/", a, b, iteration, 0);
347         if (r < 0)
348                 return r;
349
350         r = refresh_one("memory", "/", a, b, iteration, 0);
351         if (r < 0)
352                 return r;
353
354         return refresh_one("blkio", "/", a, b, iteration, 0);
355 }
356
357 static int group_compare(const void*a, const void *b) {
358         const Group *x = *(Group**)a, *y = *(Group**)b;
359
360         if (path_startswith(y->path, x->path))
361                 return -1;
362         if (path_startswith(x->path, y->path))
363                 return 1;
364
365         if (arg_order == ORDER_CPU) {
366                 if (x->cpu_valid && y->cpu_valid) {
367
368                         if (x->cpu_fraction > y->cpu_fraction)
369                                 return -1;
370                         else if (x->cpu_fraction < y->cpu_fraction)
371                                 return 1;
372                 } else if (x->cpu_valid)
373                         return -1;
374                 else if (y->cpu_valid)
375                         return 1;
376         }
377
378         if (arg_order == ORDER_TASKS) {
379
380                 if (x->n_tasks_valid && y->n_tasks_valid) {
381                         if (x->n_tasks > y->n_tasks)
382                                 return -1;
383                         else if (x->n_tasks < y->n_tasks)
384                                 return 1;
385                 } else if (x->n_tasks_valid)
386                         return -1;
387                 else if (y->n_tasks_valid)
388                         return 1;
389         }
390
391         if (arg_order == ORDER_MEMORY) {
392                 if (x->memory_valid && y->memory_valid) {
393                         if (x->memory > y->memory)
394                                 return -1;
395                         else if (x->memory < y->memory)
396                                 return 1;
397                 } else if (x->memory_valid)
398                         return -1;
399                 else if (y->memory_valid)
400                         return 1;
401         }
402
403         if (arg_order == ORDER_IO) {
404                 if (x->io_valid && y->io_valid) {
405                         if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps)
406                                 return -1;
407                         else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps)
408                                 return 1;
409                 } else if (x->io_valid)
410                         return -1;
411                 else if (y->io_valid)
412                         return 1;
413         }
414
415         return strcmp(x->path, y->path);
416 }
417
418 static int display(Hashmap *a) {
419         Iterator i;
420         Group *g;
421         Group **array;
422         unsigned rows, n = 0, j;
423
424         assert(a);
425
426         /* Set cursor to top left corner and clear screen */
427         fputs("\033[H"
428               "\033[2J", stdout);
429
430         array = alloca(sizeof(Group*) * hashmap_size(a));
431
432         HASHMAP_FOREACH(g, a, i)
433                 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
434                         array[n++] = g;
435
436         qsort(array, n, sizeof(Group*), group_compare);
437
438         rows = fd_lines(STDOUT_FILENO);
439         if (rows <= 0)
440                 rows = 25;
441
442         printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
443                arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_ON : "", "Path",     arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_OFF : "",
444                arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_ON : "", "Tasks",    arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_OFF : "",
445                arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_ON : "", "%CPU",     arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_OFF : "",
446                arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",   arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
447                arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Input/s",  arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "",
448                arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "");
449
450         for (j = 0; j < n; j++) {
451                 char *p;
452                 char m[FORMAT_BYTES_MAX];
453
454                 if (j + 5 > rows)
455                         break;
456
457                 g = array[j];
458
459                 p = ellipsize(g->path, 37, 33);
460                 printf("%-37s", p ? p : g->path);
461                 free(p);
462
463                 if (g->n_tasks_valid)
464                         printf(" %7u", g->n_tasks);
465                 else
466                         fputs("       -", stdout);
467
468                 if (g->cpu_valid)
469                         printf(" %6.1f", g->cpu_fraction*100);
470                 else
471                         fputs("      -", stdout);
472
473                 if (g->memory_valid)
474                         printf(" %8s", format_bytes(m, sizeof(m), g->memory));
475                 else
476                         fputs("        -", stdout);
477
478                 if (g->io_valid) {
479                         printf(" %8s",
480                                format_bytes(m, sizeof(m), g->io_input_bps));
481                         printf(" %8s",
482                                format_bytes(m, sizeof(m), g->io_output_bps));
483                 } else
484                         fputs("        -        -", stdout);
485
486                 putchar('\n');
487         }
488
489         return 0;
490 }
491
492 static void help(void) {
493
494         printf("%s [OPTIONS...]\n\n"
495                "Show top control groups by their resource usage.\n\n"
496                "  -h --help           Show this help\n"
497                "  -p                  Order by path\n"
498                "  -t                  Order by number of tasks\n"
499                "  -c                  Order by CPU load\n"
500                "  -m                  Order by memory load\n"
501                "  -i                  Order by IO load\n"
502                "  -d --delay=DELAY    Specify delay\n"
503                "     --depth=DEPTH    Maximum traversal depth (default: 2)\n",
504                program_invocation_short_name);
505 }
506
507 static int parse_argv(int argc, char *argv[]) {
508
509         enum {
510                 ARG_DEPTH = 0x100
511         };
512
513         static const struct option options[] = {
514                 { "help",  no_argument,       NULL, 'h'       },
515                 { "delay", required_argument, NULL, 'd'       },
516                 { "depth", required_argument, NULL, ARG_DEPTH },
517                 { NULL,    0,                 NULL, 0         }
518         };
519
520         int c;
521         int r;
522
523         assert(argc >= 1);
524         assert(argv);
525
526         while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) {
527
528                 switch (c) {
529
530                 case 'h':
531                         help();
532                         return 0;
533
534                 case ARG_DEPTH:
535                         r = safe_atou(optarg, &arg_depth);
536                         if (r < 0) {
537                                 log_error("Failed to parse depth parameter.");
538                                 return -EINVAL;
539                         }
540
541                         break;
542
543                 case 'd':
544                         r = parse_usec(optarg, &arg_delay);
545                         if (r < 0 || arg_delay <= 0) {
546                                 log_error("Failed to parse delay parameter.");
547                                 return -EINVAL;
548                         }
549
550                         break;
551
552                 case 'p':
553                         arg_order = ORDER_PATH;
554                         break;
555
556                 case 't':
557                         arg_order = ORDER_TASKS;
558                         break;
559
560                 case 'c':
561                         arg_order = ORDER_CPU;
562                         break;
563
564                 case 'm':
565                         arg_order = ORDER_MEMORY;
566                         break;
567
568                 case 'i':
569                         arg_order = ORDER_IO;
570                         break;
571
572                 case '?':
573                         return -EINVAL;
574
575                 default:
576                         log_error("Unknown option code %c", c);
577                         return -EINVAL;
578                 }
579         }
580
581         if (optind < argc) {
582                 log_error("Too many arguments.");
583                 return -EINVAL;
584         }
585
586         return 1;
587 }
588
589 int main(int argc, char *argv[]) {
590         int r;
591         Hashmap *a = NULL, *b = NULL;
592         unsigned iteration = 0;
593         usec_t last_refresh = 0;
594         bool quit = false, immediate_refresh = false;
595
596         log_parse_environment();
597         log_open();
598
599         r = parse_argv(argc, argv);
600         if (r <= 0)
601                 goto finish;
602
603         a = hashmap_new(string_hash_func, string_compare_func);
604         b = hashmap_new(string_hash_func, string_compare_func);
605         if (!a || !b) {
606                 log_error("Out of memory");
607                 r = -ENOMEM;
608                 goto finish;
609         }
610
611         while (!quit) {
612                 Hashmap *c;
613                 usec_t t;
614                 char key;
615                 char h[FORMAT_TIMESPAN_MAX];
616
617                 t = now(CLOCK_MONOTONIC);
618
619                 if (t >= last_refresh + arg_delay || immediate_refresh) {
620
621                         r = refresh(a, b, iteration++);
622                         if (r < 0)
623                                 goto finish;
624
625                         group_hashmap_clear(b);
626
627                         c = a;
628                         a = b;
629                         b = c;
630
631                         last_refresh = t;
632                         immediate_refresh = false;
633                 }
634
635                 r = display(b);
636                 if (r < 0)
637                         goto finish;
638
639                 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
640                 if (r == -ETIMEDOUT)
641                         continue;
642                 if (r < 0) {
643                         log_error("Couldn't read key: %s", strerror(-r));
644                         goto finish;
645                 }
646
647                 fputs("\r \r", stdout);
648                 fflush(stdout);
649
650                 switch (key) {
651
652                 case ' ':
653                         immediate_refresh = true;
654                         break;
655
656                 case 'q':
657                         quit = true;
658                         break;
659
660                 case 'p':
661                         arg_order = ORDER_PATH;
662                         break;
663
664                 case 't':
665                         arg_order = ORDER_TASKS;
666                         break;
667
668                 case 'c':
669                         arg_order = ORDER_CPU;
670                         break;
671
672                 case 'm':
673                         arg_order = ORDER_MEMORY;
674                         break;
675
676                 case 'i':
677                         arg_order = ORDER_IO;
678                         break;
679
680                 case '+':
681                         if (arg_delay < USEC_PER_SEC)
682                                 arg_delay += USEC_PER_MSEC*250;
683                         else
684                                 arg_delay += USEC_PER_SEC;
685
686                         fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
687                         fflush(stdout);
688                         sleep(1);
689                         break;
690
691                 case '-':
692                         if (arg_delay <= USEC_PER_MSEC*500)
693                                 arg_delay = USEC_PER_MSEC*250;
694                         else if (arg_delay < USEC_PER_MSEC*1250)
695                                 arg_delay -= USEC_PER_MSEC*250;
696                         else
697                                 arg_delay -= USEC_PER_SEC;
698
699                         fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
700                         fflush(stdout);
701                         sleep(1);
702                         break;
703
704                 case '?':
705                 case 'h':
706                         fprintf(stdout,
707                                 "\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"
708                                 "\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");
709                         fflush(stdout);
710                         sleep(3);
711                         break;
712
713                 default:
714                         fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
715                         fflush(stdout);
716                         sleep(1);
717                         break;
718                 }
719         }
720
721         log_info("Exiting.");
722
723         r = 0;
724
725 finish:
726         group_hashmap_free(a);
727         group_hashmap_free(b);
728
729         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
730 }