diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 95a6989bf0..149e7431d9 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 2de281ff35..8b2ef90ee8 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 695ac28a82..a1bc2b2d84 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 938270ccbf..9277c132a9 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 1c13a89f8b..d73bbf1c9d 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; }