[libvirt] [python PATCH 0/5] Implement bindings for bulk stats API

The last patch is to ease review to be able to build the series. Peter Krempa (5): generator: enum: Don't sort enums by names API: Skip 'virDomainStatsRecordListFree' API: Implement bindings for virConnectGetAllDomainStats API: Implement bindings for virDomainListGetStats DO NOT APPLY: Fix build with missing virDomainBlockCopy API generator.py | 6 +- libvirt-override-virConnect.py | 100 ++++++++++++++++++++++++++++ libvirt-override.c | 144 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 2 deletions(-) -- 2.0.2

Setting OCDs aside, sorting enums breaks if the definition contains links to other enums defined in the libvirt header before. Let the generator generate it in the natural order. --- generator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/generator.py b/generator.py index a12c52b..9c497be 100755 --- a/generator.py +++ b/generator.py @@ -1786,8 +1786,6 @@ def buildWrappers(module): return value enumvals = list(enums.items()) - if enumvals is not None: - enumvals.sort(key=lambda x: x[0]) for type,enum in enumvals: classes.write("# %s\n" % type) items = list(enum.items()) -- 2.0.2

On 08/28/2014 10:32 AM, Peter Krempa wrote:
Setting OCDs aside, sorting enums breaks if the definition contains links to other enums defined in the libvirt header before. Let the generator generate it in the natural order. --- generator.py | 2 -- 1 file changed, 2 deletions(-)
ACK. But I _also_ think we need to fix libvirt to recursively resolve enums defined by reference to other names, so that the API xml file it generates is fully numeric rather than making clients chase down symbolic resolutions themselves. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 08/28/2014 06:38 PM, Eric Blake wrote:
On 08/28/2014 10:32 AM, Peter Krempa wrote:
Setting OCDs aside, sorting enums breaks if the definition contains links to other enums defined in the libvirt header before. Let the generator generate it in the natural order. --- generator.py | 2 -- 1 file changed, 2 deletions(-)
ACK. But I _also_ think we need to fix libvirt to recursively resolve enums defined by reference to other names, so that the API xml file it generates is fully numeric rather than making clients chase down symbolic resolutions themselves.
This doesn't fix the issue as it's generated in random order now and sometimes it generate the enums' definition in wrong order so it cannot be resolved. I agree that we should remove that references. NACK Pavel

The new API function doesn't make sense to be exported in python. The bindings will return native types instead of the struct array. --- generator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/generator.py b/generator.py index 9c497be..cfc016e 100755 --- a/generator.py +++ b/generator.py @@ -571,6 +571,7 @@ skip_function = ( "virTypedParamsGetULLong", 'virNetworkDHCPLeaseFree', # only useful in C, python code uses list + 'virDomainStatsRecordListFree', # only useful in C, python uses dict ) lxc_skip_function = ( -- 2.0.2

