qemu: monitor: Implement HMP version for listing all block device stats

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.
This commit is contained in:
Peter Krempa 2015-03-09 17:23:49 +01:00
parent fc4713454d
commit f6563bc361
5 changed files with 240 additions and 5 deletions

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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 \

View File

@ -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;
}