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;
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,
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,
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 \
# 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
{
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;
}