On 08/28/2014 10:32 AM, Peter Krempa wrote:
The new API function doesn't make sense to be exported in python. The bindings will return native types instead of the struct array. --- generator.py | 1 + 1 file changed, 1 insertion(+)
ACK.
diff --git a/generator.py b/generator.py index 9c497be..cfc016e 100755 --- a/generator.py +++ b/generator.py @@ -571,6 +571,7 @@ skip_function = ( "virTypedParamsGetULLong",
'virNetworkDHCPLeaseFree', # only useful in C, python code uses list + 'virDomainStatsRecordListFree', # only useful in C, python uses dict )
lxc_skip_function = (
-- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 08/28/2014 06:32 PM, Peter Krempa wrote:
The new API function doesn't make sense to be exported in python. The bindings will return native types instead of the struct array. --- generator.py | 1 + 1 file changed, 1 insertion(+)
diff --git a/generator.py b/generator.py index 9c497be..cfc016e 100755 --- a/generator.py +++ b/generator.py @@ -571,6 +571,7 @@ skip_function = ( "virTypedParamsGetULLong",
'virNetworkDHCPLeaseFree', # only useful in C, python code uses list + 'virDomainStatsRecordListFree', # only useful in C, python uses dict )
lxc_skip_function = (
You also have to add this hunk to make the sanitytest happy: diff --git a/sanitytest.py b/sanitytest.py index 4f4a648..10cf9f0 100644 --- a/sanitytest.py +++ b/sanitytest.py @@ -81,6 +81,9 @@ for cname in wantfunctions: if name[0:23] == "virNetworkDHCPLeaseFree": continue + if name[0:28] == "virDomainStatsRecordListFree": + continue + # These aren't functions, they're callback signatures if name in ["virConnectAuthCallbackPtr", "virConnectCloseFunc", "virStreamSinkFunc", "virStreamSourceFunc", "virStreamEventCallback",

Implement the function by returning a list of tuples instead the array of virDomainStatsRecords and store the typed parameters as dict. --- generator.py | 1 + libvirt-override-virConnect.py | 53 ++++++++++++++++++++++++ libvirt-override.c | 94 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/generator.py b/generator.py index cfc016e..9addb89 100755 --- a/generator.py +++ b/generator.py @@ -507,6 +507,7 @@ skip_function = ( 'virConnectListAllNodeDevices', # overridden in virConnect.py 'virConnectListAllNWFilters', # overridden in virConnect.py 'virConnectListAllSecrets', # overridden in virConnect.py + 'virConnectGetAllDomainStats', # overridden in virConnect.py 'virStreamRecvAll', # Pure python libvirt-override-virStream.py 'virStreamSendAll', # Pure python libvirt-override-virStream.py diff --git a/libvirt-override-virConnect.py b/libvirt-override-virConnect.py index 31d71a3..c4c400a 100644 --- a/libvirt-override-virConnect.py +++ b/libvirt-override-virConnect.py @@ -383,3 +383,56 @@ if ret is None:raise libvirtError('virDomainCreateXMLWithFiles() failed', conn=self) __tmp = virDomain(self,_obj=ret) return __tmp + + def getAllDomainStats(self, stats = 0, flags=0): + """Query statistics for all domains on a given connection. + + Report statistics of various parameters for a running VM according to @stats + field. The statistics are returned as an array of structures for each queried + domain. The structure contains an array of typed parameters containing the + individual statistics. The typed parameter name for each statistic field + consists of a dot-separated string containing name of the requested group + followed by a group specific description of the statistic value. + + The statistic groups are enabled using the @stats parameter which is a + binary-OR of enum virDomainStatsTypes. The following groups are available + (although not necessarily implemented for each hypervisor): + + VIR_DOMAIN_STATS_STATE: Return domain state and reason for entering that + state. The typed parameter keys are in this format: + "state.state" - state of the VM, returned as int from virDomainState enum + "state.reason" - reason for entering given state, returned as int from + virDomain*Reason enum corresponding to given state. + + Using 0 for @stats returns all stats groups supported by the given + hypervisor. + + Specifying VIR_CONNECT_GET_ALL_DOMAINS_STATS_ENFORCE_STATS as @flags makes + the function return error in case some of the stat types in @stats were + not recognized by the daemon. + + Similarly to virConnectListAllDomains, @flags can contain various flags to + filter the list of domains to provide stats for. + + VIR_CONNECT_GET_ALL_DOMAINS_STATS_ACTIVE selects online domains while + VIR_CONNECT_GET_ALL_DOMAINS_STATS_INACTIVE selects offline ones. + + VIR_CONNECT_GET_ALL_DOMAINS_STATS_PERSISTENT and + VIR_CONNECT_GET_ALL_DOMAINS_STATS_TRANSIENT allow to filter the list + according to their persistence. + + To filter the list of VMs by domain state @flags can contain + VIR_CONNECT_GET_ALL_DOMAINS_STATS_RUNNING, + VIR_CONNECT_GET_ALL_DOMAINS_STATS_PAUSED, + VIR_CONNECT_GET_ALL_DOMAINS_STATS_SHUTOFF and/or + VIR_CONNECT_GET_ALL_DOMAINS_STATS_OTHER for all other states. """ + ret = libvirtmod.virConnectGetAllDomainStats(self._o, stats, flags) + if ret is None: + raise libvirtError("virConnectGetAllDomainStats() failed", conn=self) + + retlist = list() + for elem in ret: + record = (virDomain(self, _obj=elem[0]) , elem[1]) + retlist.append(record) + + return retlist diff --git a/libvirt-override.c b/libvirt-override.c index b2271ae..df4f15b 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -4964,6 +4964,97 @@ cleanup: return py_retval; } +#if LIBVIR_CHECK_VERSION(1, 2, 8) +static PyObject * +convertDomainStatsRecord(virDomainStatsRecordPtr *records, + int nrecords) +{ + PyObject *py_retval; + PyObject *py_record; + PyObject *py_record_domain; + PyObject *py_record_stats; + size_t i; + + if (!(py_retval = PyList_New(nrecords))) + return NULL; + + for (i = 0; i < nrecords; i++) { + if (!(py_record = PyTuple_New(2))) + goto error; + + /* libvirt_virDomainPtrWrap steals the object */ + virDomainRef(records[i]->dom); + if (!(py_record_domain = libvirt_virDomainPtrWrap(records[i]->dom))) { + virDomainFree(records[i]->dom); + goto error; + } + + if (!(py_record_stats = getPyVirTypedParameter(records[i]->params, + records[i]->nparams))) + goto error; + + if (PyTuple_SetItem(py_record, 0, py_record_domain) < 0) + goto error; + + py_record_domain = NULL; + + if (PyTuple_SetItem(py_record, 1, py_record_stats) < 0) + goto error; + + py_record_stats = NULL; + + if (PyList_SetItem(py_retval, i, py_record) < 0) + goto error; + + py_record = NULL; + } + + return py_retval; + + error: + Py_XDECREF(py_retval); + Py_XDECREF(py_record); + Py_XDECREF(py_record_domain); + Py_XDECREF(py_record_stats); + return NULL; +} + + +static PyObject * +libvirt_virConnectGetAllDomainStats(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + PyObject *pyobj_conn; + PyObject *py_retval; + virConnectPtr conn; + virDomainStatsRecordPtr *records; + int nrecords; + unsigned int flags; + unsigned int stats; + + if (!PyArg_ParseTuple(args, (char *)"Oii:virConnectGetAllDomainStats", + &pyobj_conn, &stats, &flags)) + return NULL; + conn = (virConnectPtr) PyvirConnect_Get(pyobj_conn); + + LIBVIRT_BEGIN_ALLOW_THREADS; + nrecords = virConnectGetAllDomainStats(conn, stats, &records, flags); + LIBVIRT_END_ALLOW_THREADS; + + if (nrecords < 0) + return VIR_PY_NONE; + + if (!(py_retval = convertDomainStatsRecord(records, nrecords))) + py_retval = VIR_PY_NONE; + + cleanup: + virDomainStatsRecordListFree(records); + + return py_retval; +} +#endif + + /******************************************* * Helper functions to avoid importing modules * for every callback @@ -8140,6 +8231,9 @@ static PyMethodDef libvirtMethods[] = { {(char *) "virNodeGetFreePages", libvirt_virNodeGetFreePages, METH_VARARGS, NULL}, {(char *) "virNetworkGetDHCPLeases", libvirt_virNetworkGetDHCPLeases, METH_VARARGS, NULL}, #endif /* LIBVIR_CHECK_VERSION(1, 2, 6) */ +#if LIBVIR_CHECK_VERSION(1, 2, 8) + {(char *) "virConnectGetAllDomainStats", libvirt_virConnectGetAllDomainStats, METH_VARARGS, NULL}, +#endif /* LIBVIR_CHECK_VERSION(1, 2, 8) */ {NULL, NULL, 0, NULL} }; -- 2.0.2

On 08/28/2014 06:32 PM, Peter Krempa wrote:
Implement the function by returning a list of tuples instead the array of virDomainStatsRecords and store the typed parameters as dict. --- generator.py | 1 + libvirt-override-virConnect.py | 53 ++++++++++++++++++++++++ libvirt-override.c | 94 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+)
ACK Pavel

Implement the function by returning a list of tuples instead the array of virDomainStatsRecords and store the typed parameters as dict. --- generator.py | 1 + libvirt-override-virConnect.py | 47 +++++++++++++++++++++++++++++++++++++++ libvirt-override.c | 50 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/generator.py b/generator.py index 9addb89..3642838 100755 --- a/generator.py +++ b/generator.py @@ -508,6 +508,7 @@ skip_function = ( 'virConnectListAllNWFilters', # overridden in virConnect.py 'virConnectListAllSecrets', # overridden in virConnect.py 'virConnectGetAllDomainStats', # overridden in virConnect.py + 'virDomainListGetStats', # overriden in virConnect.py 'virStreamRecvAll', # Pure python libvirt-override-virStream.py 'virStreamSendAll', # Pure python libvirt-override-virStream.py diff --git a/libvirt-override-virConnect.py b/libvirt-override-virConnect.py index c4c400a..218f266 100644 --- a/libvirt-override-virConnect.py +++ b/libvirt-override-virConnect.py @@ -436,3 +436,50 @@ retlist.append(record) return retlist + + def domainListGetStats(self, doms, stats=0, flags=0): + """ Query statistics for given domains. + + Report statistics of various parameters for a running VM according to @stats + field. The statistics are returned as an array of structures for each queried + domain. The structure contains an array of typed parameters containing the + individual statistics. The typed parameter name for each statistic field + consists of a dot-separated string containing name of the requested group + followed by a group specific description of the statistic value. + + The statistic groups are enabled using the @stats parameter which is a + binary-OR of enum virDomainStatsTypes. The following groups are available + (although not necessarily implemented for each hypervisor): + + VIR_DOMAIN_STATS_STATE: Return domain state and reason for entering that + state. The typed parameter keys are in this format: + "state.state" - state of the VM, returned as int from virDomainState enum + "state.reason" - reason for entering given state, returned as int from + virDomain*Reason enum corresponding to given state. + + Using 0 for @stats returns all stats groups supported by the given + hypervisor. + + Specifying VIR_CONNECT_GET_ALL_DOMAINS_STATS_ENFORCE_STATS as @flags makes + the function return error in case some of the stat types in @stats were + not recognized by the daemon. + + Get statistics about domains provided as a list in @doms. @stats is + a bit field selecting requested statistics types.""" + domlist = list() + for dom in doms: + if not isinstance(dom, virDomain): + raise libvirtError("domain list contains non-domain elements", conn=self) + + domlist.append(dom._o) + + ret = libvirtmod.virDomainListGetStats(self._o, domlist, stats, flags) + if ret is None: + raise libvirtError("virDomainListGetStats() failed", conn=self) + + retlist = list() + for elem in ret: + record = (virDomain(self, _obj=elem[0]) , elem[1]) + retlist.append(record) + + return retlist diff --git a/libvirt-override.c b/libvirt-override.c index df4f15b..7e9f570 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -5052,6 +5052,55 @@ libvirt_virConnectGetAllDomainStats(PyObject *self ATTRIBUTE_UNUSED, return py_retval; } + + +static PyObject * +libvirt_virDomainListGetStats(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + PyObject *pyobj_conn; + PyObject *py_retval; + PyObject *py_domlist; + virConnectPtr conn; + virDomainStatsRecordPtr *records; + virDomainPtr *doms = NULL; + int nrecords; + int ndoms; + size_t i; + unsigned int flags; + unsigned int stats; + + if (!PyArg_ParseTuple(args, (char *)"OOii:virDomainListGetStats", + &pyobj_conn, &py_domlist, &stats, &flags)) + return NULL; + conn = (virConnectPtr) PyvirConnect_Get(pyobj_conn); + + if (PyList_Check(py_domlist)) { + ndoms = PyList_Size(py_domlist); + + if (VIR_ALLOC_N(doms, ndoms + 1) < 0) + return PyErr_NoMemory(); + + for (i = 0; i < ndoms; i++) + doms[i] = PyvirDomain_Get(PyList_GetItem(py_domlist, i)); + } + + LIBVIRT_BEGIN_ALLOW_THREADS; + nrecords = virDomainListGetStats(doms, stats, &records, flags); + LIBVIRT_END_ALLOW_THREADS; + + if (nrecords < 0) + return VIR_PY_NONE; + + if (!(py_retval = convertDomainStatsRecord(records, nrecords))) + py_retval = VIR_PY_NONE; + + cleanup: + virDomainStatsRecordListFree(records); + VIR_FREE(doms); + + return py_retval; +} #endif @@ -8233,6 +8282,7 @@ static PyMethodDef libvirtMethods[] = { #endif /* LIBVIR_CHECK_VERSION(1, 2, 6) */ #if LIBVIR_CHECK_VERSION(1, 2, 8) {(char *) "virConnectGetAllDomainStats", libvirt_virConnectGetAllDomainStats, METH_VARARGS, NULL}, + {(char *) "virDomainListGetStats", libvirt_virDomainListGetStats, METH_VARARGS, NULL}, #endif /* LIBVIR_CHECK_VERSION(1, 2, 8) */ {NULL, NULL, 0, NULL} }; -- 2.0.2

On 08/28/2014 06:32 PM, Peter Krempa wrote:
Implement the function by returning a list of tuples instead the array of virDomainStatsRecords and store the typed parameters as dict. --- generator.py | 1 + libvirt-override-virConnect.py | 47 +++++++++++++++++++++++++++++++++++++++ libvirt-override.c | 50 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+)
diff --git a/generator.py b/generator.py index 9addb89..3642838 100755 --- a/generator.py +++ b/generator.py @@ -508,6 +508,7 @@ skip_function = ( 'virConnectListAllNWFilters', # overridden in virConnect.py 'virConnectListAllSecrets', # overridden in virConnect.py 'virConnectGetAllDomainStats', # overridden in virConnect.py + 'virDomainListGetStats', # overriden in virConnect.py
'virStreamRecvAll', # Pure python libvirt-override-virStream.py 'virStreamSendAll', # Pure python libvirt-override-virStream.py diff --git a/libvirt-override-virConnect.py b/libvirt-override-virConnect.py index c4c400a..218f266 100644 --- a/libvirt-override-virConnect.py +++ b/libvirt-override-virConnect.py @@ -436,3 +436,50 @@ retlist.append(record)
return retlist + + def domainListGetStats(self, doms, stats=0, flags=0): + """ Query statistics for given domains. + + Report statistics of various parameters for a running VM according to @stats + field. The statistics are returned as an array of structures for each queried + domain. The structure contains an array of typed parameters containing the + individual statistics. The typed parameter name for each statistic field + consists of a dot-separated string containing name of the requested group + followed by a group specific description of the statistic value. + + The statistic groups are enabled using the @stats parameter which is a + binary-OR of enum virDomainStatsTypes. The following groups are available + (although not necessarily implemented for each hypervisor): + + VIR_DOMAIN_STATS_STATE: Return domain state and reason for entering that + state. The typed parameter keys are in this format: + "state.state" - state of the VM, returned as int from virDomainState enum + "state.reason" - reason for entering given state, returned as int from + virDomain*Reason enum corresponding to given state. + + Using 0 for @stats returns all stats groups supported by the given + hypervisor. + + Specifying VIR_CONNECT_GET_ALL_DOMAINS_STATS_ENFORCE_STATS as @flags makes + the function return error in case some of the stat types in @stats were + not recognized by the daemon. + + Get statistics about domains provided as a list in @doms. @stats is + a bit field selecting requested statistics types.""" + domlist = list() + for dom in doms: + if not isinstance(dom, virDomain): + raise libvirtError("domain list contains non-domain elements", conn=self) + + domlist.append(dom._o) + + ret = libvirtmod.virDomainListGetStats(self._o, domlist, stats, flags) + if ret is None: + raise libvirtError("virDomainListGetStats() failed", conn=self) + + retlist = list() + for elem in ret: + record = (virDomain(self, _obj=elem[0]) , elem[1]) + retlist.append(record) + + return retlist
The function 'domainListGetStats' should be implemented in libvirt-override-virDomain.py as 'listGetStats'.
diff --git a/libvirt-override.c b/libvirt-override.c index df4f15b..7e9f570 100644 --- a/libvirt-override.c +++ b/libvirt-override.c @@ -5052,6 +5052,55 @@ libvirt_virConnectGetAllDomainStats(PyObject *self ATTRIBUTE_UNUSED,
return py_retval; } + + +static PyObject * +libvirt_virDomainListGetStats(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + PyObject *pyobj_conn; + PyObject *py_retval; + PyObject *py_domlist; + virConnectPtr conn; + virDomainStatsRecordPtr *records;
Set records to NULL to make 'virDomainStatsRecordListFree' happy if the 'virDomainListGetStats' fails.
+ virDomainPtr *doms = NULL; + int nrecords; + int ndoms; + size_t i; + unsigned int flags; + unsigned int stats; + + if (!PyArg_ParseTuple(args, (char *)"OOii:virDomainListGetStats", + &pyobj_conn, &py_domlist, &stats, &flags)) + return NULL; + conn = (virConnectPtr) PyvirConnect_Get(pyobj_conn); + + if (PyList_Check(py_domlist)) { + ndoms = PyList_Size(py_domlist); + + if (VIR_ALLOC_N(doms, ndoms + 1) < 0) + return PyErr_NoMemory(); + + for (i = 0; i < ndoms; i++) + doms[i] = PyvirDomain_Get(PyList_GetItem(py_domlist, i)); + } + + LIBVIRT_BEGIN_ALLOW_THREADS; + nrecords = virDomainListGetStats(doms, stats, &records, flags); + LIBVIRT_END_ALLOW_THREADS; + + if (nrecords < 0) + return VIR_PY_NONE;
There you will leak 'doms'.
+ + if (!(py_retval = convertDomainStatsRecord(records, nrecords))) + py_retval = VIR_PY_NONE; + + cleanup: + virDomainStatsRecordListFree(records); + VIR_FREE(doms); + + return py_retval; +} #endif
@@ -8233,6 +8282,7 @@ static PyMethodDef libvirtMethods[] = { #endif /* LIBVIR_CHECK_VERSION(1, 2, 6) */ #if LIBVIR_CHECK_VERSION(1, 2, 8) {(char *) "virConnectGetAllDomainStats", libvirt_virConnectGetAllDomainStats, METH_VARARGS, NULL}, + {(char *) "virDomainListGetStats", libvirt_virDomainListGetStats, METH_VARARGS, NULL}, #endif /* LIBVIR_CHECK_VERSION(1, 2, 8) */ {NULL, NULL, 0, NULL} };
ACK with the changes Pavel

--- generator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/generator.py b/generator.py index 3642838..a88c146 100755 --- a/generator.py +++ b/generator.py @@ -551,6 +551,7 @@ skip_function = ( "virStorageVolGetConnect", "virDomainSnapshotGetConnect", "virDomainSnapshotGetDomain", + 'virDomainBlockCopy', # only useful in C code, python code uses dict for typed parameters "virTypedParamsAddBoolean", -- 2.0.2
participants (3)
-
Eric Blake
-
Pavel Hrdina
-
Peter Krempa