[libvirt] [PATCH 0/5 0/1 0/1 V3] Add new public API virDomainGetPcpusUsage() and pcpuinfo command in virsh

"virt-top -1" can call virDomainGetPcpusUsage() periodically and get the CPU activities per CPU. (See the last patch in this series). virsh is also added a pcpuinfo command which calls virDomainGetPcpusUsage(), it gets information about the physical CPUs, such as the usage of CPUs, the current attached vCPUs. # virsh pcpuinfo rhel6 CPU: 0 Curr VCPU: - Usage: 47.3 CPU: 1 Curr VCPU: 1 Usage: 46.8 CPU: 2 Curr VCPU: 0 Usage: 52.7 CPU: 3 Curr VCPU: - Usage: 44.1 Changed from V2: Simple cleanup Add python implementation of virDomainGetPcpusUsage() Acked-by: "Richard W.M. Jones" <rjones@redhat.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> Patch for libvirt(5 patches): daemon/remote.c | 68 ++++++++++++++++++++++++++++ include/libvirt/libvirt.h.in | 5 ++ python/generator.py | 1 + python/libvirt-override-api.xml | 6 +++ python/libvirt-override.c | 33 ++++++++++++++ src/driver.h | 7 +++ src/libvirt.c | 51 +++++++++++++++++++++ src/libvirt_public.syms | 5 ++ src/qemu/qemu.conf | 5 +- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 74 +++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 51 +++++++++++++++++++++ src/remote/remote_protocol.x | 17 +++++++- src/remote_protocol-structs | 13 +++++ src/util/cgroup.c | 7 +++ src/util/cgroup.h | 1 + tools/virsh.c | 93 +++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 5 ++ 18 files changed, 441 insertions(+), 4 deletions(-) Patch for ocaml-libvirt (1 patch): libvirt/libvirt.ml | 1 + libvirt/libvirt.mli | 4 ++++ libvirt/libvirt_c_oneoffs.c | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 0 deletions(-) Patch for virt-top (1 patch): virt-top/virt_top.ml | 75 +++++++++++++++++-------------------------------- 1 files changed, 26 insertions(+), 49 deletions(-) -- 1.7.4.4

Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- include/libvirt/libvirt.h.in | 5 ++++ python/generator.py | 1 + src/driver.h | 7 +++++ src/libvirt.c | 51 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 ++++ 5 files changed, 69 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index e436f3c..167e89f 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -3606,6 +3606,11 @@ int virConnectSetKeepAlive(virConnectPtr conn, int interval, unsigned int count); +int virDomainGetPcpusUsage(virDomainPtr dom, + unsigned long long *usages, + int *nr_usages, + unsigned int flags); + #ifdef __cplusplus } #endif diff --git a/python/generator.py b/python/generator.py index 6fee3a4..0311004 100755 --- a/python/generator.py +++ b/python/generator.py @@ -421,6 +421,7 @@ skip_impl = ( 'virDomainGetBlockIoTune', 'virDomainSetInterfaceParameters', 'virDomainGetInterfaceParameters', + 'virDomainGetPcpusUsage', # not implemented yet ) qemu_skip_impl = ( diff --git a/src/driver.h b/src/driver.h index 24636a4..2a3c46d 100644 --- a/src/driver.h +++ b/src/driver.h @@ -794,6 +794,12 @@ typedef int int *nparams, unsigned int flags); +typedef int + (*virDrvDomainGetPcpusUsage)(virDomainPtr dom, + unsigned long long *usages, + int *nr_usages, + unsigned int flags); + /** * _virDriver: * @@ -962,6 +968,7 @@ struct _virDriver { virDrvNodeSuspendForDuration nodeSuspendForDuration; virDrvDomainSetBlockIoTune domainSetBlockIoTune; virDrvDomainGetBlockIoTune domainGetBlockIoTune; + virDrvDomainGetPcpusUsage domainGetPcpusUsage; }; typedef int diff --git a/src/libvirt.c b/src/libvirt.c index a540424..bd19bf5 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -17882,3 +17882,54 @@ error: virDispatchError(dom->conn); return -1; } + +/** + * virDomainGetPcpusUsage: + * @dom: pointer to domain object + * @usages: returned physical cpu usages + * @nr_usages: length of @usages + * @flags: flags to control the operation + * + * Get the cpu usages for every physical cpu since the domain started (in nanoseconds). + * + * Returns 0 if success, -1 on error + */ +int virDomainGetPcpusUsage(virDomainPtr dom, + unsigned long long *usages, + int *nr_usages, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "usages=%p, nr_usages=%d, flags=%x", + usages, (nr_usages) ? *nr_usages : -1, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN (dom)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + if (nr_usages == NULL && *nr_usages != 0) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + conn = dom->conn; + + if (conn->driver->domainGetPcpusUsage) { + int ret; + ret = conn->driver->domainGetPcpusUsage(dom, usages, nr_usages, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibDomainError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(dom->conn); + return -1; +} diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 4ca7216..15d944c 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -516,4 +516,9 @@ LIBVIRT_0.9.9 { virDomainSetNumaParameters; } LIBVIRT_0.9.8; +LIBVIRT_0.9.10 { + global: + virDomainGetPcpusUsage; +} LIBVIRT_0.9.9; + # .... define new API here using predicted next version number .... -- 1.7.4.4

On 01/18/2012 12:12 AM, Lai Jiangshan wrote:
Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- include/libvirt/libvirt.h.in | 5 ++++ python/generator.py | 1 + src/driver.h | 7 +++++ src/libvirt.c | 51 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 ++++ 5 files changed, 69 insertions(+), 0 deletions(-)
+ +/** + * virDomainGetPcpusUsage: + * @dom: pointer to domain object + * @usages: returned physical cpu usages + * @nr_usages: length of @usages + * @flags: flags to control the operation + * + * Get the cpu usages for every physical cpu since the domain started (in nanoseconds). + * + * Returns 0 if success, -1 on error + */ +int virDomainGetPcpusUsage(virDomainPtr dom, + unsigned long long *usages, + int *nr_usages, + unsigned int flags)
Overall, I think this patch series is headed in the right direction. And the idea of using the cpuacct cgroup for per-cpu usage numbers is a cool hack. However, I think this API is a bit too restricted, and that a few tweaks can make the API more powerful and avoid the need for adding new API in the future (instead, just adding new named parameter types to this API). First, we don't use the term Pcpu anywhere else in libvirt. I'm thinking it would be better to name this virDomainGetCPUStats, to match our existing virNodeGetCPUStats. (Too bad that we weren't consistent between CPU vs. Vcpu in capitalization.) Also, by naming it just CPUStats, rather than PcpusUsate, I think we can open the door to further stats - for example, I can envision that we might want to get stats not only on how much cpu accounting has been attributed to the host qemu process, but we can also communicate to a guest agent and get the guest's own view of its cpu usage; the difference between the two values would thus be the overhead consumed by the hypervisor in presenting an environment to the guest. Of course, we don't have to integrate with a guest agent now, but we should be designing the API with that in mind. Next, I notice that right now, cpuacct provides the following properties: cpuacct.stat (gives elapsed user and system times for the cgroup, similar to 2 of the 3 cumulative accounting values given by the time(1) utility; looks like the unit is roughly in microseconds), cpuacct.usage_percpu (gives just nanosecond usage, without division between user or system), and cpuacct.usage (the sum of cpuacct.usage). And it is conceivable that future kernels will add even more stats under the cpuacct umbrella. But your patch only uses cpuacct.usage_percpu. Why should we be hard-coding things to just one stat? Additionally, I'm comparing this with existing APIs, for things we have learned in the past about querying statistics. virNodeGetCPUStats is rather limited in that it can only return the stats of one processor at a time; but has the benefit that the magic VIR_NODE_CPU_STATS_ALL_CPUS (aka -1) can provide the sum across all processors into that one stat. So a machine with 8 processors requires 8 calls to the API, but you can also get the summary in one blow without doing any additions yourself. It may also be the case that some stats are available only on the entire process, rather than per-cpu. virNodeGetCPUStats also has the drawback that it can only return unsigned long long values, rather than using virTypedParameter; but at least it has the benefit that it can return an arbitrary number of name/value stat tuples. Meanwhile, virNodeGetCellsFreeMemory allows the user to request a start and stop limit, and thus grab multiple stats in one call, but doesn't allow passing -1 to get a summary (that is, with 8 NUMA cells you can make just 1 call, but have to do the addition yourself). It has a drawback that it can only return one specific stat; it cannot be extended to other statistics. And virDomainGetVcpuPinInfo is an example of a function that returns a 2-dimensional array through a single output pointer, by passing in two separate length parameters; but again with a limited output of only one specific stat. Borrowing from these ideas, I think your API should return an array of virTypedParameter per cpu, so that we can add new stats without needing new API. It should also allow the ability to return a summary over all cpus, instead of a 2-dimensional list of statistics per cpu. I'm thinking something like the following: /** * virDomainGetCPUStats: * @dom: domain to query * @params: array to populate on output * @nparams: number of parameters per cpu * @start_cpu: which cpu to start with, or -1 for summary * @ncpus: how many cpus to query * @flags: unused for now * * Get statistics relating to CPU usage attributable to a single * domain (in contrast to the statistics returned by * virNodeGetCPUStats() for all processes on the host). @dom * must be running (an inactive domain has no attributable cpu * usage). On input, @params must contain at least @nparams * @ncpus * entries, allocated by the caller. * * If @start_cpu is -1, then @ncpus must be 1, and the returned * results reflect the statistics attributable to the entire * domain (such as user and system time for the process as a * whole). Otherwise, @start_cpu represents which cpu to start * with, and @ncpus represents how many consecutive processors to * query, with statistics attributable per processor (such as * per-cpu usage). * * As a special case, if @params is NULL and @nparams is 0, then * @ncpus must be 1, and the return value will be how many * statistics are available for the given @start_cpu. This number * may be different for @start_cpu of -1 than for any non-negative * value, but will be the same for all non-negative @start_cpu. * The combination of @start_cpu and @ncpus must not exceed the * number of available cpus to query. * * For now, @flags is unused, and the statistics all relate to the * usage from the host perspective; the number of cpus available to * query can be determined by the cpus member of the result of * virNodeGetInfo(), even if the domain has had vcpus pinned to only * use a subset of overall host cpus. It is possible that a future * version will support a flag that queries the cpu usage from the * guest's perspective, using the number of vcpus available to the * guest, found by virDomainGetVcpusFlags(). An individual guest * vcpu cannot be reliably mapped back to a specific host cpu unless * a single-processor vcpu pinning was used, but when @start_cpu is -1, * any difference in usage between a host and guest perspective would * serve as a measure of hypervisor overhead. * * Returns -1 on failure, or the number of statistics that were * populated per cpu on success (this will be less than the total * number of populated @params, unless @ncpus was 1; and may be * less than @nparams). The populated parameters start at each * stride of @nparams; any unpopulated parameters will be zeroed * on success. The caller is responsible for freeing any returned * string parameters. */ int virDomainGetCPUStats(virDomainPtr dom, virTypedParamterPtr params, int nparams, int start_cpu, int end_cpu, unsigned int flags); where you might have the following behavior on a 2-cpu machine where the guest has 1 vcpu allocated: virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0) => 2 virDomainGetCPUStats(dom, params, 2, -1, 1, 0) => 2, with params[0] being <"user",ull,6507945> and params[1] being <"system",ull,1817278> - that is, the times found in cpuacct.stat virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0) => 1 virDomainGetCPUStats(dom, params, 1, 0, 3, 0) => -1 (end cpu too large) virDomainGetCPUStats(dom, params, 2, 0, 2, 0) => 1, with params[0] being <"usage",ull,45211932635804>, params[1] being zeroed, params[2] being <"usage",ull,44711067947567>, and params[3] being zeroed and in the future, if we add guest agent interaction via the flag, virDomainGetCPUStats(dom, NULL, 0, 0, 1, GUEST) => 1 virDomainGetCPUStats(dom, params, 1, 0, 2, GUEST) => -1 (end cpu too large) virDomainGetCPUStats(dom, params, 1, 0, 1, GUEST) => 1, with params[0] being <"guestusage",ull,35211932635804> from a query of the guest agent One particular implementation point will be that the RPC call should not transmit zeroed entries. That is, in my example where I passed in an over-allocated params, the rpc call would compress things so that the on-the-wire format should transmit just return_value*ncpus blobs, and deserialization of the wire format would then expand them back into the strides of the user's params array. Another point is that you must provide reasonable bounds for both nparams (16 is probably okay, compared to our other *_PARAMETERS_MAX values in remote_protocol.x) and ncpus. Others in this thread suggested a minimum bound of 4096 for ncpus, but that adds up fast when you have 92-byte virTypedParameters (the on-the-wire format transmits a nul-terminated string rather than all 80 bytes of any virTypedParameter.name field, but that still means on-the-wire can easily be sending 32 bytes per parameter). I'd suggest 128 as the cap for ncpus (16 * 32 * 128 is 64k, which is approaching the rpc bounds we have for a single string anyway), as well as documentation that a user with more than 128 cpus would have to break things into chunks to avoid exceeding RPC limitations. Putting limits on both nparams and ncpus also avoids any potential issues with multiplication overflow causing what looks like a small product after wraparound. If others like my thoughts, can you respin your patch series to adjust your API accordingly? -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Wed, Jan 18, 2012 at 05:07:16PM -0700, Eric Blake wrote:
If others like my thoughts, can you respin your patch series to adjust your API accordingly?
Sounds good to me. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org

Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- daemon/remote.c | 68 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 51 +++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 17 ++++++++++- src/remote_protocol-structs | 13 ++++++++ 4 files changed, 148 insertions(+), 1 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index a28a754..9b0206f 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -2084,6 +2084,74 @@ cleanup: return rv; } +static int +remoteDispatchDomainGetPcpusUsage(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr hdr ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_get_pcpus_usage_args *args, + remote_domain_get_pcpus_usage_ret *ret) +{ + int i; + virDomainPtr dom = NULL; + int rv = -1; + unsigned long long *usages; + int nr_usages = args->nr_usages; + struct daemonClientPrivate *priv = + virNetServerClientGetPrivateData(client); + + if (!priv->conn) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + if (nr_usages > REMOTE_DOMAIN_PCPUS_USAGE_PARAMETERS_MAX) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nr_usages too large")); + goto cleanup; + } + + if (VIR_ALLOC_N(usages, nr_usages) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (!(dom = get_nonnull_domain(priv->conn, args->dom))) + goto cleanup; + + if (virDomainGetPcpusUsage(dom, usages, &nr_usages, args->flags) < 0) + goto cleanup; + + ret->nr_usages = nr_usages; + + /* + * In this case, we need to send back the number of parameters + * supported + */ + if (args->nr_usages == 0) { + goto success; + } + + ret->usages.usages_len = MIN(args->nr_usages, nr_usages); + if (VIR_ALLOC_N(ret->usages.usages_val, ret->usages.usages_len) < 0) { + virReportOOMError(); + goto cleanup; + } + + for (i = 0; i < ret->usages.usages_len; i++) { + ret->usages.usages_val[i] = usages[i]; + } + +success: + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + VIR_FREE(usages); + if (dom) + virDomainFree(dom); + return rv; +} #ifdef HAVE_SASL /* diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index e28840b..9e2f931 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -2305,6 +2305,56 @@ done: return rv; } +static int remoteDomainGetPcpusUsage(virDomainPtr domain, + unsigned long long *usages, + int *nr_usages, + unsigned int flags) +{ + int i; + int rv = -1; + remote_domain_get_pcpus_usage_args args; + remote_domain_get_pcpus_usage_ret ret; + struct private_data *priv = domain->conn->privateData; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, domain); + args.nr_usages = *nr_usages; + args.flags = flags; + + memset(&ret, 0, sizeof(ret)); + + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_PCPUS_USAGE, + (xdrproc_t) xdr_remote_domain_get_pcpus_usage_args, + (char *) &args, + (xdrproc_t) xdr_remote_domain_get_pcpus_usage_ret, + (char *) &ret) == -1) { + goto done; + } + + /* Handle the case when the caller does not know the number of parameters + * and is asking for the number of parameters supported + */ + if (*nr_usages == 0) { + *nr_usages = ret.nr_usages; + goto cleanup; + } + + for (i = 0; i < MIN(*nr_usages, ret.usages.usages_len); i++) { + usages[i] = ret.usages.usages_val[i]; + } + *nr_usages = ret.nr_usages; + + rv = 0; + +cleanup: + xdr_free ((xdrproc_t) xdr_remote_domain_get_pcpus_usage_ret, + (char *) &ret); +done: + remoteDriverUnlock(priv); + return rv; +} + /*----------------------------------------------------------------------*/ static virDrvOpenStatus ATTRIBUTE_NONNULL (1) @@ -4750,6 +4800,7 @@ static virDriver remote_driver = { .domainGetBlockIoTune = remoteDomainGetBlockIoTune, /* 0.9.8 */ .domainSetNumaParameters = remoteDomainSetNumaParameters, /* 0.9.9 */ .domainGetNumaParameters = remoteDomainGetNumaParameters, /* 0.9.9 */ + .domainGetPcpusUsage = remoteDomainGetPcpusUsage, /* 0.9.10 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index ca739ff..3c1ab41 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -131,6 +131,9 @@ const REMOTE_DOMAIN_BLOCK_IO_TUNE_PARAMETERS_MAX = 16; /* Upper limit on list of numa parameters. */ const REMOTE_DOMAIN_NUMA_PARAMETERS_MAX = 16; +/* Upper limit on list of physical cpu parameters. */ +const REMOTE_DOMAIN_PCPUS_USAGE_PARAMETERS_MAX = 32; + /* Upper limit on list of node cpu stats. */ const REMOTE_NODE_CPU_STATS_MAX = 16; @@ -1148,6 +1151,17 @@ struct remote_domain_get_block_io_tune_ret { int nparams; }; +struct remote_domain_get_pcpus_usage_args { + remote_nonnull_domain dom; + int nr_usages; + unsigned int flags; +}; + +struct remote_domain_get_pcpus_usage_ret { + uint64_t usages<REMOTE_DOMAIN_PCPUS_USAGE_PARAMETERS_MAX>; + int nr_usages; +}; + /* Network calls: */ struct remote_num_of_networks_ret { @@ -2653,7 +2667,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SET_NUMA_PARAMETERS = 254, /* autogen autogen */ REMOTE_PROC_DOMAIN_GET_NUMA_PARAMETERS = 255, /* skipgen skipgen */ REMOTE_PROC_DOMAIN_SET_INTERFACE_PARAMETERS = 256, /* autogen autogen */ - REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257 /* skipgen skipgen */ + REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, /* skipgen skipgen */ + REMOTE_PROC_DOMAIN_GET_PCPUS_USAGE = 258 /* skipgen skipgen */ /* * Notice how the entries are grouped in sets of 10 ? diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 2758315..4fd7b6d 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -1832,6 +1832,18 @@ struct remote_node_suspend_for_duration_args { uint64_t duration; u_int flags; }; +struct remote_domain_get_pcpus_usage_args { + remote_nonnull_domain dom; + int nr_usages; + u_int flags; +}; +struct remote_domain_get_pcpus_usage_ret { + struct { + u_int usages_len; + unsigned long long *usages_val; + } usages; + int nr_usages; +}; enum remote_procedure { REMOTE_PROC_OPEN = 1, REMOTE_PROC_CLOSE = 2, @@ -2090,4 +2102,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_GET_NUMA_PARAMETERS = 255, REMOTE_PROC_DOMAIN_SET_INTERFACE_PARAMETERS = 256, REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, + REMOTE_PROC_DOMAIN_GET_PCPU_USAGE = 258, }; -- 1.7.4.4

There is a typo in the commit message: mplement -> implement On Wed, Jan 18, 2012 at 03:12:09PM +0800, Lai Jiangshan wrote:
+/* Upper limit on list of physical cpu parameters. */ +const REMOTE_DOMAIN_PCPUS_USAGE_PARAMETERS_MAX = 32;
This number is bigger than the previous version, but is there some reason not to make it very big (eg. 4096)? It's not completely unknown to have machines with more than 32 physical CPUs. 8*4096 bytes is fine for the libvirt protocol. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw

On Wed, Jan 18, 2012 at 02:53:50PM +0000, Richard W.M. Jones wrote:
There is a typo in the commit message: mplement -> implement
On Wed, Jan 18, 2012 at 03:12:09PM +0800, Lai Jiangshan wrote:
+/* Upper limit on list of physical cpu parameters. */ +const REMOTE_DOMAIN_PCPUS_USAGE_PARAMETERS_MAX = 32;
This number is bigger than the previous version, but is there some reason not to make it very big (eg. 4096)? It's not completely unknown to have machines with more than 32 physical CPUs. 8*4096 bytes is fine for the libvirt protocol.
Agreed, 4096 is the absolute *minimum* we should use. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/qemu/qemu.conf | 5 ++- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/cgroup.c | 7 ++++ src/util/cgroup.h | 1 + 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 4ec5e6c..5f75b3e 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -158,18 +158,19 @@ # - 'memory' - use for memory tunables # - 'blkio' - use for block devices I/O tunables # - 'cpuset' - use for CPUs and memory nodes +# - 'cpuacct' - use for CPUs' account # # NB, even if configured here, they won't be used unless # the administrator has mounted cgroups, e.g.: # # mkdir /dev/cgroup -# mount -t cgroup -o devices,cpu,memory,blkio,cpuset none /dev/cgroup +# mount -t cgroup -o devices,cpu,memory,blkio,cpuset,cpuacct none /dev/cgroup # # They can be mounted anywhere, and different controllers # can be mounted in different locations. libvirt will detect # where they are located. # -# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset" ] +# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset", "cpuacct" ] # This is the basic set of devices allowed / required by # all virtual machines. diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index bc0a646..4775638 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -307,7 +307,8 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, (1 << VIR_CGROUP_CONTROLLER_DEVICES) | (1 << VIR_CGROUP_CONTROLLER_MEMORY) | (1 << VIR_CGROUP_CONTROLLER_BLKIO) | - (1 << VIR_CGROUP_CONTROLLER_CPUSET); + (1 << VIR_CGROUP_CONTROLLER_CPUSET) | + (1 << VIR_CGROUP_CONTROLLER_CPUACCT); } for (i = 0 ; i < VIR_CGROUP_CONTROLLER_LAST ; i++) { if (driver->cgroupControllers & (1 << i)) { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 712f1fc..e90e185 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -11180,6 +11180,79 @@ cleanup: return ret; } +static int +qemuGetPcpusUsage(virDomainPtr dom, + unsigned long long *usages, + int *nr_usages, + unsigned int flags) +{ + struct qemud_driver *driver = dom->conn->privateData; + virCgroupPtr group = NULL; + virDomainObjPtr vm = NULL; + char *pos, *raw; + unsigned long long val; + int nr_cpus = 0; + int ret = -1; + int rc; + bool isActive; + + virCheckFlags(0, -1); + + qemuDriverLock(driver); + + vm = virDomainFindByUUID(&driver->domains, dom->uuid); + + if (vm == NULL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("No such domain %s"), dom->uuid); + goto cleanup; + } + + isActive = virDomainObjIsActive(vm); + + if (!isActive) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + if (!qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_CPUACCT)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cgroup CPUACCT controller is not mounted")); + goto cleanup; + } + + if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) != 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find cgroup for domain %s"), vm->def->name); + goto cleanup; + } + + rc = virCgroupGetCpuacctPcpusUsage(group, &raw); + if (rc != 0) { + virReportSystemError(-rc, "%s", _("unable to get cpu account")); + goto cleanup; + } + + pos = raw; + while (virStrToLong_ull(pos, &pos, 10, &val) >= 0) { + if (nr_cpus < *nr_usages) { + usages[nr_cpus] = val; + } + nr_cpus++; + } + + VIR_FREE(raw); + *nr_usages = nr_cpus; + ret = 0; + +cleanup: + virCgroupFree(&group); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} static virDomainPtr qemuDomainAttach(virConnectPtr conn, unsigned int pid, @@ -11983,6 +12056,7 @@ static virDriver qemuDriver = { .domainGetNumaParameters = qemuDomainGetNumaParameters, /* 0.9.9 */ .domainGetInterfaceParameters = qemuDomainGetInterfaceParameters, /* 0.9.9 */ .domainSetInterfaceParameters = qemuDomainSetInterfaceParameters, /* 0.9.9 */ + .domainGetPcpusUsage = qemuGetPcpusUsage, /* 0.9.10 */ }; diff --git a/src/util/cgroup.c b/src/util/cgroup.c index 25f2691..114eeb5 100644 --- a/src/util/cgroup.c +++ b/src/util/cgroup.c @@ -1554,6 +1554,13 @@ int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage) "cpuacct.usage", usage); } +int virCgroupGetCpuacctPcpusUsage(virCgroupPtr group, char **usage) +{ + return virCgroupGetValueStr(group, + VIR_CGROUP_CONTROLLER_CPUACCT, + "cpuacct.usage_percpu", usage); +} + int virCgroupSetFreezerState(virCgroupPtr group, const char *state) { return virCgroupSetValueStr(group, diff --git a/src/util/cgroup.h b/src/util/cgroup.h index 8d75735..f3c4b7d 100644 --- a/src/util/cgroup.h +++ b/src/util/cgroup.h @@ -115,6 +115,7 @@ int virCgroupSetCpuCfsQuota(virCgroupPtr group, long long cfs_quota); int virCgroupGetCpuCfsQuota(virCgroupPtr group, long long *cfs_quota); int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage); +int virCgroupGetCpuacctPcpusUsage(virCgroupPtr group, char **usage); int virCgroupSetFreezerState(virCgroupPtr group, const char *state); int virCgroupGetFreezerState(virCgroupPtr group, char **state); -- 1.7.4.4

This command gets information about the physical CPUs. Example: # virsh pcpuinfo rhel6 CPU: 0 Curr VCPU: - Usage: 47.3 CPU: 1 Curr VCPU: 1 Usage: 46.8 CPU: 2 Curr VCPU: 0 Usage: 52.7 CPU: 3 Curr VCPU: - Usage: 44.1 Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- tools/virsh.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 5 +++ 2 files changed, 98 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index c511e2a..e8b9221 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -4702,6 +4702,98 @@ cmdVcpuinfo(vshControl *ctl, const vshCmd *cmd) } /* + * "pcpuinfo" command + */ +static const vshCmdInfo info_pcpuinfo[] = { + {"help", N_("detailed domain pcpu information")}, + {"desc", N_("Returns basic information about the domain's physical CPUs.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_pcpuinfo[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdPcpuinfo(vshControl *ctl, const vshCmd *cmd) +{ + virDomainInfo info; + virDomainPtr dom; + virNodeInfo nodeinfo; + virVcpuInfoPtr cpuinfo; + unsigned char *cpumaps; + int ncpus, maxcpu; + size_t cpumaplen; + bool ret = true; + int n, m; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (virNodeGetInfo(ctl->conn, &nodeinfo) != 0) { + virDomainFree(dom); + return false; + } + + if (virDomainGetInfo(dom, &info) != 0) { + virDomainFree(dom); + return false; + } + + cpuinfo = vshMalloc(ctl, sizeof(virVcpuInfo)*info.nrVirtCpu); + maxcpu = VIR_NODEINFO_MAXCPUS(nodeinfo); + cpumaplen = VIR_CPU_MAPLEN(maxcpu); + cpumaps = vshMalloc(ctl, info.nrVirtCpu * cpumaplen); + + if ((ncpus = virDomainGetVcpus(dom, + cpuinfo, info.nrVirtCpu, + cpumaps, cpumaplen)) >= 0) { + unsigned long long *usages; + int nr_usages = maxcpu; + + if (VIR_ALLOC_N(usages, nr_usages) < 0) { + virReportOOMError(); + goto fail; + } + + if (virDomainGetPcpusUsage(dom, usages, &nr_usages, 0) < 0) { + VIR_FREE(usages); + goto fail; + } + + for (n = 0; n < MIN(maxcpu, nr_usages); n++) { + vshPrint(ctl, "%-15s %d\n", _("CPU:"), n); + for (m = 0; m < ncpus; m++) { + if (cpuinfo[m].cpu == n) { + vshPrint(ctl, "%-15s %d\n", _("Curr VCPU:"), m); + break; + } + } + if (m == ncpus) { + vshPrint(ctl, "%-15s %s\n", _("Curr VCPU:"), _("-")); + } + vshPrint(ctl, "%-15s %.1lf\n\n", _("Usage:"), + usages[n] / 1000000000.0); + } + VIR_FREE(usages); + goto cleanup; + } + +fail: + ret = false; + +cleanup: + VIR_FREE(cpumaps); + VIR_FREE(cpuinfo); + virDomainFree(dom); + return ret; +} + +/* * "vcpupin" command */ static const vshCmdInfo info_vcpupin[] = { @@ -15952,6 +16044,7 @@ static const vshCmdDef domManagementCmds[] = { {"migrate-getspeed", cmdMigrateGetMaxSpeed, opts_migrate_getspeed, info_migrate_getspeed, 0}, {"numatune", cmdNumatune, opts_numatune, info_numatune, 0}, + {"pcpuinfo", cmdPcpuinfo, opts_pcpuinfo, info_pcpuinfo, 0}, {"reboot", cmdReboot, opts_reboot, info_reboot, 0}, {"reset", cmdReset, opts_reset, info_reset, 0}, {"restore", cmdRestore, opts_restore, info_restore, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index c88395b..8d9cceb 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1296,6 +1296,11 @@ Thus, this command always takes exactly zero or two flags. Returns basic information about the domain virtual CPUs, like the number of vCPUs, the running time, the affinity to physical processors. +=item B<pcpuinfo> I<domain-id> + +Returns information about the physical CPUs of the domain, like the usage of +CPUs, the current attached vCPUs. + =item B<vcpupin> I<domain-id> [I<vcpu>] [I<cpulist>] [[I<--live>] [I<--config>] | [I<--current>]] -- 1.7.4.4

Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- python/generator.py | 2 +- python/libvirt-override-api.xml | 6 ++++++ python/libvirt-override.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletions(-) diff --git a/python/generator.py b/python/generator.py index 0311004..cf75c44 100755 --- a/python/generator.py +++ b/python/generator.py @@ -421,7 +421,7 @@ skip_impl = ( 'virDomainGetBlockIoTune', 'virDomainSetInterfaceParameters', 'virDomainGetInterfaceParameters', - 'virDomainGetPcpusUsage', # not implemented yet + 'virDomainGetPcpusUsage', ) qemu_skip_impl = ( diff --git a/python/libvirt-override-api.xml b/python/libvirt-override-api.xml index 704fee9..69bb159 100644 --- a/python/libvirt-override-api.xml +++ b/python/libvirt-override-api.xml @@ -421,5 +421,11 @@ <arg name='flags' type='unsigned int' info='an OR'ed set of virDomainMemoryFlags'/> <return type='char *' info='the returned buffer or None in case of error'/> </function> + <function name='virDomainGetPcpusUsage' file='python'> + <info>Get the cpu usages for every physical cpu since the domain started (in nanoseconds).</info> + <arg name='dom' type='virDomainPtr' info='pointer to the domain'/> + <arg name='flags' type='unsigned int' info='an OR'ed set of virDomainMemoryFlags'/> + <return type='unsigned long long *' info='the tuple of the cpu usages'/> + </function> </symbols> </api> diff --git a/python/libvirt-override.c b/python/libvirt-override.c index d2aad0f..ad4646d 100644 --- a/python/libvirt-override.c +++ b/python/libvirt-override.c @@ -5108,6 +5108,38 @@ cleanup: return py_retval; } +static PyObject * +libvirt_virDomainGetPcpusUsage(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) { + PyObject *py_retval = NULL; + int c_retval; + virDomainPtr domain; + PyObject *pyobj_domain; + unsigned long long usages[32]; + int i, nr_usages = sizeof(usages) / sizeof(usages[0]); + unsigned int flags; + + if (!PyArg_ParseTuple(args, (char *)"Oi:virDomainGetPcpusUsage", &pyobj_domain, + &flags)) + return NULL; + + domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain); + + LIBVIRT_BEGIN_ALLOW_THREADS; + c_retval = virDomainGetPcpusUsage(domain, usages, &nr_usages, flags); + LIBVIRT_END_ALLOW_THREADS; + + if (c_retval < 0) + return VIR_PY_NONE; + + if ((py_retval = PyTuple_New(nr_usages)) == NULL) + return VIR_PY_NONE; + for (i = 0; i < nr_usages; i++) + PyTuple_SetItem(py_retval, i, PyLong_FromLongLong(usages[i])); + + return py_retval; +} + /************************************************************************ * * * The registration stuff * @@ -5206,6 +5238,7 @@ static PyMethodDef libvirtMethods[] = { {(char *) "virDomainMigrateGetMaxSpeed", libvirt_virDomainMigrateGetMaxSpeed, METH_VARARGS, NULL}, {(char *) "virDomainBlockPeek", libvirt_virDomainBlockPeek, METH_VARARGS, NULL}, {(char *) "virDomainMemoryPeek", libvirt_virDomainMemoryPeek, METH_VARARGS, NULL}, + {(char *) "virDomainGetPcpusUsage", libvirt_virDomainGetPcpusUsage, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; -- 1.7.4.4

Acked-by: "Richard W.M. Jones" <rjones@redhat.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- libvirt/libvirt.ml | 1 + libvirt/libvirt.mli | 4 ++++ libvirt/libvirt_c_oneoffs.c | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 0 deletions(-) diff --git a/libvirt/libvirt.ml b/libvirt/libvirt.ml index fc29264..a8720a9 100644 --- a/libvirt/libvirt.ml +++ b/libvirt/libvirt.ml @@ -419,6 +419,7 @@ struct external set_vcpus : [>`W] t -> int -> unit = "ocaml_libvirt_domain_set_vcpus" external pin_vcpu : [>`W] t -> int -> string -> unit = "ocaml_libvirt_domain_pin_vcpu" external get_vcpus : [>`R] t -> int -> int -> int * vcpu_info array * string = "ocaml_libvirt_domain_get_vcpus" + external get_pcpu_usages : [>`R] t -> int -> int64 array = "ocaml_libvirt_domain_get_pcpu_usages" external get_max_vcpus : [>`R] t -> int = "ocaml_libvirt_domain_get_max_vcpus" external attach_device : [>`W] t -> xml -> unit = "ocaml_libvirt_domain_attach_device" external detach_device : [>`W] t -> xml -> unit = "ocaml_libvirt_domain_detach_device" diff --git a/libvirt/libvirt.mli b/libvirt/libvirt.mli index 7bda889..63bf830 100644 --- a/libvirt/libvirt.mli +++ b/libvirt/libvirt.mli @@ -586,6 +586,10 @@ sig for a domain. See the libvirt documentation for details of the array and bitmap returned from this function. *) + val get_pcpu_usages : [>`R] t -> int -> int64 array + (** [get_pcpu_usages dom nr_pcpu] returns the physical CPU usages + for a domain. See the libvirt documentation for details. + *) val get_max_vcpus : [>`R] t -> int (** Returns the maximum number of vCPUs supported for this domain. *) val attach_device : [>`W] t -> xml -> unit diff --git a/libvirt/libvirt_c_oneoffs.c b/libvirt/libvirt_c_oneoffs.c index d87dd21..68d5ecc 100644 --- a/libvirt/libvirt_c_oneoffs.c +++ b/libvirt/libvirt_c_oneoffs.c @@ -604,6 +604,31 @@ ocaml_libvirt_domain_get_vcpus (value domv, value maxinfov, value maplenv) CAMLreturn (rv); } +CAMLprim value +ocaml_libvirt_domain_get_pcpu_usages (value domv, value nr_pcpusv) +{ + CAMLparam2 (domv, nr_pcpusv); + CAMLlocal1 (usagev); + virDomainPtr dom = Domain_val (domv); + virConnectPtr conn = Connect_domv (domv); + int nr_pcpus = Int_val (nr_pcpusv); + unsigned long long pcpu_usages[nr_pcpus]; + int r, i; + + memset (pcpu_usages, 0, sizeof(pcpu_usages[0]) * nr_pcpus); + + NONBLOCKING (r = virDomainGetPcpusUsage (dom, pcpu_usages, &nr_pcpus, 0)); + CHECK_ERROR (r == -1, conn, "virDomainGetPcpusUsage"); + + /* Copy the pcpu_usages. */ + usagev = caml_alloc (nr_pcpus, 0); + for (i = 0; i < nr_pcpus; ++i) { + Store_field (usagev, i, caml_copy_int64 ((int64_t)pcpu_usages[i])); + } + + CAMLreturn (usagev); +} + #ifdef HAVE_WEAK_SYMBOLS #ifdef HAVE_VIRDOMAINMIGRATE extern virDomainPtr virDomainMigrate (virDomainPtr domain, virConnectPtr dconn, -- 1.7.4.4

Old "virt-top -1" is not correct, its output is generated by guess: use average usage for pinned physical CPUs. example(old "virt-top -1"): PHYCPU %CPU rhel6 Windows 0 0.6 0.1= 0.5= 1 0.6 0.1= 0.5=# 2 0.6 0.1= 0.5= 3 0.6 0.1=# 0.5= The output almost makes no sense(all the value are just average, not real). This is new implement, it use cpuacct cgroup to gain *real* physical usages via cpuacct cgroup by virDomainGetPcpusUsage() API. new result: PHYCPU %CPU rhel6 Windows 0 3.3 2.9 0.3 1 1.7 1.1 0.6 2 3.5 1.8 1.6 3 3.4 1.6 1.8 PHYCPU %CPU rhel6 Windows 0 1.2 0.8 0.4 1 1.6 1.6 2 2.2 1.7 0.5 3 3.0 2.5 0.5 Note: average flag(=) is dropped, there is not average value in here. Note: running flag(#) is dropped, because if the value is not empty, it means the guest was once running in the physical CPU in this period between updates. Acked-by: "Richard W.M. Jones" <rjones@redhat.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- virt-top/virt_top.ml | 75 +++++++++++++++++-------------------------------- 1 files changed, 26 insertions(+), 49 deletions(-) diff --git a/virt-top/virt_top.ml b/virt-top/virt_top.ml index ef5ac67..2556b77 100644 --- a/virt-top/virt_top.ml +++ b/virt-top/virt_top.ml @@ -446,14 +446,14 @@ let collect, clear_pcpu_display_data = let last_info = Hashtbl.create 13 in let last_time = ref (Unix.gettimeofday ()) in - (* Save vcpuinfo structures across redraws too (only for pCPU display). *) - let last_vcpu_info = Hashtbl.create 13 in + (* Save pcpu_usages structures across redraws too (only for pCPU display). *) + let last_pcpu_usages = Hashtbl.create 13 in let clear_pcpu_display_data () = - (* Clear out vcpu_info used by PCPUDisplay display_mode + (* Clear out pcpu_info used by PCPUDisplay display_mode * when we switch back to TaskDisplay mode. *) - Hashtbl.clear last_vcpu_info + Hashtbl.clear last_pcpu_usages in let collect (conn, _, _, _, _, node_info, _, _) = @@ -652,22 +652,23 @@ let collect, clear_pcpu_display_data = (try let domid = rd.rd_domid in let maplen = C.cpumaplen nr_pcpus in + let pcpu_usages = D.get_pcpu_usages rd.rd_dom nr_pcpus in let maxinfo = rd.rd_info.D.nr_virt_cpu in let nr_vcpus, vcpu_infos, cpumaps = D.get_vcpus rd.rd_dom maxinfo maplen in - (* Got previous vcpu_infos for this domain? *) - let prev_vcpu_infos = - try Some (Hashtbl.find last_vcpu_info domid) + (* Got previous pcpu_usages for this domain? *) + let prev_pcpu_usages = + try Some (Hashtbl.find last_pcpu_usages domid) with Not_found -> None in - (* Update last_vcpu_info. *) - Hashtbl.replace last_vcpu_info domid vcpu_infos; - - (match prev_vcpu_infos with - | Some prev_vcpu_infos - when Array.length prev_vcpu_infos = Array.length vcpu_infos -> - Some (domid, name, nr_vcpus, vcpu_infos, prev_vcpu_infos, - cpumaps, maplen) + (* Update last_pcpu_usages. *) + Hashtbl.replace last_pcpu_usages domid pcpu_usages; + + (match prev_pcpu_usages with + | Some prev_pcpu_usages + when Array.length prev_pcpu_usages = Array.length pcpu_usages -> + Some (domid, name, nr_vcpus, vcpu_infos, pcpu_usages, + prev_pcpu_usages, cpumaps, maplen) | _ -> None (* ignore missing / unequal length prev_vcpu_infos *) ); with @@ -680,37 +681,15 @@ let collect, clear_pcpu_display_data = (* Rearrange the data into a matrix. Major axis (down) is * pCPUs. Minor axis (right) is domains. At each node we store: * cpu_time (on this pCPU only, nanosecs), - * average? (if set, then cpu_time is an average because the - * vCPU is pinned to more than one pCPU) - * running? (if set, we were instantaneously running on this pCPU) *) - let empty_node = (0L, false, false) in - let pcpus = Array.make_matrix nr_pcpus nr_doms empty_node in + let pcpus = Array.make_matrix nr_pcpus nr_doms 0L in List.iteri ( - fun di (domid, name, nr_vcpus, vcpu_infos, prev_vcpu_infos, - cpumaps, maplen) -> + fun di (domid, name, nr_vcpus, vcpu_infos, pcpu_usages, + prev_pcpu_usages, cpumaps, maplen) -> (* Which pCPUs can this dom run on? *) - for v = 0 to nr_vcpus-1 do - let pcpu = vcpu_infos.(v).D.cpu in (* instantaneous pCPU *) - let nr_poss_pcpus = ref 0 in (* how many pcpus can it run on? *) - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - incr nr_poss_pcpus - done; - let nr_poss_pcpus = Int64.of_int !nr_poss_pcpus in - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - let vcpu_time_on_pcpu = - vcpu_infos.(v).D.vcpu_time - -^ prev_vcpu_infos.(v).D.vcpu_time in - let vcpu_time_on_pcpu = - vcpu_time_on_pcpu /^ nr_poss_pcpus in - pcpus.(p).(di) <- - (vcpu_time_on_pcpu, nr_poss_pcpus > 1L, p = pcpu) - done + for p = 0 to Array.length pcpu_usages - 1 do + pcpus.(p).(di) <- (pcpu_usages.(p) -^ prev_pcpu_usages.(p)) done ) doms; @@ -719,7 +698,7 @@ let collect, clear_pcpu_display_data = fun row -> let cpu_time = ref 0L in for di = 0 to Array.length row-1 do - let t, _, _ = row.(di) in + let t = row.(di) in cpu_time := !cpu_time +^ t done; Int64.to_float !cpu_time @@ -938,7 +917,7 @@ let redraw = let dom_names = String.concat "" ( List.map ( - fun (_, name, _, _, _, _, _) -> + fun (_, name, _, _, _, _, _, _) -> let len = String.length name in let width = max (len+1) 7 in pad width name @@ -957,8 +936,8 @@ let redraw = addch ' '; List.iteri ( - fun di (domid, name, _, _, _, _, _) -> - let t, is_average, is_running = pcpus.(p).(di) in + fun di (domid, name, _, _, _, _, _, _) -> + let t = pcpus.(p).(di) in let len = String.length name in let width = max (len+1) 7 in let str = @@ -966,9 +945,7 @@ let redraw = else ( let t = Int64.to_float t in let percent = 100. *. t /. total_cpu_per_pcpu in - sprintf "%s%c%c " (Show.percent percent) - (if is_average then '=' else ' ') - (if is_running then '#' else ' ') + sprintf "%s " (Show.percent percent) ) in addstr (pad width str); () -- 1.7.4.4

Is there a later version of this patch than V3? This _needs_ to be accepted into libvirt 0.9.10 (ie. in 2 days) in order for us to get this into RHEL 6.3. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora

On Wed, 25 Jan 2012 15:07:10 +0000 "Richard W.M. Jones" <rjones@redhat.com> wrote:
Is there a later version of this patch than V3?
This _needs_ to be accepted into libvirt 0.9.10 (ie. in 2 days) in order for us to get this into RHEL 6.3.
Lai-san is in new year holiday until the next week. I'll make a try v4, today. Thanks, -Kame

Previous thread title was [libvirt] [PATCH 0/5 0/1 0/1 V3] Add new public API virDomainGetPcpusUsage() and pcpuinfo command in virsh This is new version. As suggested in previous thread, this version impelements virDomainGetCPUStats(), which uses virTypedParameterPtr as argument. I wonder I've already missed the deadline. If this this version is not enough, Lai-san will post v5. Someone may think the new API is complicated. This patch set provides new virsh command, cpu-accts. This shows guest's cpu usage. [root@bluextal ~]# virsh cpu-accts GuestX1 Total: cpu_time 165.4 CPU0: cpu_time 0.0 CPU1: cpu_time 0.0 CPU2: cpu_time 0.0 CPU3: cpu_time 0.0 CPU4: cpu_time 78.9 CPU5: cpu_time 0.0 CPU6: cpu_time 0.0 CPU7: cpu_time 86.5 In this case, GuestX1 used 160sec of CPU and mainly using CPU5 and CPU7. Changes: - changed API design - handle preset_cpu_map and max cpu id - dropped Python API. Note: - This patch set doesn't include Python API etc..we'll prepare ones if C APIs are finally fixed. Thanks, -Kame

add new API virDomainGetCPUStats() for getting cpu accounting information per real cpus which is used by a domain. based on ideas by Lai Jiangshan and Eric Blake. Proposed API is a bit morified to be able to return max cpu ID. (max cpu ID != cpu num.) * src/libvirt_public.syms: add API for LIBVIRT_0.9.10 * src/libvirt.c: define virDomainGetCPUStats() * include/libvirt/libvirt.h.in: add virDomainGetCPUStats() header * src/driver.h: add driver API * python/generator.py: add python API (as not implemented) --- include/libvirt/libvirt.h.in | 6 ++ python/generator.py | 1 + src/driver.h | 8 +++ src/libvirt.c | 136 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + 5 files changed, 152 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index e99cd00..e8b5116 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -3797,6 +3797,12 @@ int virConnectSetKeepAlive(virConnectPtr conn, int interval, unsigned int count); +int virDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags); #ifdef __cplusplus } #endif diff --git a/python/generator.py b/python/generator.py index de635dc..9c74427 100755 --- a/python/generator.py +++ b/python/generator.py @@ -422,6 +422,7 @@ skip_impl = ( 'virDomainGetBlockIoTune', 'virDomainSetInterfaceParameters', 'virDomainGetInterfaceParameters', + 'virDomainGetCPUStats' # not implemented now. ) qemu_skip_impl = ( diff --git a/src/driver.h b/src/driver.h index df2aa60..0e52770 100644 --- a/src/driver.h +++ b/src/driver.h @@ -797,6 +797,13 @@ typedef int (*virDrvDomainShutdownFlags)(virDomainPtr domain, unsigned int flags); +typedef int + (*virDrvDomainGetCPUStats)(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags); /** * _virDriver: @@ -967,6 +974,7 @@ struct _virDriver { virDrvNodeSuspendForDuration nodeSuspendForDuration; virDrvDomainSetBlockIoTune domainSetBlockIoTune; virDrvDomainGetBlockIoTune domainGetBlockIoTune; + virDrvDomainGetCPUStats domainGetCPUStats; }; typedef int diff --git a/src/libvirt.c b/src/libvirt.c index e9d638b..8983f27 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -18017,3 +18017,139 @@ error: virDispatchError(dom->conn); return -1; } + +/** + * virDomainGetCPUStats: + * @domain: domain to query + * @params: array to populate on output + * @nparams: number of parameters per cpu + * @start_cpu: which cpu to start with, or -1 for summary + * @ncpus: how many cpus to query + * @flags: unused for now + * + * Get statistics relating to CPU usage attributable to a single + * domain (in contrast to the statistics returned by + * virNodeGetCPUStats() for all processes on the host). @dom + * must be running (an inactive domain has no attributable cpu + * usage). On input, @params must contain at least @nparams * @ncpus + * entries, allocated by the caller. + * + * Now, @ncpus is limited to be <= 128. If you want to get + * values in a host with more cpus, you need to call multiple times. + * + * @nparams are limited to be <= 16 but maximum params per cpu + * provided by will be smaller than this. + * + * + * If @start_cpu is -1, then @ncpus must be 1, and the returned + * results reflect the statistics attributable to the entire + * domain (such as user and system time for the process as a + * whole). Otherwise, @start_cpu represents which cpu to start + * with, and @ncpus represents how many consecutive processors to + * query, with statistics attributable per processor (such as + * per-cpu usage). + * + * As a special case, if @params is NULL and @nparams is 0 and + * @ncpus is 1, and the return value will be how many + * statistics are available for the given @start_cpu. This number + * may be different for @start_cpu of -1 than for any non-negative + * value, but will be the same for all non-negative @start_cpu. + * + * And, if @param is NULL and @ncparam is 0 and @ncpus is 0, + * Max cpu id of the node is returned. (considering cpu hotplug, + * max cpu id may be different from the number of cpu on a host.) + * + * For now, @flags is unused, and the statistics all relate to the + * usage from the host perspective; the number of cpus available to + * query can be determined by the cpus member of the result of + * virNodeGetInfo(), even if the domain has had vcpus pinned to only + * use a subset of overall host cpus. It is possible that a future + * version will support a flag that queries the cpu usage from the + * guest's perspective, using the number of vcpus available to the + * guest, found by virDomainGetVcpusFlags(). An individual guest + * vcpu cannot be reliably mapped back to a specific host cpu unless + * a single-processor vcpu pinning was used, but when @start_cpu is -1, + * any difference in usage between a host and guest perspective would + * serve as a measure of hypervisor overhead. + * + * Returns -1 on failure, or the number of statistics that were + * populated per cpu on success (this will be less than the total + * number of populated @params, unless @ncpus was 1; and may be + * less than @nparams). The populated parameters start at each + * stride of @nparams; any unpopulated parameters will be zeroed + * on success. The caller is responsible for freeing any returned + * string parameters. + * + * Note: + * Because cpu ids may be discontig, retuned @param array may contain + * zero-filled entry in the middle. + * All time related values are represented in ns, using UULONG. + * + * Typical use sequence is below. + * + * getting total stats: set start_cpu as -1, ncpus 1 + * virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0) => nparams + * VIR_ALLOC_N(params, nparams) + * virDomainGetCPUStats(dom, parasm, nparams, -1, 1, 0) => total stats. + * + * getting percpu stats: + * virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) => max_cpu_id + * virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0) => nparams + * VIR_ALLOC_N(params, max_cpu_id * nparams) + * virDomainGetCPUStats(dom, params, nparams, 0, max_cpu_id, 0) => percpu stats + * + */ + +int virDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "nparams=%d, start_cpu=%d, ncpu=%d, flags=%x", + nparams, start_cpu, ncpus, flags); + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = domain->conn; + /* start_cpu == -1 is a special case. ncpus must be 1 */ + if ((start_cpu == -1) && (ncpus != 1)) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + /* if params == NULL, nparams must be 0 */ + if ((params == NULL) && ((nparams != 0))) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + /* remote protocol doesn't welcome big args in one shot */ + if ((nparams > 16) || (ncpus > 128)) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + } + + if (conn->driver->domainGetCPUStats) { + int ret; + + ret = conn->driver->domainGetCPUStats(domain, params, nparams, + start_cpu, ncpus, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibDomainError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(domain->conn); + return -1; +} diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 1340b0c..7f9c5ab 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -520,6 +520,7 @@ LIBVIRT_0.9.10 { global: virDomainShutdownFlags; virStorageVolWipePattern; + virDomainGetCPUStats; } LIBVIRT_0.9.9; # .... define new API here using predicted next version number .... -- 1.7.4.1

