diff --git a/.gitignore b/.gitignore index 2d4d4013a6..90fee91cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ /examples/dominfo/info1 /examples/domsuspend/suspend /examples/dommigrate/dommigrate +/examples/domtop/domtop /examples/hellolibvirt/hellolibvirt /examples/openauth/openauth /gnulib/lib/* diff --git a/Makefile.am b/Makefile.am index a374e1a156..4aafe946cb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,7 +24,7 @@ SUBDIRS = . gnulib/lib include src daemon tools docs gnulib/tests \ examples/dominfo examples/domsuspend examples/apparmor \ examples/xml/nwfilter examples/openauth examples/systemtap \ tools/wireshark examples/dommigrate \ - examples/lxcconvert + examples/lxcconvert examples/domtop ACLOCAL_AMFLAGS = -I m4 diff --git a/cfg.mk b/cfg.mk index c3d89a019d..0559eddbbf 100644 --- a/cfg.mk +++ b/cfg.mk @@ -1078,7 +1078,7 @@ exclude_file_name_regexp--sc_prohibit_sprintf = \ exclude_file_name_regexp--sc_prohibit_strncpy = ^src/util/virstring\.c$$ exclude_file_name_regexp--sc_prohibit_strtol = \ - ^(src/util/.*|examples/domsuspend/suspend)\.c$$ + ^(src/util/.*|examples/dom.*/.*)\.c$$ exclude_file_name_regexp--sc_prohibit_xmlGetProp = ^src/util/virxml\.c$$ diff --git a/configure.ac b/configure.ac index 8001e24b9c..f37c716ae5 100644 --- a/configure.ac +++ b/configure.ac @@ -2755,6 +2755,7 @@ AC_CONFIG_FILES([\ examples/domsuspend/Makefile \ examples/dominfo/Makefile \ examples/dommigrate/Makefile \ + examples/domtop/Makefile \ examples/openauth/Makefile \ examples/hellolibvirt/Makefile \ examples/systemtap/Makefile \ diff --git a/examples/domtop/Makefile.am b/examples/domtop/Makefile.am new file mode 100644 index 0000000000..c5cb6c73b4 --- /dev/null +++ b/examples/domtop/Makefile.am @@ -0,0 +1,27 @@ +## Process this file with automake to produce Makefile.in + +## Copyright (C) 2014 Red Hat, Inc. +## +## 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, see +## . + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include +LDADDS = $(STATIC_BINARIES) $(WARN_CFLAGS) $(top_builddir)/src/libvirt.la \ + $(COVERAGE_LDFLAGS) + +noinst_PROGRAMS=domtop + +domtop_SOURCES=domtop.c +domtop_LDFLAGS= +domtop_LDADD= $(LDADDS) diff --git a/examples/domtop/domtop.c b/examples/domtop/domtop.c new file mode 100644 index 0000000000..8118793a06 --- /dev/null +++ b/examples/domtop/domtop.c @@ -0,0 +1,399 @@ +/* + * domtop.c: Demo program showing how to calculate CPU usage + * + * Copyright (C) 2014 Red Hat, Inc. + * + * 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, see + * . + * + * Author: Michal Privoznik + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool debug; +static bool run_top; + +#define ERROR(...) \ +do { \ + fprintf(stderr, "ERROR %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ +} while (0) + +#define DEBUG(...) \ +do { \ + if (!debug) \ + break; \ + fprintf(stderr, "DEBUG %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ +} while (0) + +#define STREQ(a, b) (strcmp(a, b) == 0) + +static void +print_usage(const char *progname) +{ + const char *unified_progname; + + if (!(unified_progname = strrchr(progname, '/'))) + unified_progname = progname; + else + unified_progname++; + + printf("\n%s [options] [domain name]\n\n" + " options:\n" + " -d | --debug enable debug messages\n" + " -h | --help print this help\n" + " -c | --connect=URI hypervisor connection URI\n" + " -D | --delay=X delay between updates in milliseconds " + "(default is 500ms)\n" + "\n" + "Print the cumulative usage of each host CPU.\n" + "Without any domain name specified the list of\n" + "all running domains is printed out.\n", + unified_progname); +} + +static int +parse_argv(int argc, char *argv[], + const char **uri, + const char **dom_name, + unsigned int *milliseconds) +{ + int ret = -1; + int arg; + unsigned long val; + char *p; + struct option opt[] = { + {"debug", no_argument, NULL, 'd'}, + {"help", no_argument, NULL, 'h'}, + {"connect", required_argument, NULL, 'c'}, + {"delay", required_argument, NULL, 'D'}, + {NULL, 0, NULL, 0} + }; + + while ((arg = getopt_long(argc, argv, "+:dhc:D:", opt, NULL)) != -1) { + switch (arg) { + case 'd': + debug = true; + break; + case 'h': + print_usage(argv[0]); + exit(EXIT_SUCCESS); + break; + case 'c': + *uri = optarg; + break; + case 'D': + /* strtoul man page suggests clearing errno prior to call */ + errno = 0; + val = strtoul(optarg, &p, 10); + if (errno || *p || p == optarg) { + ERROR("Invalid number: '%s'", optarg); + exit(EXIT_FAILURE); + } + *milliseconds = val; + if (*milliseconds != val) { + ERROR("Integer overflow: %ld", val); + exit(EXIT_FAILURE); + } + break; + case ':': + ERROR("option '-%c' requires an argument", optopt); + exit(EXIT_FAILURE); + case '?': + if (optopt) + ERROR("unsupported option '-%c'. See --help.", optopt); + else + ERROR("unsupported option '%s'. See --help.", argv[optind - 1]); + exit(EXIT_FAILURE); + default: + ERROR("unknown option"); + exit(EXIT_FAILURE); + } + } + + if (argc > optind) + *dom_name = argv[optind]; + + ret = 0; + cleanup: + return ret; +} + +static int +fetch_domains(virConnectPtr conn) +{ + int num_domains, ret = -1; + virDomainPtr *domains = NULL; + size_t i; + const int list_flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE; + + DEBUG("Fetching list of running domains"); + num_domains = virConnectListAllDomains(conn, &domains, list_flags); + + DEBUG("num_domains=%d", num_domains); + if (num_domains < 0) { + ERROR("Unable to fetch list of running domains"); + goto cleanup; + } + + printf("Running domains:\n"); + printf("----------------\n"); + for (i = 0; i < num_domains; i++) { + virDomainPtr dom = domains[i]; + const char *dom_name = virDomainGetName(dom); + printf("%s\n", dom_name); + virDomainFree(dom); + } + + ret = 0; + cleanup: + free(domains); + return ret; +} + +static void +print_cpu_usage(const char *dom_name, + size_t cpu, + size_t ncpus, + unsigned long long then, + virTypedParameterPtr then_params, + size_t then_nparams, + unsigned long long now, + virTypedParameterPtr now_params, + size_t now_nparams) +{ + size_t i, j, k; + size_t nparams = now_nparams; + bool delim = false; + + if (then_nparams != now_nparams) { + /* this should not happen (TM) */ + ERROR("parameters counts don't match"); + return; + } + + for (i = 0; i < ncpus; i++) { + size_t pos; + double usage; + + /* check if the vCPU is in the maps */ + if (now_params[i * nparams].type == 0 || + then_params[i * then_nparams].type == 0) + continue; + + for (j = 0; j < nparams; j++) { + pos = i * nparams + j; + if (STREQ(then_params[pos].field, VIR_DOMAIN_CPU_STATS_CPUTIME) || + STREQ(then_params[pos].field, VIR_DOMAIN_CPU_STATS_VCPUTIME)) + break; + } + + if (j == nparams) { + ERROR("unable to find %s", VIR_DOMAIN_CPU_STATS_CPUTIME); + return; + } + + DEBUG("now_params=%llu then_params=%llu now=%llu then=%llu", + now_params[pos].value.ul, then_params[pos].value.ul, now, then); + + /* @now_params and @then_params are in nanoseconds, @now and @then are + * in microseconds. In ideal world, we would translate them both into + * the same scale, divide one by another and multiply by factor of 100 + * to get percentage. However, the count of floating point operations + * performed has a bad effect on the precision, so instead of dividing + * @now_params and @then_params by 1000 and then multiplying again by + * 100, we divide only once by 10 and get the same result. */ + usage = (now_params[pos].value.ul - then_params[pos].value.ul) / + (now - then) / 10; + + if (delim) + printf("\t"); + printf("CPU%zu: %.2lf", cpu + i, usage); + delim = true; + } + + printf("\n"); +} + +static void +stop(int sig) +{ + DEBUG("Exiting on signal %d\n", sig); + run_top = false; +} + +static int +do_top(virConnectPtr conn, + const char *dom_name, + unsigned int milliseconds) +{ + int ret = -1; + virDomainPtr dom; + int max_id; + int nparams = 0, then_nparams = 0, now_nparams = 0; + virTypedParameterPtr then_params = NULL, now_params = NULL; + struct sigaction action_stop; + + memset(&action_stop, 0, sizeof(action_stop)); + action_stop.sa_handler = stop; + + /* Lookup the domain */ + if (!(dom = virDomainLookupByName(conn, dom_name))) { + ERROR("Unable to find domain '%s'", dom_name); + goto cleanup; + } + + /* and see how many vCPUs can we fetch stats for */ + if ((max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0)) < 0) { + ERROR("Unable to get cpu stats"); + goto cleanup; + } + + /* how many stats can we get for a vCPU? */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0)) < 0) { + ERROR("Unable to get cpu stats"); + goto cleanup; + } + + if (!(now_params = calloc(nparams * max_id, sizeof(*now_params))) || + !(then_params = calloc(nparams * max_id, sizeof(*then_params)))) { + ERROR("Unable to allocate memory"); + goto cleanup; + } + + sigaction(SIGTERM, &action_stop, NULL); + sigaction(SIGINT, &action_stop, NULL); + + run_top = true; + while (run_top) { + struct timeval then, now; + + /* Get current time */ + if (gettimeofday(&then, NULL) < 0) { + ERROR("unable to get time"); + goto cleanup; + } + + /* And current stats */ + if ((then_nparams = virDomainGetCPUStats(dom, then_params, + nparams, 0, max_id, 0)) < 0) { + ERROR("Unable to get cpu stats"); + goto cleanup; + } + + /* Now sleep some time */ + usleep(milliseconds * 1000); /* usleep expects microseconds */ + + /* And get current time */ + if (gettimeofday(&now, NULL) < 0) { + ERROR("unable to get time"); + goto cleanup; + } + + /* And current stats */ + if ((now_nparams = virDomainGetCPUStats(dom, now_params, + nparams, 0, max_id, 0)) < 0) { + ERROR("Unable to get cpu stats"); + goto cleanup; + } + + print_cpu_usage(dom_name, 0, max_id, + then.tv_sec * 1000000 + then.tv_usec, + then_params, then_nparams, + now.tv_sec * 1000000 + now.tv_usec, + now_params, now_nparams); + + virTypedParamsClear(now_params, now_nparams * max_id); + virTypedParamsClear(then_params, then_nparams * max_id); + } + + ret = 0; + cleanup: + if (max_id > 0) { + if (now_nparams > 0) + virTypedParamsFree(now_params, now_nparams * max_id); + if (then_nparams > 0) + virTypedParamsFree(then_params, then_nparams * max_id); + } + if (dom) + virDomainFree(dom); + return ret; +} + +int +main(int argc, char *argv[]) +{ + int ret = EXIT_FAILURE; + virConnectPtr conn = NULL; + const char *uri = NULL; + const char *dom_name = NULL; + unsigned int milliseconds = 500; /* Sleep this long between two API calls */ + const int connect_flags = 0; /* No connect flags for now */ + + if (parse_argv(argc, argv, &uri, &dom_name, &milliseconds) < 0) + goto cleanup; + + DEBUG("Proceeding with uri=%s dom_name=%s milliseconds=%u", + uri, dom_name, milliseconds); + + if (!(conn = virConnectOpenAuth(uri, + virConnectAuthPtrDefault, + connect_flags))) { + ERROR("Failed to connect to hypervisor"); + goto cleanup; + } + + DEBUG("Successfully connected"); + + if (!dom_name) { + if (fetch_domains(conn) == 0) + ret = EXIT_SUCCESS; + goto cleanup; + } + + if (do_top(conn, dom_name, milliseconds) < 0) + goto cleanup; + + ret = EXIT_SUCCESS; + cleanup: + if (conn) { + int tmp; + tmp = virConnectClose(conn); + if (tmp < 0) { + ERROR("Failed to disconnect from the hypervisor"); + ret = EXIT_FAILURE; + } else if (tmp > 0) { + ERROR("One or more references were leaked after " + "disconnect from the hypervisor"); + ret = EXIT_FAILURE; + } else { + DEBUG("Connection successfully closed"); + } + } + return ret; +} diff --git a/libvirt.spec.in b/libvirt.spec.in index 472fa4bf50..76e57aa934 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1505,7 +1505,7 @@ rm -fr %{buildroot} # on RHEL 5, thus we need to expand it here. make install DESTDIR=%{?buildroot} SYSTEMD_UNIT_DIR=%{_unitdir} -for i in object-events dominfo domsuspend hellolibvirt openauth xml/nwfilter systemtap dommigrate +for i in object-events dominfo domsuspend hellolibvirt openauth xml/nwfilter systemtap dommigrate domtop do (cd examples/$i ; make clean ; rm -rf .deps .libs Makefile Makefile.in) done