diff --git a/doc/lxc-info.sgml.in b/doc/lxc-info.sgml.in index 819d5cae1..791d7809c 100644 --- a/doc/lxc-info.sgml.in +++ b/doc/lxc-info.sgml.in @@ -47,20 +47,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA lxc-info - -n name + -n name -c KEY -s -p -i -t state + -S + -H Description - lxc-info queries and shows information about a - container. + lxc-info queries and shows information about + containers. @@ -70,11 +72,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - + - The container name. + The container name. It is interpreted as a regular expression, + so it is possible to get information on all containers, several + of them or just one. See + + regex + 7 + for regular expression syntax. If not specified, + name will default to '.*' which + will give information on all containers in + lxcpath. @@ -124,6 +135,41 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + + + + + + Just print the container's statistics. + Note that for performance reasons the kernel does not account + kernel memory use unless a kernel memory limit is set. If a limit + is not set, lxc-info will display kernel memory + use as 0. A limit can be set by specifying + + lxc.cgroup.memory.kmem.limit_in_bytes = number + + in your container configuration file, see + + lxc.conf + 5 + . + + + + + + + + + + + Print the container's statistics in raw, non-humanized form. The + default is to print statistics in humanized form. + + + + @@ -151,6 +197,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + lxc-info -n 'ubuntu.*' + + + Show information for all containers whose name starts with ubuntu. + + + + lxc-info -n foo -t RUNNING diff --git a/src/lxc/lxc_info.c b/src/lxc/lxc_info.c index aeaf9a896..ba43f3743 100644 --- a/src/lxc/lxc_info.c +++ b/src/lxc/lxc_info.c @@ -25,11 +25,14 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include "commands.h" @@ -38,6 +41,8 @@ static bool ips; static bool state; static bool pid; +static bool stats; +static bool humanize = true; static char *test_state = NULL; static char **key = NULL; static int keys = 0; @@ -53,6 +58,8 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg) case 'i': ips = true; break; case 's': state = true; break; case 'p': pid = true; break; + case 'S': stats = true; break; + case 'H': humanize = false; break; case 't': test_state = arg; break; } return 0; @@ -63,6 +70,8 @@ static const struct option my_longopts[] = { {"ips", no_argument, 0, 'i'}, {"state", no_argument, 0, 's'}, {"pid", no_argument, 0, 'p'}, + {"stats", no_argument, 0, 'S'}, + {"no-humanize", no_argument, 0, 'H'}, {"state-is", required_argument, 0, 't'}, LXC_COMMON_OPTIONS, }; @@ -79,33 +88,177 @@ Options :\n\ -c, --config=KEY show configuration variable KEY from running container\n\ -i, --ips shows the IP addresses\n\ -p, --pid shows the process id of the init container\n\ + -S, --stats shows usage stats\n\ + -H, --no-humanize shows stats as raw numbers, not humanized\n\ -s, --state shows the state of the container\n\ -t, --state-is=STATE test if current state is STATE\n\ returns success if it matches, false otherwise\n", + .name = ".*", .options = my_longopts, .parser = my_parser, .checker = NULL, }; -int main(int argc, char *argv[]) +static void str_chomp(char *buf) { + char *ch; + + /* remove trailing whitespace from buf */ + for(ch = &buf[strlen(buf)-1]; + ch >= buf && (*ch == '\t' || *ch == '\n' || *ch == ' '); + ch--) + *ch = '\0'; +} + +static void size_humanize(unsigned long long val, char *buf, size_t bufsz) +{ + if (val > 1 << 30) { + snprintf(buf, bufsz, "%u.%2.2u GiB", + (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 MiB", + 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 KiB", + x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); + } else { + snprintf(buf, bufsz, "%u bytes", (int)val); + } +} + +static unsigned long long str_size_humanize(char *iobuf, size_t iobufsz) +{ + unsigned long long val; + char *end = NULL; + + val = strtoull(iobuf, &end, 0); + if (humanize) { + if (*end == '\0' || *end == '\n') + size_humanize(val, iobuf, iobufsz); + else + *iobuf = '\0'; + } + return val; +} + +static void print_net_stats(const char *name, const char *lxcpath) +{ + int rc,netnr; + unsigned long long rx_bytes = 0, tx_bytes = 0; + char *ifname, *type; + char path[PATH_MAX]; + char buf[256]; + + for(netnr = 0; ;netnr++) { + sprintf(buf, "lxc.network.%d.type", netnr); + type = lxc_cmd_get_config_item(name, buf, lxcpath); + if (!type) + break; + + if (!strcmp(type, "veth")) { + sprintf(buf, "lxc.network.%d.veth.pair", netnr); + } else { + sprintf(buf, "lxc.network.%d.link", netnr); + } + free(type); + ifname = lxc_cmd_get_config_item(name, buf, lxcpath); + if (!ifname) + return; + printf("%-15s %s\n", "Link:", ifname); + + /* XXX: tx and rx are reversed from the host vs container + * perspective, print them from the container perspective + */ + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_bytes", ifname); + rc = lxc_read_from_file(path, buf, sizeof(buf)); + if (rc > 0) { + str_chomp(buf); + rx_bytes = str_size_humanize(buf, sizeof(buf)); + printf("%-15s %s\n", " TX bytes:", buf); + } + + snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/tx_bytes", ifname); + rc = lxc_read_from_file(path, buf, sizeof(buf)); + if (rc > 0) { + str_chomp(buf); + tx_bytes = str_size_humanize(buf, sizeof(buf)); + printf("%-15s %s\n", " RX bytes:", buf); + } + + sprintf(buf, "%llu", rx_bytes + tx_bytes); + str_size_humanize(buf, sizeof(buf)); + printf("%-15s %s\n", " Total bytes:", buf); + free(ifname); + } +} + +static void print_stats(struct lxc_container *c) +{ + int i, ret; + char buf[256]; + + ret = c->get_cgroup_item(c, "cpuacct.usage", buf, sizeof(buf)); + if (ret > 0 && ret < sizeof(buf)) { + str_chomp(buf); + if (humanize) { + float seconds = strtof(buf, NULL) / 1000000000.0; + printf("%-15s %.2f seconds\n", "CPU use:", seconds); + } else { + printf("%-15s %s\n", "CPU use:", buf); + } + } + + ret = c->get_cgroup_item(c, "blkio.throttle.io_service_bytes", buf, sizeof(buf)); + if (ret > 0 && ret < sizeof(buf)) { + char *ch; + + /* put ch on last "Total" line */ + str_chomp(buf); + for(ch = &buf[strlen(buf)-1]; ch > buf && *ch != '\n'; ch--) + ; + if (*ch == '\n') + ch++; + + if (strncmp(ch, "Total", 5) == 0) { + ch += 6; + memmove(buf, ch, strlen(ch)+1); + str_size_humanize(buf, sizeof(buf)); + printf("%-15s %s\n", "BlkIO use:", buf); + } + } + + static const struct { + const char *name; + const char *file; + } lxstat[] = { + { "Memory use:", "memory.usage_in_bytes" }, + { "KMem use:", "memory.kmem.usage_in_bytes" }, + { NULL, NULL }, + }; + + for (i = 0; lxstat[i].name; i++) { + ret = c->get_cgroup_item(c, lxstat[i].file, buf, sizeof(buf)); + if (ret > 0 && ret < sizeof(buf)) { + str_chomp(buf); + str_size_humanize(buf, sizeof(buf)); + printf("%-15s %s\n", lxstat[i].name, buf); + } + } +} + +static int print_info(const char *name, const char *lxcpath) +{ + int i; struct lxc_container *c; - int i; - - if (lxc_arguments_parse(&my_args, argc, argv)) - return -1; - - if (!my_args.log_file) - my_args.log_file = "none"; - - if (lxc_log_init(my_args.name, my_args.log_file, my_args.log_priority, - my_args.progname, my_args.quiet, my_args.lxcpath[0])) - return -1; - - c = lxc_container_new(my_args.name, my_args.lxcpath[0]); - if (!c) + c = lxc_container_new(name, lxcpath); + if (!c) { + fprintf(stderr, "Insufficent privileges to control %s\n", c->name); return -1; + } if (!c->may_control(c)) { fprintf(stderr, "Insufficent privileges to control %s\n", c->name); @@ -113,14 +266,16 @@ int main(int argc, char *argv[]) return -1; } - if (!state && !pid && !ips && keys <= 0) - state = pid = ips = true; + if (!state && !pid && !ips && !stats && keys <= 0) + state = pid = ips = stats = true; + + printf("%-15s %s\n", "Name:", c->name); if (state || test_state) { if (test_state) return strcmp(c->state(c), test_state) != 0; - printf("state: \t%s\n", c->state(c)); + printf("%-15s %s\n", "State:", c->state(c)); } if (pid) { @@ -128,7 +283,7 @@ int main(int argc, char *argv[]) initpid = c->init_pid(c); if (initpid >= 0) - printf("pid: \t%d\n", initpid); + printf("%-15s %d\n", "Pid:", initpid); } if (ips) { @@ -138,12 +293,17 @@ int main(int argc, char *argv[]) i = 0; while (addresses[i]) { address = addresses[i]; - printf("ip: \t%s\n", address); + printf("%-15s %s\n", "IP:", address); i++; } } } + if (stats) { + print_stats(c); + print_net_stats(name, lxcpath); + } + for(i = 0; i < keys; i++) { int len = c->get_config_item(c, key[i], NULL, 0); @@ -164,3 +324,66 @@ int main(int argc, char *argv[]) lxc_container_put(c); return 0; } + +int main(int argc, char *argv[]) +{ + int rc, i, len, ret = EXIT_FAILURE; + char *regexp; + regex_t preg; + int ct_cnt; + char **ct_name; + bool printed; + + if (lxc_arguments_parse(&my_args, argc, argv)) + goto err1; + + if (!my_args.log_file) + my_args.log_file = "none"; + + if (lxc_log_init(my_args.name, my_args.log_file, my_args.log_priority, + my_args.progname, my_args.quiet, my_args.lxcpath[0])) + goto err1; + + len = strlen(my_args.name) + 3; + regexp = malloc(len + 3); + if (!regexp) { + fprintf(stderr, "failed to allocate memory"); + goto err1; + } + rc = snprintf(regexp, len, "^%s$", my_args.name); + if (rc < 0 || rc >= len) { + fprintf(stderr, "Name too long"); + goto err2; + } + + if (regcomp(&preg, regexp, REG_NOSUB|REG_EXTENDED)) { + fprintf(stderr, "failed to compile the regex '%s'", my_args.name); + goto err2; + } + + printed = false; + ct_cnt = list_all_containers(my_args.lxcpath[0], &ct_name, NULL); + if (ct_cnt < 0) + goto err3; + + for (i = 0; i < ct_cnt; i++) { + if (regexec(&preg, ct_name[i], 0, NULL, 0) == 0) + { + if (printed) + printf("\n"); + print_info(ct_name[i], my_args.lxcpath[0]); + printed = true; + } + free(ct_name[i]); + } + if (ct_name) + free(ct_name); + ret = EXIT_SUCCESS; + +err3: + regfree(&preg); +err2: + free(regexp); +err1: + return ret; +}