On 01/27/2012 11:20 PM, KAMEZAWA Hiroyuki wrote:
add new API virDomainGetCPUStats() for getting cpu accounting information per real cpus which is used by a domain.
based on ideas by Lai Jiangshan and Eric Blake. Proposed API is a bit morified to be able to return max cpu ID.
s/morified/modified/
(max cpu ID != cpu num.)
Nice extension to my proposal. And you made it - I'm going to push this today, so your API is definitely in 0.9.10, even if we need a few touchups discovered during testing during the freeze. For example, it's fine if you don't have the Python implementation done before the freeze; that's something I don't mind taking during the freeze week on the grounds that it is rounding out a feature that we have committed to, and that it won't be altering the API itself. But of course, sooner means more review time and testing :) I'm hoisting the hunk from 4/5 on libvirt.h.in, where you define the first stat, "cpu_time".
+++ b/src/libvirt.c @@ -18017,3 +18017,139 @@ error: virDispatchError(dom->conn); return -1; } + +/** + * virDomainGetCPUStats: + * @domain: domain to query + * @params: array to populate on output + * @nparams: number of parameters per cpu + * @start_cpu: which cpu to start with, or -1 for summary + * @ncpus: how many cpus to query + * @flags: unused for now
This should use the common text we have elsewhere.
+ * + * Get statistics relating to CPU usage attributable to a single + * domain (in contrast to the statistics returned by + * virNodeGetCPUStats() for all processes on the host). @dom + * must be running (an inactive domain has no attributable cpu + * usage). On input, @params must contain at least @nparams * @ncpus + * entries, allocated by the caller. + * + * Now, @ncpus is limited to be <= 128. If you want to get + * values in a host with more cpus, you need to call multiple times. + * + * @nparams are limited to be <= 16 but maximum params per cpu + * provided by will be smaller than this.
I'd combine these limits, and mention that they mainly apply to remote protocols.
+ * + * + * If @start_cpu is -1, then @ncpus must be 1, and the returned + * results reflect the statistics attributable to the entire + * domain (such as user and system time for the process as a + * whole). Otherwise, @start_cpu represents which cpu to start + * with, and @ncpus represents how many consecutive processors to + * query, with statistics attributable per processor (such as + * per-cpu usage). + * + * As a special case, if @params is NULL and @nparams is 0 and + * @ncpus is 1, and the return value will be how many + * statistics are available for the given @start_cpu. This number + * may be different for @start_cpu of -1 than for any non-negative + * value, but will be the same for all non-negative @start_cpu. + * + * And, if @param is NULL and @ncparam is 0 and @ncpus is 0,
s/ncparam/nparams/
+ * Max cpu id of the node is returned. (considering cpu hotplug, + * max cpu id may be different from the number of cpu on a host.)
Yes, this is useful.
+ * + * For now, @flags is unused, and the statistics all relate to the + * usage from the host perspective; the number of cpus available to + * query can be determined by the cpus member of the result of + * virNodeGetInfo(),
but it means this information is not quite right.
even if the domain has had vcpus pinned to only + * use a subset of overall host cpus. It is possible that a future + * version will support a flag that queries the cpu usage from the + * guest's perspective, using the number of vcpus available to the + * guest, found by virDomainGetVcpusFlags(). An individual guest + * vcpu cannot be reliably mapped back to a specific host cpu unless + * a single-processor vcpu pinning was used, but when @start_cpu is -1, + * any difference in usage between a host and guest perspective would + * serve as a measure of hypervisor overhead. + * + * Returns -1 on failure, or the number of statistics that were + * populated per cpu on success (this will be less than the total + * number of populated @params, unless @ncpus was 1; and may be + * less than @nparams). The populated parameters start at each + * stride of @nparams; any unpopulated parameters will be zeroed + * on success. The caller is responsible for freeing any returned + * string parameters.
The return paragraph should be last.
+ * + * Note: + * Because cpu ids may be discontig, retuned @param array may contain + * zero-filled entry in the middle.
This repeats part of the return paragraph.
+ * All time related values are represented in ns, using UULONG.
This should be documented by the various macros for well-defined stat names.
+ * + * Typical use sequence is below. + * + * getting total stats: set start_cpu as -1, ncpus 1 + * virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0) => nparams + * VIR_ALLOC_N(params, nparams)
Example code is written for apps that aren't using libvirt internals, therefore it must use calloc instead of VIR_ALLOC_N.
+ * virDomainGetCPUStats(dom, parasm, nparams, -1, 1, 0) => total stats.
s/parasm/params/
+ * + * getting percpu stats: + * virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) => max_cpu_id + * virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0) => nparams + * VIR_ALLOC_N(params, max_cpu_id * nparams) + * virDomainGetCPUStats(dom, params, nparams, 0, max_cpu_id, 0) => percpu stats
This should be moved up right after the documentation of special casing.
+ * + */ + +int virDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "nparams=%d, start_cpu=%d, ncpu=%d, flags=%x",
Missing params.
+ nparams, start_cpu, ncpus, flags); + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = domain->conn; + /* start_cpu == -1 is a special case. ncpus must be 1 */ + if ((start_cpu == -1) && (ncpus != 1)) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error;
We can further check that start_cpu is -1 or non-negative.
+ } + /* if params == NULL, nparams must be 0 */ + if ((params == NULL) && ((nparams != 0))) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error;
We can further check that nparams is non-zero if params is non-NULL. We can further check that ncpus is non-zero unless params is NULL. Since we don't distinguish the error message, we could join these conditionals.
+ } + + /* remote protocol doesn't welcome big args in one shot */ + if ((nparams > 16) || (ncpus > 128)) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + }
This restriction should only be forced by the remote protocol. See virDomainMemoryPeek for an example of a documented RPC limitation, but which is only enforced in the RPC code.
+++ b/src/libvirt_public.syms @@ -520,6 +520,7 @@ LIBVIRT_0.9.10 { global: virDomainShutdownFlags; virStorageVolWipePattern; + virDomainGetCPUStats;
I like to keep this sorted. Overall, ACK - you picked up on the review suggestions, and the API looks good enough now to commit to. Here's what I'm squashing before pushing, which means we now have the API in place before the freeze! I'm not sure if I will finish reviewing the rest of the series today, seeing as it is my weekend, but we'll certainly get it all in before the release. diff --git i/include/libvirt/libvirt.h.in w/include/libvirt/libvirt.h.in index 10a8862..f55fac3 100644 --- i/include/libvirt/libvirt.h.in +++ w/include/libvirt/libvirt.h.in @@ -3811,6 +3811,14 @@ int virConnectSetKeepAlive(virConnectPtr conn, int interval, unsigned int count); +/* Collecting CPU statistics */ + +/** + * VIR_DOMAIN_CPU_STATS_CPUTIME: + * cpu usage in nanoseconds, as a ullong + */ +#define VIR_DOMAIN_CPU_STATS_CPUTIME "cpu_time" + int virDomainGetCPUStats(virDomainPtr domain, virTypedParameterPtr params, unsigned int nparams, diff --git i/src/libvirt.c w/src/libvirt.c index 6ff93cc..7efea67 100644 --- i/src/libvirt.c +++ w/src/libvirt.c @@ -18159,7 +18159,7 @@ error: * @nparams: number of parameters per cpu * @start_cpu: which cpu to start with, or -1 for summary * @ncpus: how many cpus to query - * @flags: unused for now + * @flags: extra flags; not used yet, so callers should always pass 0 * * Get statistics relating to CPU usage attributable to a single * domain (in contrast to the statistics returned by @@ -18168,13 +18168,6 @@ error: * usage). On input, @params must contain at least @nparams * @ncpus * entries, allocated by the caller. * - * Now, @ncpus is limited to be <= 128. If you want to get - * values in a host with more cpus, you need to call multiple times. - * - * @nparams are limited to be <= 16 but maximum params per cpu - * provided by will be smaller than this. - * - * * If @start_cpu is -1, then @ncpus must be 1, and the returned * results reflect the statistics attributable to the entire * domain (such as user and system time for the process as a @@ -18183,57 +18176,52 @@ error: * query, with statistics attributable per processor (such as * per-cpu usage). * - * As a special case, if @params is NULL and @nparams is 0 and + * The remote driver imposes a limit of 128 @ncpus and 16 @nparams; + * the number of parameters per cpu should not exceed 16, but if you + * have a host with more than 128 CPUs, your program should split + * the request into multiple calls. + * + * As special cases, if @params is NULL and @nparams is 0 and * @ncpus is 1, and the return value will be how many * statistics are available for the given @start_cpu. This number * may be different for @start_cpu of -1 than for any non-negative * value, but will be the same for all non-negative @start_cpu. - * - * And, if @param is NULL and @ncparam is 0 and @ncpus is 0, - * Max cpu id of the node is returned. (considering cpu hotplug, - * max cpu id may be different from the number of cpu on a host.) + * Likewise, if @params is NULL and @nparams is 0 and @ncpus is 0, + * the number of cpus available to query is returned. From the + * host perspective, this would typically match the cpus member + * of virNodeGetInfo(), but might be less due to host cpu hotplug. * * For now, @flags is unused, and the statistics all relate to the - * usage from the host perspective; the number of cpus available to - * query can be determined by the cpus member of the result of - * virNodeGetInfo(), even if the domain has had vcpus pinned to only - * use a subset of overall host cpus. It is possible that a future + * usage from the host perspective. It is possible that a future * version will support a flag that queries the cpu usage from the - * guest's perspective, using the number of vcpus available to the - * guest, found by virDomainGetVcpusFlags(). An individual guest - * vcpu cannot be reliably mapped back to a specific host cpu unless - * a single-processor vcpu pinning was used, but when @start_cpu is -1, - * any difference in usage between a host and guest perspective would - * serve as a measure of hypervisor overhead. - * - * Returns -1 on failure, or the number of statistics that were - * populated per cpu on success (this will be less than the total - * number of populated @params, unless @ncpus was 1; and may be - * less than @nparams). The populated parameters start at each - * stride of @nparams; any unpopulated parameters will be zeroed - * on success. The caller is responsible for freeing any returned - * string parameters. - * - * Note: - * Because cpu ids may be discontig, retuned @param array may contain - * zero-filled entry in the middle. - * All time related values are represented in ns, using UULONG. + * guest's perspective, where the maximum cpu to query would be + * related to virDomainGetVcpusFlags() rather than virNodeGetInfo(). + * An individual guest vcpu cannot be reliably mapped back to a + * specific host cpu unless a single-processor vcpu pinning was used, + * but when @start_cpu is -1, any difference in usage between a host + * and guest perspective would serve as a measure of hypervisor overhead. * * Typical use sequence is below. * * getting total stats: set start_cpu as -1, ncpus 1 * virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0) => nparams - * VIR_ALLOC_N(params, nparams) - * virDomainGetCPUStats(dom, parasm, nparams, -1, 1, 0) => total stats. + * params = calloc(nparams, sizeof (virTypedParameter)) + * virDomainGetCPUStats(dom, params, nparams, -1, 1, 0) => total stats. * - * getting percpu stats: - * virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) => max_cpu_id + * getting per-cpu stats: + * virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) => ncpus * virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0) => nparams - * VIR_ALLOC_N(params, max_cpu_id * nparams) - * virDomainGetCPUStats(dom, params, nparams, 0, max_cpu_id, 0) => percpu stats + * params = calloc(ncpus * nparams, sizeof (virTypedParameter)) + * virDomainGetCPUStats(dom, params, nparams, 0, ncpus, 0) => per-cpu stats * + * Returns -1 on failure, or the number of statistics that were + * populated per cpu on success (this will be less than the total + * number of populated @params, unless @ncpus was 1; and may be + * less than @nparams). The populated parameters start at each + * stride of @nparams, which means the results may be discontiguous; + * any unpopulated parameters will be zeroed on success. The caller + * is responsible for freeing any returned string parameters. */ - int virDomainGetCPUStats(virDomainPtr domain, virTypedParameterPtr params, unsigned int nparams, @@ -18243,8 +18231,9 @@ int virDomainGetCPUStats(virDomainPtr domain, { virConnectPtr conn; - VIR_DOMAIN_DEBUG(domain, "nparams=%d, start_cpu=%d, ncpu=%d, flags=%x", - nparams, start_cpu, ncpus, flags); + VIR_DOMAIN_DEBUG(domain, + "params=%p, nparams=%d, start_cpu=%d, ncpus=%u, flags=%x", + params, nparams, start_cpu, ncpus, flags); virResetLastError(); if (!VIR_IS_CONNECTED_DOMAIN(domain)) { @@ -18254,13 +18243,16 @@ int virDomainGetCPUStats(virDomainPtr domain, } conn = domain->conn; - /* start_cpu == -1 is a special case. ncpus must be 1 */ - if ((start_cpu == -1) && (ncpus != 1)) { - virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); - goto error; - } - /* if params == NULL, nparams must be 0 */ - if ((params == NULL) && ((nparams != 0))) { + /* Special cases: + * start_cpu must be non-negative, or else -1 + * if start_cpu is -1, ncpus must be 1 + * params == NULL must match nparams == 0 + * ncpus must be non-zero unless params == NULL + */ + if (start_cpu < -1 || + (start_cpu == -1 && ncpus != 1) || + ((params == NULL) != (nparams == 0)) || + (ncpus == 0 && params != NULL)) { virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); goto error; } -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Sat, 28 Jan 2012 07:19:42 -0700 Eric Blake <eblake@redhat.com> wrote:
On 01/27/2012 11:20 PM, KAMEZAWA Hiroyuki wrote:
add new API virDomainGetCPUStats() for getting cpu accounting information per real cpus which is used by a domain.
based on ideas by Lai Jiangshan and Eric Blake. Proposed API is a bit morified to be able to return max cpu ID.
s/morified/modified/
(max cpu ID != cpu num.)
Nice extension to my proposal. And you made it - I'm going to push this today, so your API is definitely in 0.9.10, even if we need a few touchups discovered during testing during the freeze.
Thank you. good news :)
For example, it's fine if you don't have the Python implementation done before the freeze; that's something I don't mind taking during the freeze week on the grounds that it is rounding out a feature that we have committed to, and that it won't be altering the API itself. But of course, sooner means more review time and testing :)
I'm hoisting the hunk from 4/5 on libvirt.h.in, where you define the first stat, "cpu_time".
Thanks.
+++ b/src/libvirt.c @@ -18017,3 +18017,139 @@ error: virDispatchError(dom->conn); return -1; } + +/** + * virDomainGetCPUStats: + * @domain: domain to query + * @params: array to populate on output + * @nparams: number of parameters per cpu + * @start_cpu: which cpu to start with, or -1 for summary + * @ncpus: how many cpus to query + * @flags: unused for now
This should use the common text we have elsewhere.
Sure.
+ * + * Get statistics relating to CPU usage attributable to a single + * domain (in contrast to the statistics returned by + * virNodeGetCPUStats() for all processes on the host). @dom + * must be running (an inactive domain has no attributable cpu + * usage). On input, @params must contain at least @nparams * @ncpus + * entries, allocated by the caller. + * + * Now, @ncpus is limited to be <= 128. If you want to get + * values in a host with more cpus, you need to call multiple times. + * + * @nparams are limited to be <= 16 but maximum params per cpu + * provided by will be smaller than this.
I'd combine these limits, and mention that they mainly apply to remote protocols.
Ok, it will be better.
+ * + * + * If @start_cpu is -1, then @ncpus must be 1, and the returned + * results reflect the statistics attributable to the entire + * domain (such as user and system time for the process as a + * whole). Otherwise, @start_cpu represents which cpu to start + * with, and @ncpus represents how many consecutive processors to + * query, with statistics attributable per processor (such as + * per-cpu usage). + * + * As a special case, if @params is NULL and @nparams is 0 and + * @ncpus is 1, and the return value will be how many + * statistics are available for the given @start_cpu. This number + * may be different for @start_cpu of -1 than for any non-negative + * value, but will be the same for all non-negative @start_cpu. + * + * And, if @param is NULL and @ncparam is 0 and @ncpus is 0,
s/ncparam/nparams/
+ * Max cpu id of the node is returned. (considering cpu hotplug, + * max cpu id may be different from the number of cpu on a host.)
Yes, this is useful.
+ * + * For now, @flags is unused, and the statistics all relate to the + * usage from the host perspective; the number of cpus available to + * query can be determined by the cpus member of the result of + * virNodeGetInfo(),
but it means this information is not quite right.
We'll check other users of calculation as max_cpu = sockets*cores*threads. I wonder it may be better that NodeGetInfo() returns max_cpu "ID", online cpu map etc..but I can't change the interface _virNodeInfo, right ?
even if the domain has had vcpus pinned to only + * use a subset of overall host cpus. It is possible that a future + * version will support a flag that queries the cpu usage from the + * guest's perspective, using the number of vcpus available to the + * guest, found by virDomainGetVcpusFlags(). An individual guest + * vcpu cannot be reliably mapped back to a specific host cpu unless + * a single-processor vcpu pinning was used, but when @start_cpu is -1, + * any difference in usage between a host and guest perspective would + * serve as a measure of hypervisor overhead. + * + * Returns -1 on failure, or the number of statistics that were + * populated per cpu on success (this will be less than the total + * number of populated @params, unless @ncpus was 1; and may be + * less than @nparams). The populated parameters start at each + * stride of @nparams; any unpopulated parameters will be zeroed + * on success. The caller is responsible for freeing any returned + * string parameters.
The return paragraph should be last.
Okay.
+ * + * Note: + * Because cpu ids may be discontig, retuned @param array may contain + * zero-filled entry in the middle.
This repeats part of the return paragraph.
Sorry.
+ * All time related values are represented in ns, using UULONG.
This should be documented by the various macros for well-defined stat names.
+ * + * Typical use sequence is below. + * + * getting total stats: set start_cpu as -1, ncpus 1 + * virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0) => nparams + * VIR_ALLOC_N(params, nparams)
Example code is written for apps that aren't using libvirt internals, therefore it must use calloc instead of VIR_ALLOC_N.
+ * virDomainGetCPUStats(dom, parasm, nparams, -1, 1, 0) => total stats.
s/parasm/params/
+ * + * getting percpu stats: + * virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) => max_cpu_id + * virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0) => nparams + * VIR_ALLOC_N(params, max_cpu_id * nparams) + * virDomainGetCPUStats(dom, params, nparams, 0, max_cpu_id, 0) => percpu stats
This should be moved up right after the documentation of special casing.
That sounds better.
+ * + */ + +int virDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "nparams=%d, start_cpu=%d, ncpu=%d, flags=%x",
Missing params.
Ah, yes.
+ nparams, start_cpu, ncpus, flags); + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(domain)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = domain->conn; + /* start_cpu == -1 is a special case. ncpus must be 1 */ + if ((start_cpu == -1) && (ncpus != 1)) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error;
We can further check that start_cpu is -1 or non-negative.
+ } + /* if params == NULL, nparams must be 0 */ + if ((params == NULL) && ((nparams != 0))) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error;
We can further check that nparams is non-zero if params is non-NULL.
We can further check that ncpus is non-zero unless params is NULL.
Since we don't distinguish the error message, we could join these conditionals.
I see.
+ } + + /* remote protocol doesn't welcome big args in one shot */ + if ((nparams > 16) || (ncpus > 128)) { + virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); + goto error; + }
This restriction should only be forced by the remote protocol. See virDomainMemoryPeek for an example of a documented RPC limitation, but which is only enforced in the RPC code.
Hmm, ok.
+++ b/src/libvirt_public.syms @@ -520,6 +520,7 @@ LIBVIRT_0.9.10 { global: virDomainShutdownFlags; virStorageVolWipePattern; + virDomainGetCPUStats;
I like to keep this sorted.
Overall, ACK - you picked up on the review suggestions, and the API looks good enough now to commit to. Here's what I'm squashing before pushing, which means we now have the API in place before the freeze! I'm not sure if I will finish reviewing the rest of the series today, seeing as it is my weekend, but we'll certainly get it all in before the release.
Thank you for pushing and lots of fixes. Regards, -Kame

[I see you cc'd Eric@redhat.com; unfortunately, that doesn't forward to me - at least I saw it on-list :) ] On 01/27/2012 11:20 PM, KAMEZAWA Hiroyuki wrote:
add new API virDomainGetCPUStats() for getting cpu accounting information per real cpus which is used by a domain.
+++ b/include/libvirt/libvirt.h.in @@ -3797,6 +3797,12 @@ int virConnectSetKeepAlive(virConnectPtr conn, int interval, unsigned int count);
+int virDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags); #ifdef __cplusplus
Phooey - I didn't notice this earlier, but we like new API to occur in context (all the virDomain calls in sequence), and not slammed at the bottom of the file (where we sunk the deprecated interfaces). I'm pushing this followup as trivial (it is purely code motion and doc comments).diff --git i/include/libvirt/libvirt.h.in w/include/libvirt/libvirt.h.in index f55fac3..0a7b324 100644 --- i/include/libvirt/libvirt.h.in +++ w/include/libvirt/libvirt.h.in @@ -1127,6 +1127,10 @@ char * virConnectGetURI (virConnectPtr conn); char * virConnectGetSysinfo (virConnectPtr conn, unsigned int flags); +int virConnectSetKeepAlive(virConnectPtr conn, + int interval, + unsigned int count); + /* * Capabilities of the connection / driver. @@ -1299,14 +1303,29 @@ char * virDomainScreenshot (virDomainPtr domain, unsigned int flags); /* - * Domain runtime information + * Domain runtime information, and collecting CPU statistics */ + int virDomainGetInfo (virDomainPtr domain, virDomainInfoPtr info); int virDomainGetState (virDomainPtr domain, int *state, int *reason, unsigned int flags); + +/** + * VIR_DOMAIN_CPU_STATS_CPUTIME: + * cpu usage in nanoseconds, as a ullong + */ +#define VIR_DOMAIN_CPU_STATS_CPUTIME "cpu_time" + +int virDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags); + int virDomainGetControlInfo (virDomainPtr domain, virDomainControlInfoPtr info, unsigned int flags); @@ -3807,24 +3826,6 @@ typedef struct _virTypedParameter virMemoryParameter; */ typedef virMemoryParameter *virMemoryParameterPtr; -int virConnectSetKeepAlive(virConnectPtr conn, - int interval, - unsigned int count); - -/* Collecting CPU statistics */ - -/** - * VIR_DOMAIN_CPU_STATS_CPUTIME: - * cpu usage in nanoseconds, as a ullong - */ -#define VIR_DOMAIN_CPU_STATS_CPUTIME "cpu_time" - -int virDomainGetCPUStats(virDomainPtr domain, - virTypedParameterPtr params, - unsigned int nparams, - int start_cpu, - unsigned int ncpus, - unsigned int flags); #ifdef __cplusplus } #endif -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Sat, Jan 28, 2012 at 07:37:49AM -0700, Eric Blake wrote:
[I see you cc'd Eric@redhat.com; unfortunately, that doesn't forward to me - at least I saw it on-list :) ]
On 01/27/2012 11:20 PM, KAMEZAWA Hiroyuki wrote:
add new API virDomainGetCPUStats() for getting cpu accounting information per real cpus which is used by a domain.
+++ b/include/libvirt/libvirt.h.in @@ -3797,6 +3797,12 @@ int virConnectSetKeepAlive(virConnectPtr conn, int interval, unsigned int count);
+int virDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags); #ifdef __cplusplus
Phooey - I didn't notice this earlier, but we like new API to occur in context (all the virDomain calls in sequence), and not slammed at the bottom of the file (where we sunk the deprecated interfaces).
To be honest, even before this, the libvirt.h header file was a horrible disorganized mess, and has been for a very long time. Over 4 years ago now I suggested splitting it up based on functional area, such that the top level libvirt.h that apps include, just #includes further sub-headers https://www.redhat.com/archives/libvir-list/2007-October/msg00255.html The benefits of doing this today would be even greater than back then... Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Changed from V3: use new virDomainGetCPUStats() libvirt-API. use C code to construct the typed_param list array Acked-by: "Richard W.M. Jones" <rjones@redhat.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- libvirt/libvirt.ml | 8 ++++ libvirt/libvirt.mli | 11 +++++ libvirt/libvirt_c_oneoffs.c | 91 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 0 deletions(-) diff --git a/libvirt/libvirt.ml b/libvirt/libvirt.ml index fc29264..08c501b 100644 --- a/libvirt/libvirt.ml +++ b/libvirt/libvirt.ml @@ -343,6 +343,13 @@ struct | SchedFieldInt64 of int64 | SchedFieldUInt64 of int64 | SchedFieldFloat of float | SchedFieldBool of bool + type typed_param = string * typed_param_value + and typed_param_value = + | TypedFieldInt32 of int32 | TypedFieldUInt32 of int32 + | TypedFieldInt64 of int64 | TypedFieldUInt64 of int64 + | TypedFieldFloat of float | TypedFieldBool of bool + | TypedFieldString of string + type migrate_flag = Live type memory_flag = Virtual @@ -419,6 +426,7 @@ struct external set_vcpus : [>`W] t -> int -> unit = "ocaml_libvirt_domain_set_vcpus" external pin_vcpu : [>`W] t -> int -> string -> unit = "ocaml_libvirt_domain_pin_vcpu" external get_vcpus : [>`R] t -> int -> int -> int * vcpu_info array * string = "ocaml_libvirt_domain_get_vcpus" + external get_cpu_stats : [>`R] t -> int -> typed_param list array = "ocaml_libvirt_domain_get_cpu_stats" external get_max_vcpus : [>`R] t -> int = "ocaml_libvirt_domain_get_max_vcpus" external attach_device : [>`W] t -> xml -> unit = "ocaml_libvirt_domain_attach_device" external detach_device : [>`W] t -> xml -> unit = "ocaml_libvirt_domain_detach_device" diff --git a/libvirt/libvirt.mli b/libvirt/libvirt.mli index 7bda889..31f2ca2 100644 --- a/libvirt/libvirt.mli +++ b/libvirt/libvirt.mli @@ -439,6 +439,13 @@ sig | SchedFieldInt64 of int64 | SchedFieldUInt64 of int64 | SchedFieldFloat of float | SchedFieldBool of bool + type typed_param = string * typed_param_value + and typed_param_value = + | TypedFieldInt32 of int32 | TypedFieldUInt32 of int32 + | TypedFieldInt64 of int64 | TypedFieldUInt64 of int64 + | TypedFieldFloat of float | TypedFieldBool of bool + | TypedFieldString of string + type migrate_flag = Live type memory_flag = Virtual @@ -586,6 +593,10 @@ sig for a domain. See the libvirt documentation for details of the array and bitmap returned from this function. *) + val get_cpu_stats : [>`R] t -> int -> typed_param list array + (** [get_pcpu_stats dom nr_pcpu] returns the physic CPU stats + for a domain. See the libvirt documentation for details. + *) val get_max_vcpus : [>`R] t -> int (** Returns the maximum number of vCPUs supported for this domain. *) val attach_device : [>`W] t -> xml -> unit diff --git a/libvirt/libvirt_c_oneoffs.c b/libvirt/libvirt_c_oneoffs.c index d87dd21..a0c74be 100644 --- a/libvirt/libvirt_c_oneoffs.c +++ b/libvirt/libvirt_c_oneoffs.c @@ -604,6 +604,97 @@ ocaml_libvirt_domain_get_vcpus (value domv, value maxinfov, value maplenv) CAMLreturn (rv); } +CAMLprim value +ocaml_libvirt_domain_get_cpu_stats (value domv, value nr_pcpusv) +{ + CAMLparam2 (domv, nr_pcpusv); + CAMLlocal5 (cpustats, param_head, param_node, typed_param, typed_param_value); + virDomainPtr dom = Domain_val (domv); + virConnectPtr conn = Connect_domv (domv); + int nr_pcpus = Int_val (nr_pcpusv); + virTypedParameterPtr params; + int r, cpu, ncpus, nparams, i, j, pos; + + /* get percpu information */ + NONBLOCKING (nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)); + CHECK_ERROR (nparams < 0, conn, "virDomainGetCPUStats"); + + if ((params = malloc(sizeof(*params) * nparams * 128)) == NULL) + caml_failwith ((char *)__FUNCTION__); + + cpustats = caml_alloc (nr_pcpus, 0); /* cpustats: array of params(list of typed_param) */ + cpu = 0; + while (cpu < nr_pcpus) { + ncpus = nr_pcpus - cpu > 128 ? 128 : nr_pcpus - cpu; + + NONBLOCKING (r = virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0)); + CHECK_ERROR (r < 0, conn, "virDomainGetCPUStats"); + + for (i = 0; i < ncpus; i++) { + /* list of typed_param: single linked list of param_nodes */ + param_head = Val_emptylist; /* param_head: the head param_node of list of typed_param */ + + if (params[i * nparams].type == 0) { + Store_field(cpustats, cpu + i, param_head); + continue; + } + + for (j = nparams - 1; j >= 0; j--) { + pos = i * nparams + j; + if (params[pos].type == 0) + continue; + + param_node = caml_alloc(2, 0); /* param_node: typed_param, next param_node */ + Store_field(param_node, 1, param_head); + param_head = param_node; + + typed_param = caml_alloc(2, 0); /* typed_param: field name(string), typed_param_value */ + Store_field(param_node, 0, typed_param); + Store_field(typed_param, 0, caml_copy_string(params[pos].field)); + + /* typed_param_value: value with the corresponding type tag */ + switch(params[pos].type) { + case VIR_TYPED_PARAM_INT: + typed_param_value = caml_alloc (1, 0); + Store_field(typed_param_value, 0, caml_copy_int32(params[pos].value.i)); + break; + case VIR_TYPED_PARAM_UINT: + typed_param_value = caml_alloc (1, 1); + Store_field(typed_param_value, 0, caml_copy_int32(params[pos].value.ui)); + break; + case VIR_TYPED_PARAM_LLONG: + typed_param_value = caml_alloc (1, 2); + Store_field(typed_param_value, 0, caml_copy_int64(params[pos].value.l)); + break; + case VIR_TYPED_PARAM_ULLONG: + typed_param_value = caml_alloc (1, 3); + Store_field(typed_param_value, 0, caml_copy_int64(params[pos].value.ul)); + break; + case VIR_TYPED_PARAM_DOUBLE: + typed_param_value = caml_alloc (1, 4); + Store_field(typed_param_value, 0, caml_copy_double(params[pos].value.d)); + break; + case VIR_TYPED_PARAM_BOOLEAN: + typed_param_value = caml_alloc (1, 5); + Store_field(typed_param_value, 0, Int_val(!!(params[pos].value.b))); + break; + case VIR_TYPED_PARAM_STRING: + typed_param_value = caml_alloc (1, 6); + Store_field(typed_param_value, 0, caml_copy_string(params[pos].value.s)); + break; + default: + free(params); + caml_failwith ((char *)__FUNCTION__); + } + Store_field(typed_param, 1, typed_param_value); + } + Store_field(cpustats, cpu + i, param_head); + } + cpu += ncpus; + } + free(params); + CAMLreturn (cpustats); +} #ifdef HAVE_WEAK_SYMBOLS #ifdef HAVE_VIRDOMAINMIGRATE extern virDomainPtr virDomainMigrate (virDomainPtr domain, virConnectPtr dconn,

