chiark / gitweb /
use "Out of memory." consistantly (or with "\n")
[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                 log_error("Out of memory.");
612                 r = -ENOMEM;
613                 goto finish;
614         }
615
616         while (!quit) {
617                 Hashmap *c;
618                 usec_t t;
619                 char key;
620                 char h[FORMAT_TIMESPAN_MAX];
621
622                 t = now(CLOCK_MONOTONIC);
623
624                 if (t >= last_refresh + arg_delay || immediate_refresh) {
625
626                         r = refresh(a, b, iteration++);
627                         if (r < 0)
628                                 goto finish;
629
630                         group_hashmap_clear(b);
631
632                         c = a;
633                         a = b;
634                         b = c;
635
636                         last_refresh = t;
637                         immediate_refresh = false;
638                 }
639
640                 r = display(b);
641                 if (r < 0)
642                         goto finish;
643
644                 r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
645                 if (r == -ETIMEDOUT)
646                         continue;
647                 if (r < 0) {
648                         log_error("Couldn't read key: %s", strerror(-r));
649                         goto finish;
650                 }
651
652                 fputs("\r \r", stdout);
653                 fflush(stdout);
654
655                 switch (key) {
656
657                 case ' ':
658                         immediate_refresh = true;
659                         break;
660
661                 case 'q':
662                         quit = true;
663                         break;
664
665                 case 'p':
666                         arg_order = ORDER_PATH;
667                         break;
668
669                 case 't':
670                         arg_order = ORDER_TASKS;
671                         break;
672
673                 case 'c':
674                         arg_order = ORDER_CPU;
675                         break;
676
677                 case 'm':
678                         arg_order = ORDER_MEMORY;
679                         break;
680
681                 case 'i':
682                         arg_order = ORDER_IO;
683                         break;
684
685                 case '+':
686                         if (arg_delay < USEC_PER_SEC)
687                                 arg_delay += USEC_PER_MSEC*250;
688                         else
689                                 arg_delay += USEC_PER_SEC;
690
691                         fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
692                         fflush(stdout);
693                         sleep(1);
694                         break;
695
696                 case '-':
697                         if (arg_delay <= USEC_PER_MSEC*500)
698                                 arg_delay = USEC_PER_MSEC*250;
699                         else if (arg_delay < USEC_PER_MSEC*1250)
700                                 arg_delay -= USEC_PER_MSEC*250;
701                         else
702                                 arg_delay -= USEC_PER_SEC;
703
704                         fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
705                         fflush(stdout);
706                         sleep(1);
707                         break;
708
709                 case '?':
710                 case 'h':
711                         fprintf(stdout,
712                                 "\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"
713                                 "\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");
714                         fflush(stdout);
715                         sleep(3);
716                         break;
717
718                 default:
719                         fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key);
720                         fflush(stdout);
721                         sleep(1);
722                         break;
723                 }
724         }
725
726         log_info("Exiting.");
727
728         r = 0;
729
730 finish:
731         group_hashmap_free(a);
732         group_hashmap_free(b);
733
734         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
735 }