From: Peter Krempa Date: Fri, 23 Jan 2026 16:09:27 +0000 (+0100) Subject: Introduce support for disk operation latency histogram collection X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b874c944bd8c4ffa6c51394557587c8c203f1656;p=thirdparty%2Flibvirt.git Introduce support for disk operation latency histogram collection Add config and docs allowing enabling latency histogram collection for block device operations. This patch sets up the docs, schema and XML infrastructure. Signed-off-by: Peter Krempa Reviewed-by: Michal Privoznik --- diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 2a1c821769..82788c15a2 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -3648,6 +3648,47 @@ paravirtualized driver is specified via the ``disk`` element. :since:`Since 11.9.0 (QEMU 10.2, virtio, ide, scsi disks only)`. + Block operation latency histogram collection can be configured using + ```` sub-element. The histogram is collected for + the whole runtime of the VM, but can be re-started or reconfigured using + the `virDomainUpdateDeviceFlags `__ + API. Using the same config re-starts histogram collection. + + The optional ``type`` attribute configures specific operation to collect + the histogram for. Supported types are ``read``, ``write``, ``zone``, and + ``flush``. If the ``type`` attribute is omitted the histogram collection + bins bins apply to all of the aforementioned types, which can be overriden + with specific config. + + The ```` has multiple mandatory ```` sub-elements + with mandatory ``start`` attribute configuring the starting boundary of + the histogram bin configured in nanosecods of the operation duration and + the intervals must be properly ordered and non-duplicate. + + Example:: + + + + + + + + + + + [or for specific operation types] + + + + + + + + + + + :since:`Since 12.1.0`. + - The optional ``queues`` attribute specifies the number of virt queues for virtio-blk ( :since:`Since 3.9.0` ) or vhost-user-blk ( :since:`Since 7.1.0` ) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index bd27575d30..e3c69aee4d 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2462,6 +2462,11 @@ virDomainDiskDefFree(virDomainDiskDef *def) virObjectUnref(def->privateData); g_slist_free_full(def->iothreads, (GDestroyNotify) virDomainIothreadMappingDefFree); g_free(def->statistics); + g_free(def->histogram_boundaries); + g_free(def->histogram_boundaries_read); + g_free(def->histogram_boundaries_write); + g_free(def->histogram_boundaries_zone); + g_free(def->histogram_boundaries_flush); if (def->throttlefilters) { size_t i; @@ -8363,6 +8368,91 @@ virDomainIothreadMappingDefParse(xmlNodePtr driverNode, } +static int +virDomainDiskDefDriverParseXMLHistogramOne(virDomainDiskDef *def, + xmlNodePtr cur) +{ + g_autofree char *histogram_type = NULL; + unsigned int **histogram_config = NULL; + g_autoptr(GPtrArray) binNodes = virXMLNodeGetSubelementList(cur, "bin"); + size_t nbins = 0; + size_t i; + + if ((histogram_type = virXMLPropString(cur, "type"))) { + if (STREQ(histogram_type, "read")) { + histogram_config = &def->histogram_boundaries_read; + } else if (STREQ(histogram_type, "write")) { + histogram_config = &def->histogram_boundaries_write; + } else if (STREQ(histogram_type, "zone")) { + histogram_config = &def->histogram_boundaries_zone; + } else if (STREQ(histogram_type, "flush")) { + histogram_config = &def->histogram_boundaries_flush; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown latency_histogram type '%1$s'"), + histogram_type); + return -1; + } + } else { + histogram_config = &def->histogram_boundaries; + } + + if (*histogram_config) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("only one latency-histogram of a given type is supported")); + return -1; + } + + if (binNodes->len == 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing 'bin' elements for 'latency-histogram'")); + return -1; + } + + *histogram_config = g_new0(unsigned int, binNodes->len + 1); + + for (i = 0; i < binNodes->len; i++) { + unsigned int val; + + if (virXMLPropUInt(g_ptr_array_index(binNodes, i), + "start", 10, + VIR_XML_PROP_REQUIRED, + &val) < 0) + return -1; + + if (nbins > 0 && + (val == 0 || + val <= (*histogram_config)[nbins-1])) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("the values of 'start' attribute of a 'latency-histogram' 'bin' configuration must be sorted and non-overlapping")); + return -1; + } + + if (val > 0) + (*histogram_config)[nbins++] = val; + } + + return 0; +} + + +static int +virDomainDiskDefDriverParseXMLHistograms(virDomainDiskDef *def, + xmlNodePtr cur) +{ + g_autoptr(GPtrArray) histogramNodes = virXMLNodeGetSubelementList(cur, "latency-histogram"); + size_t i; + + for (i = 0; i < histogramNodes->len; i++) { + if (virDomainDiskDefDriverParseXMLHistogramOne(def, + g_ptr_array_index(histogramNodes, i)) < 0) + return -1; + } + + return 0; +} + + static int virDomainDiskDefDriverParseXML(virDomainDiskDef *def, xmlNodePtr cur) @@ -8436,6 +8526,9 @@ virDomainDiskDefDriverParseXML(virDomainDiskDef *def, return -1; } } + + if (virDomainDiskDefDriverParseXMLHistograms(def, statisticsNode) < 0) + return -1; } if (virXMLPropEnum(cur, "detect_zeroes", @@ -24118,12 +24211,37 @@ virDomainDiskDefFormatThrottleFilters(virBuffer *buf, } +static void +virDomainDiskDefFormatDriverHistogram(virBuffer *buf, + const char *type, + unsigned int *bins) +{ + g_auto(virBuffer) histogramAttrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) histogramChildBuf = VIR_BUFFER_INIT_CHILD(buf); + + if (!bins || bins[0] == 0) + return; + + if (type) + virBufferAsprintf(&histogramAttrBuf, " type='%s'", type); + + /* we dont store the start boundary of the first bin but it's always there */ + virBufferAddLit(&histogramChildBuf, "\n"); + + for (; *bins > 0; bins++) + virBufferAsprintf(&histogramChildBuf, "\n", *bins); + + virXMLFormatElement(buf, "latency-histogram", &histogramAttrBuf, &histogramChildBuf); +} + + static void virDomainDiskDefFormatDriver(virBuffer *buf, virDomainDiskDef *disk) { g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); + g_auto(virBuffer) statisticsChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf); virBufferEscapeString(&attrBuf, " name='%s'", virDomainDiskGetDriver(disk)); @@ -24195,16 +24313,25 @@ virDomainDiskDefFormatDriver(virBuffer *buf, virDomainIothreadMappingDefFormat(&childBuf, disk->iothreads); if (disk->statistics) { - g_auto(virBuffer) statisticsChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf); size_t i; for (i = 0; disk->statistics[i] > 0; i++) virBufferAsprintf(&statisticsChildBuf, "\n", disk->statistics[i]); - - virXMLFormatElement(&childBuf, "statistics", NULL, &statisticsChildBuf); } + virDomainDiskDefFormatDriverHistogram(&statisticsChildBuf, NULL, + disk->histogram_boundaries); + virDomainDiskDefFormatDriverHistogram(&statisticsChildBuf, "read", + disk->histogram_boundaries_read); + virDomainDiskDefFormatDriverHistogram(&statisticsChildBuf, "write", + disk->histogram_boundaries_write); + virDomainDiskDefFormatDriverHistogram(&statisticsChildBuf, "zone", + disk->histogram_boundaries_zone); + virDomainDiskDefFormatDriverHistogram(&statisticsChildBuf, "flush", + disk->histogram_boundaries_flush); + + virXMLFormatElement(&childBuf, "statistics", NULL, &statisticsChildBuf); virXMLFormatElement(buf, "driver", &attrBuf, &childBuf); } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 165a61181d..be96617981 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -598,6 +598,13 @@ struct _virDomainDiskDef { GSList *iothreads; /* List of virDomainIothreadMappingDef */ unsigned int *statistics; /* Optional, zero terminated list of intervals to collect statistics for */ + /* optional zero terminated lists of bin boundaries for latency histograms */ + unsigned int *histogram_boundaries; + unsigned int *histogram_boundaries_read; + unsigned int *histogram_boundaries_write; + unsigned int *histogram_boundaries_zone; + unsigned int *histogram_boundaries_flush; + virDomainDiskDetectZeroes detect_zeroes; virTristateSwitch discard_no_unref; char *domain_name; /* backend domain name */ diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index 8fe1346b05..dafbdc63e7 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -2741,13 +2741,36 @@ - - - - - - - + + + + + + + + + + + + + + read + write + zone + flush + + + + + + + + + + + + + diff --git a/tests/qemuxmlconfdata/disk-statistics-intervals.x86_64-latest.xml b/tests/qemuxmlconfdata/disk-statistics-intervals.x86_64-latest.xml index 4c55c50ef5..d02f954073 100644 --- a/tests/qemuxmlconfdata/disk-statistics-intervals.x86_64-latest.xml +++ b/tests/qemuxmlconfdata/disk-statistics-intervals.x86_64-latest.xml @@ -22,6 +22,11 @@ + + + + + @@ -33,6 +38,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/qemuxmlconfdata/disk-statistics-intervals.xml b/tests/qemuxmlconfdata/disk-statistics-intervals.xml index f5e801f5a8..5f9e9470d7 100644 --- a/tests/qemuxmlconfdata/disk-statistics-intervals.xml +++ b/tests/qemuxmlconfdata/disk-statistics-intervals.xml @@ -19,6 +19,11 @@ + + + + + @@ -29,6 +34,26 @@ + + + + + + + + + + + + + + + + + + + +