Old "virt-top -1" is not correct, its output is generated by guess: use average usage for pinned physical CPUs. example(old "virt-top -1"): PHYCPU %CPU rhel6 Windows 0 0.6 0.1= 0.5= 1 0.6 0.1= 0.5=# 2 0.6 0.1= 0.5= 3 0.6 0.1=# 0.5= The output almost makes no sense(all the value are just average, not real). This is new implement, it use cpuacct cgroup to gain *real* physical usages via cpuacct cgroup by virDomainGetCPUStats() API. new result: PHYCPU %CPU rhel6 Windows 0 1.3 0.3 1.0 1 2.3 0.3 2.0 2 2.2 0.5 1.7 3 2.5 0.4 2.1 PHYCPU %CPU rhel6 Windows 0 1.7 0.4 1.3 1 3.6 1.0 2.7 2 1.6 0.4 1.2 3 4.8 3.1 1.7 Note: average flag(=) is dropped, there is not average value in here. Note: running flag(#) is dropped, because if the value is not empty, it means the guest was once running in the physical CPU in this period between updates. Changed from V3: use new virDomainGetCPUStats() libvirt-API. add a new function find_usages_from_stats() to gain cpu usages. Acked-by: "Richard W.M. Jones" <rjones@redhat.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- virt-top/virt_top.ml | 80 +++++++++++++++++++------------------------------ 1 files changed, 31 insertions(+), 49 deletions(-) diff --git a/virt-top/virt_top.ml b/virt-top/virt_top.ml index ef5ac67..f7bd072 100644 --- a/virt-top/virt_top.ml +++ b/virt-top/virt_top.ml @@ -446,14 +446,14 @@ let collect, clear_pcpu_display_data = let last_info = Hashtbl.create 13 in let last_time = ref (Unix.gettimeofday ()) in - (* Save vcpuinfo structures across redraws too (only for pCPU display). *) - let last_vcpu_info = Hashtbl.create 13 in + (* Save pcpu_usages structures across redraws too (only for pCPU display). *) + let last_pcpu_usages = Hashtbl.create 13 in let clear_pcpu_display_data () = - (* Clear out vcpu_info used by PCPUDisplay display_mode + (* Clear out pcpu_info used by PCPUDisplay display_mode * when we switch back to TaskDisplay mode. *) - Hashtbl.clear last_vcpu_info + Hashtbl.clear last_pcpu_usages in let collect (conn, _, _, _, _, node_info, _, _) = @@ -652,22 +652,28 @@ let collect, clear_pcpu_display_data = (try let domid = rd.rd_domid in let maplen = C.cpumaplen nr_pcpus in + let cpu_stats = D.get_cpu_stats rd.rd_dom nr_pcpus in + let rec find_usages_from_stats = (function + | ("cpu_time", D.TypedFieldUInt64(usages)) :: _ -> usages + | (_, _) :: params -> find_usages_from_stats params + | [] -> 0L) in + let pcpu_usages = Array.map find_usages_from_stats cpu_stats in let maxinfo = rd.rd_info.D.nr_virt_cpu in let nr_vcpus, vcpu_infos, cpumaps = D.get_vcpus rd.rd_dom maxinfo maplen in - (* Got previous vcpu_infos for this domain? *) - let prev_vcpu_infos = - try Some (Hashtbl.find last_vcpu_info domid) + (* Got previous pcpu_usages for this domain? *) + let prev_pcpu_usages = + try Some (Hashtbl.find last_pcpu_usages domid) with Not_found -> None in - (* Update last_vcpu_info. *) - Hashtbl.replace last_vcpu_info domid vcpu_infos; - - (match prev_vcpu_infos with - | Some prev_vcpu_infos - when Array.length prev_vcpu_infos = Array.length vcpu_infos -> - Some (domid, name, nr_vcpus, vcpu_infos, prev_vcpu_infos, - cpumaps, maplen) + (* Update last_pcpu_usages. *) + Hashtbl.replace last_pcpu_usages domid pcpu_usages; + + (match prev_pcpu_usages with + | Some prev_pcpu_usages + when Array.length prev_pcpu_usages = Array.length pcpu_usages -> + Some (domid, name, nr_vcpus, vcpu_infos, pcpu_usages, + prev_pcpu_usages, cpumaps, maplen) | _ -> None (* ignore missing / unequal length prev_vcpu_infos *) ); with @@ -680,37 +686,15 @@ let collect, clear_pcpu_display_data = (* Rearrange the data into a matrix. Major axis (down) is * pCPUs. Minor axis (right) is domains. At each node we store: * cpu_time (on this pCPU only, nanosecs), - * average? (if set, then cpu_time is an average because the - * vCPU is pinned to more than one pCPU) - * running? (if set, we were instantaneously running on this pCPU) *) - let empty_node = (0L, false, false) in - let pcpus = Array.make_matrix nr_pcpus nr_doms empty_node in + let pcpus = Array.make_matrix nr_pcpus nr_doms 0L in List.iteri ( - fun di (domid, name, nr_vcpus, vcpu_infos, prev_vcpu_infos, - cpumaps, maplen) -> + fun di (domid, name, nr_vcpus, vcpu_infos, pcpu_usages, + prev_pcpu_usages, cpumaps, maplen) -> (* Which pCPUs can this dom run on? *) - for v = 0 to nr_vcpus-1 do - let pcpu = vcpu_infos.(v).D.cpu in (* instantaneous pCPU *) - let nr_poss_pcpus = ref 0 in (* how many pcpus can it run on? *) - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - incr nr_poss_pcpus - done; - let nr_poss_pcpus = Int64.of_int !nr_poss_pcpus in - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - let vcpu_time_on_pcpu = - vcpu_infos.(v).D.vcpu_time - -^ prev_vcpu_infos.(v).D.vcpu_time in - let vcpu_time_on_pcpu = - vcpu_time_on_pcpu /^ nr_poss_pcpus in - pcpus.(p).(di) <- - (vcpu_time_on_pcpu, nr_poss_pcpus > 1L, p = pcpu) - done + for p = 0 to Array.length pcpu_usages - 1 do + pcpus.(p).(di) <- (pcpu_usages.(p) -^ prev_pcpu_usages.(p)) done ) doms; @@ -719,7 +703,7 @@ let collect, clear_pcpu_display_data = fun row -> let cpu_time = ref 0L in for di = 0 to Array.length row-1 do - let t, _, _ = row.(di) in + let t = row.(di) in cpu_time := !cpu_time +^ t done; Int64.to_float !cpu_time @@ -938,7 +922,7 @@ let redraw = let dom_names = String.concat "" ( List.map ( - fun (_, name, _, _, _, _, _) -> + fun (_, name, _, _, _, _, _, _) -> let len = String.length name in let width = max (len+1) 7 in pad width name @@ -957,8 +941,8 @@ let redraw = addch ' '; List.iteri ( - fun di (domid, name, _, _, _, _, _) -> - let t, is_average, is_running = pcpus.(p).(di) in + fun di (domid, name, _, _, _, _, _, _) -> + let t = pcpus.(p).(di) in let len = String.length name in let width = max (len+1) 7 in let str = @@ -966,9 +950,7 @@ let redraw = else ( let t = Int64.to_float t in let percent = 100. *. t /. total_cpu_per_pcpu in - sprintf "%s%c%c " (Show.percent percent) - (if is_average then '=' else ' ') - (if is_running then '#' else ' ') + sprintf "%s " (Show.percent percent) ) in addstr (pad width str); ()

