Add a different version of parser for "info blockstats" that basically
parses the same information as the existing copy of the function.
This will allow us to remove the single device version
qemuMonitorGetBlockStatsInfo in the future.
The new implementation uses few new helpers so it should be more
understandable and provides a test case to verify that it works.
---
src/qemu/qemu_monitor.c | 16 +++++-
src/qemu/qemu_monitor_text.c | 129 +++++++++++++++++++++++++++++++++++++++++++
src/qemu/qemu_monitor_text.h | 3 +
tests/Makefile.am | 8 ++-
tests/qemumonitortest.c | 89 ++++++++++++++++++++++++++++-
5 files changed, 240 insertions(+), 5 deletions(-)
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
index 95a6989..149e743 100644
--- a/src/qemu/qemu_monitor.c
+++ b/src/qemu/qemu_monitor.c
@@ -1867,8 +1867,20 @@ qemuMonitorGetAllBlockStatsInfo(qemuMonitorPtr mon,
if (!(*ret_stats = virHashCreate(10, virHashValueFree)))
goto error;
- if (qemuMonitorJSONGetAllBlockStatsInfo(mon, *ret_stats, backingChain) < 0)
- goto error;
+ if (mon->json) {
+ if (qemuMonitorJSONGetAllBlockStatsInfo(mon, *ret_stats, backingChain) < 0)
+ goto error;
+ } else {
+ if (backingChain) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("text monitor doesn't support block stats for
"
+ "backing chain members"));
+ goto error;
+ }
+
+ if (qemuMonitorTextGetAllBlockStatsInfo(mon, *ret_stats) < 0)
+ goto error;
+ }
return 0;
diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c
index 2de281f..8b2ef90 100644
--- a/src/qemu/qemu_monitor_text.c
+++ b/src/qemu/qemu_monitor_text.c
@@ -838,6 +838,135 @@ int qemuMonitorTextGetBlockInfo(qemuMonitorPtr mon,
return ret;
}
+
+int
+qemuMonitorTextGetAllBlockStatsInfo(qemuMonitorPtr mon,
+ virHashTablePtr hash)
+{
+ qemuBlockStatsPtr stats = NULL;
+ char *info = NULL;
+ char *dev_name;
+ char **lines = NULL;
+ char **values = NULL;
+ char *line;
+ char *value;
+ char *key;
+ size_t i;
+ size_t j;
+ int ret = -1;
+
+ if (qemuMonitorHMPCommand(mon, "info blockstats", &info) < 0)
+ goto cleanup;
+
+ /* If the command isn't supported then qemu prints the supported info
+ * commands, so the output starts "info ". Since this is unlikely to be
+ * the name of a block device, we can use this to detect if qemu supports
+ * the command. */
+ if (strstr(info, "\ninfo ")) {
+ virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+ _("'info blockstats' not supported by this
qemu"));
+ goto cleanup;
+ }
+
+ /* The output format for both qemu & KVM is:
+ * blockdevice: rd_bytes=% wr_bytes=% rd_operations=% wr_operations=%
+ * (repeated for each block device)
+ * where '%' is a 64 bit number.
+ */
+ if (!(lines = virStringSplit(info, "\n", 0)))
+ goto cleanup;
+
+ for (i = 0; lines[i] && *lines[i]; i++) {
+ line = lines[i];
+
+ if (VIR_ALLOC(stats) < 0)
+ goto cleanup;
+
+ /* set the entries to -1, the JSON monitor enforces them, but it would
+ * be overly complex to achieve this here */
+ stats->rd_req = -1;
+ stats->rd_bytes = -1;
+ stats->wr_req = -1;
+ stats->wr_bytes = -1;
+ stats->rd_total_times = -1;
+ stats->wr_total_times = -1;
+ stats->flush_req = -1;
+ stats->flush_total_times = -1;
+
+ /* extract device name and make sure that it's followed by
+ * a colon and space */
+ dev_name = line;
+ if (!(line = strchr(line, ':')) && line[1] != ' ') {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("info blockstats reply was malformed"));
+ goto cleanup;
+ }
+
+ *line = '\0';
+ line += 2;
+
+ if (STRPREFIX(dev_name, QEMU_DRIVE_HOST_PREFIX))
+ dev_name += strlen(QEMU_DRIVE_HOST_PREFIX);
+
+ if (!(values = virStringSplit(line, " ", 0)))
+ goto cleanup;
+
+ for (j = 0; values[j] && *values[j]; j++) {
+ key = values[j];
+
+ if (!(value = strchr(key, '='))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("info blockstats entry was malformed"));
+ goto cleanup;
+ }
+
+ *value = '\0';
+ value++;
+
+#define QEMU_MONITOR_TEXT_READ_BLOCK_STAT(NAME, VAR) \
+ if (STREQ(key, NAME)) { \
+ if (virStrToLong_ll(value, NULL, 10, &VAR) < 0) { \
+ virReportError(VIR_ERR_INTERNAL_ERROR, \
+ _("'info blockstats' contains malformed
" \
+ "parameter '%s' value
'%s'"), NAME, value);\
+ goto cleanup; \
+ } \
+ continue; \
+ }
+
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("rd_bytes", stats->rd_bytes);
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("wr_bytes", stats->wr_bytes);
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("rd_operations",
stats->rd_req);
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("wr_operations",
stats->wr_req);
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("rd_total_time_ns",
stats->rd_total_times);
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("wr_total_time_ns",
stats->wr_total_times);
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("flush_operations",
stats->flush_req);
+ QEMU_MONITOR_TEXT_READ_BLOCK_STAT("flush_total_time_ns",
stats->flush_total_times);
+#undef QEMU_MONITOR_TEXT_READ_BLOCK_STAT
+
+ /* log if we get statistic element different from the above */
+ VIR_DEBUG("unknown block stat field '%s'", key);
+ }
+
+ if (virHashAddEntry(hash, dev_name, stats) < 0)
+ goto cleanup;
+ stats = NULL;
+
+ virStringFreeList(values);
+ values = NULL;
+ }
+
+ ret = 0;
+
+ cleanup:
+ virStringFreeList(lines);
+ virStringFreeList(values);
+ VIR_FREE(stats);
+ VIR_FREE(info);
+ return ret;
+}
+
+
int qemuMonitorTextGetBlockStatsInfo(qemuMonitorPtr mon,
const char *dev_name,
long long *rd_req,
diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h
index 695ac28..a1bc2b2 100644
--- a/src/qemu/qemu_monitor_text.h
+++ b/src/qemu/qemu_monitor_text.h
@@ -60,6 +60,9 @@ int qemuMonitorTextGetMemoryStats(qemuMonitorPtr mon,
unsigned int nr_stats);
int qemuMonitorTextGetBlockInfo(qemuMonitorPtr mon,
virHashTablePtr table);
+
+int qemuMonitorTextGetAllBlockStatsInfo(qemuMonitorPtr mon,
+ virHashTablePtr hash);
int qemuMonitorTextGetBlockStatsInfo(qemuMonitorPtr mon,
const char *dev_name,
long long *rd_req,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 938270c..9277c13 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -568,8 +568,12 @@ qemuargv2xmltest_LDADD = $(qemu_LDADDS) $(LDADDS)
qemuhelptest_SOURCES = qemuhelptest.c testutils.c testutils.h
qemuhelptest_LDADD = $(qemu_LDADDS) $(LDADDS)
-qemumonitortest_SOURCES = qemumonitortest.c testutils.c testutils.h
-qemumonitortest_LDADD = $(qemu_LDADDS) $(LDADDS)
+qemumonitortest_SOURCES = \
+ qemumonitortest.c \
+ testutils.c testutils.h \
+ testutilsqemu.c testutilsqemu.h
+qemumonitortest_LDADD = libqemumonitortestutils.la \
+ $(qemu_LDADDS) $(LDADDS)
qemumonitorjsontest_SOURCES = \
qemumonitorjsontest.c \
diff --git a/tests/qemumonitortest.c b/tests/qemumonitortest.c
index 1c13a89..d73bbf1 100644
--- a/tests/qemumonitortest.c
+++ b/tests/qemumonitortest.c
@@ -12,6 +12,10 @@
# include "internal.h"
# include "viralloc.h"
# include "qemu/qemu_monitor.h"
+# include "qemu/qemu_monitor_text.h"
+# include "qemumonitortestutils.h"
+
+# define VIR_FROM_THIS VIR_FROM_NONE
struct testEscapeString
{
@@ -86,21 +90,104 @@ static int testUnescapeArg(const void *data ATTRIBUTE_UNUSED)
return 0;
}
+struct blockInfoData {
+ const char *dev;
+ qemuBlockStats data;
+};
+
+static const struct blockInfoData testBlockInfoData[] =
+{
+/* NAME, rd_req, rd_bytes, wr_req, wr_bytes, rd_total_time, wr_total_time, flush_req,
flush_total_time */
+ {"vda", {11, 12, 13, 14, 15, 16, 17, 18, 0, 0, 0}},
+ {"vdb", {21, 22, 23, 24, 25, 26, 27, 28, 0, 0, 0}},
+ {"vdc", {31, 32, 33, -1, 35, 36, 37, 38, 0, 0, 0}},
+ {"vdd", {-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0}},
+ {"vde", {41, 42, 43, 44, 45, 46, 47, 48, 0, 0, 0}}
+};
+
+static const char testBlockInfoReply[] =
+"(qemu) info blockstats\r\n"
+"vda: rd_operations=11 rd_bytes=12 wr_operations=13 wr_bytes=14 rd_total_time_ns=15
wr_total_time_ns=16 flush_operations=17 flush_total_time_ns=18\n"
+"vdb: rd_total_time_ns=25 wr_total_time_ns=26 flush_operations=27
flush_total_time_ns=28 rd_operations=21 rd_bytes=22 wr_operations=23 wr_bytes=24 \n"
+"drive-vdc: rd_operations=31 rd_bytes=32 wr_operations=33 rd_total_time_ns=35
wr_total_time_ns=36 flush_operations=37 flush_total_time_ns=38\n"
+"vdd: \n"
+"vde: rd_operations=41 rd_bytes=42 wr_operations=43 wr_bytes=44 rd_total_time_ns=45
wr_total_time_ns=46 flush_operations=47 flush_total_time_ns=48\n"
+"(qemu) ";
+
+static int
+testMonitorTextBlockInfo(const void *opaque)
+{
+ virDomainXMLOptionPtr xmlopt = (virDomainXMLOptionPtr) opaque;
+ qemuMonitorTestPtr test = qemuMonitorTestNewSimple(false, xmlopt);
+ virHashTablePtr blockstats = NULL;
+ size_t i;
+ int ret = -1;
+
+ if (!test)
+ return -1;
+
+ if (!(blockstats = virHashCreate(10, virHashValueFree)))
+ goto cleanup;
+
+ if (qemuMonitorTestAddItem(test, "info", testBlockInfoReply) < 0)
+ goto cleanup;
+
+ if (qemuMonitorTextGetAllBlockStatsInfo(qemuMonitorTestGetMonitor(test),
+ blockstats) < 0)
+ goto cleanup;
+
+ for (i = 0; i < ARRAY_CARDINALITY(testBlockInfoData); i++) {
+ qemuBlockStatsPtr entry;
+
+ if (!(entry = virHashLookup(blockstats, testBlockInfoData[i].dev))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "device '%s' was not found in text block stats
reply",
+ testBlockInfoData[i].dev);
+ goto cleanup;
+ }
+
+ if (memcmp(entry, &testBlockInfoData[i].data, sizeof(qemuBlockStats)) != 0)
{
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "block stats for device '%s' differ",
+ testBlockInfoData[i].dev);
+ goto cleanup;
+ }
+ }
+
+ ret = 0;
+
+ cleanup:
+ qemuMonitorTestFree(test);
+ virHashFree(blockstats);
+ return ret;
+}
+
+
static int
mymain(void)
{
+ virDomainXMLOptionPtr xmlopt;
int result = 0;
+ if (virThreadInitialize() < 0 ||
+ !(xmlopt = virQEMUDriverCreateXMLConf(NULL)))
+ return EXIT_FAILURE;
+
+ virEventRegisterDefaultImpl();
+
# define DO_TEST(_name) \
do { \
if (virtTestRun("qemu monitor "#_name, test##_name, \
- NULL) < 0) { \
+ xmlopt) < 0) { \
result = -1; \
} \
} while (0)
DO_TEST(EscapeArg);
DO_TEST(UnescapeArg);
+ DO_TEST(MonitorTextBlockInfo);
+
+ virObjectUnref(xmlopt);
return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
--
2.2.2