chiark / gitweb /
install, cgtop: adjust hashmap_move_one() callers for -ENOMEM possibility
[elogind.git] / src / cgtop / cgtop.c
index ddb57094b1c527328ee1b152f8660babec1f9b39..932a7ba7c6c360c8bf5d1c2ad8842d7f47e0c21b 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#define __STDC_FORMAT_MACROS
 #include <errno.h>
 #include <string.h>
 #include <stdlib.h>
+#include <stdint.h>
 #include <unistd.h>
 #include <alloca.h>
 #include <getopt.h>
@@ -30,6 +32,8 @@
 #include "util.h"
 #include "hashmap.h"
 #include "cgroup-util.h"
+#include "build.h"
+#include "fileio.h"
 
 typedef struct Group {
         char *path;
@@ -55,6 +59,8 @@ typedef struct Group {
 } Group;
 
 static unsigned arg_depth = 3;
+static unsigned arg_iterations = 0;
+static bool arg_batch = false;
 static usec_t arg_delay = 1*USEC_PER_SEC;
 
 static enum {
@@ -65,6 +71,11 @@ static enum {
         ORDER_IO
 } arg_order = ORDER_CPU;
 
+static enum {
+        CPU_PERCENT,
+        CPU_TIME,
+} arg_cpu_type = CPU_PERCENT;
+
 static void group_free(Group *g) {
         assert(g);
 
@@ -87,7 +98,7 @@ static void group_hashmap_free(Hashmap *h) {
 static int process(const char *controller, const char *path, Hashmap *a, Hashmap *b, unsigned iteration) {
         Group *g;
         int r;
-        FILE *f;
+        FILE *f = NULL;
         pid_t pid;
         unsigned n;
 
@@ -115,7 +126,9 @@ static int process(const char *controller, const char *path, Hashmap *a, Hashmap
                                 return r;
                         }
                 } else {
-                        assert_se(hashmap_move_one(a, b, path) == 0);
+                        r = hashmap_move_one(a, b, path);
+                        if (r < 0)
+                                return r;
                         g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false;
                 }
         }
@@ -123,7 +136,7 @@ static int process(const char *controller, const char *path, Hashmap *a, Hashmap
         /* Regardless which controller, let's find the maximum number
          * of processes in any of it */
 
-        r = cg_enumerate_tasks(controller, path, &f);
+        r = cg_enumerate_processes(controller, path, &f);
         if (r < 0)
                 return r;
 
@@ -297,7 +310,7 @@ static int refresh_one(
 
         r = cg_enumerate_subgroups(controller, path, &d);
         if (r < 0) {
-                if (r == ENOENT)
+                if (r == -ENOENT)
                         return 0;
 
                 return r;
@@ -310,7 +323,7 @@ static int refresh_one(
                 if (r <= 0)
                         goto finish;
 
-                p = join(path, "/", fn, NULL);
+                p = strjoin(path, "/", fn, NULL);
                 free(fn);
 
                 if (!p) {
@@ -341,17 +354,22 @@ static int refresh(Hashmap *a, Hashmap *b, unsigned iteration) {
 
         r = refresh_one("name=systemd", "/", a, b, iteration, 0);
         if (r < 0)
-                return r;
-
+                if (r != -ENOENT)
+                    return r;
         r = refresh_one("cpuacct", "/", a, b, iteration, 0);
         if (r < 0)
-                return r;
-
+                if (r != -ENOENT)
+                    return r;
         r = refresh_one("memory", "/", a, b, iteration, 0);
         if (r < 0)
-                return r;
+                if (r != -ENOENT)
+                    return r;
 
-        return refresh_one("blkio", "/", a, b, iteration, 0);
+        r = refresh_one("blkio", "/", a, b, iteration, 0);
+        if (r < 0)
+                if (r != -ENOENT)
+                    return r;
+        return 0;
 }
 
 static int group_compare(const void*a, const void *b) {
@@ -363,16 +381,22 @@ static int group_compare(const void*a, const void *b) {
                 return 1;
 
         if (arg_order == ORDER_CPU) {
-                if (x->cpu_valid && y->cpu_valid) {
-
-                        if (x->cpu_fraction > y->cpu_fraction)
+                if (arg_cpu_type == CPU_PERCENT) {
+                        if (x->cpu_valid && y->cpu_valid) {
+                                if (x->cpu_fraction > y->cpu_fraction)
+                                        return -1;
+                                else if (x->cpu_fraction < y->cpu_fraction)
+                                        return 1;
+                        } else if (x->cpu_valid)
                                 return -1;
-                        else if (x->cpu_fraction < y->cpu_fraction)
+                        else if (y->cpu_valid)
                                 return 1;
-                } else if (x->cpu_valid)
-                        return -1;
-                else if (y->cpu_valid)
-                        return 1;
+                } else {
+                        if (x->cpu_usage > y->cpu_usage)
+                                return -1;
+                        else if (x->cpu_usage < y->cpu_usage)
+                                return 1;
+                }
         }
 
         if (arg_order == ORDER_TASKS) {
@@ -415,17 +439,23 @@ static int group_compare(const void*a, const void *b) {
         return strcmp(x->path, y->path);
 }
 
+#define ON ANSI_HIGHLIGHT_ON
+#define OFF ANSI_HIGHLIGHT_OFF
+
 static int display(Hashmap *a) {
         Iterator i;
         Group *g;
         Group **array;
-        unsigned rows, n = 0, j;
+        signed path_columns;
+        unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 0;
+        char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)];
 
         assert(a);
 
         /* Set cursor to top left corner and clear screen */
-        fputs("\033[H"
-              "\033[2J", stdout);
+        if (on_tty())
+                fputs("\033[H"
+                      "\033[2J", stdout);
 
         array = alloca(sizeof(Group*) * hashmap_size(a));
 
@@ -433,31 +463,59 @@ static int display(Hashmap *a) {
                 if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid)
                         array[n++] = g;
 
-        qsort(array, n, sizeof(Group*), group_compare);
+        qsort_safe(array, n, sizeof(Group*), group_compare);
 
-        rows = fd_lines(STDOUT_FILENO);
-        if (rows <= 0)
-                rows = 25;
+        /* Find the longest names in one run */
+        for (j = 0; j < n; j++) {
+                unsigned cputlen, pathtlen;
+
+                format_timespan(buffer, sizeof(buffer), (nsec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0);
+                cputlen = strlen(buffer);
+                maxtcpu = MAX(maxtcpu, cputlen);
+                pathtlen = strlen(array[j]->path);
+                maxtpath = MAX(maxtpath, pathtlen);
+        }
 
-        printf("%s%-37s%s %s%7s%s %s%6s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
-               arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_ON : "", "Path",     arg_order == ORDER_PATH   ? ANSI_HIGHLIGHT_OFF : "",
-               arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_ON : "", "Tasks",    arg_order == ORDER_TASKS  ? ANSI_HIGHLIGHT_OFF : "",
-               arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_ON : "", "%CPU",     arg_order == ORDER_CPU    ? ANSI_HIGHLIGHT_OFF : "",
-               arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_ON : "", "Memory",   arg_order == ORDER_MEMORY ? ANSI_HIGHLIGHT_OFF : "",
-               arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Input/s",  arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "",
-               arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_ON : "", "Output/s", arg_order == ORDER_IO     ? ANSI_HIGHLIGHT_OFF : "");
+        if (arg_cpu_type == CPU_PERCENT)
+                snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
+        else
+                snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
+
+        rows = lines();
+        if (rows <= 10)
+                rows = 10;
+
+        if (on_tty()) {
+                path_columns = columns() - 36 - strlen(buffer);
+                if (path_columns < 10)
+                        path_columns = 10;
+
+                printf("%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s\n\n",
+                       arg_order == ORDER_PATH ? ON : "", path_columns, "Path",
+                       arg_order == ORDER_PATH ? OFF : "",
+                       arg_order == ORDER_TASKS ? ON : "", "Tasks",
+                       arg_order == ORDER_TASKS ? OFF : "",
+                       arg_order == ORDER_CPU ? ON : "", buffer,
+                       arg_order == ORDER_CPU ? OFF : "",
+                       arg_order == ORDER_MEMORY ? ON : "", "Memory",
+                       arg_order == ORDER_MEMORY ? OFF : "",
+                       arg_order == ORDER_IO ? ON : "", "Input/s",
+                       arg_order == ORDER_IO ? OFF : "",
+                       arg_order == ORDER_IO ? ON : "", "Output/s",
+                       arg_order == ORDER_IO ? OFF : "");
+        } else
+                path_columns = maxtpath;
 
         for (j = 0; j < n; j++) {
                 char *p;
-                char m[FORMAT_BYTES_MAX];
 
-                if (j + 5 > rows)
+                if (on_tty() && j + 5 > rows)
                         break;
 
                 g = array[j];
 
-                p = ellipsize(g->path, 37, 33);
-                printf("%-37s", p ? p : g->path);
+                p = ellipsize(g->path, path_columns, 33);
+                printf("%-*s", path_columns, p ? p : g->path);
                 free(p);
 
                 if (g->n_tasks_valid)
@@ -465,21 +523,24 @@ static int display(Hashmap *a) {
                 else
                         fputs("       -", stdout);
 
-                if (g->cpu_valid)
-                        printf(" %6.1f", g->cpu_fraction*100);
-                else
-                        fputs("      -", stdout);
+                if (arg_cpu_type == CPU_PERCENT) {
+                        if (g->cpu_valid)
+                                printf(" %6.1f", g->cpu_fraction*100);
+                        else
+                                fputs("      -", stdout);
+                } else
+                        printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (nsec_t) (g->cpu_usage / NSEC_PER_USEC), 0));
 
                 if (g->memory_valid)
-                        printf(" %8s", format_bytes(m, sizeof(m), g->memory));
+                        printf(" %8s", format_bytes(buffer, sizeof(buffer), g->memory));
                 else
                         fputs("        -", stdout);
 
                 if (g->io_valid) {
                         printf(" %8s",
-                               format_bytes(m, sizeof(m), g->io_input_bps));
+                               format_bytes(buffer, sizeof(buffer), g->io_input_bps));
                         printf(" %8s",
-                               format_bytes(m, sizeof(m), g->io_output_bps));
+                               format_bytes(buffer, sizeof(buffer), g->io_output_bps));
                 } else
                         fputs("        -        -", stdout);
 
@@ -490,31 +551,40 @@ static int display(Hashmap *a) {
 }
 
 static void help(void) {
-
         printf("%s [OPTIONS...]\n\n"
                "Show top control groups by their resource usage.\n\n"
                "  -h --help           Show this help\n"
+               "  --version           Print version and exit\n"
                "  -p                  Order by path\n"
                "  -t                  Order by number of tasks\n"
                "  -c                  Order by CPU load\n"
                "  -m                  Order by memory load\n"
                "  -i                  Order by IO load\n"
-               "  -d --delay=DELAY    Specify delay\n"
-               "     --depth=DEPTH    Maximum traversal depth (default: 2)\n",
-               program_invocation_short_name);
+               "     --cpu[=TYPE]     Show CPU usage as time or percentage (default)\n"
+               "  -d --delay=DELAY    Delay between updates\n"
+               "  -n --iterations=N   Run for N iterations before exiting\n"
+               "  -b --batch          Run in batch mode, accepting no input\n"
+               "     --depth=DEPTH    Maximum traversal depth (default: %u)\n"
+               , program_invocation_short_name, arg_depth);
 }
 
 static int parse_argv(int argc, char *argv[]) {
 
         enum {
-                ARG_DEPTH = 0x100
+                ARG_VERSION = 0x100,
+                ARG_DEPTH,
+                ARG_CPU_TYPE
         };
 
         static const struct option options[] = {
-                { "help",  no_argument,       NULL, 'h'       },
-                { "delay", required_argument, NULL, 'd'       },
-                { "depth", required_argument, NULL, ARG_DEPTH },
-                { NULL,    0,                 NULL, 0         }
+                { "help",       no_argument,       NULL, 'h'         },
+                { "version",    no_argument,       NULL, ARG_VERSION },
+                { "delay",      required_argument, NULL, 'd'         },
+                { "iterations", required_argument, NULL, 'n'         },
+                { "batch",      no_argument,       NULL, 'b'         },
+                { "depth",      required_argument, NULL, ARG_DEPTH   },
+                { "cpu",        optional_argument, NULL, ARG_CPU_TYPE},
+                {}
         };
 
         int c;
@@ -523,7 +593,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 1);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hptcmid:", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "hptcmin:bd:", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -531,6 +601,22 @@ static int parse_argv(int argc, char *argv[]) {
                         help();
                         return 0;
 
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_CPU_TYPE:
+                        if (optarg) {
+                                if (strcmp(optarg, "time") == 0)
+                                        arg_cpu_type = CPU_TIME;
+                                else if (strcmp(optarg, "percentage") == 0)
+                                        arg_cpu_type = CPU_PERCENT;
+                                else
+                                        return -EINVAL;
+                        }
+                        break;
+
                 case ARG_DEPTH:
                         r = safe_atou(optarg, &arg_depth);
                         if (r < 0) {
@@ -541,7 +627,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'd':
-                        r = parse_usec(optarg, &arg_delay);
+                        r = parse_sec(optarg, &arg_delay);
                         if (r < 0 || arg_delay <= 0) {
                                 log_error("Failed to parse delay parameter.");
                                 return -EINVAL;
@@ -549,6 +635,19 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case 'n':
+                        r = safe_atou(optarg, &arg_iterations);
+                        if (r < 0) {
+                                log_error("Failed to parse iterations parameter.");
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case 'b':
+                        arg_batch = true;
+                        break;
+
                 case 'p':
                         arg_order = ORDER_PATH;
                         break;
@@ -573,10 +672,8 @@ static int parse_argv(int argc, char *argv[]) {
                         return -EINVAL;
 
                 default:
-                        log_error("Unknown option code %c", c);
-                        return -EINVAL;
+                        assert_not_reached("Unhandled option");
                 }
-        }
 
         if (optind < argc) {
                 log_error("Too many arguments.");
@@ -600,14 +697,18 @@ int main(int argc, char *argv[]) {
         if (r <= 0)
                 goto finish;
 
-        a = hashmap_new(string_hash_func, string_compare_func);
-        b = hashmap_new(string_hash_func, string_compare_func);
+        a = hashmap_new(&string_hash_ops);
+        b = hashmap_new(&string_hash_ops);
         if (!a || !b) {
-                log_error("Out of memory");
-                r = -ENOMEM;
+                r = log_oom();
                 goto finish;
         }
 
+        signal(SIGWINCH, columns_lines_cache_reset);
+
+        if (!on_tty())
+                arg_iterations = 1;
+
         while (!quit) {
                 Hashmap *c;
                 usec_t t;
@@ -636,17 +737,28 @@ int main(int argc, char *argv[]) {
                 if (r < 0)
                         goto finish;
 
-                r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL);
-                if (r == -ETIMEDOUT)
-                        continue;
-                if (r < 0) {
-                        log_error("Couldn't read key: %s", strerror(-r));
-                        goto finish;
+                if (arg_iterations && iteration >= arg_iterations)
+                        break;
+
+                if (arg_batch) {
+                        usleep(last_refresh + arg_delay - t);
+                } else {
+                        r = read_one_char(stdin, &key,
+                                          last_refresh + arg_delay - t, NULL);
+                        if (r == -ETIMEDOUT)
+                                continue;
+                        if (r < 0) {
+                                log_error("Couldn't read key: %s", strerror(-r));
+                                goto finish;
+                        }
                 }
 
                 fputs("\r \r", stdout);
                 fflush(stdout);
 
+                if (arg_batch)
+                        continue;
+
                 switch (key) {
 
                 case ' ':
@@ -677,13 +789,17 @@ int main(int argc, char *argv[]) {
                         arg_order = ORDER_IO;
                         break;
 
+                case '%':
+                        arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME;
+                        break;
+
                 case '+':
                         if (arg_delay < USEC_PER_SEC)
                                 arg_delay += USEC_PER_MSEC*250;
                         else
                                 arg_delay += USEC_PER_SEC;
 
-                        fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
+                        fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
                         fflush(stdout);
                         sleep(1);
                         break;
@@ -696,7 +812,7 @@ int main(int argc, char *argv[]) {
                         else
                                 arg_delay -= USEC_PER_SEC;
 
-                        fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay));
+                        fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0));
                         fflush(stdout);
                         sleep(1);
                         break;
@@ -704,8 +820,9 @@ int main(int argc, char *argv[]) {
                 case '?':
                 case 'h':
                         fprintf(stdout,
-                                "\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"
-                                "\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");
+                                "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n"
+                                "\t<" ON "+" OFF "> Increase delay; <" ON "-" OFF "> Decrease delay; <" ON "%%" OFF "> Toggle time\n"
+                                "\t<" ON "q" OFF "> Quit; <" ON "SPACE" OFF "> Refresh");
                         fflush(stdout);
                         sleep(3);
                         break;
@@ -718,13 +835,16 @@ int main(int argc, char *argv[]) {
                 }
         }
 
-        log_info("Exiting.");
-
         r = 0;
 
 finish:
         group_hashmap_free(a);
         group_hashmap_free(b);
 
-        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+        if (r < 0) {
+                log_error("Exiting with failure: %s", strerror(-r));
+                return EXIT_FAILURE;
+        }
+
+        return EXIT_SUCCESS;
 }