Hi, Richard virt-top and ocaml site patches can work and are completed weeks ago since public API of libvirt is merged. Could you merged them? virt-top and ocaml site patches only depend on the public API of libvirt. but the testing of them may depend on the qemu-driver-site patch of libvirt, which is sent today. Thanks, Lai On 02/08/2012 04:59 PM, Lai Jiangshan wrote:
Old "virt-top -1" is not correct, its output is generated by guess: use average usage for pinned physical CPUs.
example(old "virt-top -1"):
PHYCPU %CPU rhel6 Windows 0 0.6 0.1= 0.5= 1 0.6 0.1= 0.5=# 2 0.6 0.1= 0.5= 3 0.6 0.1=# 0.5=
The output almost makes no sense(all the value are just average, not real).
This is new implement, it use cpuacct cgroup to gain *real* physical usages via cpuacct cgroup by virDomainGetCPUStats() API.
new result:
PHYCPU %CPU rhel6 Windows 0 1.3 0.3 1.0 1 2.3 0.3 2.0 2 2.2 0.5 1.7 3 2.5 0.4 2.1
PHYCPU %CPU rhel6 Windows 0 1.7 0.4 1.3 1 3.6 1.0 2.7 2 1.6 0.4 1.2 3 4.8 3.1 1.7
Note: average flag(=) is dropped, there is not average value in here. Note: running flag(#) is dropped, because if the value is not empty, it means the guest was once running in the physical CPU in this period between updates.
Changed from V3: use new virDomainGetCPUStats() libvirt-API. add a new function find_usages_from_stats() to gain cpu usages.
Acked-by: "Richard W.M. Jones" <rjones@redhat.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- virt-top/virt_top.ml | 80 +++++++++++++++++++------------------------------ 1 files changed, 31 insertions(+), 49 deletions(-)
diff --git a/virt-top/virt_top.ml b/virt-top/virt_top.ml index ef5ac67..f7bd072 100644 --- a/virt-top/virt_top.ml +++ b/virt-top/virt_top.ml @@ -446,14 +446,14 @@ let collect, clear_pcpu_display_data = let last_info = Hashtbl.create 13 in let last_time = ref (Unix.gettimeofday ()) in
- (* Save vcpuinfo structures across redraws too (only for pCPU display). *) - let last_vcpu_info = Hashtbl.create 13 in + (* Save pcpu_usages structures across redraws too (only for pCPU display). *) + let last_pcpu_usages = Hashtbl.create 13 in
let clear_pcpu_display_data () = - (* Clear out vcpu_info used by PCPUDisplay display_mode + (* Clear out pcpu_info used by PCPUDisplay display_mode * when we switch back to TaskDisplay mode. *) - Hashtbl.clear last_vcpu_info + Hashtbl.clear last_pcpu_usages in
let collect (conn, _, _, _, _, node_info, _, _) = @@ -652,22 +652,28 @@ let collect, clear_pcpu_display_data = (try let domid = rd.rd_domid in let maplen = C.cpumaplen nr_pcpus in + let cpu_stats = D.get_cpu_stats rd.rd_dom nr_pcpus in + let rec find_usages_from_stats = (function + | ("cpu_time", D.TypedFieldUInt64(usages)) :: _ -> usages + | (_, _) :: params -> find_usages_from_stats params + | [] -> 0L) in + let pcpu_usages = Array.map find_usages_from_stats cpu_stats in let maxinfo = rd.rd_info.D.nr_virt_cpu in let nr_vcpus, vcpu_infos, cpumaps = D.get_vcpus rd.rd_dom maxinfo maplen in
- (* Got previous vcpu_infos for this domain? *) - let prev_vcpu_infos = - try Some (Hashtbl.find last_vcpu_info domid) + (* Got previous pcpu_usages for this domain? *) + let prev_pcpu_usages = + try Some (Hashtbl.find last_pcpu_usages domid) with Not_found -> None in - (* Update last_vcpu_info. *) - Hashtbl.replace last_vcpu_info domid vcpu_infos; - - (match prev_vcpu_infos with - | Some prev_vcpu_infos - when Array.length prev_vcpu_infos = Array.length vcpu_infos -> - Some (domid, name, nr_vcpus, vcpu_infos, prev_vcpu_infos, - cpumaps, maplen) + (* Update last_pcpu_usages. *) + Hashtbl.replace last_pcpu_usages domid pcpu_usages; + + (match prev_pcpu_usages with + | Some prev_pcpu_usages + when Array.length prev_pcpu_usages = Array.length pcpu_usages -> + Some (domid, name, nr_vcpus, vcpu_infos, pcpu_usages, + prev_pcpu_usages, cpumaps, maplen) | _ -> None (* ignore missing / unequal length prev_vcpu_infos *) ); with @@ -680,37 +686,15 @@ let collect, clear_pcpu_display_data = (* Rearrange the data into a matrix. Major axis (down) is * pCPUs. Minor axis (right) is domains. At each node we store: * cpu_time (on this pCPU only, nanosecs), - * average? (if set, then cpu_time is an average because the - * vCPU is pinned to more than one pCPU) - * running? (if set, we were instantaneously running on this pCPU) *) - let empty_node = (0L, false, false) in - let pcpus = Array.make_matrix nr_pcpus nr_doms empty_node in + let pcpus = Array.make_matrix nr_pcpus nr_doms 0L in
List.iteri ( - fun di (domid, name, nr_vcpus, vcpu_infos, prev_vcpu_infos, - cpumaps, maplen) -> + fun di (domid, name, nr_vcpus, vcpu_infos, pcpu_usages, + prev_pcpu_usages, cpumaps, maplen) -> (* Which pCPUs can this dom run on? *) - for v = 0 to nr_vcpus-1 do - let pcpu = vcpu_infos.(v).D.cpu in (* instantaneous pCPU *) - let nr_poss_pcpus = ref 0 in (* how many pcpus can it run on? *) - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - incr nr_poss_pcpus - done; - let nr_poss_pcpus = Int64.of_int !nr_poss_pcpus in - for p = 0 to nr_pcpus-1 do - (* vcpu v can reside on pcpu p *) - if C.cpu_usable cpumaps maplen v p then - let vcpu_time_on_pcpu = - vcpu_infos.(v).D.vcpu_time - -^ prev_vcpu_infos.(v).D.vcpu_time in - let vcpu_time_on_pcpu = - vcpu_time_on_pcpu /^ nr_poss_pcpus in - pcpus.(p).(di) <- - (vcpu_time_on_pcpu, nr_poss_pcpus > 1L, p = pcpu) - done + for p = 0 to Array.length pcpu_usages - 1 do + pcpus.(p).(di) <- (pcpu_usages.(p) -^ prev_pcpu_usages.(p)) done ) doms;
@@ -719,7 +703,7 @@ let collect, clear_pcpu_display_data = fun row -> let cpu_time = ref 0L in for di = 0 to Array.length row-1 do - let t, _, _ = row.(di) in + let t = row.(di) in cpu_time := !cpu_time +^ t done; Int64.to_float !cpu_time @@ -938,7 +922,7 @@ let redraw = let dom_names = String.concat "" ( List.map ( - fun (_, name, _, _, _, _, _) -> + fun (_, name, _, _, _, _, _, _) -> let len = String.length name in let width = max (len+1) 7 in pad width name @@ -957,8 +941,8 @@ let redraw = addch ' ';
List.iteri ( - fun di (domid, name, _, _, _, _, _) -> - let t, is_average, is_running = pcpus.(p).(di) in + fun di (domid, name, _, _, _, _, _, _) -> + let t = pcpus.(p).(di) in let len = String.length name in let width = max (len+1) 7 in let str = @@ -966,9 +950,7 @@ let redraw = else ( let t = Int64.to_float t in let percent = 100. *. t /. total_cpu_per_pcpu in - sprintf "%s%c%c " (Show.percent percent) - (if is_average then '=' else ' ') - (if is_running then '#' else ' ') + sprintf "%s " (Show.percent percent) ) in addstr (pad width str); ()
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

Hi, Eric Are any problem/suggestion with 3/5 4/5 5/5 of V4 patchset? If not, I will rebase them and resend them with tiny fixes applied. Thanks, Lai

On 02/08/2012 03:04 AM, Lai Jiangshan wrote:
Hi, Eric
Are any problem/suggestion with 3/5 4/5 5/5 of V4 patchset? If not, I will rebase them and resend them with tiny fixes applied.
Thanks, Lai Hi,
I believe you are sending these to the wrong Eric. Thanks. -- Eric Sauer Consultant, Red Hat Consulting Red Hat 773.257.3103 eric@redhat.com http://www.redhat.com

On 02/08/2012 01:21 PM, Eric Sauer wrote:
On 02/08/2012 03:04 AM, Lai Jiangshan wrote:
Hi, Eric
Are any problem/suggestion with 3/5 4/5 5/5 of V4 patchset? If not, I will rebase them and resend them with tiny fixes applied.
Thanks, Lai Hi,
I believe you are sending these to the wrong Eric.
Yes, they were intended for me (eblake, not eric); thankfully, I saw the mail through the list. We're sorry for any confusion that filled up your inbox :) -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> add virBitmapParseCommaSeparetedFormat() for parsing bitmap in comma separeted ascii format. This format of bitmap is used in Linux sysfs and cpuset. Changelog: - fixed typos. - fixed string scan routine. Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt_private.syms | 2 + src/nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 4 +++ src/util/bitmap.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d6ad36c..14b144f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -17,6 +17,7 @@ virBitmapFree; virBitmapGetBit; virBitmapSetBit; virBitmapString; +virBitmapParseCommaSeparatedFormat; # buf.h @@ -797,6 +798,7 @@ nodeGetCellsFreeMemory; nodeGetFreeMemory; nodeGetInfo; nodeGetMemoryStats; +nodeGetCPUmap; # nwfilter_conf.h diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..b0ebb88 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -47,6 +47,7 @@ #include "count-one-bits.h" #include "intprops.h" #include "virfile.h" +#include "bitmap.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -569,6 +570,33 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This functin parses + * it and returns bitmap. + */ +static virBitmapPtr linuxParseCPUmap(int *max_cpuid, const char *path) +{ + char str[1024]; + FILE *fp; + virBitmapPtr map = NULL; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto cleanup; + } + if (fgets(str, sizeof(str), fp) == NULL) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto cleanup; + } + map = virBitmapParseCommaSeparatedFormat(str, max_cpuid); + +cleanup: + VIR_FORCE_FCLOSE(fp); + return map; +} #endif int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +740,29 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif } +virBitmapPtr nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id, + const char *mapname) +{ +#ifdef __linux__ + char *path; + virBitmapPtr map; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + map = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return map; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1; +#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..7f26b77 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -26,6 +26,7 @@ # include "libvirt/libvirt.h" # include "capabilities.h" +# include "bitmap.h" int nodeGetInfo(virConnectPtr conn, virNodeInfoPtr nodeinfo); int nodeCapsInitNUMA(virCapsPtr caps); @@ -46,4 +47,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn); +virBitmapPtr nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/ diff --git a/src/util/bitmap.c b/src/util/bitmap.c index 8c326c4..6dead3b 100644 --- a/src/util/bitmap.c +++ b/src/util/bitmap.c @@ -33,6 +33,11 @@ #include "bitmap.h" #include "memory.h" #include "buf.h" +#include "c-ctype.h" +#include "util.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE struct _virBitmap { @@ -180,3 +185,63 @@ char *virBitmapString(virBitmapPtr bitmap) return virBufferContentAndReset(&buf); } + +/** + * virBitmapParseCommaSeparatedFormat: + * + * When bitmap is printed in ascii format, especially in Linux, + * comma-separated format is sometimes used. For example, a bitmap 11111011 is + * represetned as 0-4,6-7. This function parses comma-separated format + * and returns virBitmap. Found max bit is returned, too. + * + * This functions stops if characters other than digits, ',', '-' are + * found. This function will be useful for parsing linux's bitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) +{ + char *pos = buf; + virBitmapPtr map = NULL; + int val, x; + + /* at first, find the highest number */ + val = 0; + while ((*pos != '\n') && (*pos != 0)) { + if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + } else if ((*pos == ',') || (*pos == '-')) { + ++pos; + } else + break; + } + *max_bit = val; + + map = virBitmapAlloc(val + 1); + if (map == NULL) { /* we may return NULL at failure of parsing */ + virReportOOMError(); + return NULL; + } + + pos = buf; + while ((*pos != '\n') && (*pos != 0)) { + if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + if (virBitmapSetBit(map, val) < 0) + goto failed; + } else if (*pos == ',') { + ++pos; + } else if (*pos == '-') { + x = val; + pos++; + virStrToLong_i(pos, &pos, 10, &val); + for (;x <= val; ++x) { + if (virBitmapSetBit(map, x) < 0) + goto failed; + } + } else + break; + } + return map; +failed: + virBitmapFree(map); + return NULL; +} diff --git a/src/util/bitmap.h b/src/util/bitmap.h index ef62cca..e3d3e2b 100644 --- a/src/util/bitmap.h +++ b/src/util/bitmap.h @@ -61,5 +61,9 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) char *virBitmapString(virBitmapPtr bitmap) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; - +/* + * parese comma-separeted bitmap format and allocate virBitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) + ATTRIBUTE_RETURN_CHECK; #endif -- 1.7.4.4

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> * Now, only "cpu_time" is supported. * cpuacct cgroup is used for providing percpu cputime information. * include/libvirt/libvirt.h.in - defines VIR_DOMAIN_CPU_STATS_CPUTIME * src/qemu/qemu.conf - take care of cpuacct cgroup. * src/qemu/qemu_conf.c - take care of cpuacct cgroup. * src/qemu/qemu_driver.c - added an interface * src/util/cgroup.c/h - added interface for getting percpu cputime Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/qemu/qemu.conf | 3 +- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/cgroup.c | 6 ++ src/util/cgroup.h | 1 + 5 files changed, 168 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 95428c1..db07b8a 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -166,6 +166,7 @@ # - 'memory' - use for memory tunables # - 'blkio' - use for block devices I/O tunables # - 'cpuset' - use for CPUs and memory nodes +# - cpuacct - use for CPUs statistics. # # NB, even if configured here, they won't be used unless # the administrator has mounted cgroups, e.g.: @@ -177,7 +178,7 @@ # can be mounted in different locations. libvirt will detect # where they are located. # -# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset" ] +# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset", "cpuacct" ] # This is the basic set of devices allowed / required by # all virtual machines. diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index e95c7a5..a709cbf 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -318,7 +318,8 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, (1 << VIR_CGROUP_CONTROLLER_DEVICES) | (1 << VIR_CGROUP_CONTROLLER_MEMORY) | (1 << VIR_CGROUP_CONTROLLER_BLKIO) | - (1 << VIR_CGROUP_CONTROLLER_CPUSET); + (1 << VIR_CGROUP_CONTROLLER_CPUSET) | + (1 << VIR_CGROUP_CONTROLLER_CPUACCT); } for (i = 0 ; i < VIR_CGROUP_CONTROLLER_LAST ; i++) { if (driver->cgroupControllers & (1 << i)) { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 0cac6a5..7aa9d33 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12036,6 +12036,162 @@ cleanup: return ret; } + +/* qemuDomainGetCPUStats() with start_cpu == -1 */ +static int qemuDomainGetTotalcpuStats(virDomainPtr domain ATTRIBUTE_UNUSED, + virCgroupPtr group, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + unsigned long long cpu_time; + int param_idx = 0; + int ret; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1); + + if (nparams == 0) /* return supprted number of params */ + return 1; + /* entry 0 is cputime */ + ret = virCgroupGetCpuacctUsage(group, &cpu_time); + if (ret < 0) { + virReportSystemError(-ret, "%s", _("unable to get cpu account")); + return -1; + } + + virTypedParameterAssign(¶ms[param_idx], VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + return param_idx + 1; +} + +static int qemuDomainGetPercpuStats(virDomainPtr domain, + virCgroupPtr group, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + virBitmapPtr map = NULL; + int rv = -1; + int i, max_id; + char *pos; + char *buf = NULL; + virTypedParameterPtr ent; + int param_idx; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1); + /* return the number of supported params ? */ + if (nparams == 0 && ncpus != 0) + return 1; /* only cpu_time is supported */ + + /* return percpu cputime in index 0 */ + param_idx = 0; + /* to parse account file, we need "present" cpu map */ + map = nodeGetCPUmap(domain->conn, &max_id, "present"); + if (!map) + return rv; + + if (ncpus == 0) { /* returns max cpu ID */ + rv = max_id; + goto cleanup; + } + /* we get percpu cputime accoutning info. */ + if (virCgroupGetCpuacctPercpuUsage(group, &buf)) + goto cleanup; + pos = buf; + + if (max_id > start_cpu + ncpus - 1) + max_id = start_cpu + ncpus - 1; + + for (i = 0; i <= max_id; i++) { + bool exist; + unsigned long long cpu_time; + + if (virBitmapGetBit(map, i, &exist) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, _("bitmap parse error")); + goto cleanup; + } + if (!exist) + continue; + if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cpuacct parse error")); + goto cleanup; + } + if (i < start_cpu) + continue; + ent = ¶ms[ (i - start_cpu) * nparams + param_idx]; + virTypedParameterAssign(ent, VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + } + rv = param_idx + 1; +cleanup: + VIR_FREE(buf); + virBitmapFree(map); + return rv; +} + + +static int +qemuDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct qemud_driver *driver = domain->conn->privateData; + virCgroupPtr group = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + bool isActive; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1); + + qemuDriverLock(driver); + + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (vm == NULL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("No such domain %s"), domain->uuid); + goto cleanup; + } + + isActive = virDomainObjIsActive(vm); + if (!isActive) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + if (!qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_CPUACCT)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cgroup CPUACCT controller is not mounted")); + goto cleanup; + } + + if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) != 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find cgroup for domain %s"), vm->def->name); + goto cleanup; + } + + if (nparams == 0 && ncpus == 0) /* returns max cpu id */ + ret = qemuDomainGetPercpuStats(domain, group, NULL, 0, 0, 0, flags); + else if (start_cpu == -1) /* get total */ + ret = qemuDomainGetTotalcpuStats(domain, group, params, nparams, flags); + else + ret = qemuDomainGetPercpuStats(domain, group, params, nparams, + start_cpu, ncpus ,flags); +cleanup: + virCgroupFree(&group); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + static virDriver qemuDriver = { .no = VIR_DRV_QEMU, .name = "QEMU", @@ -12193,6 +12349,7 @@ static virDriver qemuDriver = { .domainGetDiskErrors = qemuDomainGetDiskErrors, /* 0.9.10 */ .domainSetMetadata = qemuDomainSetMetadata, /* 0.9.10 */ .domainGetMetadata = qemuDomainGetMetadata, /* 0.9.10 */ + .domainGetCPUStats = qemuDomainGetCPUStats, /* 0.9.10 */ }; diff --git a/src/util/cgroup.c b/src/util/cgroup.c index 15b870d..2d34c50 100644 --- a/src/util/cgroup.c +++ b/src/util/cgroup.c @@ -1555,6 +1555,12 @@ int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage) "cpuacct.usage", usage); } +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage) +{ + return virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPUACCT, + "cpuacct.usage_percpu", usage); +} + int virCgroupSetFreezerState(virCgroupPtr group, const char *state) { return virCgroupSetValueStr(group, diff --git a/src/util/cgroup.h b/src/util/cgroup.h index 8d75735..b4e0f37 100644 --- a/src/util/cgroup.h +++ b/src/util/cgroup.h @@ -115,6 +115,7 @@ int virCgroupSetCpuCfsQuota(virCgroupPtr group, long long cfs_quota); int virCgroupGetCpuCfsQuota(virCgroupPtr group, long long *cfs_quota); int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage); +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage); int virCgroupSetFreezerState(virCgroupPtr group, const char *state); int virCgroupGetFreezerState(virCgroupPtr group, char **state); -- 1.7.4.4

