]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
port lxc-top from lua to C for wider availability
authorDwight Engen <dwight.engen@oracle.com>
Tue, 23 Sep 2014 17:37:50 +0000 (13:37 -0400)
committerStéphane Graber <stgraber@ubuntu.com>
Tue, 23 Sep 2014 19:23:05 +0000 (15:23 -0400)
- keep but rename the lua version as an example of how to use the lua API

- got rid of the fairly useless --max argument

Signed-off-by: Dwight Engen <dwight.engen@oracle.com>
Acked-by: Serge E. Hallyn <serge.hallyn@ubuntu.com>
doc/lxc-top.sgml.in
src/lxc/Makefile.am
src/lxc/lxc-top.lua [moved from src/lxc/lxc-top with 100% similarity]
src/lxc/lxc_top.c [new file with mode: 0644]

index ba727c57144958bc00c9ca3b0ee96d0509f85e9c..2e2f77473b7b1f3e7d173a9e5ccb95c450538741 100644 (file)
@@ -47,7 +47,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
     <cmdsynopsis>
       <command>lxc-top</command>
       <arg choice="opt">--help</arg>
-      <arg choice="opt">--max <replaceable>count</replaceable></arg>
       <arg choice="opt">--delay <replaceable>delay</replaceable></arg>
       <arg choice="opt">--sort <replaceable>sortby</replaceable></arg>
       <arg choice="opt">--reverse</arg>
@@ -60,9 +59,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
       <command>lxc-top</command> displays container statistics. The output
       is updated every <replaceable>delay</replaceable> seconds, and is
       ordered according to the <replaceable>sortby</replaceable> value
-      given. Specifying <replaceable>count</replaceable> will limit the
-      number of containers displayed, otherwise <command>lxc-top</command>
-      will display as many containers as can fit in your terminal.
+      given. <command>lxc-top</command> will display as many containers as
+      can fit in your terminal. Press 'q' to quit. Press one of the sort
+      key letters to sort by that statistic. Pressing a sort key letter a
+      second time reverses the sort order.
     </para>
   </refsect1>
 
@@ -70,18 +70,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
     <title>Options</title>
     <variablelist>
 
-      <varlistentry>
-        <term>
-          <option><optional>-m, --max <replaceable>count</replaceable></optional></option>
-        </term>
-        <listitem>
-          <para>
-            Limit the number of containers displayed to
-            <replaceable>count</replaceable>.
-          </para>
-        </listitem>
-      </varlistentry>
-
       <varlistentry>
         <term>
           <option><optional>-d, --delay <replaceable>delay</replaceable></optional></option>
@@ -89,9 +77,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
         <listitem>
           <para>
             Amount of time in seconds to delay between screen updates.
-            This can be specified as less than a second by giving a
-            rational number, for example 0.5 for a half second delay. The
-            default is 3 seconds.
+            The default is 3 seconds.
           </para>
         </listitem>
       </varlistentry>
@@ -103,7 +89,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
           <para>
             Sort the containers by name, cpu use, or memory use. The
             <replaceable>sortby</replaceable> argument should be one of
-            the letters n,c,d,m,k to sort by name, cpu use, disk I/O, memory,
+            the letters n,c,b,m,k to sort by name, cpu use, block I/O, memory,
             or kernel memory use respectively. The default is 'n'.
           </para>
         </listitem>
index 8322e628d5f1db9502286f402e1964573513f251..c65a15db9a332cf70b02e66c8b369286b8319dcf 100644 (file)
@@ -165,7 +165,7 @@ bin_SCRIPTS = lxc-checkconfig
 EXTRA_DIST = \
        lxc-device \
        lxc-ls \
-       lxc-top \
+       lxc-top.lua \
        lxc.net \
        lxc-restore-net
 
@@ -177,10 +177,6 @@ else
 bin_SCRIPTS += legacy/lxc-ls
 endif
 
-if ENABLE_LUA
-bin_SCRIPTS += lxc-top
-endif
-
 bin_PROGRAMS = \
        lxc-attach \
        lxc-autostart \
@@ -198,6 +194,7 @@ bin_PROGRAMS = \
        lxc-snapshot \
        lxc-start \
        lxc-stop \
+       lxc-top \
        lxc-unfreeze \
        lxc-unshare \
        lxc-usernsexec \