On 02/09/2012 03:43 AM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
* Now, only "cpu_time" is supported. * cpuacct cgroup is used for providing percpu cputime information.
* include/libvirt/libvirt.h.in - defines VIR_DOMAIN_CPU_STATS_CPUTIME * src/qemu/qemu.conf - take care of cpuacct cgroup. * src/qemu/qemu_conf.c - take care of cpuacct cgroup. * src/qemu/qemu_driver.c - added an interface * src/util/cgroup.c/h - added interface for getting percpu cputime
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/qemu/qemu.conf | 3 +- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/cgroup.c | 6 ++ src/util/cgroup.h | 1 + 5 files changed, 168 insertions(+), 2 deletions(-)
diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 95428c1..db07b8a 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -166,6 +166,7 @@ # - 'memory' - use for memory tunables # - 'blkio' - use for block devices I/O tunables # - 'cpuset' - use for CPUs and memory nodes +# - cpuacct - use for CPUs statistics.
Needs consistent quoting with lines above.
+ +/* qemuDomainGetCPUStats() with start_cpu == -1 */ +static int qemuDomainGetTotalcpuStats(virDomainPtr domain ATTRIBUTE_UNUSED, + virCgroupPtr group, + virTypedParameterPtr params, + int nparams, + unsigned int flags)
Formatting nit: these days, we are preferring to start new function names on column 1, with the return type on the previous line, as in: static int qemuDomainGetTotalCpuStats(...) Why are you passing an unused parameter through to a static function? The only time that should happen is in a callback interface, but you are directly calling this function, so I'd prune the parameter instead.
+{ + unsigned long long cpu_time; + int param_idx = 0; + int ret; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1);
No need to re-check flags; we already checked it up front in the primary entry point. At which point, you probably don't need the flags parameter in this function.
+ + if (nparams == 0) /* return supprted number of params */
s/supprted/supported/
+ return 1; + /* entry 0 is cputime */ + ret = virCgroupGetCpuacctUsage(group, &cpu_time); + if (ret < 0) { + virReportSystemError(-ret, "%s", _("unable to get cpu account")); + return -1; + } + + virTypedParameterAssign(¶ms[param_idx], VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + return param_idx + 1;
Why are you using the 'param_idx' variable, which is always 0? I'd simplify this to just 'return 1;', and worry about things if we ever add additional parameters later.
+} + +static int qemuDomainGetPercpuStats(virDomainPtr domain, + virCgroupPtr group, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + virBitmapPtr map = NULL; + int rv = -1; + int i, max_id; + char *pos; + char *buf = NULL; + virTypedParameterPtr ent; + int param_idx; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1);
Again, why check flags here? It should have already been done in the caller.
+ /* return the number of supported params ? */ + if (nparams == 0 && ncpus != 0) + return 1; /* only cpu_time is supported */ + + /* return percpu cputime in index 0 */ + param_idx = 0; + /* to parse account file, we need "present" cpu map */ + map = nodeGetCPUmap(domain->conn, &max_id, "present");
I'm wondering if you can just use virDomainCpuSetParse to get a cpumap, and then use macros like VIR_CPU_USABLE in parsing that map, rather than going through virBitmap.
+ if (!map) + return rv; + + if (ncpus == 0) { /* returns max cpu ID */ + rv = max_id; + goto cleanup; + } + /* we get percpu cputime accoutning info. */
s/accoutning/accounting/
+ if (virCgroupGetCpuacctPercpuUsage(group, &buf)) + goto cleanup; + pos = buf; + + if (max_id > start_cpu + ncpus - 1) + max_id = start_cpu + ncpus - 1; + + for (i = 0; i <= max_id; i++) { + bool exist; + unsigned long long cpu_time; + + if (virBitmapGetBit(map, i, &exist) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, _("bitmap parse error")); + goto cleanup; + } + if (!exist) + continue; + if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cpuacct parse error")); + goto cleanup; + } + if (i < start_cpu) + continue; + ent = ¶ms[ (i - start_cpu) * nparams + param_idx]; + virTypedParameterAssign(ent, VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + } + rv = param_idx + 1;
This part looks right, even if we aren't incrementing param_idx until some future date when we add a second parameter.
+cleanup: + VIR_FREE(buf); + virBitmapFree(map); + return rv; +} + + +static int +qemuDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct qemud_driver *driver = domain->conn->privateData; + virCgroupPtr group = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + bool isActive; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1); + + qemuDriverLock(driver); + + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (vm == NULL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("No such domain %s"), domain->uuid); + goto cleanup; + } + + isActive = virDomainObjIsActive(vm); + if (!isActive) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + if (!qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_CPUACCT)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cgroup CPUACCT controller is not mounted")); + goto cleanup; + } + + if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) != 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find cgroup for domain %s"), vm->def->name); + goto cleanup; + } + + if (nparams == 0 && ncpus == 0) /* returns max cpu id */ + ret = qemuDomainGetPercpuStats(domain, group, NULL, 0, 0, 0, flags); + else if (start_cpu == -1) /* get total */ + ret = qemuDomainGetTotalcpuStats(domain, group, params, nparams, flags); + else + ret = qemuDomainGetPercpuStats(domain, group, params, nparams, + start_cpu, ncpus ,flags);
Formatting nit: s/ ,/, /
+cleanup: + virCgroupFree(&group); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + static virDriver qemuDriver = { .no = VIR_DRV_QEMU, .name = "QEMU", @@ -12193,6 +12349,7 @@ static virDriver qemuDriver = { .domainGetDiskErrors = qemuDomainGetDiskErrors, /* 0.9.10 */ .domainSetMetadata = qemuDomainSetMetadata, /* 0.9.10 */ .domainGetMetadata = qemuDomainGetMetadata, /* 0.9.10 */ + .domainGetCPUStats = qemuDomainGetCPUStats, /* 0.9.10 */
We've now missed 0.9.10, so this should be 0.9.11. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Total: cpu_time 15.8 CPU0: cpu_time 8.6 CPU1: cpu_time 3.5 CPU2: cpu_time 2.4 CPU3: cpu_time 1.3 --- tools/virsh.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 5 ++ 2 files changed, 121 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 66ba61c..e9d2f86 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5385,6 +5385,121 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) } /* + * "cputime" command + */ +static const vshCmdInfo info_cpu_accts[] = { + {"help", N_("show domain cpu accounting information")}, + {"desc", N_("Returns information about the domain's cpu accounting")}, + {NULL, NULL}, +}; + +static const vshCmdOptDef opts_cpu_accts[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {NULL, 0, 0, NULL}, +}; + +static bool +cmdCPUAccts(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + virTypedParameterPtr params = NULL; + bool ret = true; + int i, j, pos, cpu, nparams; + int max_id; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + /* get max cpu id on the node */ + max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0); + if (max_id < 0) { + vshPrint(ctl, "max id %d\n", max_id); + goto cleanup; + } + /* get supported num of parameter for total statistics */ + nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + if (VIR_ALLOC_N(params, nparams)) { + virReportOOMError(); + goto cleanup; + } + /* passing start_cpu == -1 gives us domain's total status */ + nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + vshPrint(ctl, "Total:\n"); + for (i = 0; i < nparams; i++) { + vshPrint(ctl, "\t%-15s ", params[i].field); + switch (params[i].type) { + case VIR_TYPED_PARAM_ULLONG: + /* Now all UULONG param is used for time accounting in ns */ + vshPrint(ctl, "%.1lf\n", + params[i].value.ul / 1000000000.0); + break; + default: + vshError(ctl, "%s", _("API mismatch?")); + goto cleanup; + } + } + VIR_FREE(params); + /* get percpu information */ + nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + cpu = 0; + while (cpu <= max_id) { + int ncpus = 128; + + if (cpu + ncpus - 1 > max_id) /* id starts from 0. */ + ncpus = max_id + 1 - cpu; + + if (VIR_ALLOC_N(params, nparams * ncpus)) { + virReportOOMError(); + goto cleanup; + } + + if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0) < 0) + goto cleanup; + + for (i = 0; i < ncpus; i++) { + if (params[i * nparams].type == 0) + continue; + vshPrint(ctl, "CPU%d:\n", cpu + i); + + for (j = 0; j < nparams; j++) { + pos = i * nparams + j; + if (params[pos].type == 0) + continue; + vshPrint(ctl, "\t%-15s ", params[pos].field); + switch(params[j].type) { + case VIR_TYPED_PARAM_ULLONG: + vshPrint(ctl, "%.1lf\n", + params[pos].value.ul / 1000000000.0); + break; + default: + vshError(ctl, "%s", _("API mismatch?")); + goto cleanup; + } + } + } + cpu += ncpus; + /* Note: If we handle string type, it should be freed */ + VIR_FREE(params); + } +cleanup: + VIR_FREE(params); + virDomainFree(dom); + return ret; +} + +/* * "inject-nmi" command */ static const vshCmdInfo info_inject_nmi[] = { @@ -16441,6 +16556,7 @@ static const vshCmdDef domManagementCmds[] = { #endif {"cpu-baseline", cmdCPUBaseline, opts_cpu_baseline, info_cpu_baseline, 0}, {"cpu-compare", cmdCPUCompare, opts_cpu_compare, info_cpu_compare, 0}, + {"cpu-accts", cmdCPUAccts, opts_cpu_accts, info_cpu_accts, 0}, {"create", cmdCreate, opts_create, info_create, 0}, {"define", cmdDefine, opts_define, info_define, 0}, {"desc", cmdDesc, opts_desc, info_desc, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index bd79b4c..2b2f70b 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -762,6 +762,11 @@ Provide the maximum number of virtual CPUs supported for a guest VM on this connection. If provided, the I<type> parameter must be a valid type attribute for the <domain> element of XML. +=item B<cpu-accts> I<domain> + +Provide cpu accounting information of a domain. The domain should +be running. + =item B<migrate> [I<--live>] [I<--direct>] [I<--p2p> [I<--tunnelled>]] [I<--persistent>] [I<--undefinesource>] [I<--suspend>] [I<--copy-storage-all>] [I<--copy-storage-inc>] [I<--change-protection>] [I<--verbose>] -- 1.7.4.4

On 02/09/2012 03:43 AM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Total: cpu_time 15.8 CPU0: cpu_time 8.6 CPU1: cpu_time 3.5 CPU2: cpu_time 2.4 CPU3: cpu_time 1.3
We should align the output so all '.' are in the same column. Given that the kernel gives us more than just tenths of a second, should we be exposing more granularity to the user?
/* + * "cputime" command
Wrong comment, since you installed it under "cpu-accts".
+ */ +static const vshCmdInfo info_cpu_accts[] = {
But I still don't like that name, either; since we're wrapping virDomainGetCPUStats, I think a better name would be "cpu-stats".
+ {"help", N_("show domain cpu accounting information")},
Here, I'd use "show domain cpu usage statistics",
+ {"desc", N_("Returns information about the domain's cpu accounting")},
and for the longer text, maybe "Display information about the domain's CPU usage, including per-CPU accounting"
+ {NULL, NULL}, +}; + +static const vshCmdOptDef opts_cpu_accts[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {NULL, 0, 0, NULL},
Should we expose the ability to pass in '--all' or an optional '--start=n --count=n' to let the user limit how much input they want? Given your above example, virsh cpu-stats dom => Stats for all CPUs, and a total virsh cpu-stats dom --all => Only the total stats virsh cpu-stats dom --start=2 => Only the per-cpu stats, for CPU 2 to the end virsh cpu-stats dom --start=2 --count=1 => Only the per-cpu stats of just CPU 2
+}; + +static bool +cmdCPUAccts(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + virTypedParameterPtr params = NULL; + bool ret = true; + int i, j, pos, cpu, nparams; + int max_id; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + /* get max cpu id on the node */ + max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0); + if (max_id < 0) { + vshPrint(ctl, "max id %d\n", max_id);
If max_id is less than 0, then it will be -1 and the command failed; we should print out the libvirt error at that point.
+ goto cleanup; + } + /* get supported num of parameter for total statistics */ + nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + if (VIR_ALLOC_N(params, nparams)) { + virReportOOMError(); + goto cleanup; + } + /* passing start_cpu == -1 gives us domain's total status */ + nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + vshPrint(ctl, "Total:\n"); + for (i = 0; i < nparams; i++) { + vshPrint(ctl, "\t%-15s ", params[i].field); + switch (params[i].type) { + case VIR_TYPED_PARAM_ULLONG: + /* Now all UULONG param is used for time accounting in ns */
That may not be true in the future. I'd rather see this specifically do a STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_CPUTIME) before reformatting the output...
+ vshPrint(ctl, "%.1lf\n", + params[i].value.ul / 1000000000.0); + break; + default: + vshError(ctl, "%s", _("API mismatch?")); + goto cleanup;
...and the default, rather than erroring out, should instead use vshGetTypedParamValue and output the name/value pair for all statistics that were unknown by this version of virsh but which may have been added in the server.
+ } + } + VIR_FREE(params);
Memory leak if you don't use virTypedParameterArrayClear first. Just because we don't pass strings back now doesn't mean we won't do it in the future.
+ /* get percpu information */ + nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + cpu = 0; + while (cpu <= max_id) { + int ncpus = 128; + + if (cpu + ncpus - 1 > max_id) /* id starts from 0. */ + ncpus = max_id + 1 - cpu; + + if (VIR_ALLOC_N(params, nparams * ncpus)) {
Why are we re-allocating this each time through the loop? It should be enough to allocate it once before the loop, and reuse it thereafter.
+ virReportOOMError(); + goto cleanup; + } + + if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0) < 0) + goto cleanup; + + for (i = 0; i < ncpus; i++) { + if (params[i * nparams].type == 0) + continue;
This shouldn't be possible (if it is true, we have a bug in our server).
+ vshPrint(ctl, "CPU%d:\n", cpu + i); + + for (j = 0; j < nparams; j++) { + pos = i * nparams + j; + if (params[pos].type == 0) + continue;
This shouldn't be possible (if it is true, we have a bug in our server).
+ vshPrint(ctl, "\t%-15s ", params[pos].field); + switch(params[j].type) { + case VIR_TYPED_PARAM_ULLONG: + vshPrint(ctl, "%.1lf\n", + params[pos].value.ul / 1000000000.0); + break; + default: + vshError(ctl, "%s", _("API mismatch?")); + goto cleanup;
Same story about formatting - we want to format all name/value pairs, even those that were added in the server after this version of virsh was compiled; and we can only do the conversion from nanoseconds to fractional seconds if the field name is known.
+ } + } + } + cpu += ncpus; + /* Note: If we handle string type, it should be freed */ + VIR_FREE(params);
Your comment was right - you are missing a call to virTypedParameterArrayClear. And even if you hoist the array allocation outside of the loop, you still need an array clear in the loop between successive iterations.
+ } +cleanup: + VIR_FREE(params); + virDomainFree(dom); + return ret; +} + +/* * "inject-nmi" command */ static const vshCmdInfo info_inject_nmi[] = { @@ -16441,6 +16556,7 @@ static const vshCmdDef domManagementCmds[] = { #endif {"cpu-baseline", cmdCPUBaseline, opts_cpu_baseline, info_cpu_baseline, 0}, {"cpu-compare", cmdCPUCompare, opts_cpu_compare, info_cpu_compare, 0}, + {"cpu-accts", cmdCPUAccts, opts_cpu_accts, info_cpu_accts, 0}, {"create", cmdCreate, opts_create, info_create, 0}, {"define", cmdDefine, opts_define, info_define, 0}, {"desc", cmdDesc, opts_desc, info_desc, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index bd79b4c..2b2f70b 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -762,6 +762,11 @@ Provide the maximum number of virtual CPUs supported for a guest VM on this connection. If provided, the I<type> parameter must be a valid type attribute for the <domain> element of XML.
+=item B<cpu-accts> I<domain> + +Provide cpu accounting information of a domain. The domain should +be running. + =item B<migrate> [I<--live>] [I<--direct>] [I<--p2p> [I<--tunnelled>]] [I<--persistent>] [I<--undefinesource>] [I<--suspend>] [I<--copy-storage-all>] [I<--copy-storage-inc>] [I<--change-protection>] [I<--verbose>]
-- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Changelog: - fixed typos. - fixed string scan routine. Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt_private.syms | 1 + src/nodeinfo.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 3 + 3 files changed, 96 insertions(+), 0 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0c22dec..6e99243 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -792,6 +792,7 @@ virNodeDeviceObjUnlock; # nodeinfo.h nodeCapsInitNUMA; +nodeGetCPUmap; nodeGetCPUStats; nodeGetCellsFreeMemory; nodeGetFreeMemory; diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..fc1aaea 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -31,6 +31,7 @@ #include <dirent.h> #include <sys/utsname.h> #include <sched.h> +#include <conf/domain_conf.h> #if HAVE_NUMACTL # define NUMA_VERSION1_COMPATIBILITY 1 @@ -569,6 +570,73 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This function parses + * it and returns cpumap. + */ +static const char * +linuxParseCPUmap(int *max_cpuid, const char *path) +{ + FILE *fp; + char *map = NULL; + char *str = NULL; + size_t size = 128, pos = 0; + int max_id, i; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto error; + } + + if (VIR_ALLOC_N(str, size) < 0) { + virReportOOMError(); + goto error; + } + for (;;) { + pos += fread(str + pos, 1, size - pos, fp); + if (pos < size) + break; + + size = size << 1; + if (VIR_REALLOC_N(str, size) < 0) { + virReportOOMError(); + goto error; + } + } + if (pos == 0) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto error; + } + str[pos - 1] = 0; + + if (VIR_ALLOC_N(map, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportOOMError(); + goto error; + } + if (virDomainCpuSetParse(str, 0, map, + VIR_DOMAIN_CPUMASK_LEN) < 0) { + goto error; + } + + for (i = 0; i < VIR_DOMAIN_CPUMASK_LEN; i++) { + if (map[i]) { + max_id = i; + } + } + *max_cpuid = max_id; + + VIR_FORCE_FCLOSE(fp); + return map; + +error: + VIR_FORCE_FCLOSE(fp); + VIR_FREE(str); + VIR_FREE(map); + return NULL; +} #endif int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +780,30 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif } +const char * +nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id ATTRIBUTE_UNUSED, + const char *mapname ATTRIBUTE_UNUSED) +{ +#ifdef __linux__ + char *path; + const char *cpumap; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + cpumap = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return cpumap; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1; +#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..852e19d 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -46,4 +46,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn); +const char *nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/ -- 1.7.4.4

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> * Now, only "cpu_time" is supported. * cpuacct cgroup is used for providing percpu cputime information. * include/libvirt/libvirt.h.in - defines VIR_DOMAIN_CPU_STATS_CPUTIME * src/qemu/qemu.conf - take care of cpuacct cgroup. * src/qemu/qemu_conf.c - take care of cpuacct cgroup. * src/qemu/qemu_driver.c - added an interface * src/util/cgroup.c/h - added interface for getting percpu cputime Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/qemu/qemu.conf | 3 +- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/cgroup.c | 6 ++ src/util/cgroup.h | 1 + 5 files changed, 158 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 95428c1..cb87728 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -166,6 +166,7 @@ # - 'memory' - use for memory tunables # - 'blkio' - use for block devices I/O tunables # - 'cpuset' - use for CPUs and memory nodes +# - 'cpuacct' - use for CPUs statistics. # # NB, even if configured here, they won't be used unless # the administrator has mounted cgroups, e.g.: @@ -177,7 +178,7 @@ # can be mounted in different locations. libvirt will detect # where they are located. # -# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset" ] +# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset", "cpuacct" ] # This is the basic set of devices allowed / required by # all virtual machines. diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index e95c7a5..a709cbf 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -318,7 +318,8 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, (1 << VIR_CGROUP_CONTROLLER_DEVICES) | (1 << VIR_CGROUP_CONTROLLER_MEMORY) | (1 << VIR_CGROUP_CONTROLLER_BLKIO) | - (1 << VIR_CGROUP_CONTROLLER_CPUSET); + (1 << VIR_CGROUP_CONTROLLER_CPUSET) | + (1 << VIR_CGROUP_CONTROLLER_CPUACCT); } for (i = 0 ; i < VIR_CGROUP_CONTROLLER_LAST ; i++) { if (driver->cgroupControllers & (1 << i)) { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e2e73b7..e7871ab 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12078,6 +12078,152 @@ cleanup: return ret; } + +/* qemuDomainGetCPUStats() with start_cpu == -1 */ +static int +qemuDomainGetTotalcpuStats(virCgroupPtr group, + virTypedParameterPtr params, + int nparams) +{ + unsigned long long cpu_time; + int param_idx = 0; + int ret; + + if (nparams == 0) /* return supported number of params */ + return 1; + /* entry 0 is cputime */ + ret = virCgroupGetCpuacctUsage(group, &cpu_time); + if (ret < 0) { + virReportSystemError(-ret, "%s", _("unable to get cpu account")); + return -1; + } + + virTypedParameterAssign(¶ms[param_idx], VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + return 1; +} + +static int qemuDomainGetPercpuStats(virDomainPtr domain, + virCgroupPtr group, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus) +{ + const char *map = NULL; + int rv = -1; + int i, max_id; + char *pos; + char *buf = NULL; + virTypedParameterPtr ent; + int param_idx; + + /* return the number of supported params ? */ + if (nparams == 0 && ncpus != 0) + return 1; /* only cpu_time is supported */ + + /* return percpu cputime in index 0 */ + param_idx = 0; + /* to parse account file, we need "present" cpu map */ + map = nodeGetCPUmap(domain->conn, &max_id, "present"); + if (!map) + return rv; + + if (ncpus == 0) { /* returns max cpu ID */ + rv = max_id; + goto cleanup; + } + /* we get percpu cputime accounting info. */ + if (virCgroupGetCpuacctPercpuUsage(group, &buf)) + goto cleanup; + pos = buf; + + if (max_id > start_cpu + ncpus - 1) + max_id = start_cpu + ncpus - 1; + + for (i = 0; i <= max_id; i++) { + unsigned long long cpu_time; + + if (!map[i]) + continue; + if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cpuacct parse error")); + goto cleanup; + } + if (i < start_cpu) + continue; + ent = ¶ms[ (i - start_cpu) * nparams + param_idx]; + virTypedParameterAssign(ent, VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + } + rv = param_idx + 1; +cleanup: + VIR_FREE(buf); + VIR_FREE(map); + return rv; +} + + +static int +qemuDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct qemud_driver *driver = domain->conn->privateData; + virCgroupPtr group = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + bool isActive; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1); + + qemuDriverLock(driver); + + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (vm == NULL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("No such domain %s"), domain->uuid); + goto cleanup; + } + + isActive = virDomainObjIsActive(vm); + if (!isActive) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + if (!qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_CPUACCT)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cgroup CPUACCT controller is not mounted")); + goto cleanup; + } + + if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) != 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find cgroup for domain %s"), vm->def->name); + goto cleanup; + } + + if (nparams == 0 && ncpus == 0) /* returns max cpu id */ + ret = qemuDomainGetPercpuStats(domain, group, NULL, 0, 0, 0); + else if (start_cpu == -1) /* get total */ + ret = qemuDomainGetTotalcpuStats(group, params, nparams); + else + ret = qemuDomainGetPercpuStats(domain, group, params, nparams, + start_cpu, ncpus); +cleanup: + virCgroupFree(&group); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + static virDriver qemuDriver = { .no = VIR_DRV_QEMU, .name = "QEMU", @@ -12235,6 +12381,7 @@ static virDriver qemuDriver = { .domainGetDiskErrors = qemuDomainGetDiskErrors, /* 0.9.10 */ .domainSetMetadata = qemuDomainSetMetadata, /* 0.9.10 */ .domainGetMetadata = qemuDomainGetMetadata, /* 0.9.10 */ + .domainGetCPUStats = qemuDomainGetCPUStats, /* 0.9.11 */ }; diff --git a/src/util/cgroup.c b/src/util/cgroup.c index 15b870d..2d34c50 100644 --- a/src/util/cgroup.c +++ b/src/util/cgroup.c @@ -1555,6 +1555,12 @@ int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage) "cpuacct.usage", usage); } +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage) +{ + return virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPUACCT, + "cpuacct.usage_percpu", usage); +} + int virCgroupSetFreezerState(virCgroupPtr group, const char *state) { return virCgroupSetValueStr(group, diff --git a/src/util/cgroup.h b/src/util/cgroup.h index 8d75735..b4e0f37 100644 --- a/src/util/cgroup.h +++ b/src/util/cgroup.h @@ -115,6 +115,7 @@ int virCgroupSetCpuCfsQuota(virCgroupPtr group, long long cfs_quota); int virCgroupGetCpuCfsQuota(virCgroupPtr group, long long *cfs_quota); int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage); +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage); int virCgroupSetFreezerState(virCgroupPtr group, const char *state); int virCgroupGetFreezerState(virCgroupPtr group, char **state); -- 1.7.4.4

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Total: cpu_time 46.534 CPU0: cpu_time 8.596 CPU1: cpu_time 12.667 CPU2: cpu_time 16.027 CPU3: cpu_time 9.244 Changed from V5: add --all, --start, --count option Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- tools/virsh.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 8 +++ 2 files changed, 162 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 66ba61c..dd55fbf 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5385,6 +5385,159 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) } /* + * "cpu-stats" command + */ +static const vshCmdInfo info_cpu_stats[] = { + {"help", N_("show domain cpu statistics")}, + {"desc", N_("Display statistics about the domain's CPUs, including per-CPU statistics.")}, + {NULL, NULL}, +}; + +static const vshCmdOptDef opts_cpu_stats[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"all", VSH_OT_BOOL, 0, N_("Show total statistics only")}, + {"start", VSH_OT_INT, 0, N_("Show statistics from this CPU")}, + {"count", VSH_OT_INT, 0, N_("Number of shown CPUs at most")}, + {NULL, 0, 0, NULL}, +}; + +static bool +cmdCPUStats(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + virTypedParameterPtr params = NULL; + int i, j, pos, max_id, cpu = -1, show_count = -1, nparams; + bool show_all = false, show_per_cpu = false; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + show_all = vshCommandOptBool(cmd, "all"); + if (vshCommandOptInt(cmd, "start", &cpu) > 0) + show_per_cpu = true; + if (vshCommandOptInt(cmd, "count", &show_count) > 0) + show_per_cpu = true; + + /* default show per_cpu and total */ + if (!show_all && !show_per_cpu) { + show_all = true; + show_per_cpu = true; + } + + if (!show_all) + goto do_show_per_cpu; + + /* get supported num of parameter for total statistics */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) + goto failed_stats; + if (VIR_ALLOC_N(params, nparams)) + goto failed_params; + + /* passing start_cpu == -1 gives us domain's total status */ + if ((nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0)) < 0) + goto failed_stats; + + vshPrint(ctl, "Total:\n"); + for (i = 0; i < nparams; i++) { + vshPrint(ctl, "\t%-10s ", params[i].field); + if (STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { + if (params[i].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%12.3lf\n", + params[i].value.ul / 1000000000.0); + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s); + VIR_FREE(s); + } + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, _("%s\n"), s); + VIR_FREE(s); + } + } + virTypedParameterArrayClear(params, nparams); + VIR_FREE(params); + + if (!show_per_cpu) /* show all stats only */ + goto cleanup; + +do_show_per_cpu: + /* check cpu, show_count, and ignore wrong argument */ + if (cpu < 0) + cpu = 0; + if (show_count < 0) + show_count = INT_MAX; + + /* get max cpu id on the node */ + if ((max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0)) < 0) + goto failed_stats; + /* get percpu information */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) + goto failed_stats; + + if (VIR_ALLOC_N(params, nparams * 128)) + goto failed_params; + + while (cpu <= max_id) { + int ncpus = 128; + + if (cpu + ncpus - 1 > max_id) /* id starts from 0. */ + ncpus = max_id + 1 - cpu; + + if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0) < 0) + goto failed_stats; + + for (i = 0; i < ncpus; i++) { + if (params[i * nparams].type == 0) /* this cpu is not in the map */ + continue; + vshPrint(ctl, "CPU%d:\n", cpu + i); + + for (j = 0; j < nparams; j++) { + pos = i * nparams + j; + vshPrint(ctl, "\t%-10s ", params[pos].field); + if (STREQ(params[pos].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { + if (params[j].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%12.3lf\n", + params[pos].value.ul / 1000000000.0); + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); + vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s); + VIR_FREE(s); + } + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); + vshPrint(ctl, _("%s\n"), s); + VIR_FREE(s); + } + } + + if (--show_count <= 0) /* mark done to exit the outmost loop */ + cpu = max_id; + } + cpu += ncpus; + virTypedParameterArrayClear(params, nparams * ncpus); + } + VIR_FREE(params); +cleanup: + virDomainFree(dom); + return true; + +failed_params: + virReportOOMError(); + virDomainFree(dom); + return false; + +failed_stats: + vshError(ctl, _("Failed to virDomainGetCPUStats()\n")); + VIR_FREE(params); + virDomainFree(dom); + return false; +} + +/* * "inject-nmi" command */ static const vshCmdInfo info_inject_nmi[] = { @@ -16441,6 +16594,7 @@ static const vshCmdDef domManagementCmds[] = { #endif {"cpu-baseline", cmdCPUBaseline, opts_cpu_baseline, info_cpu_baseline, 0}, {"cpu-compare", cmdCPUCompare, opts_cpu_compare, info_cpu_compare, 0}, + {"cpu-stats", cmdCPUStats, opts_cpu_stats, info_cpu_stats, 0}, {"create", cmdCreate, opts_create, info_create, 0}, {"define", cmdDefine, opts_define, info_define, 0}, {"desc", cmdDesc, opts_desc, info_desc, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index 6730b8b..a90361c 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -763,6 +763,14 @@ Provide the maximum number of virtual CPUs supported for a guest VM on this connection. If provided, the I<type> parameter must be a valid type attribute for the <domain> element of XML. +=item B<cpu-stats> I<domain> [I<--all>] [I<start>] [I<count>] + +Provide cpu statistics information of a domain. The domain should +be running. Default it shows stats for all CPUs, and a total. Use +I<--all> for only the total stats, I<start> for only the per-cpu +stats of the CPUs from I<start>, I<count> for only I<count> CPUs' +stats. + =item B<migrate> [I<--live>] [I<--direct>] [I<--p2p> [I<--tunnelled>]] [I<--persistent>] [I<--undefinesource>] [I<--suspend>] [I<--copy-storage-all>] [I<--copy-storage-inc>] [I<--change-protection>] [I<--verbose>] -- 1.7.4.4

Hi, Eric Did you received these new patches? Thanks, Lai On 02/16/2012 03:15 PM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Changelog: - fixed typos. - fixed string scan routine.
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt_private.syms | 1 + src/nodeinfo.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 3 + 3 files changed, 96 insertions(+), 0 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0c22dec..6e99243 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -792,6 +792,7 @@ virNodeDeviceObjUnlock;
# nodeinfo.h nodeCapsInitNUMA; +nodeGetCPUmap; nodeGetCPUStats; nodeGetCellsFreeMemory; nodeGetFreeMemory; diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..fc1aaea 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -31,6 +31,7 @@ #include <dirent.h> #include <sys/utsname.h> #include <sched.h> +#include <conf/domain_conf.h>
#if HAVE_NUMACTL # define NUMA_VERSION1_COMPATIBILITY 1 @@ -569,6 +570,73 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This function parses + * it and returns cpumap. + */ +static const char * +linuxParseCPUmap(int *max_cpuid, const char *path) +{ + FILE *fp; + char *map = NULL; + char *str = NULL; + size_t size = 128, pos = 0; + int max_id, i; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto error; + } + + if (VIR_ALLOC_N(str, size) < 0) { + virReportOOMError(); + goto error; + } + for (;;) { + pos += fread(str + pos, 1, size - pos, fp); + if (pos < size) + break; + + size = size << 1; + if (VIR_REALLOC_N(str, size) < 0) { + virReportOOMError(); + goto error; + } + } + if (pos == 0) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto error; + } + str[pos - 1] = 0; + + if (VIR_ALLOC_N(map, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportOOMError(); + goto error; + } + if (virDomainCpuSetParse(str, 0, map, + VIR_DOMAIN_CPUMASK_LEN) < 0) { + goto error; + } + + for (i = 0; i < VIR_DOMAIN_CPUMASK_LEN; i++) { + if (map[i]) { + max_id = i; + } + } + *max_cpuid = max_id; + + VIR_FORCE_FCLOSE(fp); + return map; + +error: + VIR_FORCE_FCLOSE(fp); + VIR_FREE(str); + VIR_FREE(map); + return NULL; +} #endif
int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +780,30 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif }
+const char * +nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id ATTRIBUTE_UNUSED, + const char *mapname ATTRIBUTE_UNUSED) +{ +#ifdef __linux__ + char *path; + const char *cpumap; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + cpumap = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return cpumap; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1; +#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..852e19d 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -46,4 +46,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn);
+const char *nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/

On 02/16/2012 12:15 AM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Changelog: - fixed typos. - fixed string scan routine.
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt_private.syms | 1 + src/nodeinfo.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 3 + 3 files changed, 96 insertions(+), 0 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0c22dec..6e99243 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -792,6 +792,7 @@ virNodeDeviceObjUnlock;
# nodeinfo.h nodeCapsInitNUMA; +nodeGetCPUmap; nodeGetCPUStats; nodeGetCellsFreeMemory; nodeGetFreeMemory; diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..fc1aaea 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -31,6 +31,7 @@ #include <dirent.h> #include <sys/utsname.h> #include <sched.h> +#include <conf/domain_conf.h>
This is a header internal to libvirt, so it should use "", not <>. And somehow, introducing this header pulled in enough other stuff to make the compiler complain about a later use of socket as a local variable name: nodeinfo.c: In function 'linuxNodeInfoCPUPopulate': nodeinfo.c:210:25: error: declaration of 'socket' shadows a global declaration [-Werror=shadow] cc1: all warnings being treated as errors
#if HAVE_NUMACTL # define NUMA_VERSION1_COMPATIBILITY 1 @@ -569,6 +570,73 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This function parses + * it and returns cpumap. + */ +static const char * +linuxParseCPUmap(int *max_cpuid, const char *path) +{ + FILE *fp; + char *map = NULL; + char *str = NULL; + size_t size = 128, pos = 0; + int max_id, i; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto error; + } + + if (VIR_ALLOC_N(str, size) < 0) { + virReportOOMError(); + goto error; + } + for (;;) { + pos += fread(str + pos, 1, size - pos, fp);
Rather than using an fread() loop, I think we can simplify things by using virFileReadAll().
+ if (pos < size) + break; + + size = size << 1; + if (VIR_REALLOC_N(str, size) < 0) { + virReportOOMError(); + goto error; + } + } + if (pos == 0) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto error; + } + str[pos - 1] = 0; + + if (VIR_ALLOC_N(map, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportOOMError(); + goto error; + } + if (virDomainCpuSetParse(str, 0, map, + VIR_DOMAIN_CPUMASK_LEN) < 0) { + goto error; + } + + for (i = 0; i < VIR_DOMAIN_CPUMASK_LEN; i++) { + if (map[i]) { + max_id = i;
That's off by a factor of 8 - map[i] means that you have visited i*8 bits in cpumask.
+ } + } + *max_cpuid = max_id; + + VIR_FORCE_FCLOSE(fp); + return map; + +error: + VIR_FORCE_FCLOSE(fp); + VIR_FREE(str); + VIR_FREE(map); + return NULL; +} #endif
int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +780,30 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif }
+const char * +nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id ATTRIBUTE_UNUSED, + const char *mapname ATTRIBUTE_UNUSED) +{ +#ifdef __linux__ + char *path; + const char *cpumap; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + cpumap = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return cpumap; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1;
Returning -1 as a const char * won't compile. You want NULL here.
+#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..852e19d 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -46,4 +46,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn);
+const char *nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/
I'll see if I can fix these things myself - I'm anxious to get this pushed sooner rather than later. I'll continue my review, and if I polish the full set, I'll post my diffs before pushing it, rather than waiting for you to go through a v7. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 02/23/2012 08:10 AM, Eric Blake wrote:
+ if (virDomainCpuSetParse(str, 0, map, + VIR_DOMAIN_CPUMASK_LEN) < 0) { + goto error; + } + + for (i = 0; i < VIR_DOMAIN_CPUMASK_LEN; i++) { + if (map[i]) { + max_id = i;
That's off by a factor of 8 - map[i] means that you have visited i*8 bits in cpumask.
@map is not bitmask, virDomainCpuSetParse() filled it a char per a cpu. And the return of this function is also cpumap(byte per cpu). I will rework the patches soon after you comments them. Thank you very much. --lai.

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Changelog: - fixed typos. - fixed string scan routine. Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt_private.syms | 1 + src/nodeinfo.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 3 ++ 3 files changed, 70 insertions(+), 0 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a104e70..0f4e64c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -793,6 +793,7 @@ virNodeDeviceObjUnlock; # nodeinfo.h nodeCapsInitNUMA; +nodeGetCPUmap; nodeGetCPUStats; nodeGetCellsFreeMemory; nodeGetFreeMemory; diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..2950306 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -31,6 +31,7 @@ #include <dirent.h> #include <sys/utsname.h> #include <sched.h> +#include "conf/domain_conf.h" #if HAVE_NUMACTL # define NUMA_VERSION1_COMPATIBILITY 1 @@ -569,6 +570,47 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This function parses + * it and returns cpumap. + */ +static const char * +linuxParseCPUmap(int *max_cpuid, const char *path) +{ + char *map = NULL; + char *str = NULL; + int max_id, i; + + if (virFileReadAll(path, 5 * VIR_DOMAIN_CPUMASK_LEN, &str) < 0) { + virReportOOMError(); + goto error; + } + + if (VIR_ALLOC_N(map, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportOOMError(); + goto error; + } + if (virDomainCpuSetParse(str, 0, map, + VIR_DOMAIN_CPUMASK_LEN) < 0) { + goto error; + } + + for (i = 0; i < VIR_DOMAIN_CPUMASK_LEN; i++) { + if (map[i]) { + max_id = i; + } + } + *max_cpuid = max_id; + + return map; + +error: + VIR_FREE(str); + VIR_FREE(map); + return NULL; +} #endif int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +754,30 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif } +const char * +nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id ATTRIBUTE_UNUSED, + const char *mapname ATTRIBUTE_UNUSED) +{ +#ifdef __linux__ + char *path; + const char *cpumap; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + cpumap = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return cpumap; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return NULL; +#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..852e19d 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -46,4 +46,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn); +const char *nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/ -- 1.7.4.4

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> * Now, only "cpu_time" is supported. * cpuacct cgroup is used for providing percpu cputime information. * include/libvirt/libvirt.h.in - defines VIR_DOMAIN_CPU_STATS_CPUTIME * src/qemu/qemu.conf - take care of cpuacct cgroup. * src/qemu/qemu_conf.c - take care of cpuacct cgroup. * src/qemu/qemu_driver.c - added an interface * src/util/cgroup.c/h - added interface for getting percpu cputime Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/qemu/qemu.conf | 3 +- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/cgroup.c | 6 ++ src/util/cgroup.h | 1 + 5 files changed, 157 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 95428c1..cb87728 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -166,6 +166,7 @@ # - 'memory' - use for memory tunables # - 'blkio' - use for block devices I/O tunables # - 'cpuset' - use for CPUs and memory nodes +# - 'cpuacct' - use for CPUs statistics. # # NB, even if configured here, they won't be used unless # the administrator has mounted cgroups, e.g.: @@ -177,7 +178,7 @@ # can be mounted in different locations. libvirt will detect # where they are located. # -# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset" ] +# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset", "cpuacct" ] # This is the basic set of devices allowed / required by # all virtual machines. diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index e95c7a5..a709cbf 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -318,7 +318,8 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, (1 << VIR_CGROUP_CONTROLLER_DEVICES) | (1 << VIR_CGROUP_CONTROLLER_MEMORY) | (1 << VIR_CGROUP_CONTROLLER_BLKIO) | - (1 << VIR_CGROUP_CONTROLLER_CPUSET); + (1 << VIR_CGROUP_CONTROLLER_CPUSET) | + (1 << VIR_CGROUP_CONTROLLER_CPUACCT); } for (i = 0 ; i < VIR_CGROUP_CONTROLLER_LAST ; i++) { if (driver->cgroupControllers & (1 << i)) { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index c6bdd29..d6b56ae 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12087,6 +12087,151 @@ cleanup: return ret; } +/* qemuDomainGetCPUStats() with start_cpu == -1 */ +static int +qemuDomainGetTotalcpuStats(virCgroupPtr group, + virTypedParameterPtr params, + int nparams) +{ + unsigned long long cpu_time; + int param_idx = 0; + int ret; + + if (nparams == 0) /* return supported number of params */ + return 1; + /* entry 0 is cputime */ + ret = virCgroupGetCpuacctUsage(group, &cpu_time); + if (ret < 0) { + virReportSystemError(-ret, "%s", _("unable to get cpu account")); + return -1; + } + + virTypedParameterAssign(¶ms[param_idx], VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + return 1; +} + +static int qemuDomainGetPercpuStats(virDomainPtr domain, + virCgroupPtr group, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus) +{ + const char *map = NULL; + int rv = -1; + int i, max_id; + char *pos; + char *buf = NULL; + virTypedParameterPtr ent; + int param_idx; + + /* return the number of supported params ? */ + if (nparams == 0 && ncpus != 0) + return 1; /* only cpu_time is supported */ + + /* return percpu cputime in index 0 */ + param_idx = 0; + /* to parse account file, we need "present" cpu map */ + map = nodeGetCPUmap(domain->conn, &max_id, "present"); + if (!map) + return rv; + + if (ncpus == 0) { /* returns max cpu ID */ + rv = max_id; + goto cleanup; + } + /* we get percpu cputime accounting info. */ + if (virCgroupGetCpuacctPercpuUsage(group, &buf)) + goto cleanup; + pos = buf; + + if (max_id > start_cpu + ncpus - 1) + max_id = start_cpu + ncpus - 1; + + for (i = 0; i <= max_id; i++) { + unsigned long long cpu_time; + + if (!map[i]) + continue; + if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cpuacct parse error")); + goto cleanup; + } + if (i < start_cpu) + continue; + ent = ¶ms[ (i - start_cpu) * nparams + param_idx]; + virTypedParameterAssign(ent, VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + } + rv = param_idx + 1; +cleanup: + VIR_FREE(buf); + VIR_FREE(map); + return rv; +} + + +static int +qemuDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct qemud_driver *driver = domain->conn->privateData; + virCgroupPtr group = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + bool isActive; + + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1); + + qemuDriverLock(driver); + + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (vm == NULL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("No such domain %s"), domain->uuid); + goto cleanup; + } + + isActive = virDomainObjIsActive(vm); + if (!isActive) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + if (!qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_CPUACCT)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cgroup CPUACCT controller is not mounted")); + goto cleanup; + } + + if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) != 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find cgroup for domain %s"), vm->def->name); + goto cleanup; + } + + if (nparams == 0 && ncpus == 0) /* returns max cpu id */ + ret = qemuDomainGetPercpuStats(domain, group, NULL, 0, 0, 0); + else if (start_cpu == -1) /* get total */ + ret = qemuDomainGetTotalcpuStats(group, params, nparams); + else + ret = qemuDomainGetPercpuStats(domain, group, params, nparams, + start_cpu, ncpus); +cleanup: + virCgroupFree(&group); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + static int qemuDomainPMSuspendForDuration(virDomainPtr dom, unsigned int target, @@ -12387,6 +12532,7 @@ static virDriver qemuDriver = { .domainGetMetadata = qemuDomainGetMetadata, /* 0.9.10 */ .domainPMSuspendForDuration = qemuDomainPMSuspendForDuration, /* 0.9.11 */ .domainPMWakeup = qemuDomainPMWakeup, /* 0.9.11 */ + .domainGetCPUStats = qemuDomainGetCPUStats, /* 0.9.11 */ }; diff --git a/src/util/cgroup.c b/src/util/cgroup.c index 15b870d..2d34c50 100644 --- a/src/util/cgroup.c +++ b/src/util/cgroup.c @@ -1555,6 +1555,12 @@ int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage) "cpuacct.usage", usage); } +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage) +{ + return virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPUACCT, + "cpuacct.usage_percpu", usage); +} + int virCgroupSetFreezerState(virCgroupPtr group, const char *state) { return virCgroupSetValueStr(group, diff --git a/src/util/cgroup.h b/src/util/cgroup.h index 8d75735..b4e0f37 100644 --- a/src/util/cgroup.h +++ b/src/util/cgroup.h @@ -115,6 +115,7 @@ int virCgroupSetCpuCfsQuota(virCgroupPtr group, long long cfs_quota); int virCgroupGetCpuCfsQuota(virCgroupPtr group, long long *cfs_quota); int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage); +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage); int virCgroupSetFreezerState(virCgroupPtr group, const char *state); int virCgroupGetFreezerState(virCgroupPtr group, char **state); -- 1.7.4.4

On 03/01/2012 07:54 PM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
* Now, only "cpu_time" is supported. * cpuacct cgroup is used for providing percpu cputime information.
* include/libvirt/libvirt.h.in - defines VIR_DOMAIN_CPU_STATS_CPUTIME
Stale commit message; this change was committed earlier.
* src/qemu/qemu.conf - take care of cpuacct cgroup. * src/qemu/qemu_conf.c - take care of cpuacct cgroup. * src/qemu/qemu_driver.c - added an interface * src/util/cgroup.c/h - added interface for getting percpu cputime
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/qemu/qemu.conf | 3 +- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/cgroup.c | 6 ++ src/util/cgroup.h | 1 + 5 files changed, 157 insertions(+), 2 deletions(-)
+++ b/src/qemu/qemu_driver.c @@ -12087,6 +12087,151 @@ cleanup: return ret; }
+/* qemuDomainGetCPUStats() with start_cpu == -1 */ +static int +qemuDomainGetTotalcpuStats(virCgroupPtr group, + virTypedParameterPtr params, + int nparams)
Indentation.
+static int qemuDomainGetPercpuStats(virDomainPtr domain, + virCgroupPtr group, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus) +{ + const char *map = NULL;
Same issue with 'const' as in 1/3.
+ int rv = -1; + int i, max_id; + char *pos; + char *buf = NULL; + virTypedParameterPtr ent; + int param_idx; + + /* return the number of supported params ? */
The ? makes it look like you weren't sure.
+ if (ncpus == 0) { /* returns max cpu ID */ + rv = max_id; + goto cleanup; + }
Actually, rv must be max_id + 1 (the size of the array, not the index of the last element in the array).
+ if (max_id > start_cpu + ncpus - 1) + max_id = start_cpu + ncpus - 1; + + for (i = 0; i <= max_id; i++) { + unsigned long long cpu_time;
Hmm - start_cpu and ncpus are user-supplied values; their sum could overflow, and we should not misbehave in that case. If the user passes a start_cpu of 1000000, it's probably better to return an error stating that start_cpu is out of range, rather than returning 1 without populating information. On the other hand, if max_id is 3, and the user passes start_cpu 3 and ncpus 2, it makes sense to return just the one in-range entity, rather than erroring out because ncpus was too large.
+ if (!map[i]) + continue; + if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cpuacct parse error")); + goto cleanup; + } + if (i < start_cpu) + continue;
Hmm - this leaves the return struct unpopulated for offline cpus, but the RPC protocol strips out holes on the sending side, and reconstructing on the receiving side won't know where to restore holes. It is only safe to leave holes at the end of the array (if ncpus goes beyond max_id), and we must explicitly populate 0 rather than skipping offline cpus, if they are in range. I confirmed that it also means we need to tweak the RPC code to allow reconstruction with fewer elements than ncpus, in the case where ncpus is too large. But I will submit that as a separate patch.
+ +static int +qemuDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{
+ + if (nparams == 0 && ncpus == 0) /* returns max cpu id */ + ret = qemuDomainGetPercpuStats(domain, group, NULL, 0, 0, 0);
If start_cpu is -1, then ncpus is non-zero; therefore, this conditional could safely be deferred to be second, at which point it becomes redundant.
+ else if (start_cpu == -1) /* get total */ + ret = qemuDomainGetTotalcpuStats(group, params, nparams); + else + ret = qemuDomainGetPercpuStats(domain, group, params, nparams, + start_cpu, ncpus);
+++ b/src/util/cgroup.c @@ -1555,6 +1555,12 @@ int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage) "cpuacct.usage", usage); }
+int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage)
Missing an export for this one. ACK with this squashed in: diff --git i/src/libvirt_private.syms w/src/libvirt_private.syms index 1f58832..c44c617 100644 --- i/src/libvirt_private.syms +++ w/src/libvirt_private.syms @@ -74,16 +74,17 @@ virCgroupForDriver; virCgroupForVcpu; virCgroupFree; virCgroupGetBlkioWeight; -virCgroupGetCpuShares; virCgroupGetCpuCfsPeriod; virCgroupGetCpuCfsQuota; +virCgroupGetCpuShares; +virCgroupGetCpuacctPercpuUsage; virCgroupGetCpuacctUsage; virCgroupGetCpusetMems; virCgroupGetFreezerState; +virCgroupGetMemSwapHardLimit; virCgroupGetMemoryHardLimit; virCgroupGetMemorySoftLimit; virCgroupGetMemoryUsage; -virCgroupGetMemSwapHardLimit; virCgroupKill; virCgroupKillPainfully; virCgroupKillRecursive; @@ -92,15 +93,15 @@ virCgroupPathOfController; virCgroupRemove; virCgroupSetBlkioDeviceWeight; virCgroupSetBlkioWeight; -virCgroupSetCpuShares; virCgroupSetCpuCfsPeriod; virCgroupSetCpuCfsQuota; +virCgroupSetCpuShares; virCgroupSetCpusetMems; virCgroupSetFreezerState; +virCgroupSetMemSwapHardLimit; virCgroupSetMemory; virCgroupSetMemoryHardLimit; virCgroupSetMemorySoftLimit; -virCgroupSetMemSwapHardLimit; # command.h diff --git i/src/qemu/qemu_driver.c w/src/qemu/qemu_driver.c index 98b3c25..538a419 100644 --- i/src/qemu/qemu_driver.c +++ w/src/qemu/qemu_driver.c @@ -12098,7 +12098,7 @@ cleanup: /* qemuDomainGetCPUStats() with start_cpu == -1 */ static int qemuDomainGetTotalcpuStats(virCgroupPtr group, - virTypedParameterPtr params, + virTypedParameterPtr params, int nparams) { unsigned long long cpu_time; @@ -12119,14 +12119,15 @@ qemuDomainGetTotalcpuStats(virCgroupPtr group, return 1; } -static int qemuDomainGetPercpuStats(virDomainPtr domain, - virCgroupPtr group, - virTypedParameterPtr params, - unsigned int nparams, - int start_cpu, - unsigned int ncpus) +static int +qemuDomainGetPercpuStats(virDomainPtr domain, + virCgroupPtr group, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus) { - const char *map = NULL; + char *map = NULL; int rv = -1; int i, max_id; char *pos; @@ -12134,7 +12135,7 @@ static int qemuDomainGetPercpuStats(virDomainPtr domain, virTypedParameterPtr ent; int param_idx; - /* return the number of supported params ? */ + /* return the number of supported params */ if (nparams == 0 && ncpus != 0) return 1; /* only cpu_time is supported */ @@ -12146,23 +12147,31 @@ static int qemuDomainGetPercpuStats(virDomainPtr domain, return rv; if (ncpus == 0) { /* returns max cpu ID */ - rv = max_id; + rv = max_id + 1; + goto cleanup; + } + + if (start_cpu > max_id) { + qemuReportError(VIR_ERR_INVALID_ARG, + _("start_cpu %d larger than maximum of %d"), + start_cpu, max_id); goto cleanup; } + /* we get percpu cputime accounting info. */ if (virCgroupGetCpuacctPercpuUsage(group, &buf)) goto cleanup; pos = buf; - if (max_id > start_cpu + ncpus - 1) + if (max_id - start_cpu > ncpus - 1) max_id = start_cpu + ncpus - 1; for (i = 0; i <= max_id; i++) { unsigned long long cpu_time; - if (!map[i]) - continue; - if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { + if (!map[i]) { + cpu_time = 0; + } else if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { qemuReportError(VIR_ERR_INTERNAL_ERROR, _("cpuacct parse error")); goto cleanup; @@ -12225,9 +12234,7 @@ qemuDomainGetCPUStats(virDomainPtr domain, goto cleanup; } - if (nparams == 0 && ncpus == 0) /* returns max cpu id */ - ret = qemuDomainGetPercpuStats(domain, group, NULL, 0, 0, 0); - else if (start_cpu == -1) /* get total */ + if (start_cpu == -1) ret = qemuDomainGetTotalcpuStats(group, params, nparams); else ret = qemuDomainGetPercpuStats(domain, group, params, nparams, -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Total: cpu_time 14.312 CPU0: cpu_time 3.253 CPU1: cpu_time 1.923 CPU2: cpu_time 7.424 CPU3: cpu_time 1.712 Changed from V5: add --all, --start, --count option Changed from V: rebase Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- tools/virsh.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 8 +++ 2 files changed, 162 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index dc362dc..a431826 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5537,6 +5537,159 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) } /* + * "cpu-stats" command + */ +static const vshCmdInfo info_cpu_stats[] = { + {"help", N_("show domain cpu statistics")}, + {"desc", N_("Display statistics about the domain's CPUs, including per-CPU statistics.")}, + {NULL, NULL}, +}; + +static const vshCmdOptDef opts_cpu_stats[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"all", VSH_OT_BOOL, 0, N_("Show total statistics only")}, + {"start", VSH_OT_INT, 0, N_("Show statistics from this CPU")}, + {"count", VSH_OT_INT, 0, N_("Number of shown CPUs at most")}, + {NULL, 0, 0, NULL}, +}; + +static bool +cmdCPUStats(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + virTypedParameterPtr params = NULL; + int i, j, pos, max_id, cpu = -1, show_count = -1, nparams; + bool show_all = false, show_per_cpu = false; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + show_all = vshCommandOptBool(cmd, "all"); + if (vshCommandOptInt(cmd, "start", &cpu) > 0) + show_per_cpu = true; + if (vshCommandOptInt(cmd, "count", &show_count) > 0) + show_per_cpu = true; + + /* default show per_cpu and total */ + if (!show_all && !show_per_cpu) { + show_all = true; + show_per_cpu = true; + } + + if (!show_all) + goto do_show_per_cpu; + + /* get supported num of parameter for total statistics */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) + goto failed_stats; + if (VIR_ALLOC_N(params, nparams)) + goto failed_params; + + /* passing start_cpu == -1 gives us domain's total status */ + if ((nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0)) < 0) + goto failed_stats; + + vshPrint(ctl, "Total:\n"); + for (i = 0; i < nparams; i++) { + vshPrint(ctl, "\t%-10s ", params[i].field); + if (STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { + if (params[i].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%12.3lf\n", + params[i].value.ul / 1000000000.0); + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s); + VIR_FREE(s); + } + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, _("%s\n"), s); + VIR_FREE(s); + } + } + virTypedParameterArrayClear(params, nparams); + VIR_FREE(params); + + if (!show_per_cpu) /* show all stats only */ + goto cleanup; + +do_show_per_cpu: + /* check cpu, show_count, and ignore wrong argument */ + if (cpu < 0) + cpu = 0; + if (show_count < 0) + show_count = INT_MAX; + + /* get max cpu id on the node */ + if ((max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0)) < 0) + goto failed_stats; + /* get percpu information */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) + goto failed_stats; + + if (VIR_ALLOC_N(params, nparams * 128)) + goto failed_params; + + while (cpu <= max_id) { + int ncpus = 128; + + if (cpu + ncpus - 1 > max_id) /* id starts from 0. */ + ncpus = max_id + 1 - cpu; + + if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0) < 0) + goto failed_stats; + + for (i = 0; i < ncpus; i++) { + if (params[i * nparams].type == 0) /* this cpu is not in the map */ + continue; + vshPrint(ctl, "CPU%d:\n", cpu + i); + + for (j = 0; j < nparams; j++) { + pos = i * nparams + j; + vshPrint(ctl, "\t%-10s ", params[pos].field); + if (STREQ(params[pos].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { + if (params[j].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%12.3lf\n", + params[pos].value.ul / 1000000000.0); + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); + vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s); + VIR_FREE(s); + } + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); + vshPrint(ctl, _("%s\n"), s); + VIR_FREE(s); + } + } + + if (--show_count <= 0) /* mark done to exit the outmost loop */ + cpu = max_id; + } + cpu += ncpus; + virTypedParameterArrayClear(params, nparams * ncpus); + } + VIR_FREE(params); +cleanup: + virDomainFree(dom); + return true; + +failed_params: + virReportOOMError(); + virDomainFree(dom); + return false; + +failed_stats: + vshError(ctl, _("Failed to virDomainGetCPUStats()\n")); + VIR_FREE(params); + virDomainFree(dom); + return false; +} + +/* * "inject-nmi" command */ static const vshCmdInfo info_inject_nmi[] = { @@ -16910,6 +17063,7 @@ static const vshCmdDef domManagementCmds[] = { #endif {"cpu-baseline", cmdCPUBaseline, opts_cpu_baseline, info_cpu_baseline, 0}, {"cpu-compare", cmdCPUCompare, opts_cpu_compare, info_cpu_compare, 0}, + {"cpu-stats", cmdCPUStats, opts_cpu_stats, info_cpu_stats, 0}, {"create", cmdCreate, opts_create, info_create, 0}, {"define", cmdDefine, opts_define, info_define, 0}, {"desc", cmdDesc, opts_desc, info_desc, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index 8f6a2d6..b23868d 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -790,6 +790,14 @@ Provide the maximum number of virtual CPUs supported for a guest VM on this connection. If provided, the I<type> parameter must be a valid type attribute for the <domain> element of XML. +=item B<cpu-stats> I<domain> [I<--all>] [I<start>] [I<count>] + +Provide cpu statistics information of a domain. The domain should +be running. Default it shows stats for all CPUs, and a total. Use +I<--all> for only the total stats, I<start> for only the per-cpu +stats of the CPUs from I<start>, I<count> for only I<count> CPUs' +stats. + =item B<migrate> [I<--live>] [I<--direct>] [I<--p2p> [I<--tunnelled>]] [I<--persistent>] [I<--undefinesource>] [I<--suspend>] [I<--copy-storage-all>] [I<--copy-storage-inc>] [I<--change-protection>] [I<--unsafe>] [I<--verbose>] -- 1.7.4.4

On 03/01/2012 07:54 PM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Total: cpu_time 14.312 CPU0: cpu_time 3.253 CPU1: cpu_time 1.923 CPU2: cpu_time 7.424 CPU3: cpu_time 1.712
Personally, I like totals to appear last :) Meanwhile, since the API returns nanoseconds, but we are printing in seconds, it might be nice to output a unit.
Changed from V5: add --all, --start, --count option Changed from V: rebase
Again, the 'changed from' lines are better after the ---.
Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- tools/virsh.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 8 +++ 2 files changed, 162 insertions(+), 0 deletions(-)
/* + * "cpu-stats" command + */ +static const vshCmdInfo info_cpu_stats[] = { + {"help", N_("show domain cpu statistics")}, + {"desc", N_("Display statistics about the domain's CPUs, including per-CPU statistics.")}, + {NULL, NULL}, +}; + +static const vshCmdOptDef opts_cpu_stats[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"all", VSH_OT_BOOL, 0, N_("Show total statistics only")},
After thinking about this a bit more, "total" works better for the name of this option. That is, we default to per-cpu then total, --total limits us to total only, and --start or --count limits to per-cpu only. That means my squash below will be a bit hard to follow, since it does a big block of code motion; oh well.
+ {"start", VSH_OT_INT, 0, N_("Show statistics from this CPU")}, + {"count", VSH_OT_INT, 0, N_("Number of shown CPUs at most")}, + {NULL, 0, 0, NULL}, +}; + +static bool +cmdCPUStats(vshControl *ctl, const vshCmd *cmd) +{
+ + /* get supported num of parameter for total statistics */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) + goto failed_stats; + if (VIR_ALLOC_N(params, nparams)) + goto failed_params; + + /* passing start_cpu == -1 gives us domain's total status */ + if ((nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0)) < 0) + goto failed_stats; + + vshPrint(ctl, "Total:\n");
This should be marked for translation.
+ for (i = 0; i < nparams; i++) { + vshPrint(ctl, "\t%-10s ", params[i].field); + if (STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { + if (params[i].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%12.3lf\n", + params[i].value.ul / 1000000000.0);
We're losing information; both by chopping fractional digits, and also by conversion to floating point. It's better to give the user everything and let them round.
+ } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s);
Not sure this message is worth it.
+ VIR_FREE(s); + } + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[i]);
Again, malloc'd result strings should generally not be marked const.
+ vshPrint(ctl, _("%s\n"), s);
This string doesn't need translation.
+ VIR_FREE(s); + } + } + virTypedParameterArrayClear(params, nparams); + VIR_FREE(params); + + if (!show_per_cpu) /* show all stats only */ + goto cleanup; + +do_show_per_cpu: + /* check cpu, show_count, and ignore wrong argument */ + if (cpu < 0) + cpu = 0; + if (show_count < 0) + show_count = INT_MAX; + + /* get max cpu id on the node */ + if ((max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0)) < 0) + goto failed_stats; + /* get percpu information */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0)
This should be 0, not -1, as the total may have a different number of stats than per-cpu. It is feasible that some hypervisors might return 0 for one of the two stats (that is, provide total but not per-cpu stats); we shouldn't error out in those cases.
+ goto failed_stats; + + if (VIR_ALLOC_N(params, nparams * 128)) + goto failed_params; + + while (cpu <= max_id) {
Per 2/3, max_id should be the array size, not the last index in the array. We also want to stop iterating if show_count was specified...
+ int ncpus = 128; + + if (cpu + ncpus - 1 > max_id) /* id starts from 0. */ + ncpus = max_id + 1 - cpu; + + if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0) < 0)
...and to fully test the underlying API, if the user passes show_count of 1, we want ncpus to be 1, not 128. So rather than futzing around with max_id, it's easier to base the entire loop on show_count.
+ goto failed_stats; + + for (i = 0; i < ncpus; i++) { + if (params[i * nparams].type == 0) /* this cpu is not in the map */ + continue; + vshPrint(ctl, "CPU%d:\n", cpu + i); + + for (j = 0; j < nparams; j++) { + pos = i * nparams + j; + vshPrint(ctl, "\t%-10s ", params[pos].field); + if (STREQ(params[pos].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { + if (params[j].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%12.3lf\n", + params[pos].value.ul / 1000000000.0);
Same comments as for totals.
+ } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); + vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s); + VIR_FREE(s); + } + } else { + const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); + vshPrint(ctl, _("%s\n"), s); + VIR_FREE(s); + } + } + + if (--show_count <= 0) /* mark done to exit the outmost loop */
s/outmost/outermost/
+++ b/tools/virsh.pod @@ -790,6 +790,14 @@ Provide the maximum number of virtual CPUs supported for a guest VM on this connection. If provided, the I<type> parameter must be a valid type attribute for the <domain> element of XML.
+=item B<cpu-stats> I<domain> [I<--all>] [I<start>] [I<count>] + +Provide cpu statistics information of a domain. The domain should +be running. Default it shows stats for all CPUs, and a total. Use
s/Default it/By default, it/
+I<--all> for only the total stats, I<start> for only the per-cpu +stats of the CPUs from I<start>, I<count> for only I<count> CPUs' +stats. +
ACK with these changes squashed in, so I'll push the series shortly once I figure out how to properly handle the case where the driver truncates the array. From 6fb1f35cc283e08be3f62fc8d60b3297467fdafd Mon Sep 17 00:00:00 2001 From: Eric Blake <eblake@redhat.com> Date: Tue, 6 Mar 2012 17:24:39 -0700 Subject: [PATCH] fixup to 3/3 --- tools/virsh.c | 131 ++++++++++++++++++++++++++++--------------------------- tools/virsh.pod | 4 +- 2 files changed, 68 insertions(+), 67 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index ab52b5b..70a932b 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5541,13 +5541,14 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) */ static const vshCmdInfo info_cpu_stats[] = { {"help", N_("show domain cpu statistics")}, - {"desc", N_("Display statistics about the domain's CPUs, including per-CPU statistics.")}, + {"desc", + N_("Display per-CPU and total statistics about the domain's CPUs")}, {NULL, NULL}, }; static const vshCmdOptDef opts_cpu_stats[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, - {"all", VSH_OT_BOOL, 0, N_("Show total statistics only")}, + {"total", VSH_OT_BOOL, 0, N_("Show total statistics only")}, {"start", VSH_OT_INT, 0, N_("Show statistics from this CPU")}, {"count", VSH_OT_INT, 0, N_("Number of shown CPUs at most")}, {NULL, 0, 0, NULL}, @@ -5559,7 +5560,7 @@ cmdCPUStats(vshControl *ctl, const vshCmd *cmd) virDomainPtr dom; virTypedParameterPtr params = NULL; int i, j, pos, max_id, cpu = -1, show_count = -1, nparams; - bool show_all = false, show_per_cpu = false; + bool show_total = false, show_per_cpu = false; if (!vshConnectionUsability(ctl, ctl->conn)) return false; @@ -5567,77 +5568,45 @@ cmdCPUStats(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) return false; - show_all = vshCommandOptBool(cmd, "all"); + show_total = vshCommandOptBool(cmd, "total"); if (vshCommandOptInt(cmd, "start", &cpu) > 0) show_per_cpu = true; if (vshCommandOptInt(cmd, "count", &show_count) > 0) show_per_cpu = true; /* default show per_cpu and total */ - if (!show_all && !show_per_cpu) { - show_all = true; + if (!show_total && !show_per_cpu) { + show_total = true; show_per_cpu = true; } - if (!show_all) - goto do_show_per_cpu; - - /* get supported num of parameter for total statistics */ - if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) - goto failed_stats; - if (VIR_ALLOC_N(params, nparams)) - goto failed_params; + if (!show_per_cpu) /* show total stats only */ + goto do_show_total; - /* passing start_cpu == -1 gives us domain's total status */ - if ((nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0)) < 0) - goto failed_stats; - - vshPrint(ctl, "Total:\n"); - for (i = 0; i < nparams; i++) { - vshPrint(ctl, "\t%-10s ", params[i].field); - if (STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { - if (params[i].type == VIR_TYPED_PARAM_ULLONG) { - vshPrint(ctl, "%12.3lf\n", - params[i].value.ul / 1000000000.0); - } else { - const char *s = vshGetTypedParamValue(ctl, ¶ms[i]); - vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s); - VIR_FREE(s); - } - } else { - const char *s = vshGetTypedParamValue(ctl, ¶ms[i]); - vshPrint(ctl, _("%s\n"), s); - VIR_FREE(s); - } - } - virTypedParameterArrayClear(params, nparams); - VIR_FREE(params); - - if (!show_per_cpu) /* show all stats only */ - goto cleanup; - -do_show_per_cpu: /* check cpu, show_count, and ignore wrong argument */ if (cpu < 0) cpu = 0; - if (show_count < 0) - show_count = INT_MAX; - /* get max cpu id on the node */ + /* get number of cpus on the node */ if ((max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0)) < 0) goto failed_stats; + if (show_count < 0 || show_count > max_id) + show_count = max_id; + /* get percpu information */ - if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0)) < 0) goto failed_stats; - if (VIR_ALLOC_N(params, nparams * 128)) - goto failed_params; + if (!nparams) { + vshPrint(ctl, "%s", _("No per-CPU stats available")); + goto do_show_total; + } - while (cpu <= max_id) { - int ncpus = 128; + if (VIR_ALLOC_N(params, nparams * MIN(show_count, 128)) < 0) + goto failed_params; - if (cpu + ncpus - 1 > max_id) /* id starts from 0. */ - ncpus = max_id + 1 - cpu; + while (show_count) { + int ncpus = MIN(show_count, 128); if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0) < 0) goto failed_stats; @@ -5650,29 +5619,61 @@ do_show_per_cpu: for (j = 0; j < nparams; j++) { pos = i * nparams + j; vshPrint(ctl, "\t%-10s ", params[pos].field); - if (STREQ(params[pos].field, VIR_DOMAIN_CPU_STATS_CPUTIME)) { - if (params[j].type == VIR_TYPED_PARAM_ULLONG) { - vshPrint(ctl, "%12.3lf\n", - params[pos].value.ul / 1000000000.0); - } else { - const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); - vshPrint(ctl, _("%s(ABI changed? ullong is expected)\n"), s); - VIR_FREE(s); - } + if (STREQ(params[pos].field, VIR_DOMAIN_CPU_STATS_CPUTIME) && + params[j].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%lld.%09lld seconds\n", + params[pos].value.ul / 1000000000, + params[pos].value.ul % 1000000000); } else { const char *s = vshGetTypedParamValue(ctl, ¶ms[pos]); vshPrint(ctl, _("%s\n"), s); VIR_FREE(s); } } - - if (--show_count <= 0) /* mark done to exit the outmost loop */ - cpu = max_id; } cpu += ncpus; + show_count -= ncpus; virTypedParameterArrayClear(params, nparams * ncpus); } VIR_FREE(params); + +do_show_total: + if (!show_total) + goto cleanup; + + /* get supported num of parameter for total statistics */ + if ((nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0)) < 0) + goto failed_stats; + + if (!nparams) { + vshPrint(ctl, "%s", _("No total stats available")); + goto cleanup; + } + + if (VIR_ALLOC_N(params, nparams)) + goto failed_params; + + /* passing start_cpu == -1 gives us domain's total status */ + if ((nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0)) < 0) + goto failed_stats; + + vshPrint(ctl, _("Total:\n")); + for (i = 0; i < nparams; i++) { + vshPrint(ctl, "\t%-10s ", params[i].field); + if (STREQ(params[i].field, VIR_DOMAIN_CPU_STATS_CPUTIME) && + params[i].type == VIR_TYPED_PARAM_ULLONG) { + vshPrint(ctl, "%llu.%09llu seconds\n", + params[i].value.ul / 1000000000, + params[i].value.ul % 1000000000); + } else { + char *s = vshGetTypedParamValue(ctl, ¶ms[i]); + vshPrint(ctl, "%s\n", s); + VIR_FREE(s); + } + } + virTypedParameterArrayClear(params, nparams); + VIR_FREE(params); + cleanup: virDomainFree(dom); return true; diff --git a/tools/virsh.pod b/tools/virsh.pod index b23868d..1eb9499 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -790,11 +790,11 @@ Provide the maximum number of virtual CPUs supported for a guest VM on this connection. If provided, the I<type> parameter must be a valid type attribute for the <domain> element of XML. -=item B<cpu-stats> I<domain> [I<--all>] [I<start>] [I<count>] +=item B<cpu-stats> I<domain> [I<--total>] [I<start>] [I<count>] Provide cpu statistics information of a domain. The domain should be running. Default it shows stats for all CPUs, and a total. Use -I<--all> for only the total stats, I<start> for only the per-cpu +I<--total> for only the total stats, I<start> for only the per-cpu stats of the CPUs from I<start>, I<count> for only I<count> CPUs' stats. -- 1.7.7.6 -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Fri, Mar 02, 2012 at 10:54:22AM +0800, Lai Jiangshan wrote:
+/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This function parses + * it and returns cpumap. + */ +static const char * +linuxParseCPUmap(int *max_cpuid, const char *path) +{ + char *map = NULL; + char *str = NULL; + int max_id, i; + + if (virFileReadAll(path, 5 * VIR_DOMAIN_CPUMASK_LEN, &str) < 0) { + virReportOOMError(); + goto error; + } + + if (VIR_ALLOC_N(map, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportOOMError(); + goto error; + } + if (virDomainCpuSetParse(str, 0, map, + VIR_DOMAIN_CPUMASK_LEN) < 0) { + goto error; + } + + for (i = 0; i < VIR_DOMAIN_CPUMASK_LEN; i++) { + if (map[i]) { + max_id = i; + } + } + *max_cpuid = max_id;
Please compile with './configure --enable-compile-warnings=error'. The compiler spots that max_id could be used uninitialized here. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top

This all appears to work. I built my own libvirt with the three remaining patches and was able to read pCPU information for a running domain. I have pushed the required bits upstream in ocaml-libvirt and virt-top: http://git.annexia.org/?p=ocaml-libvirt.git;a=summary http://git.annexia.org/?p=virt-top.git;a=summary (ocaml-libvirt >= 0.6.1.1 and virt-top >= 1.0.7) Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org

On 03/06/2012 05:10 AM, Richard W.M. Jones wrote:
This all appears to work. I built my own libvirt with the three remaining patches and was able to read pCPU information for a running domain.
The libvirt side is now pushed. Rich: The documentation in libvirt.c correctly says that calling virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) must tell you part of the information needed to compute the size of an array to allocate; but in v7, the return was off by one too small. But it was generally masked by the v7 virsh code overallocating to a fixed 128 slots rather than paying attention to the return value. I fixed both those problems before pushing the libvirt patches, but since you tested with v7 instead of my fixed version, you may need to double check that virt-top isn't making the same mistakes, as it might be a boundary case that only strikes on machines with 128 physical CPUs. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Tue, Mar 06, 2012 at 10:02:15PM -0700, Eric Blake wrote:
On 03/06/2012 05:10 AM, Richard W.M. Jones wrote:
This all appears to work. I built my own libvirt with the three remaining patches and was able to read pCPU information for a running domain.
The libvirt side is now pushed.
Rich: The documentation in libvirt.c correctly says that calling virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) must tell you part of the information needed to compute the size of an array to allocate; but in v7, the return was off by one too small. But it was generally masked by the v7 virsh code overallocating to a fixed 128 slots rather than paying attention to the return value. I fixed both those problems before pushing the libvirt patches, but since you tested with v7 instead of my fixed version, you may need to double check that virt-top isn't making the same mistakes, as it might be a boundary case that only strikes on machines with 128 physical CPUs.
TBH I found the documentation for virDomainGetCPUStats to be very confusing indeed. I couldn't really tell if virt-top is calling the API correctly or not, so I simply used Fujitsu's code directly. Do you have any comments on whether this is correct or not? http://git.annexia.org/?p=ocaml-libvirt.git;a=blob;f=libvirt/libvirt_c_oneof... Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw

On 03/07/2012 03:36 AM, Richard W.M. Jones wrote:
TBH I found the documentation for virDomainGetCPUStats to be very confusing indeed. I couldn't really tell if virt-top is calling the API correctly or not, so I simply used Fujitsu's code directly.
That's a shame about the documentation not being clear; anything we can do to improve it? There are basically 5 calling modes: * Typical use sequence is below. * * getting total stats: set start_cpu as -1, ncpus 1 * virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0) => nparams * params = calloc(nparams, sizeof (virTypedParameter)) * virDomainGetCPUStats(dom, params, nparams, -1, 1, 0) => total stats. * * getting per-cpu stats: * virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0) => ncpus * virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0) => nparams * params = calloc(ncpus * nparams, sizeof (virTypedParameter)) * virDomainGetCPUStats(dom, params, nparams, 0, ncpus, 0) => per-cpu stats * 3 of the calling modes (when params is NULL) are there to let you determine how large to size your arrays; the remaining two modes exist to query the total stats, and to query a slice of up to 128 per-cpu stats. The number of total stats can be different from the number of per-cpu stats (right now, it's 1 each for qemu, but I have a pending patch that I will post later today that adds two new total stats with no per-cpu counterpart). When querying total stats, start_cpu is -1 and ncpus is 1 (this is a hard-coded requirement), so the return value is the number of stats populated. When querying per-cpu stats, the single 'params' pointer is actually representing a 2D array. So if you allocate params with 3x4 slots, and call virDomainGetCPUStats(dom, params, 3, 0, 4, 0), but there are only 3 online CPUs and the result of the call is 2, then the result will be laid out as: params[0] = CPU0 stat 0 params[1] = CPU0 stat 1 params[2] = NULL params[3] = CPU1 stat 0 params[4] = CPU1 stat 1 params[5] = NULL params[6] = CPU2 stat 0 params[7] = CPU2 stat 1 params[8] = NULL params[9] = NULL params[10] = NULL params[11] = NULL Furthermore, if you have a beefy system with more than 128 cpus, you have to break the call into chunks of 128 at a time, using start_cpu at 0, 128, and so forth.
Do you have any comments on whether this is correct or not?
http://git.annexia.org/?p=ocaml-libvirt.git;a=blob;f=libvirt/libvirt_c_oneof...
546 547 /* get percpu information */ 548 NONBLOCKING (nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0));
This gets the number of total params, but this might be different than the number of per-cpu params. I think you want this to use virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0) or even the maximum of the two, if you plan on combining total and per-cpu usage into the same call.
549 CHECK_ERROR (nparams < 0, conn, "virDomainGetCPUStats"); 550 551 if ((params = malloc(sizeof(*params) * nparams * 128)) == NULL)
Here, you are blindly sizing params based on the maximum supported by the API. It might be more efficient to s/128/nr_pcpus/ or even MIN(128, nr_pcpus). It would also be possible to use virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0), which should be the same or less than nr_pcpus, if I'm correctly understanding how nr_pcpus was computed.
552 caml_failwith ("virDomainGetCPUStats: malloc"); 553 554 cpustats = caml_alloc (nr_pcpus, 0); /* cpustats: array of params(list of typed_param) */ 555 cpu = 0; 556 while (cpu < nr_pcpus) { 557 ncpus = nr_pcpus - cpu > 128 ? 128 : nr_pcpus - cpu;
Good, this looks like the correct way to divide things into slices of 128 cpus per read.
558 559 NONBLOCKING (r = virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0));
Here, nparams should be at least as large as the value from virDomainGetCPUStats(dom, NULL, 0, 0, 1, 0), since there might be more per-cpu stats than there are total stats. If nparams is too small, you will be silently losing out on those extra stats.
560 CHECK_ERROR (r < 0, conn, "virDomainGetCPUStats"); 561 562 for (i = 0; i < ncpus; i++) { 563 /* list of typed_param: single linked list of param_nodes */ 564 param_head = Val_emptylist; /* param_head: the head param_node of list of typed_param */ 565 566 if (params[i * nparams].type == 0) { 567 Store_field(cpustats, cpu + i, param_head); 568 continue; 569 } 570 571 for (j = nparams - 1; j >= 0; j--) {
Here, I'd start the iteration on j = r - 1, since we already know r <= nparams, and that any slots beyond r are NULL.
572 pos = i * nparams + j; 573 if (params[pos].type == 0) 574 continue; 575 576 param_node = caml_alloc(2, 0); /* param_node: typed_param, next param_node */ 577 Store_field(param_node, 1, param_head); 578 param_head = param_node; 579 580 typed_param = caml_alloc(2, 0); /* typed_param: field name(string), typed_param_value */ 581 Store_field(param_node, 0, typed_param); ... Skipping this part; it looks reasonable (keeping in mind that this is my first time reviewing ocaml bindings).
610 case VIR_TYPED_PARAM_STRING: 611 typed_param_value = caml_alloc (1, 6); 612 v = caml_copy_string (params[pos].value.s); 613 free (params[pos].value.s); 614 break; 615 default: 616 free (params);
Memory leak - if there are any VIR_TYPED_PARAM_STRING embedded later in params than where you reached in your iteration, you leaked that memory.
617 caml_failwith ("virDomainGetCPUStats: " 618 "unknown parameter type returned"); 619 } 620 Store_field (typed_param_value, 0, v); 621 Store_field (typed_param, 1, typed_param_value); 622 } 623 Store_field (cpustats, cpu + i, param_head); 624 } 625 cpu += ncpus; 626 } 627 free(params);
Not very consistent on your style of space before (.
628 CAMLreturn (cpustats);
This only grabs the per-cpu stats; it doesn't grab any total stats. Does anyone need the ocaml bindings to be able to grab the total stats? -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 03/01/2012 07:54 PM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Changelog: - fixed typos. - fixed string scan routine.
While useful for reviews, information like this doesn't help the permanent libvirt.git log, so it is best to place it after the --- divider.
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt_private.syms | 1 + src/nodeinfo.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 3 ++ 3 files changed, 70 insertions(+), 0 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a104e70..0f4e64c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -793,6 +793,7 @@ virNodeDeviceObjUnlock;
# nodeinfo.h nodeCapsInitNUMA; +nodeGetCPUmap; nodeGetCPUStats; nodeGetCellsFreeMemory; nodeGetFreeMemory; diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..2950306 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -31,6 +31,7 @@ #include <dirent.h> #include <sys/utsname.h> #include <sched.h> +#include "conf/domain_conf.h"
I'm not convinced that this cross-directory include is safe, but my recent syntax check doesn't flag it (which may point to a weakness in the syntax check). But if it needs fixing, we can do that later.
+const char * +nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id ATTRIBUTE_UNUSED, + const char *mapname ATTRIBUTE_UNUSED)
This function is returning malloc'd memory; our convention is to type it 'char *', not 'const char *', to make it obvious that the caller is responsible to free the data ('const char *' should be reserved for static data that the caller need not free).
+ return cpumap; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s",
Indentation. ACK with this squashed in: diff --git i/src/nodeinfo.c w/src/nodeinfo.c index 9b19440..709e94a 100644 --- i/src/nodeinfo.c +++ w/src/nodeinfo.c @@ -1,7 +1,7 @@ /* * nodeinfo.c: Helper routines for OS specific node information * - * Copyright (C) 2006-2008, 2010-2011 Red Hat, Inc. + * Copyright (C) 2006-2008, 2010-2012 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -576,7 +576,7 @@ cleanup: * and max cpu is 7. The map file shows 0-4,6-7. This function parses * it and returns cpumap. */ -static const char * +static char * linuxParseCPUmap(int *max_cpuid, const char *path) { char *map = NULL; @@ -754,14 +754,14 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif } -const char * +char * nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, int *max_id ATTRIBUTE_UNUSED, const char *mapname ATTRIBUTE_UNUSED) { #ifdef __linux__ char *path; - const char *cpumap; + char *cpumap; if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { virReportOOMError(); @@ -772,9 +772,9 @@ nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, VIR_FREE(path); return cpumap; #else - nodeReportError(VIR_ERR_NO_SUPPORT, "%s", - _("node cpumap not implemented on this platform")); - return NULL; + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return NULL; #endif } diff --git i/src/nodeinfo.h w/src/nodeinfo.h index 852e19d..7d2ef1d 100644 --- i/src/nodeinfo.h +++ w/src/nodeinfo.h @@ -1,7 +1,7 @@ /* * nodeinfo.c: Helper routines for OS specific node information * - * Copyright (C) 2006-2008, 2011 Red Hat, Inc. + * Copyright (C) 2006-2008, 2011-2012 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -46,7 +46,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn); -const char *nodeGetCPUmap(virConnectPtr conn, - int *max_id, - const char *mapname); +char *nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/ -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/libvirt.c b/src/libvirt.c index 8035add..a55d823 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -18541,7 +18541,7 @@ int virDomainGetCPUStats(virDomainPtr domain, (start_cpu == -1 && ncpus != 1) || ((params == NULL) != (nparams == 0)) || (ncpus == 0 && params != NULL) || - ncpus < UINT_MAX / nparams) { + (nparams && ncpus > UINT_MAX / nparams)) { virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); goto error; } -- 1.7.4.4

On 02/09/2012 03:43 AM, Lai Jiangshan wrote:
Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/src/libvirt.c b/src/libvirt.c index 8035add..a55d823 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -18541,7 +18541,7 @@ int virDomainGetCPUStats(virDomainPtr domain, (start_cpu == -1 && ncpus != 1) || ((params == NULL) != (nparams == 0)) || (ncpus == 0 && params != NULL) || - ncpus < UINT_MAX / nparams) { + (nparams && ncpus > UINT_MAX / nparams)) {
Good catch, and I'm pushing this one now, before taking longer to review the others. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 02/09/2012 03:43 AM, Lai Jiangshan wrote:
From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
add virBitmapParseCommaSeparetedFormat() for parsing bitmap in
s/Separeted/Separated/
comma separeted ascii format.
and again
This format of bitmap is used in Linux sysfs and cpuset.
Changelog: - fixed typos. - fixed string scan routine.
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- src/libvirt_private.syms | 2 + src/nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 4 +++ src/util/bitmap.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d6ad36c..14b144f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -17,6 +17,7 @@ virBitmapFree; virBitmapGetBit; virBitmapSetBit; virBitmapString; +virBitmapParseCommaSeparatedFormat;
Sorting.
# buf.h @@ -797,6 +798,7 @@ nodeGetCellsFreeMemory; nodeGetFreeMemory; nodeGetInfo; nodeGetMemoryStats; +nodeGetCPUmap;
# nwfilter_conf.h diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..b0ebb88 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -47,6 +47,7 @@ #include "count-one-bits.h" #include "intprops.h" #include "virfile.h" +#include "bitmap.h"
#define VIR_FROM_THIS VIR_FROM_NONE @@ -569,6 +570,33 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This functin parses
s/functin/function/
+ * it and returns bitmap. + */ +static virBitmapPtr linuxParseCPUmap(int *max_cpuid, const char *path)
How is this function different from virDomainCpuSetParse in domain_conf.c? That function parses out a cpuset rather than a bitmap, but the syntax string being parsed looks to be a superset of what you are parsing here. Maybe it's more efficient to write a generic conversion from cpumap to bitset, and reuse the existing parser. I'll have to see how you actually use the parsed bitset later in the series to see which approach might be best.
+{ + char str[1024]; + FILE *fp; + virBitmapPtr map = NULL; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto cleanup; + } + if (fgets(str, sizeof(str), fp) == NULL) {
Are we sure that is long enough? Suppose I have a machine with 512 cores - I can come up with a cpumap that exceeds 1024 bytes.
+ virReportSystemError(errno, _("cannot read from %s"), path); + goto cleanup; + } + map = virBitmapParseCommaSeparatedFormat(str, max_cpuid); + +cleanup: + VIR_FORCE_FCLOSE(fp); + return map; +} #endif
int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +740,29 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif }
+virBitmapPtr nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id,
max_id has to be marked unused, to avoid gcc warnings on non-Linux compilation.
+ const char *mapname)
same for mapname.
+{ +#ifdef __linux__ + char *path; + virBitmapPtr map; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + map = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return map; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1; +#endif +} +
+ +/** + * virBitmapParseCommaSeparatedFormat: + * + * When bitmap is printed in ascii format, especially in Linux, + * comma-separated format is sometimes used. For example, a bitmap 11111011 is + * represetned as 0-4,6-7. This function parses comma-separated format
s/represetned/represented/
+ * and returns virBitmap. Found max bit is returned, too. + * + * This functions stops if characters other than digits, ',', '-' are + * found. This function will be useful for parsing linux's bitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit)
const char *buf, since we aren't modifying it.
+{ + char *pos = buf;
const char *pos, since buf is const.
+ virBitmapPtr map = NULL; + int val, x; + + /* at first, find the highest number */ + val = 0; + while ((*pos != '\n') && (*pos != 0)) { + if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + } else if ((*pos == ',') || (*pos == '-')) { + ++pos; + } else + break; + } + *max_bit = val;
Yeah, I'm definitely starting to think that reusing our existing cpumap parser, then a loop that converts bytes of the cpumap into longs of a virBitmap, might be a more reusable prospect.
- +/* + * parese comma-separeted bitmap format and allocate virBitmap.
s/parese/parse/; s/separeted/separated/
+ */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) + ATTRIBUTE_RETURN_CHECK;
ATTRIBUTE_NONNULL(1), and decide whether max_bit can be NULL (if so, fix the code to allow it; if not, add the attribute) If we go with my reuse suggestion, this would be: virBitmapPtr virBitmapCreateFromCpumap(char *cpumap, int maplen, int *max_bit) -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

remote protocol driver for virDomainGetCPUStats() Note: Unlike other users of virTypedParameter with RPC, this interface can return zero-filled entries because the interface assumes 2 dimentional array. Then, the function has its own serialize/deserialize routine. daemon/remote.c | 146 +++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 ++++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++ src/remote_protocol-structs | 15 ++++ --- daemon/remote.c | 147 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 +++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++- src/remote_protocol-structs | 15 ++++ 4 files changed, 300 insertions(+), 1 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 1ada146..0bf3262 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -3505,6 +3505,153 @@ cleanup: return rv; } +static int +remoteDispatchDomainGetCPUStats(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr hdr ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_get_cpu_stats_args *args, + remote_domain_get_cpu_stats_ret *ret) +{ + virDomainPtr dom = NULL; + struct daemonClientPrivate *priv; + virTypedParameterPtr params = NULL; + remote_typed_param *info = NULL; + int cpu, idx, src, dest; + int rv = -1; + unsigned int return_size = 0; + int percpu_len = 0; + + priv = virNetServerClientGetPrivateData(client); + if (!priv->conn) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + /* + * nparams * ncpus should be smaller than + * REMOTE_DOMAIN_GET_CPU_STATS_MAX but nparams + * and ncpus are limited by API. check it. + */ + if (args->nparams > 16) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nparams too large")); + goto cleanup; + } + if (args->ncpus > 128) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("ncpus too large")); + goto cleanup; + } + + if (args->nparams > 0) { + if (VIR_ALLOC_N(params, args->ncpus * args->nparams) < 0) + goto no_memory; + } + + if (!(dom = get_nonnull_domain(priv->conn, args->dom))) + goto cleanup; + + percpu_len = virDomainGetCPUStats(dom, params, args->nparams, + args->start_cpu, args->ncpus, args->flags); + if (percpu_len < 0) + goto cleanup; + /* If nparams == 0, the function returns a sigle value */ + if (args->nparams == 0) + goto success; + + /* + * Here, we return values based on the real param size returned by + * a driver rather than the passed one. RPC Client stub should decode + * it to fit callar's expectation. + * + * If cpu ID is sparse, The whole array for some cpu IDs will be + * zero-cleared. This doesn't meet to remoteSerializeTypedParameters() + * and we do serialization by ourselves. + */ + return_size = percpu_len * args->ncpus; + if (VIR_ALLOC_N(info, return_size) < 0) + goto no_memory; + + for (cpu = 0; cpu < args->ncpus; ++cpu) { + for (idx = 0; idx < percpu_len; ++idx) { + src = cpu * args->nparams + idx; + dest = cpu * percpu_len + idx; + + /* If CPU ID is discontiguous, this can happen */ + if (params[src].type == 0) + continue; + + info[dest].field = strdup(params[src].field); + if (info[dest].field == NULL) + goto no_memory; + + info[dest].value.type = params[src].type; + + switch (params[src].type) { + case VIR_TYPED_PARAM_INT: + info[dest].value.remote_typed_param_value_u.i = + params[src].value.i; + break; + case VIR_TYPED_PARAM_UINT: + info[dest].value.remote_typed_param_value_u.ui = + params[src].value.ui; + break; + case VIR_TYPED_PARAM_LLONG: + info[dest].value.remote_typed_param_value_u.l = + params[src].value.l; + break; + case VIR_TYPED_PARAM_ULLONG: + info[dest].value.remote_typed_param_value_u.ul = + params[src].value.ul; + break; + case VIR_TYPED_PARAM_DOUBLE: + info[dest].value.remote_typed_param_value_u.d = + params[src].value.d; + break; + case VIR_TYPED_PARAM_BOOLEAN: + info[dest].value.remote_typed_param_value_u.b = + params[src].value.b; + break; + case VIR_TYPED_PARAM_STRING: + info[dest].value.remote_typed_param_value_u.s = + strdup(params[src].value.s); + if (info[dest].value.remote_typed_param_value_u.s == NULL) + goto no_memory; + break; + default: + virNetError(VIR_ERR_RPC, _("unknown parameter type: %d"), + params[src].type); + goto cleanup; + } + } + } +success: + rv = 0; + ret->params.params_len = return_size; + ret->params.params_val = info; + ret->nparams = percpu_len; +cleanup: + if (rv < 0) { + virNetMessageSaveError(rerr); + if (info) { + int i; + for (i = 0; i < return_size; i++) { + VIR_FREE(info[i].field); + if (info[i].value.type == VIR_TYPED_PARAM_STRING) + VIR_FREE(info[i].value.remote_typed_param_value_u.s); + } + VIR_FREE(info); + } + } + virTypedParameterArrayClear(params, args->ncpus * args->nparams); + VIR_FREE(params); + if (dom) + virDomainFree(dom); + return rv; +no_memory: + virReportOOMError(); + goto cleanup; +} + /*----- Helpers. -----*/ /* get_nonnull_domain and get_nonnull_network turn an on-wire diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index f79f53e..9f548ed 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -2305,6 +2305,122 @@ done: return rv; } +static int remoteDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct private_data *priv = domain->conn->privateData; + remote_domain_get_cpu_stats_args args; + remote_domain_get_cpu_stats_ret ret; + remote_typed_param *info; + int rv = -1; + int cpu, idx, src, dest; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, domain); + args.nparams = nparams; + args.start_cpu = start_cpu; + args.ncpus = ncpus; + args.flags = flags; + + memset(&ret, 0, sizeof(ret)); + + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_CPU_STATS, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_args, + (char *) &args, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_ret, + (char *) &ret) == -1) + goto done; + + if (nparams == 0) { + rv = ret.nparams; + goto cleanup; + } + /* + * the returned arrray's size is not same to nparams * ncpus. And + * if cpu ID is not contiguous, all-zero entries can be found. + */ + memset(params, 0, sizeof(virTypedParameter) * nparams * ncpus); + + /* Here, ret.nparams is always smaller than nparams */ + info = ret.params.params_val; + + for (cpu = 0; cpu < ncpus; ++cpu) { + for (idx = 0; idx < ret.nparams; ++idx) { + src = cpu * ret.nparams + idx; + dest = cpu * nparams + idx; + + if (info[src].value.type == 0) /* skip zeroed ones */ + continue; + + params[dest].type = info[src].value.type; + strcpy(params[dest].field, info[src].field); + + switch (params[dest].type) { + case VIR_TYPED_PARAM_INT: + params[dest].value.i = + info[src].value.remote_typed_param_value_u.i; + break; + case VIR_TYPED_PARAM_UINT: + params[dest].value.ui = + info[src].value.remote_typed_param_value_u.ui; + break; + case VIR_TYPED_PARAM_LLONG: + params[dest].value.l = + info[src].value.remote_typed_param_value_u.l; + break; + case VIR_TYPED_PARAM_ULLONG: + params[dest].value.ul = + info[src].value.remote_typed_param_value_u.ul; + break; + case VIR_TYPED_PARAM_DOUBLE: + params[dest].value.d = + info[src].value.remote_typed_param_value_u.d; + break; + case VIR_TYPED_PARAM_BOOLEAN: + params[dest].value.b = + info[src].value.remote_typed_param_value_u.b; + break; + case VIR_TYPED_PARAM_STRING: + params[dest].value.s = + strdup(info[src].value.remote_typed_param_value_u.s); + if (params[dest].value.s == NULL) + goto out_of_memory; + break; + default: + remoteError(VIR_ERR_RPC, _("unknown parameter type: %d"), + params[dest].type); + goto cleanup; + } + } + } + + rv = ret.nparams; +cleanup: + if (rv < 0) { + int max = nparams * ncpus; + int i; + + for (i = 0; i < max; i++) { + if (params[i].type == VIR_TYPED_PARAM_STRING) + VIR_FREE(params[i].value.s); + } + } + xdr_free ((xdrproc_t) xdr_remote_domain_get_cpu_stats_ret, + (char *) &ret); +done: + remoteDriverUnlock(priv); + return rv; +out_of_memory: + virReportOOMError(); + goto cleanup; +} + + /*----------------------------------------------------------------------*/ static virDrvOpenStatus ATTRIBUTE_NONNULL (1) @@ -4751,6 +4867,7 @@ static virDriver remote_driver = { .domainGetBlockIoTune = remoteDomainGetBlockIoTune, /* 0.9.8 */ .domainSetNumaParameters = remoteDomainSetNumaParameters, /* 0.9.9 */ .domainGetNumaParameters = remoteDomainGetNumaParameters, /* 0.9.9 */ + .domainGetCPUStats = remoteDomainGetCPUStats, /* 0.9.10 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 0f354bb..205aeeb 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -208,6 +208,12 @@ const REMOTE_DOMAIN_SEND_KEY_MAX = 16; */ const REMOTE_DOMAIN_INTERFACE_PARAMETERS_MAX = 16; +/* + * Upper limit on list of (real) cpu parameters + * nparams(<=16) * ncpus(<=128) <= 2048. + */ +const REMOTE_DOMAIN_GET_CPU_STATS_MAX = 2048; + /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN]; @@ -1149,6 +1155,19 @@ struct remote_domain_get_block_io_tune_ret { int nparams; }; +struct remote_domain_get_cpu_stats_args { + remote_nonnull_domain dom; + unsigned int nparams; + int start_cpu; + unsigned int ncpus; + unsigned int flags; +}; + +struct remote_domain_get_cpu_stats_ret { + remote_typed_param params<REMOTE_DOMAIN_GET_CPU_STATS_MAX>; + int nparams; +}; + /* Network calls: */ struct remote_num_of_networks_ret { @@ -2667,7 +2686,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SET_INTERFACE_PARAMETERS = 256, /* autogen autogen */ REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, /* skipgen skipgen */ REMOTE_PROC_DOMAIN_SHUTDOWN_FLAGS = 258, /* autogen autogen */ - REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259 /* autogen autogen */ + REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259, /* autogen autogen */ + REMOTE_PROC_DOMAIN_GET_CPU_STATS = 260 /* skipgen skipgen */ /* * Notice how the entries are grouped in sets of 10 ? diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index de85862..8dd5801 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -1841,6 +1841,20 @@ struct remote_domain_shutdown_flags_args { remote_nonnull_domain dom; u_int flags; }; +struct remote_domain_get_cpu_stats_args { + remote_nonnull_domain dom; + u_int nparams; + int start_cpu; + u_int ncpus; + u_int flags; +}; +struct remote_domain_get_cpu_stats_ret { + struct { + u_int params_len; + remote_typed_param * params_val; + } params; + int nparams; +}; enum remote_procedure { REMOTE_PROC_OPEN = 1, REMOTE_PROC_CLOSE = 2, @@ -2101,4 +2115,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, REMOTE_PROC_DOMAIN_SHUTDOWN_FLAGS = 258, REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259, + REMOTE_PROC_DOMAIN_GET_CPU_STATS = 260, }; -- 1.7.4.1

On 01/27/2012 11:21 PM, KAMEZAWA Hiroyuki wrote:
remote protocol driver for virDomainGetCPUStats()
Note: Unlike other users of virTypedParameter with RPC, this interface can return zero-filled entries because the interface assumes 2 dimentional array. Then, the function has its own serialize/deserialize
s/dimentional/dimensional/
routine.
daemon/remote.c | 146 +++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 ++++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++ src/remote_protocol-structs | 15 ++++ --- daemon/remote.c | 147 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 +++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++- src/remote_protocol-structs | 15 +++
Odd that the diffstat listed twice. I'm going to reorder my review, to start with the .x file.
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 0f354bb..205aeeb 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -208,6 +208,12 @@ const REMOTE_DOMAIN_SEND_KEY_MAX = 16; */ const REMOTE_DOMAIN_INTERFACE_PARAMETERS_MAX = 16;
+/* + * Upper limit on list of (real) cpu parameters + * nparams(<=16) * ncpus(<=128) <= 2048. + */ +const REMOTE_DOMAIN_GET_CPU_STATS_MAX = 2048;
We should enforce the two limits independently, which means we also need two smaller constants (it's a shame that rpcgen doesn't allow products when computing a constant, but we can document how we arrive at various numbers). We can reuse VIR_NODE_CPU_STATS_MAX for one of the two limits.
+ /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN];
@@ -1149,6 +1155,19 @@ struct remote_domain_get_block_io_tune_ret { int nparams; };
+struct remote_domain_get_cpu_stats_args { + remote_nonnull_domain dom; + unsigned int nparams; + int start_cpu; + unsigned int ncpus; + unsigned int flags; +}; + +struct remote_domain_get_cpu_stats_ret { + remote_typed_param params<REMOTE_DOMAIN_GET_CPU_STATS_MAX>; + int nparams; +};
Looks good.
+ /* Network calls: */
struct remote_num_of_networks_ret { @@ -2667,7 +2686,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SET_INTERFACE_PARAMETERS = 256, /* autogen autogen */ REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, /* skipgen skipgen */ REMOTE_PROC_DOMAIN_SHUTDOWN_FLAGS = 258, /* autogen autogen */ - REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259 /* autogen autogen */ + REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259, /* autogen autogen */ + REMOTE_PROC_DOMAIN_GET_CPU_STATS = 260 /* skipgen skipgen */
Trivial conflict resolution.
+static int +remoteDispatchDomainGetCPUStats(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr hdr ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_get_cpu_stats_args *args, + remote_domain_get_cpu_stats_ret *ret) +{ + virDomainPtr dom = NULL; + struct daemonClientPrivate *priv; + virTypedParameterPtr params = NULL; + remote_typed_param *info = NULL; + int cpu, idx, src, dest; + int rv = -1; + unsigned int return_size = 0; + int percpu_len = 0; + + priv = virNetServerClientGetPrivateData(client); + if (!priv->conn) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + /* + * nparams * ncpus should be smaller than + * REMOTE_DOMAIN_GET_CPU_STATS_MAX but nparams + * and ncpus are limited by API. check it. + */ + if (args->nparams > 16) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nparams too large")); + goto cleanup; + } + if (args->ncpus > 128) {
Use the constants from the .x file, rather than open-coding things. Oops, I just realized that I mentioned that libvirt.c should not have a length check, but then I forgot to delete it. Meanwhile, even if libvirt.c does not have a length check, it still needs to have a multiplication overflow check.
+ /* + * Here, we return values based on the real param size returned by + * a driver rather than the passed one. RPC Client stub should decode + * it to fit callar's expectation.
s/callar/caller/
+ * + * If cpu ID is sparse, The whole array for some cpu IDs will be + * zero-cleared. This doesn't meet to remoteSerializeTypedParameters() + * and we do serialization by ourselves.
We can still use remoteSerializeTypedParameters; we just have to teach it to skip zero entries like it already can skip string entries. Oh, I just realized that to support string entries, we have to support a flags of VIR_TYPED_PARAM_STRING_OKAY.
+success: + rv = 0; + ret->params.params_len = return_size; + ret->params.params_val = info; + ret->nparams = percpu_len; +cleanup: + if (rv < 0) { + virNetMessageSaveError(rerr); + if (info) {
Cleanup is a lot simpler if we let the existing serialization skip zero entries.
+++ b/src/remote/remote_driver.c @@ -2305,6 +2305,122 @@ done: return rv; }
+static int remoteDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct private_data *priv = domain->conn->privateData; + remote_domain_get_cpu_stats_args args; + remote_domain_get_cpu_stats_ret ret; + remote_typed_param *info; + int rv = -1; + int cpu, idx, src, dest; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, domain); + args.nparams = nparams; + args.start_cpu = start_cpu; + args.ncpus = ncpus; + args.flags = flags; + + memset(&ret, 0, sizeof(ret));
This side needs to validate incoming parameters before sending over the wire. Hmm, remoteNodeGetCPUStats needs to do likewise.
+ + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_CPU_STATS, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_args, + (char *) &args, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_ret, + (char *) &ret) == -1) + goto done; + + if (nparams == 0) { + rv = ret.nparams; + goto cleanup; + } + /* + * the returned arrray's size is not same to nparams * ncpus. And + * if cpu ID is not contiguous, all-zero entries can be found. + */ + memset(params, 0, sizeof(virTypedParameter) * nparams * ncpus);
I prefer sizeof(expr) over sizeof(type), in case expr ever changes types.
+ + /* Here, ret.nparams is always smaller than nparams */ + info = ret.params.params_val; + + for (cpu = 0; cpu < ncpus; ++cpu) { + for (idx = 0; idx < ret.nparams; ++idx) { + src = cpu * ret.nparams + idx; + dest = cpu * nparams + idx; + + if (info[src].value.type == 0) /* skip zeroed ones */ + continue;
Again, open coding things is not nice. If we write the server to never send zero entries (which is a good thing - less data over the wire), then the client needs to re-create zero entries by calling deserialization in a loop - deserialize ret.nparams entries at every nparams slot.
+ + rv = ret.nparams; +cleanup: + if (rv < 0) { + int max = nparams * ncpus; + int i; + + for (i = 0; i < max; i++) { + if (params[i].type == VIR_TYPED_PARAM_STRING) + VIR_FREE(params[i].value.s);
Potential free of bogus memory if anything can reach cleanup with rv < 0 but before we sanitized the contents of params. Here's what I'm squashing in: diff --git i/daemon/remote.c w/daemon/remote.c index 15857a8..cb8423a 100644 --- i/daemon/remote.c +++ w/daemon/remote.c @@ -704,8 +704,11 @@ remoteSerializeTypedParameters(virTypedParameterPtr params, } for (i = 0, j = 0; i < nparams; ++i) { - if (!(flags & VIR_TYPED_PARAM_STRING_OKAY) && - params[i].type == VIR_TYPED_PARAM_STRING) { + /* virDomainGetCPUStats can return a sparse array; also, we + * can't pass back strings to older clients. */ + if (!params[i].type || + (!(flags & VIR_TYPED_PARAM_STRING_OKAY) && + params[i].type == VIR_TYPED_PARAM_STRING)) { --*ret_params_len; continue; } @@ -3534,10 +3537,7 @@ remoteDispatchDomainGetCPUStats(virNetServerPtr server ATTRIBUTE_UNUSED, virDomainPtr dom = NULL; struct daemonClientPrivate *priv; virTypedParameterPtr params = NULL; - remote_typed_param *info = NULL; - int cpu, idx, src, dest; int rv = -1; - unsigned int return_size = 0; int percpu_len = 0; priv = virNetServerClientGetPrivateData(client); @@ -3546,128 +3546,53 @@ remoteDispatchDomainGetCPUStats(virNetServerPtr server ATTRIBUTE_UNUSED, goto cleanup; } - /* - * nparams * ncpus should be smaller than - * REMOTE_DOMAIN_GET_CPU_STATS_MAX but nparams - * and ncpus are limited by API. check it. - */ - if (args->nparams > 16) { + if (args->nparams > REMOTE_NODE_CPU_STATS_MAX) { virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nparams too large")); goto cleanup; } - if (args->ncpus > 128) { + if (args->ncpus > REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX) { virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("ncpus too large")); goto cleanup; } - if (args->nparams > 0) { - if (VIR_ALLOC_N(params, args->ncpus * args->nparams) < 0) - goto no_memory; + if (args->nparams > 0 && + VIR_ALLOC_N(params, args->ncpus * args->nparams) < 0) { + virReportOOMError(); + goto cleanup; } if (!(dom = get_nonnull_domain(priv->conn, args->dom))) goto cleanup; percpu_len = virDomainGetCPUStats(dom, params, args->nparams, - args->start_cpu, args->ncpus, args->flags); + args->start_cpu, args->ncpus, + args->flags); if (percpu_len < 0) goto cleanup; - /* If nparams == 0, the function returns a sigle value */ + /* If nparams == 0, the function returns a single value */ if (args->nparams == 0) goto success; - /* - * Here, we return values based on the real param size returned by - * a driver rather than the passed one. RPC Client stub should decode - * it to fit callar's expectation. - * - * If cpu ID is sparse, The whole array for some cpu IDs will be - * zero-cleared. This doesn't meet to remoteSerializeTypedParameters() - * and we do serialization by ourselves. - */ - return_size = percpu_len * args->ncpus; - if (VIR_ALLOC_N(info, return_size) < 0) - goto no_memory; + if (remoteSerializeTypedParameters(params, args->nparams * args->ncpus, + &ret->params.params_val, + &ret->params.params_len, + args->flags) < 0) + goto cleanup; + + percpu_len = ret->params.params_len / args->ncpus; - for (cpu = 0; cpu < args->ncpus; ++cpu) { - for (idx = 0; idx < percpu_len; ++idx) { - src = cpu * args->nparams + idx; - dest = cpu * percpu_len + idx; - - /* If CPU ID is discontiguous, this can happen */ - if (params[src].type == 0) - continue; - - info[dest].field = strdup(params[src].field); - if (info[dest].field == NULL) - goto no_memory; - - info[dest].value.type = params[src].type; - - switch (params[src].type) { - case VIR_TYPED_PARAM_INT: - info[dest].value.remote_typed_param_value_u.i = - params[src].value.i; - break; - case VIR_TYPED_PARAM_UINT: - info[dest].value.remote_typed_param_value_u.ui = - params[src].value.ui; - break; - case VIR_TYPED_PARAM_LLONG: - info[dest].value.remote_typed_param_value_u.l = - params[src].value.l; - break; - case VIR_TYPED_PARAM_ULLONG: - info[dest].value.remote_typed_param_value_u.ul = - params[src].value.ul; - break; - case VIR_TYPED_PARAM_DOUBLE: - info[dest].value.remote_typed_param_value_u.d = - params[src].value.d; - break; - case VIR_TYPED_PARAM_BOOLEAN: - info[dest].value.remote_typed_param_value_u.b = - params[src].value.b; - break; - case VIR_TYPED_PARAM_STRING: - info[dest].value.remote_typed_param_value_u.s = - strdup(params[src].value.s); - if (info[dest].value.remote_typed_param_value_u.s == NULL) - goto no_memory; - break; - default: - virNetError(VIR_ERR_RPC, _("unknown parameter type: %d"), - params[src].type); - goto cleanup; - } - } - } success: rv = 0; - ret->params.params_len = return_size; - ret->params.params_val = info; ret->nparams = percpu_len; + cleanup: - if (rv < 0) { + if (rv < 0) virNetMessageSaveError(rerr); - if (info) { - int i; - for (i = 0; i < return_size; i++) { - VIR_FREE(info[i].field); - if (info[i].value.type == VIR_TYPED_PARAM_STRING) - VIR_FREE(info[i].value.remote_typed_param_value_u.s); - } - VIR_FREE(info); - } - } virTypedParameterArrayClear(params, args->ncpus * args->nparams); VIR_FREE(params); if (dom) virDomainFree(dom); return rv; -no_memory: - virReportOOMError(); - goto cleanup; } /*----- Helpers. -----*/ diff --git i/src/libvirt.c w/src/libvirt.c index 7060f5a..e702a34 100644 --- i/src/libvirt.c +++ w/src/libvirt.c @@ -18162,7 +18162,7 @@ error: * @nparams: number of parameters per cpu * @start_cpu: which cpu to start with, or -1 for summary * @ncpus: how many cpus to query - * @flags: extra flags; not used yet, so callers should always pass 0 + * @flags: bitwise-OR of virTypedParameterFlags * * Get statistics relating to CPU usage attributable to a single * domain (in contrast to the statistics returned by @@ -18251,20 +18251,19 @@ int virDomainGetCPUStats(virDomainPtr domain, * if start_cpu is -1, ncpus must be 1 * params == NULL must match nparams == 0 * ncpus must be non-zero unless params == NULL + * nparams * ncpus must not overflow (RPC may restrict it even more) */ if (start_cpu < -1 || (start_cpu == -1 && ncpus != 1) || ((params == NULL) != (nparams == 0)) || - (ncpus == 0 && params != NULL)) { - virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); - goto error; - } - - /* remote protocol doesn't welcome big args in one shot */ - if ((nparams > 16) || (ncpus > 128)) { + (ncpus == 0 && params != NULL) || + ncpus < UINT_MAX / nparams) { virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__); goto error; } + if (VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn, + VIR_DRV_FEATURE_TYPED_PARAM_STRING)) + flags |= VIR_TYPED_PARAM_STRING_OKAY; if (conn->driver->domainGetCPUStats) { int ret; diff --git i/src/remote/remote_driver.c w/src/remote/remote_driver.c index a3ca63e..2312cf9 100644 --- i/src/remote/remote_driver.c +++ w/src/remote/remote_driver.c @@ -2,7 +2,7 @@ * remote_driver.c: driver to provide access to libvirtd running * on a remote machine * - * Copyright (C) 2007-2011 Red Hat, Inc. + * Copyright (C) 2007-2012 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 @@ -2315,12 +2315,24 @@ static int remoteDomainGetCPUStats(virDomainPtr domain, struct private_data *priv = domain->conn->privateData; remote_domain_get_cpu_stats_args args; remote_domain_get_cpu_stats_ret ret; - remote_typed_param *info; int rv = -1; - int cpu, idx, src, dest; + int cpu; remoteDriverLock(priv); + if (nparams > REMOTE_NODE_CPU_STATS_MAX) { + remoteError(VIR_ERR_RPC, + _("nparams count exceeds maximum: %u > %u"), + nparams, REMOTE_NODE_CPU_STATS_MAX); + goto done; + } + if (ncpus > REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX) { + remoteError(VIR_ERR_RPC, + _("ncpus count exceeds maximum: %u > %u"), + ncpus, REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX); + goto done; + } + make_nonnull_domain(&args.dom, domain); args.nparams = nparams; args.start_cpu = start_cpu; @@ -2336,67 +2348,38 @@ static int remoteDomainGetCPUStats(virDomainPtr domain, (char *) &ret) == -1) goto done; + /* Check the length of the returned list carefully. */ + if (ret.params.params_len > nparams * ncpus || + (ret.params.params_len && + ret.nparams * ncpus != ret.params.params_len)) { + remoteError(VIR_ERR_RPC, "%s", + _("remoteDomainGetCPUStats: " + "returned number of stats exceeds limit")); + memset(params, 0, sizeof(*params) * nparams * ncpus); + goto cleanup; + } + + /* Handle the case when the caller does not know the number of stats + * and is asking for the number of stats supported + */ if (nparams == 0) { rv = ret.nparams; goto cleanup; } - /* - * the returned arrray's size is not same to nparams * ncpus. And - * if cpu ID is not contiguous, all-zero entries can be found. + + /* The remote side did not send back any zero entries, so we have + * to expand things back into a possibly sparse array. */ - memset(params, 0, sizeof(virTypedParameter) * nparams * ncpus); - - /* Here, ret.nparams is always smaller than nparams */ - info = ret.params.params_val; - - for (cpu = 0; cpu < ncpus; ++cpu) { - for (idx = 0; idx < ret.nparams; ++idx) { - src = cpu * ret.nparams + idx; - dest = cpu * nparams + idx; - - if (info[src].value.type == 0) /* skip zeroed ones */ - continue; - - params[dest].type = info[src].value.type; - strcpy(params[dest].field, info[src].field); - - switch (params[dest].type) { - case VIR_TYPED_PARAM_INT: - params[dest].value.i = - info[src].value.remote_typed_param_value_u.i; - break; - case VIR_TYPED_PARAM_UINT: - params[dest].value.ui = - info[src].value.remote_typed_param_value_u.ui; - break; - case VIR_TYPED_PARAM_LLONG: - params[dest].value.l = - info[src].value.remote_typed_param_value_u.l; - break; - case VIR_TYPED_PARAM_ULLONG: - params[dest].value.ul = - info[src].value.remote_typed_param_value_u.ul; - break; - case VIR_TYPED_PARAM_DOUBLE: - params[dest].value.d = - info[src].value.remote_typed_param_value_u.d; - break; - case VIR_TYPED_PARAM_BOOLEAN: - params[dest].value.b = - info[src].value.remote_typed_param_value_u.b; - break; - case VIR_TYPED_PARAM_STRING: - params[dest].value.s = - strdup(info[src].value.remote_typed_param_value_u.s); - if (params[dest].value.s == NULL) - goto out_of_memory; - break; - default: - remoteError(VIR_ERR_RPC, _("unknown parameter type: %d"), - params[dest].type); - goto cleanup; - } - } + memset(params, 0, sizeof(*params) * nparams * ncpus); + for (cpu = 0; cpu < ncpus; cpu++) { + int tmp = nparams; + remote_typed_param *stride = &ret.params.params_val[cpu * ret.nparams]; + + if (remoteDeserializeTypedParameters(stride, ret.nparams, + REMOTE_NODE_CPU_STATS_MAX, + ¶ms[cpu * nparams], + &tmp) < 0) + goto cleanup; } rv = ret.nparams; @@ -2415,9 +2398,6 @@ cleanup: done: remoteDriverUnlock(priv); return rv; -out_of_memory: - virReportOOMError(); - goto cleanup; } diff --git i/src/remote/remote_protocol.x w/src/remote/remote_protocol.x index 0bd399c..b58925a 100644 --- i/src/remote/remote_protocol.x +++ w/src/remote/remote_protocol.x @@ -209,8 +209,13 @@ const REMOTE_DOMAIN_SEND_KEY_MAX = 16; const REMOTE_DOMAIN_INTERFACE_PARAMETERS_MAX = 16; /* - * Upper limit on list of (real) cpu parameters - * nparams(<=16) * ncpus(<=128) <= 2048. + * Upper limit on cpus involved in per-cpu stats + */ +const REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX = 128; + +/* + * Upper limit on list of per-cpu stats: + * REMOTE_NODE_CPU_STATS_MAX * REMOTE_DOMAIN_GET_CPU_STATS_MAX */ const REMOTE_DOMAIN_GET_CPU_STATS_MAX = 2048; -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Sat, 28 Jan 2012 11:16:03 -0700 Eric Blake <eblake@redhat.com> wrote:
On 01/27/2012 11:21 PM, KAMEZAWA Hiroyuki wrote:
remote protocol driver for virDomainGetCPUStats()
Note: Unlike other users of virTypedParameter with RPC, this interface can return zero-filled entries because the interface assumes 2 dimentional array. Then, the function has its own serialize/deserialize
s/dimentional/dimensional/
routine.
daemon/remote.c | 146 +++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 ++++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++ src/remote_protocol-structs | 15 ++++ --- daemon/remote.c | 147 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 117 +++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 22 ++++++- src/remote_protocol-structs | 15 +++
Odd that the diffstat listed twice.
I'm going to reorder my review, to start with the .x file.
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 0f354bb..205aeeb 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -208,6 +208,12 @@ const REMOTE_DOMAIN_SEND_KEY_MAX = 16; */ const REMOTE_DOMAIN_INTERFACE_PARAMETERS_MAX = 16;
+/* + * Upper limit on list of (real) cpu parameters + * nparams(<=16) * ncpus(<=128) <= 2048. + */ +const REMOTE_DOMAIN_GET_CPU_STATS_MAX = 2048;
We should enforce the two limits independently, which means we also need two smaller constants (it's a shame that rpcgen doesn't allow products when computing a constant, but we can document how we arrive at various numbers). We can reuse VIR_NODE_CPU_STATS_MAX for one of the two limits.
Ok.
+ /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN];
@@ -1149,6 +1155,19 @@ struct remote_domain_get_block_io_tune_ret { int nparams; };
+struct remote_domain_get_cpu_stats_args { + remote_nonnull_domain dom; + unsigned int nparams; + int start_cpu; + unsigned int ncpus; + unsigned int flags; +}; + +struct remote_domain_get_cpu_stats_ret { + remote_typed_param params<REMOTE_DOMAIN_GET_CPU_STATS_MAX>; + int nparams; +};
Looks good.
+ /* Network calls: */
struct remote_num_of_networks_ret { @@ -2667,7 +2686,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SET_INTERFACE_PARAMETERS = 256, /* autogen autogen */ REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, /* skipgen skipgen */ REMOTE_PROC_DOMAIN_SHUTDOWN_FLAGS = 258, /* autogen autogen */ - REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259 /* autogen autogen */ + REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259, /* autogen autogen */ + REMOTE_PROC_DOMAIN_GET_CPU_STATS = 260 /* skipgen skipgen */
Trivial conflict resolution.
+static int +remoteDispatchDomainGetCPUStats(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr hdr ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_get_cpu_stats_args *args, + remote_domain_get_cpu_stats_ret *ret) +{ + virDomainPtr dom = NULL; + struct daemonClientPrivate *priv; + virTypedParameterPtr params = NULL; + remote_typed_param *info = NULL; + int cpu, idx, src, dest; + int rv = -1; + unsigned int return_size = 0; + int percpu_len = 0; + + priv = virNetServerClientGetPrivateData(client); + if (!priv->conn) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + /* + * nparams * ncpus should be smaller than + * REMOTE_DOMAIN_GET_CPU_STATS_MAX but nparams + * and ncpus are limited by API. check it. + */ + if (args->nparams > 16) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nparams too large")); + goto cleanup; + } + if (args->ncpus > 128) {
Use the constants from the .x file, rather than open-coding things.
Sorry.
Oops, I just realized that I mentioned that libvirt.c should not have a length check, but then I forgot to delete it. Meanwhile, even if libvirt.c does not have a length check, it still needs to have a multiplication overflow check.
+ /* + * Here, we return values based on the real param size returned by + * a driver rather than the passed one. RPC Client stub should decode + * it to fit callar's expectation.
s/callar/caller/
+ * + * If cpu ID is sparse, The whole array for some cpu IDs will be + * zero-cleared. This doesn't meet to remoteSerializeTypedParameters() + * and we do serialization by ourselves.
We can still use remoteSerializeTypedParameters; we just have to teach it to skip zero entries like it already can skip string entries.
Yes. you're right.
Oh, I just realized that to support string entries, we have to support a flags of VIR_TYPED_PARAM_STRING_OKAY.
Ah, I missed that.
+success: + rv = 0; + ret->params.params_len = return_size; + ret->params.params_val = info; + ret->nparams = percpu_len; +cleanup: + if (rv < 0) { + virNetMessageSaveError(rerr); + if (info) {
Cleanup is a lot simpler if we let the existing serialization skip zero entries.
Ah, skip is not
+++ b/src/remote/remote_driver.c @@ -2305,6 +2305,122 @@ done: return rv; }
+static int remoteDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct private_data *priv = domain->conn->privateData; + remote_domain_get_cpu_stats_args args; + remote_domain_get_cpu_stats_ret ret; + remote_typed_param *info; + int rv = -1; + int cpu, idx, src, dest; + + remoteDriverLock(priv); + + make_nonnull_domain(&args.dom, domain); + args.nparams = nparams; + args.start_cpu = start_cpu; + args.ncpus = ncpus; + args.flags = flags; + + memset(&ret, 0, sizeof(ret));
This side needs to validate incoming parameters before sending over the wire. Hmm, remoteNodeGetCPUStats needs to do likewise.
+ + if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_CPU_STATS, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_args, + (char *) &args, + (xdrproc_t) xdr_remote_domain_get_cpu_stats_ret, + (char *) &ret) == -1) + goto done; + + if (nparams == 0) { + rv = ret.nparams; + goto cleanup; + } + /* + * the returned arrray's size is not same to nparams * ncpus. And + * if cpu ID is not contiguous, all-zero entries can be found. + */ + memset(params, 0, sizeof(virTypedParameter) * nparams * ncpus);
I prefer sizeof(expr) over sizeof(type), in case expr ever changes types.
+ + /* Here, ret.nparams is always smaller than nparams */ + info = ret.params.params_val; + + for (cpu = 0; cpu < ncpus; ++cpu) { + for (idx = 0; idx < ret.nparams; ++idx) { + src = cpu * ret.nparams + idx; + dest = cpu * nparams + idx; + + if (info[src].value.type == 0) /* skip zeroed ones */ + continue;
Again, open coding things is not nice. If we write the server to never send zero entries (which is a good thing - less data over the wire), then the client needs to re-create zero entries by calling deserialization in a loop - deserialize ret.nparams entries at every nparams slot.
+ + rv = ret.nparams; +cleanup: + if (rv < 0) { + int max = nparams * ncpus; + int i; + + for (i = 0; i < max; i++) { + if (params[i].type == VIR_TYPED_PARAM_STRING) + VIR_FREE(params[i].value.s);
Potential free of bogus memory if anything can reach cleanup with rv < 0 but before we sanitized the contents of params.
Here's what I'm squashing in:
seems nicer ! Thank you for kindly reviewing. Thanks, -Kame

add nodeGetCPUmap() for getting available CPU IDs in a bitmap. add virBitmapParseCommaSeparetedFormat() for parsing bitmap in comma separeted ascii format. This format of bitmap is used in Linux sysfs and cpuset. * cpuacct's percpu usage information is provided based on /sys/devices/system/cpu/present cpu map. So, this kind of call is required. This routine itself may be useful for other purpose. libvirt_private.syms | 2 + nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++++++ nodeinfo.h | 4 +++ util/bitmap.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletion(-) --- src/libvirt_private.syms | 2 + src/nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 4 +++ src/util/bitmap.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 915a43f..fd44322 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -17,6 +17,7 @@ virBitmapFree; virBitmapGetBit; virBitmapSetBit; virBitmapString; +virBitmapParseCommaSeparatedFormat; # buf.h @@ -800,6 +801,7 @@ nodeGetCellsFreeMemory; nodeGetFreeMemory; nodeGetInfo; nodeGetMemoryStats; +nodeGetCPUmap; # nwfilter_conf.h diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..92bada7 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -47,6 +47,7 @@ #include "count-one-bits.h" #include "intprops.h" #include "virfile.h" +#include "bitmap.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -569,6 +570,33 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This functin parse + * it and returns bitmap. + */ +static virBitmapPtr linuxParseCPUmap(int *max_cpuid, const char *path) +{ + char str[1024]; + FILE *fp; + virBitmapPtr map = NULL; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto cleanup; + } + if (fgets(str, sizeof(str), fp) == NULL) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto cleanup; + } + map = virBitmapParseCommaSeparatedFormat(str, max_cpuid); + +cleanup: + VIR_FORCE_FCLOSE(fp); + return map; +} #endif int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +740,29 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif } +virBitmapPtr nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id, + const char *mapname) +{ +#ifdef __linux__ + char *path; + virBitmapPtr map; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + map = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return map; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1; +#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..7f26b77 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -26,6 +26,7 @@ # include "libvirt/libvirt.h" # include "capabilities.h" +# include "bitmap.h" int nodeGetInfo(virConnectPtr conn, virNodeInfoPtr nodeinfo); int nodeCapsInitNUMA(virCapsPtr caps); @@ -46,4 +47,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn); +virBitmapPtr nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/ diff --git a/src/util/bitmap.c b/src/util/bitmap.c index 8c326c4..c7e1a81 100644 --- a/src/util/bitmap.c +++ b/src/util/bitmap.c @@ -33,6 +33,11 @@ #include "bitmap.h" #include "memory.h" #include "buf.h" +#include "c-ctype.h" +#include "util.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE struct _virBitmap { @@ -180,3 +185,63 @@ char *virBitmapString(virBitmapPtr bitmap) return virBufferContentAndReset(&buf); } + +/** + * virBitmapParseCommaSeparatedFormat: + * + * When bitmap is printed in ascii format, expecially in Linux, + * comma-separated format is sometimes used. For example, a bitmap 11111011 is + * represetned as 0-4,6-7. This function parse comma-separated format + * and returns virBitmap. Found max bit is returend, too. + * + * This functions stops if characters other than digits, ',', '-' are + * found. This function will be useful for parsing linux's bitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) +{ + char *pos = buf; + virBitmapPtr map = NULL; + int val, x; + + /* at first, find the highest number */ + val = 0; + while ((*pos != '\n') || (*pos != 0)) { + if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + } else if ((*pos == ',') || (*pos == '-')) { + ++pos; + } else + break; + } + *max_bit = val; + + map = virBitmapAlloc(val + 1); + if (map == NULL) { /* we may return NULL at failure of parsing */ + virReportOOMError(); + return NULL; + } + + pos = buf; + while ((*pos != '\n') || (*pos != 0)) { + if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + if (virBitmapSetBit(map, val) < 0) + goto failed; + } else if (*pos == ',') { + ++pos; + } else if (*pos == '-') { + x = val; + pos++; + virStrToLong_i(pos, &pos, 10, &val); + for (;x <= val; ++x) { + if (virBitmapSetBit(map, x) < 0) + goto failed; + } + } else + break; + } + return map; +failed: + virBitmapFree(map); + return NULL; +} diff --git a/src/util/bitmap.h b/src/util/bitmap.h index ef62cca..e3d3e2b 100644 --- a/src/util/bitmap.h +++ b/src/util/bitmap.h @@ -61,5 +61,9 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) char *virBitmapString(virBitmapPtr bitmap) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; - +/* + * parese comma-separeted bitmap format and allocate virBitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) + ATTRIBUTE_RETURN_CHECK; #endif -- 1.7.4.1

On Sat, Jan 28, 2012 at 03:24:04PM +0900, KAMEZAWA Hiroyuki wrote:
add nodeGetCPUmap() for getting available CPU IDs in a bitmap. add virBitmapParseCommaSeparetedFormat() for parsing bitmap in comma separeted ascii format. This format of bitmap is used in Linux sysfs and cpuset.
* cpuacct's percpu usage information is provided based on /sys/devices/system/cpu/present cpu map. So, this kind of call is required. This routine itself may be useful for other purpose.
libvirt_private.syms | 2 + nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++++++ nodeinfo.h | 4 +++ util/bitmap.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletion(-) --- src/libvirt_private.syms | 2 + src/nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 4 +++ src/util/bitmap.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 915a43f..fd44322 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -17,6 +17,7 @@ virBitmapFree; virBitmapGetBit; virBitmapSetBit; virBitmapString; +virBitmapParseCommaSeparatedFormat;
# buf.h @@ -800,6 +801,7 @@ nodeGetCellsFreeMemory; nodeGetFreeMemory; nodeGetInfo; nodeGetMemoryStats; +nodeGetCPUmap;
# nwfilter_conf.h diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..92bada7 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -47,6 +47,7 @@ #include "count-one-bits.h" #include "intprops.h" #include "virfile.h" +#include "bitmap.h"
#define VIR_FROM_THIS VIR_FROM_NONE @@ -569,6 +570,33 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This functin parse + * it and returns bitmap. + */ +static virBitmapPtr linuxParseCPUmap(int *max_cpuid, const char *path) +{ + char str[1024]; + FILE *fp; + virBitmapPtr map = NULL; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto cleanup; + } + if (fgets(str, sizeof(str), fp) == NULL) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto cleanup; + } + map = virBitmapParseCommaSeparatedFormat(str, max_cpuid); + +cleanup: + VIR_FORCE_FCLOSE(fp); + return map; +} #endif
int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +740,29 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif }
+virBitmapPtr nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id, + const char *mapname) +{ +#ifdef __linux__ + char *path; + virBitmapPtr map; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + map = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return map; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1; +#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..7f26b77 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -26,6 +26,7 @@
# include "libvirt/libvirt.h" # include "capabilities.h" +# include "bitmap.h"
int nodeGetInfo(virConnectPtr conn, virNodeInfoPtr nodeinfo); int nodeCapsInitNUMA(virCapsPtr caps); @@ -46,4 +47,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn);
+virBitmapPtr nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/ diff --git a/src/util/bitmap.c b/src/util/bitmap.c index 8c326c4..c7e1a81 100644 --- a/src/util/bitmap.c +++ b/src/util/bitmap.c @@ -33,6 +33,11 @@ #include "bitmap.h" #include "memory.h" #include "buf.h" +#include "c-ctype.h" +#include "util.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE
struct _virBitmap { @@ -180,3 +185,63 @@ char *virBitmapString(virBitmapPtr bitmap)
return virBufferContentAndReset(&buf); } + +/** + * virBitmapParseCommaSeparatedFormat: + * + * When bitmap is printed in ascii format, expecially in Linux,
s/expecially/especially/
+ * comma-separated format is sometimes used. For example, a bitmap 11111011 is + * represetned as 0-4,6-7. This function parse comma-separated format
s/parse/parses/
+ * and returns virBitmap. Found max bit is returend, too.
s/returend/returned/
+ * + * This functions stops if characters other than digits, ',', '-' are + * found. This function will be useful for parsing linux's bitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) +{ + char *pos = buf; + virBitmapPtr map = NULL; + int val, x; + + /* at first, find the highest number */ + val = 0; + while ((*pos != '\n') || (*pos != 0)) {
(*pos != '\n') && (*pos != 0)
+ if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + } else if ((*pos == ',') || (*pos == '-')) { + ++pos; + } else + break; + } + *max_bit = val; + + map = virBitmapAlloc(val + 1); + if (map == NULL) { /* we may return NULL at failure of parsing */ + virReportOOMError(); + return NULL; + } + + pos = buf; + while ((*pos != '\n') || (*pos != 0)) {
(*pos != '\n') && (*pos != 0)
+ if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + if (virBitmapSetBit(map, val) < 0) + goto failed; + } else if (*pos == ',') { + ++pos; + } else if (*pos == '-') { + x = val; + pos++; + virStrToLong_i(pos, &pos, 10, &val); + for (;x <= val; ++x) { + if (virBitmapSetBit(map, x) < 0) + goto failed; + } + } else + break; + } + return map; +failed: + virBitmapFree(map); + return NULL; +} diff --git a/src/util/bitmap.h b/src/util/bitmap.h index ef62cca..e3d3e2b 100644 --- a/src/util/bitmap.h +++ b/src/util/bitmap.h @@ -61,5 +61,9 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result)
char *virBitmapString(virBitmapPtr bitmap) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; - +/* + * parese comma-separeted bitmap format and allocate virBitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) + ATTRIBUTE_RETURN_CHECK; #endif -- 1.7.4.1
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list
-- Thanks, Hu Tao

On Tue, 31 Jan 2012 17:58:17 +0800 Hu Tao <hutao@cn.fujitsu.com> wrote:
+ +/** + * virBitmapParseCommaSeparatedFormat: + * + * When bitmap is printed in ascii format, expecially in Linux,
s/expecially/especially/
+ * comma-separated format is sometimes used. For example, a bitmap 11111011 is + * represetned as 0-4,6-7. This function parse comma-separated format
s/parse/parses/
+ * and returns virBitmap. Found max bit is returend, too.
s/returend/returned/
will fix.
+ * + * This functions stops if characters other than digits, ',', '-' are + * found. This function will be useful for parsing linux's bitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) +{ + char *pos = buf; + virBitmapPtr map = NULL; + int val, x; + + /* at first, find the highest number */ + val = 0; + while ((*pos != '\n') || (*pos != 0)) {
(*pos != '\n') && (*pos != 0)
Ah, yes. Fixed one here. ==
From 6267799ae373aa259b7da6350c2bf8056eee4ed3 Mon Sep 17 00:00:00 2001 From: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> Date: Fri, 27 Jan 2012 19:38:24 +0900 Subject: [PATCH] add nodeGetCPUmap() for getting available CPU IDs in a bitmap. add virBitmapParseCommaSeparetedFormat() for parsing bitmap in comma separeted ascii format. This format of bitmap is used in Linux sysfs and cpuset.
Changelog: - fixed typos. - fixed string scan routine. Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com> libvirt_private.syms | 2 + nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++++++ nodeinfo.h | 4 +++ util/bitmap.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletion(-) --- src/libvirt_private.syms | 2 + src/nodeinfo.c | 51 ++++++++++++++++++++++++++++++++++++ src/nodeinfo.h | 4 +++ src/util/bitmap.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/bitmap.h | 6 +++- 5 files changed, 127 insertions(+), 1 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e300f06..f6fc5d2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -17,6 +17,7 @@ virBitmapFree; virBitmapGetBit; virBitmapSetBit; virBitmapString; +virBitmapParseCommaSeparatedFormat; # buf.h @@ -801,6 +802,7 @@ nodeGetCellsFreeMemory; nodeGetFreeMemory; nodeGetInfo; nodeGetMemoryStats; +nodeGetCPUmap; # nwfilter_conf.h diff --git a/src/nodeinfo.c b/src/nodeinfo.c index e0b66f7..b0ebb88 100644 --- a/src/nodeinfo.c +++ b/src/nodeinfo.c @@ -47,6 +47,7 @@ #include "count-one-bits.h" #include "intprops.h" #include "virfile.h" +#include "bitmap.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -569,6 +570,33 @@ int linuxNodeGetMemoryStats(FILE *meminfo, cleanup: return ret; } + +/* + * Linux maintains cpu bit map. For example, if cpuid=5's flag is not set + * and max cpu is 7. The map file shows 0-4,6-7. This functin parses + * it and returns bitmap. + */ +static virBitmapPtr linuxParseCPUmap(int *max_cpuid, const char *path) +{ + char str[1024]; + FILE *fp; + virBitmapPtr map = NULL; + + fp = fopen(path, "r"); + if (!fp) { + virReportSystemError(errno, _("cannot open %s"), path); + goto cleanup; + } + if (fgets(str, sizeof(str), fp) == NULL) { + virReportSystemError(errno, _("cannot read from %s"), path); + goto cleanup; + } + map = virBitmapParseCommaSeparatedFormat(str, max_cpuid); + +cleanup: + VIR_FORCE_FCLOSE(fp); + return map; +} #endif int nodeGetInfo(virConnectPtr conn ATTRIBUTE_UNUSED, virNodeInfoPtr nodeinfo) { @@ -712,6 +740,29 @@ int nodeGetMemoryStats(virConnectPtr conn ATTRIBUTE_UNUSED, #endif } +virBitmapPtr nodeGetCPUmap(virConnectPtr conn ATTRIBUTE_UNUSED, + int *max_id, + const char *mapname) +{ +#ifdef __linux__ + char *path; + virBitmapPtr map; + + if (virAsprintf(&path, CPU_SYS_PATH "/%s", mapname) < 0) { + virReportOOMError(); + return NULL; + } + + map = linuxParseCPUmap(max_id, path); + VIR_FREE(path); + return map; +#else + nodeReportError(VIR_ERR_NO_SUPPORT, "%s", + _("node cpumap not implemented on this platform")); + return -1; +#endif +} + #if HAVE_NUMACTL # if LIBNUMA_API_VERSION <= 1 # define NUMA_MAX_N_CPUS 4096 diff --git a/src/nodeinfo.h b/src/nodeinfo.h index 4766152..7f26b77 100644 --- a/src/nodeinfo.h +++ b/src/nodeinfo.h @@ -26,6 +26,7 @@ # include "libvirt/libvirt.h" # include "capabilities.h" +# include "bitmap.h" int nodeGetInfo(virConnectPtr conn, virNodeInfoPtr nodeinfo); int nodeCapsInitNUMA(virCapsPtr caps); @@ -46,4 +47,7 @@ int nodeGetCellsFreeMemory(virConnectPtr conn, int maxCells); unsigned long long nodeGetFreeMemory(virConnectPtr conn); +virBitmapPtr nodeGetCPUmap(virConnectPtr conn, + int *max_id, + const char *mapname); #endif /* __VIR_NODEINFO_H__*/ diff --git a/src/util/bitmap.c b/src/util/bitmap.c index 8c326c4..6dead3b 100644 --- a/src/util/bitmap.c +++ b/src/util/bitmap.c @@ -33,6 +33,11 @@ #include "bitmap.h" #include "memory.h" #include "buf.h" +#include "c-ctype.h" +#include "util.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_NONE struct _virBitmap { @@ -180,3 +185,63 @@ char *virBitmapString(virBitmapPtr bitmap) return virBufferContentAndReset(&buf); } + +/** + * virBitmapParseCommaSeparatedFormat: + * + * When bitmap is printed in ascii format, especially in Linux, + * comma-separated format is sometimes used. For example, a bitmap 11111011 is + * represetned as 0-4,6-7. This function parses comma-separated format + * and returns virBitmap. Found max bit is returned, too. + * + * This functions stops if characters other than digits, ',', '-' are + * found. This function will be useful for parsing linux's bitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) +{ + char *pos = buf; + virBitmapPtr map = NULL; + int val, x; + + /* at first, find the highest number */ + val = 0; + while ((*pos != '\n') && (*pos != 0)) { + if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + } else if ((*pos == ',') || (*pos == '-')) { + ++pos; + } else + break; + } + *max_bit = val; + + map = virBitmapAlloc(val + 1); + if (map == NULL) { /* we may return NULL at failure of parsing */ + virReportOOMError(); + return NULL; + } + + pos = buf; + while ((*pos != '\n') && (*pos != 0)) { + if (c_isdigit(*pos)) { + virStrToLong_i(pos, &pos, 10, &val); + if (virBitmapSetBit(map, val) < 0) + goto failed; + } else if (*pos == ',') { + ++pos; + } else if (*pos == '-') { + x = val; + pos++; + virStrToLong_i(pos, &pos, 10, &val); + for (;x <= val; ++x) { + if (virBitmapSetBit(map, x) < 0) + goto failed; + } + } else + break; + } + return map; +failed: + virBitmapFree(map); + return NULL; +} diff --git a/src/util/bitmap.h b/src/util/bitmap.h index ef62cca..e3d3e2b 100644 --- a/src/util/bitmap.h +++ b/src/util/bitmap.h @@ -61,5 +61,9 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) char *virBitmapString(virBitmapPtr bitmap) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; - +/* + * parese comma-separeted bitmap format and allocate virBitmap. + */ +virBitmapPtr virBitmapParseCommaSeparatedFormat(char *buf, int *max_bit) + ATTRIBUTE_RETURN_CHECK; #endif -- 1.7.4.1

qemu driver for virDomainGetCPUstats using cpuacct cgroup. * Now, only "cpu_time" is supported. * cpuacct cgroup is used for providing percpu cputime information. * include/libvirt/libvirt.h.in - defines VIR_DOMAIN_CPU_STATS_CPUTIME * src/qemu/qemu.conf - take care of cpuacct cgroup. * src/qemu/qemu_conf.c - take care of cpuacct cgroup. * src/qemu/qemu_driver.c - added an interface * src/util/cgroup.c/h - added interface for getting percpu cputime --- include/libvirt/libvirt.h.in | 6 ++ src/qemu/qemu.conf | 3 +- src/qemu/qemu_conf.c | 3 +- src/qemu/qemu_driver.c | 157 ++++++++++++++++++++++++++++++++++++++++++ src/util/cgroup.c | 6 ++ src/util/cgroup.h | 1 + 6 files changed, 174 insertions(+), 2 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index e8b5116..2020059 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -3797,6 +3797,12 @@ int virConnectSetKeepAlive(virConnectPtr conn, int interval, unsigned int count); +/** + * VIR_DOMAIN_CPU_STATS_CPUTIME: + * cpu usage in nanosecond, as a ullong + */ +#define VIR_DOMAIN_CPU_STATS_CPUTIME "cpu_time" + int virDomainGetCPUStats(virDomainPtr domain, virTypedParameterPtr params, unsigned int nparams, diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 4ec5e6c..181d853 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -158,6 +158,7 @@ # - 'memory' - use for memory tunables # - 'blkio' - use for block devices I/O tunables # - 'cpuset' - use for CPUs and memory nodes +# - cpuacct - use for CPUs statistics. # # NB, even if configured here, they won't be used unless # the administrator has mounted cgroups, e.g.: @@ -169,7 +170,7 @@ # can be mounted in different locations. libvirt will detect # where they are located. # -# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset" ] +# cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset", "cpuacct" ] # This is the basic set of devices allowed / required by # all virtual machines. diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index bc0a646..4775638 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -307,7 +307,8 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, (1 << VIR_CGROUP_CONTROLLER_DEVICES) | (1 << VIR_CGROUP_CONTROLLER_MEMORY) | (1 << VIR_CGROUP_CONTROLLER_BLKIO) | - (1 << VIR_CGROUP_CONTROLLER_CPUSET); + (1 << VIR_CGROUP_CONTROLLER_CPUSET) | + (1 << VIR_CGROUP_CONTROLLER_CPUACCT); } for (i = 0 ; i < VIR_CGROUP_CONTROLLER_LAST ; i++) { if (driver->cgroupControllers & (1 << i)) { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ab69dca..52f9ebd 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -11740,6 +11740,162 @@ cleanup: return ret; } + +/* qemuDomainGetCPUStats() with start_cpu == -1 */ +static int qemuDomainGetTotalcpuStats(virDomainPtr domain ATTRIBUTE_UNUSED, + virCgroupPtr group, + virTypedParameterPtr params, + int nparams, + unsigned int flags) +{ + unsigned long long cpu_time; + int param_idx = 0; + int ret; + + virCheckFlags(0, -1); + + if (nparams == 0) /* return supprted number of params */ + return 1; + /* entry 0 is cputime */ + ret = virCgroupGetCpuacctUsage(group, &cpu_time); + if (ret < 0) { + virReportSystemError(-ret, "%s", _("unable to get cpu account")); + return -1; + } + + virTypedParameterAssign(¶ms[param_idx], VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + return param_idx + 1; +} + +static int qemuDomainGetPercpuStats(virDomainPtr domain, + virCgroupPtr group, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + virBitmapPtr map = NULL; + int rv = -1; + int i, max_id; + char *pos; + char *buf = NULL; + virTypedParameterPtr ent; + int param_idx; + + virCheckFlags(0, -1); + /* return the number of supported params ? */ + if (nparams == 0 && ncpus != 0) + return 1; /* only cpu_time is supported */ + + /* return percpu cputime in index 0 */ + param_idx = 0; + /* to parse account file, we need "present" cpu map */ + map = nodeGetCPUmap(domain->conn, &max_id, "present"); + if (!map) + return rv; + + if (ncpus == 0) { /* returns max cpu ID */ + rv = max_id; + goto cleanup; + } + /* we get percpu cputime accoutning info. */ + if (virCgroupGetCpuacctPercpuUsage(group, &buf)) + goto cleanup; + pos = buf; + + if (max_id > start_cpu + ncpus - 1) + max_id = start_cpu + ncpus - 1; + + for (i = 0; i <= max_id; i++) { + bool exist; + unsigned long long cpu_time; + + if (virBitmapGetBit(map, i, &exist) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, _("bitmap parse error")); + goto cleanup; + } + if (!exist) + continue; + if (virStrToLong_ull(pos, &pos, 10, &cpu_time) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cpuacct parse error")); + goto cleanup; + } + if (i < start_cpu) + continue; + ent = ¶ms[ (i - start_cpu) * nparams + param_idx]; + virTypedParameterAssign(ent, VIR_DOMAIN_CPU_STATS_CPUTIME, + VIR_TYPED_PARAM_ULLONG, cpu_time); + } + rv = param_idx + 1; +cleanup: + VIR_FREE(buf); + virBitmapFree(map); + return rv; +} + + +static int +qemuDomainGetCPUStats(virDomainPtr domain, + virTypedParameterPtr params, + unsigned int nparams, + int start_cpu, + unsigned int ncpus, + unsigned int flags) +{ + struct qemud_driver *driver = domain->conn->privateData; + virCgroupPtr group = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + bool isActive; + + virCheckFlags(0, -1); + + qemuDriverLock(driver); + + vm = virDomainFindByUUID(&driver->domains, domain->uuid); + if (vm == NULL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("No such domain %s"), domain->uuid); + goto cleanup; + } + + isActive = virDomainObjIsActive(vm); + if (!isActive) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + if (!qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_CPUACCT)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cgroup CPUACCT controller is not mounted")); + goto cleanup; + } + + if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) != 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot find cgroup for domain %s"), vm->def->name); + goto cleanup; + } + + if (nparams == 0 && ncpus == 0) /* returns max cpu id */ + ret = qemuDomainGetPercpuStats(domain, group, NULL, 0, 0, 0, flags); + else if (start_cpu == -1) /* get total */ + ret = qemuDomainGetTotalcpuStats(domain, group, params, nparams, flags); + else + ret = qemuDomainGetPercpuStats(domain, group, params, nparams, + start_cpu, ncpus ,flags); +cleanup: + virCgroupFree(&group); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + return ret; +} + static virDriver qemuDriver = { .no = VIR_DRV_QEMU, .name = "QEMU", @@ -11893,6 +12049,7 @@ static virDriver qemuDriver = { .domainGetNumaParameters = qemuDomainGetNumaParameters, /* 0.9.9 */ .domainGetInterfaceParameters = qemuDomainGetInterfaceParameters, /* 0.9.9 */ .domainSetInterfaceParameters = qemuDomainSetInterfaceParameters, /* 0.9.9 */ + .domainGetCPUStats = qemuDomainGetCPUStats, /* 0.9.10 */ }; diff --git a/src/util/cgroup.c b/src/util/cgroup.c index 15b870d..2d34c50 100644 --- a/src/util/cgroup.c +++ b/src/util/cgroup.c @@ -1555,6 +1555,12 @@ int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage) "cpuacct.usage", usage); } +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage) +{ + return virCgroupGetValueStr(group, VIR_CGROUP_CONTROLLER_CPUACCT, + "cpuacct.usage_percpu", usage); +} + int virCgroupSetFreezerState(virCgroupPtr group, const char *state) { return virCgroupSetValueStr(group, diff --git a/src/util/cgroup.h b/src/util/cgroup.h index 8d75735..b4e0f37 100644 --- a/src/util/cgroup.h +++ b/src/util/cgroup.h @@ -115,6 +115,7 @@ int virCgroupSetCpuCfsQuota(virCgroupPtr group, long long cfs_quota); int virCgroupGetCpuCfsQuota(virCgroupPtr group, long long *cfs_quota); int virCgroupGetCpuacctUsage(virCgroupPtr group, unsigned long long *usage); +int virCgroupGetCpuacctPercpuUsage(virCgroupPtr group, char **usage); int virCgroupSetFreezerState(virCgroupPtr group, const char *state); int virCgroupGetFreezerState(virCgroupPtr group, char **state); -- 1.7.4.1

cpu-accts command shows cpu accounting information of a domain. [root@bluextal ~]# virsh cpu-accts GuestX1 Total: cpu_time 165.4 CPU0: cpu_time 0.0 CPU1: cpu_time 0.0 CPU2: cpu_time 0.0 CPU3: cpu_time 0.0 CPU4: cpu_time 78.9 CPU5: cpu_time 0.0 CPU6: cpu_time 0.0 CPU7: cpu_time 86.5 This guest using 165secs of cpu in total. CPU4/7 is used mainly. --- tools/virsh.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 5 ++ 2 files changed, 121 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 74655c2..9746500 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -5106,6 +5106,121 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) } /* + * "cputime" command + */ +static const vshCmdInfo info_cpu_accts[] = { + {"help", N_("show domain cpu accounting information")}, + {"desc", N_("Returns information about the domain's cpu accounting")}, + {NULL, NULL}, +}; + +static const vshCmdOptDef opts_cpu_accts[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {NULL, 0, 0, NULL}, +}; + +static bool +cmdCPUAccts(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + virTypedParameterPtr params = NULL; + bool ret = true; + int i, j, pos, cpu, nparams; + int max_id; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + /* get max cpu id on the node */ + max_id = virDomainGetCPUStats(dom, NULL, 0, 0, 0, 0); + if (max_id < 0) { + vshPrint(ctl, "max id %d\n", max_id); + goto cleanup; + } + /* get supported num of parameter for total statistics */ + nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + if (VIR_ALLOC_N(params, nparams)) { + virReportOOMError(); + goto cleanup; + } + /* passing start_cpu == -1 gives us domain's total status */ + nparams = virDomainGetCPUStats(dom, params, nparams, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + vshPrint(ctl, "Total:\n"); + for (i = 0; i < nparams; i++) { + vshPrint(ctl, "\t%-15s ", params[i].field); + switch (params[i].type) { + case VIR_TYPED_PARAM_ULLONG: + /* Now all UULONG param is used for time accounting in ns */ + vshPrint(ctl, "%.1lf\n", + params[i].value.ul / 1000000000.0); + break; + default: + vshError(ctl, "%s", _("API mismatch?")); + goto cleanup; + } + } + VIR_FREE(params); + /* get percpu information */ + nparams = virDomainGetCPUStats(dom, NULL, 0, -1, 1, 0); + if (nparams < 0) + goto cleanup; + + cpu = 0; + while (cpu <= max_id) { + int ncpus = 128; + + if (cpu + ncpus - 1 > max_id) /* id starts from 0. */ + ncpus = max_id + 1 - cpu; + + if (VIR_ALLOC_N(params, nparams * ncpus)) { + virReportOOMError(); + goto cleanup; + } + + if (virDomainGetCPUStats(dom, params, nparams, cpu, ncpus, 0) < 0) + goto cleanup; + + for (i = 0; i < ncpus; i++) { + if (params[i * nparams].type == 0) + continue; + vshPrint(ctl, "CPU%d:\n", cpu + i); + + for (j = 0; j < nparams; j++) { + pos = i * nparams + j; + if (params[pos].type == 0) + continue; + vshPrint(ctl, "\t%-15s ", params[pos].field); + switch(params[j].type) { + case VIR_TYPED_PARAM_ULLONG: + vshPrint(ctl, "%.1lf\n", + params[pos].value.ul / 1000000000.0); + break; + default: + vshError(ctl, "%s", _("API mismatch?")); + goto cleanup; + } + } + } + cpu += ncpus; + /* Note: If we handle string type, it should be freed */ + VIR_FREE(params); + } +cleanup: + VIR_FREE(params); + virDomainFree(dom); + return ret; +} + +/* * "inject-nmi" command */ static const vshCmdInfo info_inject_nmi[] = { @@ -16014,6 +16129,7 @@ static const vshCmdDef domManagementCmds[] = { #endif {"cpu-baseline", cmdCPUBaseline, opts_cpu_baseline, info_cpu_baseline, 0}, {"cpu-compare", cmdCPUCompare, opts_cpu_compare, info_cpu_compare, 0}, + {"cpu-accts", cmdCPUAccts, opts_cpu_accts, info_cpu_accts, 0}, {"create", cmdCreate, opts_create, info_create, 0}, {"define", cmdDefine, opts_define, info_define, 0}, {"destroy", cmdDestroy, opts_destroy, info_destroy, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index 8599f66..df65935 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -721,6 +721,11 @@ Provide the maximum number of virtual CPUs supported for a guest VM on this connection. If provided, the I<type> parameter must be a valid type attribute for the <domain> element of XML. +=item B<cpu-accts> I<domain> + +Provide cpu accounting information of a domain. The domain should +be running. + =item B<migrate> [I<--live>] [I<--direct>] [I<--p2p> [I<--tunnelled>]] [I<--persistent>] [I<--undefinesource>] [I<--suspend>] [I<--copy-storage-all>] [I<--copy-storage-inc>] [I<--change-protection>] [I<--verbose>] -- 1.7.4.1

On Sat, Jan 28, 2012 at 03:12:12PM +0900, KAMEZAWA Hiroyuki wrote:
Previous thread title was
[libvirt] [PATCH 0/5 0/1 0/1 V3] Add new public API virDomainGetPcpusUsage() and pcpuinfo command in virsh
This is new version. As suggested in previous thread, this version impelements virDomainGetCPUStats(), which uses virTypedParameterPtr as argument. I wonder I've already missed the deadline. If this this version is not enough, Lai-san will post v5. Someone may think the new API is complicated.
Is v5 posted yet? I can take a look at it this weekend or Monday. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org

On Sat, Jan 28, 2012 at 07:11:10AM +0000, Richard W.M. Jones wrote:
On Sat, Jan 28, 2012 at 03:12:12PM +0900, KAMEZAWA Hiroyuki wrote:
Previous thread title was
[libvirt] [PATCH 0/5 0/1 0/1 V3] Add new public API virDomainGetPcpusUsage() and pcpuinfo command in virsh
This is new version. As suggested in previous thread, this version impelements virDomainGetCPUStats(), which uses virTypedParameterPtr as argument. I wonder I've already missed the deadline. If this this version is not enough, Lai-san will post v5. Someone may think the new API is complicated.
Is v5 posted yet? I can take a look at it this weekend or Monday.
Ignore that ... My email server was being slow. I can see V4 now: https://www.redhat.com/archives/libvir-list/2012-January/msg01282.html Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
participants (7)
-
Daniel P. Berrange
-
Eric Blake
-
Eric Sauer
-
Hu Tao
-
KAMEZAWA Hiroyuki
-
Lai Jiangshan
-
Richard W.M. Jones