@@ -231,6 +228,7 @@ lxc_monitord_SOURCES = lxc_monitord.c
 lxc_clone_SOURCES = lxc_clone.c
 lxc_start_SOURCES = lxc_start.c
 lxc_stop_SOURCES = lxc_stop.c
+lxc_top_SOURCES = lxc_top.c
 lxc_unfreeze_SOURCES = lxc_unfreeze.c
 lxc_unshare_SOURCES = lxc_unshare.c
 lxc_wait_SOURCES = lxc_wait.c
similarity index 100%
rename from src/lxc/lxc-top
rename to src/lxc/lxc-top.lua
diff --git a/src/lxc/lxc_top.c b/src/lxc/lxc_top.c
new file mode 100644 (file)
index 0000000..aec4b52
--- /dev/null
@@ -0,0 +1,511 @@
+/*
+ * lxc: linux Container library
+ *
+ * Copyright © 2014 Oracle.
+ *
+ * Authors:
+ * Dwight Engen <dwight.engen@oracle.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <lxc/lxccontainer.h>
+
+#include "arguments.h"
+#include "log.h"
+#include "lxc.h"
+#include "mainloop.h"
+#include "utils.h"
+
+lxc_log_define(lxc_top_ui, lxc);
+
+#define USER_HZ   100
+#define ESC       "\033"
+#define TERMCLEAR ESC "[H" ESC "[J"
+#define TERMNORM  ESC "[0m"
+#define TERMBOLD  ESC "[1m"
+#define TERMRVRS  ESC "[7m"
+
+struct stats {
+       uint64_t mem_used;
+       uint64_t mem_limit;
+       uint64_t kmem_used;
+       uint64_t kmem_limit;
+       uint64_t cpu_use_nanos;
+       uint64_t cpu_use_user;
+       uint64_t cpu_use_sys;
+       uint64_t blkio;
+};
+
+struct ct {
+       struct lxc_container *c;
+       struct stats *stats;
+};
+
+static int delay = 3;
+static char sort_by = 'n';
+static int sort_reverse = 0;
+
+static struct termios oldtios;
+static struct ct *ct = NULL;
+static int ct_alloc_cnt = 0;
+
+static int my_parser(struct lxc_arguments* args, int c, char* arg)
+{
+       switch (c) {
+       case 'd': delay = atoi(arg); break;
+       case 's': sort_by = arg[0]; break;
+       case 'r': sort_reverse = 1; break;
+       }
+       return 0;
+}
+
+static const struct option my_longopts[] = {
+       {"delay",   required_argument, 0, 'd'},
+       {"sort",    required_argument, 0, 's'},
+       {"reverse", no_argument,       0, 'r'},
+       LXC_COMMON_OPTIONS
+};
+
+static struct lxc_arguments my_args = {
+       .progname = "lxc-top",
+       .help     = "\
+[--name=NAME]\n\
+\n\
+lxc-top monitors the state of the active containers\n\
+\n\
+Options :\n\
+  -d, --delay     delay in seconds between refreshes (default: 3.0)\n\
+  -s, --sort      sort by [n,c,b,m] (default: n) where\n\
+                  n = Name\n\
+                  c = CPU use\n\
+                  b = Block I/O use\n\
+                  m = Memory use\n\
+                  k = Kernel memory use\n\
+  -r, --reverse   sort in reverse (descending) order\n",
+       .name     = ".*",
+       .options  = my_longopts,
+       .parser   = my_parser,
+       .checker  = NULL,
+       .lxcpath_additional = -1,
+};
+
+static void stdin_tios_restore(void)
+{
+       tcsetattr(0, TCSAFLUSH, &oldtios);
+}
+
+static int stdin_tios_setup(void)
+{
+       struct termios newtios;
+
+       if (!isatty(0)) {
+               ERROR("stdin is not a tty");
+               return -1;
+       }
+
+       if (tcgetattr(0, &oldtios)) {
+               SYSERROR("failed to get current terminal settings");
+               return -1;
+       }
+
+       newtios = oldtios;
+
+       /* turn off echo and line buffering */
+       newtios.c_iflag &= ~IGNBRK;
+       newtios.c_iflag &= BRKINT;
+       newtios.c_lflag &= ~(ECHO|ICANON);
+       newtios.c_cc[VMIN] = 1;
+       newtios.c_cc[VTIME] = 0;
+
+       if (tcsetattr(0, TCSAFLUSH, &newtios)) {
+               ERROR("failed to set new terminal settings");
+               return -1;
+       }
+
+       return 0;
+}
+
+static int stdin_tios_rows(void)
+{
+       struct winsize wsz;
+       if (isatty(0) && ioctl(0, TIOCGWINSZ, &wsz) == 0)
+               return wsz.ws_row;
+       return 25;
+}
+
+static int stdin_handler(int fd, uint32_t events, void *data,
+                        struct lxc_epoll_descr *descr)
+{
+       char *in_char = data;
+
+       if (events & EPOLLIN) {
+               int rc;
+
+               rc = read(fd, in_char, sizeof(*in_char));
+               if (rc <= 0)
+                       *in_char = '\0';
+       }
+
+       if (events & EPOLLHUP)
+               *in_char = 'q';
+       return 1;
+}
+
+static void sig_handler(int sig)
+{
+       exit(EXIT_SUCCESS);
+}
+
+static void size_humanize(unsigned long long val, char *buf, size_t bufsz)
+{
+       if (val > 1 << 30) {
+               snprintf(buf, bufsz, "%u.%2.2u GB",
+                           (int)(val >> 30),
+                           (int)(val & ((1 << 30) - 1)) / 10737419);
+       } else if (val > 1 << 20) {
+               int x = val + 5243;  /* for rounding */
+               snprintf(buf, bufsz, "%u.%2.2u MB",
+                           x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20);
+       } else if (val > 1 << 10) {
+               int x = val + 5;  /* for rounding */
+               snprintf(buf, bufsz, "%u.%2.2u KB",
+                           x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10);
+       } else {
+               snprintf(buf, bufsz, "%3u.00   ", (int)val);
+       }
+}
+
+static uint64_t stat_get_int(struct lxc_container *c, const char *item)
+{
+       char buf[80];
+       int len;
+       uint64_t val;
+
+       len = c->get_cgroup_item(c, item, buf, sizeof(buf));
+       if (len <= 0) {
+               ERROR("unable to read cgroup item %s", item);
+               return 0;
+       }
+
+       val = strtoull(buf, NULL, 0);
+       return val;
+}
+
+static uint64_t stat_match_get_int(struct lxc_container *c, const char *item,
+                                  const char *match, int column)
+{
+       char buf[4096];
+       int i,j,len;
+       uint64_t val = 0;
+       char **lines, **cols;
+       size_t matchlen;
+
+       len = c->get_cgroup_item(c, item, buf, sizeof(buf));
+       if (len <= 0) {
+               ERROR("unable to read cgroup item %s", item);
+               goto out;
+       }
+
+       lines = lxc_string_split_and_trim(buf, '\n');
+       if (!lines)
+               goto out;
+
+       matchlen = strlen(match);
+       for (i = 0; lines[i]; i++) {
+               if (strncmp(lines[i], match, matchlen) == 0) {
+                       cols = lxc_string_split_and_trim(lines[i], ' ');
+                       if (!cols)
+                               goto err1;
+                       for (j = 0; cols[j]; j++) {
+                               if (j == column) {
+                                       val = strtoull(cols[j], NULL, 0);
+                                       break;
+                               }
+                       }
+                       lxc_free_array((void **)cols, free);
+                       break;
+               }
+       }
+err1:
+       lxc_free_array((void **)lines, free);
+out:
+       return val;
+}
+
+static void stats_get(struct lxc_container *c, struct ct *ct, struct stats *total)
+{
+       ct->c = c;
+       ct->stats->mem_used      = stat_get_int(c, "memory.usage_in_bytes");
+       ct->stats->mem_limit     = stat_get_int(c, "memory.limit_in_bytes");
+       ct->stats->kmem_used     = stat_get_int(c, "memory.kmem.usage_in_bytes");
+       ct->stats->kmem_limit    = stat_get_int(c, "memory.kmem.limit_in_bytes");
+       ct->stats->cpu_use_nanos = stat_get_int(c, "cpuacct.usage");
+       ct->stats->cpu_use_user  = stat_match_get_int(c, "cpuacct.stat", "user", 1);
+       ct->stats->cpu_use_sys   = stat_match_get_int(c, "cpuacct.stat", "system", 1);
+       ct->stats->blkio         = stat_match_get_int(c, "blkio.throttle.io_service_bytes", "Total", 1);
+
+       if (total) {
+               total->mem_used      = total->mem_used      + ct->stats->mem_used;
+               total->mem_limit     = total->mem_limit     + ct->stats->mem_limit;
+               total->kmem_used     = total->kmem_used     + ct->stats->kmem_used;
+               total->kmem_limit    = total->kmem_limit    + ct->stats->kmem_limit;
+               total->cpu_use_nanos = total->cpu_use_nanos + ct->stats->cpu_use_nanos;
+               total->cpu_use_user  = total->cpu_use_user  + ct->stats->cpu_use_user;
+               total->cpu_use_sys   = total->cpu_use_sys   + ct->stats->cpu_use_sys;
+               total->blkio         = total->blkio         + ct->stats->blkio;
+       }
+}
+
+static void stats_print_header(struct stats *stats)
+{
+       printf(TERMRVRS TERMBOLD);
+       printf("%-18s %8s %8s %8s %10s %10s", "Container", "CPU",  "CPU",  "CPU",  "BlkIO", "Mem");
+       if (stats->kmem_used > 0)
+               printf(" %10s", "KMem");
+       printf("\n");
+
+       printf("%-18s %8s %8s %8s %10s %10s", "Name",      "Used", "Sys",  "User", "Total", "Used");
+       if (stats->kmem_used > 0)
+               printf(" %10s", "Used");
+       printf("\n");
+       printf(TERMNORM);
+}
+
+static void stats_print(const char *name, const struct stats *stats,
+                       const struct stats *total)
+{
+       char blkio_str[20];
+       char mem_used_str[20];
+       char kmem_used_str[20];
+
+       size_humanize(stats->blkio, blkio_str, sizeof(blkio_str));
+       size_humanize(stats->mem_used, mem_used_str, sizeof(mem_used_str));
+
+       printf("%-18s %8.2f %8.2f %8.2f %10s %10s",
+              name,
+              (float)stats->cpu_use_nanos / 1000000000,
+              (float)stats->cpu_use_sys  / USER_HZ,
+              (float)stats->cpu_use_user / USER_HZ,
+              blkio_str,
+              mem_used_str);
+       if (total->kmem_used > 0) {
+               size_humanize(stats->kmem_used, kmem_used_str, sizeof(kmem_used_str));
+               printf(" %10s", kmem_used_str);
+       }
+}
+
+static int cmp_name(const void *sct1, const void *sct2)
+{
+       const struct ct *ct1 = sct1;
+       const struct ct *ct2 = sct2;
+
+       if (sort_reverse)
+               return strcmp(ct2->c->name, ct1->c->name);
+       return strcmp(ct1->c->name, ct2->c->name);
+}
+
+static int cmp_cpuuse(const void *sct1, const void *sct2)
+{
+       const struct ct *ct1 = sct1;
+       const struct ct *ct2 = sct2;
+
+       if (sort_reverse)
+               return ct2->stats->cpu_use_nanos < ct1->stats->cpu_use_nanos;
+       return ct1->stats->cpu_use_nanos < ct2->stats->cpu_use_nanos;
+}
+
+static int cmp_blkio(const void *sct1, const void *sct2)
+{
+       const struct ct *ct1 = sct1;
+       const struct ct *ct2 = sct2;
+
+       if (sort_reverse)
+               return ct2->stats->blkio < ct1->stats->blkio;
+       return ct1->stats->blkio < ct2->stats->blkio;
+}
+
+static int cmp_memory(const void *sct1, const void *sct2)
+{
+       const struct ct *ct1 = sct1;
+       const struct ct *ct2 = sct2;
+
+       if (sort_reverse)
+               return ct2->stats->mem_used < ct1->stats->mem_used;
+       return ct1->stats->mem_used < ct2->stats->mem_used;
+}
+
+static int cmp_kmemory(const void *sct1, const void *sct2)
+{
+       const struct ct *ct1 = sct1;
+       const struct ct *ct2 = sct2;
+
+       if (sort_reverse)
+               return ct2->stats->kmem_used < ct1->stats->kmem_used;
+       return ct1->stats->kmem_used < ct2->stats->kmem_used;
+}
+
+static void ct_sort(int active)
+{
+       int (*cmp_func)(const void *, const void *);
+
+       switch(sort_by) {
+       default:
+       case 'n': cmp_func = cmp_name; break;
+       case 'c': cmp_func = cmp_cpuuse; break;
+       case 'b': cmp_func = cmp_blkio; break;
+       case 'm': cmp_func = cmp_memory; break;
+       case 'k': cmp_func = cmp_kmemory; break;
+       }
+       qsort(ct, active, sizeof(*ct), (int (*)(const void *,const void *))cmp_func);
+}
+
+static void ct_free(void)
+{
+       int i;
+
+       for (i = 0; i < ct_alloc_cnt; i++) {
+               if (ct[i].c) {
+                       lxc_container_put(ct[i].c);
+                       ct[i].c = NULL;
+               }
+               if (ct[i].stats) {
+                       free(ct[i].stats);
+                       ct[i].stats = NULL;
+               }
+       }
+}
+
+static void ct_realloc(int active_cnt)
+{
+       int i;
+
+       if (active_cnt > ct_alloc_cnt) {
+               ct_free();
+               ct = realloc(ct, sizeof(*ct) * active_cnt);
+               if (!ct) {
+                       ERROR("cannot alloc mem");
+                       exit(EXIT_FAILURE);
+               }
+               for (i = 0; i < active_cnt; i++) {
+                       ct[i].stats = malloc(sizeof(*ct[0].stats));
+                       if (!ct[i].stats) {
+                               ERROR("cannot alloc mem");
+                               exit(EXIT_FAILURE);
+                       }
+               }
+               ct_alloc_cnt = active_cnt;
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       struct lxc_epoll_descr descr;
+       int ret, ct_print_cnt;
+       char in_char;
+
+       ret = EXIT_FAILURE;
+       if (lxc_arguments_parse(&my_args, argc, argv))
+               goto out;
+
+       ct_print_cnt = stdin_tios_rows() - 3; /* 3 -> header and total */
+       if (stdin_tios_setup() < 0) {
+               ERROR("failed to setup terminal");
+               goto out;
+       }
+
+       /* ensure the terminal gets restored */
+       atexit(stdin_tios_restore);
+       signal(SIGINT, sig_handler);
+       signal(SIGQUIT, sig_handler);
+
+       if (lxc_mainloop_open(&descr)) {
+               ERROR("failed to create mainloop");
+               goto out;
+       }
+
+       ret = lxc_mainloop_add_handler(&descr, 0, stdin_handler, &in_char);
+       if (ret) {
+               ERROR("failed to add stdin handler");
+               ret = EXIT_FAILURE;
+               goto err1;
+       }
+
+       for(;;) {
+               struct lxc_container **active;
+               int i, active_cnt;
+               struct stats total;
+               char total_name[30];
+
+               active_cnt = list_active_containers(my_args.lxcpath[0], NULL, &active);
+               ct_realloc(active_cnt);
+
+               memset(&total, 0, sizeof(total));
+               for (i = 0; i < active_cnt; i++)
+                       stats_get(active[i], &ct[i], &total);
+
+               ct_sort(active_cnt);
+
+               printf(TERMCLEAR);
+               stats_print_header(&total);
+               for (i = 0; i < active_cnt && i < ct_print_cnt; i++) {
+                       stats_print(ct[i].c->name, ct[i].stats, &total);
+                       printf("\n");
+               }
+               sprintf(total_name, "TOTAL %d of %d", i, active_cnt);
+               stats_print(total_name, &total, &total);
+               fflush(stdout);
+
+               for (i = 0; i < active_cnt; i++) {
+                       lxc_container_put(ct[i].c);
+                       ct[i].c = NULL;
+               }
+
+               in_char = '\0';
+               ret = lxc_mainloop(&descr, 1000 * delay);
+               if (ret != 0 || in_char == 'q')
+                       break;
+               switch(in_char) {
+               case 'r':
+                       sort_reverse ^= 1;
+                       break;
+               case 'n':
+               case 'c':
+               case 'b':
+               case 'm':
+               case 'k':
+                       if (sort_by == in_char)
+                               sort_reverse ^= 1;
+                       else
+                               sort_reverse = 0;
+                       sort_by = in_char;
+               }
+       }
+       ret = EXIT_SUCCESS;
+
+err1:
+       lxc_mainloop_close(&descr);
+out:
+       return ret;
+}