Add qemu-speed-test program to test speed of qemu.

Currently it tests the upload and download speed of virtio-serial and
the read and write speed of the block device (eg. virtio-scsi).
This commit is contained in:
Richard W.M. Jones
2014-07-25 17:12:19 +01:00
parent 115fcc3432
commit 7378edb9fa
4 changed files with 505 additions and 5 deletions

1
.gitignore vendored
View File

@@ -530,6 +530,7 @@ Makefile.in
/tests/parallel/test-parallel
/tests/protocol/test-error-messages
/tests/qemu/qemu-boot
/tests/qemu/qemu-speed-test
/tests/regressions/rhbz501893
/tests/regressions/rhbz790721
/tests/regressions/rhbz914931

View File

@@ -1,5 +1,5 @@
/* libguestfs - the guestfsd daemon
* Copyright (C) 2009 Red Hat Inc.
* Copyright (C) 2009-2014 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -64,6 +64,7 @@ struct cmd {
static char *debug_help (const char *subcmd, size_t argc, char *const *const argv);
static char *debug_binaries (const char *subcmd, size_t argc, char *const *const argv);
static char *debug_core_pattern (const char *subcmd, size_t argc, char *const *const argv);
static char *debug_device_speed (const char *subcmd, size_t argc, char *const *const argv);
static char *debug_env (const char *subcmd, size_t argc, char *const *const argv);
static char *debug_error (const char *subcmd, size_t argc, char *const *const argv);
static char *debug_fds (const char *subcmd, size_t argc, char *const *const argv);
@@ -83,6 +84,7 @@ static struct cmd cmds[] = {
{ "help", debug_help },
{ "binaries", debug_binaries },
{ "core_pattern", debug_core_pattern },
{ "device_speed", debug_device_speed },
{ "env", debug_env },
{ "error", debug_error },
{ "fds", debug_fds },
@@ -749,6 +751,130 @@ debug_qtrace (const char *subcmd, size_t argc, char *const *const argv)
return ret;
}
/* Used to test read and write speed. */
static char *
debug_device_speed (const char *subcmd, size_t argc, char *const *const argv)
{
const char *device;
int writing, err;
unsigned secs;
int64_t size, position, copied;
CLEANUP_FREE void *buf = NULL;
struct timeval now, end;
ssize_t r;
int fd;
char *ret;
if (argc != 3) {
bad_args:
reply_with_error ("device_speed <device> <r|w> <secs>");
return NULL;
}
device = argv[0];
if (STREQ (argv[1], "r") || STREQ (argv[1], "read"))
writing = 0;
else if (STREQ (argv[1], "w") || STREQ (argv[1], "write"))
writing = 1;
else
goto bad_args;
if (sscanf (argv[2], "%u", &secs) != 1)
goto bad_args;
/* Find the size of the device. */
size = do_blockdev_getsize64 (device);
if (size == -1)
return NULL;
if (size < BUFSIZ) {
reply_with_error ("%s: device is too small", device);
return NULL;
}
/* Because we're using O_DIRECT, the buffer must be aligned. */
err = posix_memalign (&buf, 4096, BUFSIZ);
if (err != 0) {
reply_with_error_errno (err, "posix_memalign");
return NULL;
}
/* Any non-zero data will do. */
memset (buf, 100, BUFSIZ);
fd = open (device, (writing ? O_WRONLY : O_RDONLY) | O_CLOEXEC | O_DIRECT);
if (fd == -1) {
reply_with_perror ("open: %s", device);
return NULL;
}
/* Now we read or write to the device, wrapping around to the
* beginning when we reach the end, and only stop when <secs>
* seconds has elapsed.
*/
gettimeofday (&end, NULL);
end.tv_sec += secs;
position = copied = 0;
for (;;) {
gettimeofday (&now, NULL);
if (now.tv_sec > end.tv_sec ||
(now.tv_sec == end.tv_sec && now.tv_usec > end.tv_usec))
break;
/* Because of O_DIRECT, only write whole, aligned buffers. */
again:
if (size - position < BUFSIZ) {
position = 0;
goto again;
}
/*
if (verbose) {
fprintf (stderr, "p%s (fd, buf, %d, %" PRIi64 ")\n",
writing ? "write" : "read", BUFSIZ, position);
}
*/
if (writing) {
r = pwrite (fd, buf, BUFSIZ, position);
if (r == -1) {
reply_with_perror ("write: %s", device);
goto error;
}
}
else {
r = pread (fd, buf, BUFSIZ, position);
if (r == -1) {
reply_with_perror ("read: %s", device);
goto error;
}
if (r == 0) {
reply_with_error ("unexpected end of file while reading");
goto error;
}
}
position += BUFSIZ;
copied += r;
}
if (close (fd) == -1) {
reply_with_perror ("close: %s", device);
return NULL;
}
if (asprintf (&ret, "%" PRIi64, copied) == -1) {
reply_with_perror ("asprintf");
return NULL;
}
return ret;
error:
close (fd);
return NULL;
}
/* Has one FileIn parameter. */
int
do_debug_upload (const char *filename, int mode)

View File

@@ -30,12 +30,13 @@ TESTS_ENVIRONMENT = $(top_builddir)/run --test
EXTRA_DIST = \
$(TESTS) \
qemu-boot.c
qemu-boot.c \
qemu-speed-test.c
# qemu-boot is built but not run by default as it is mainly
# a qemu & kernel diagnostic tool.
# qemu-boot & qemu-speed-test are built but not run by default as they
# are mainly qemu & kernel diagnostic tools.
check_PROGRAMS = qemu-boot
check_PROGRAMS = qemu-boot qemu-speed-test
qemu_boot_SOURCES = \
../../df/estimate-max-threads.c \
@@ -55,3 +56,19 @@ qemu_boot_LDADD = \
$(LIBXML2_LIBS) \
$(LIBVIRT_LIBS) \
$(top_builddir)/gnulib/lib/libgnu.la
qemu_speed_test_SOURCES = \
qemu-speed-test.c
qemu_speed_test_CPPFLAGS = \
-DGUESTFS_PRIVATE=1 \
-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
-I$(top_srcdir)/src -I$(top_builddir)/src \
-I$(top_srcdir)/df
qemu_speed_test_CFLAGS = \
$(WARN_CFLAGS) $(WERROR_CFLAGS)
qemu_speed_test_LDADD = \
$(top_builddir)/src/libutils.la \
$(top_builddir)/src/libguestfs.la \
$(LIBXML2_LIBS) \
$(LIBVIRT_LIBS) \
$(top_builddir)/gnulib/lib/libgnu.la

View File

@@ -0,0 +1,356 @@
/* libguestfs
* Copyright (C) 2014 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Test the speed of various qemu features. Currently tested are:
* - virtio-serial upload
* - virtio-serial download
* - block device read
* - block device write
* More to come in future.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include <sys/time.h>
#include "guestfs.h"
#include "guestfs-internal-frontend.h"
static void test_virtio_serial (void);
static void test_block_device (void);
int
main (int argc, char *argv[])
{
if (argc != 1) {
fprintf (stderr, "%s: this program takes no arguments\n",
program_name);
exit (EXIT_FAILURE);
}
test_virtio_serial ();
test_block_device ();
exit (EXIT_SUCCESS);
}
static void
print_rate (const char *msg, int64_t rate)
{
printf ("%-40s %" PRIi64 " bytes/sec (%" PRIi64 " Mbytes/sec)\n",
msg, rate, rate / 1024 / 1024);
fflush (stdout);
}
/* The maximum time we will spend running the test (seconds). */
#define TEST_SERIAL_MAX_TIME 30
/* The maximum amount of data to copy. You can safely make this very
* large because it's only making sparse files.
*/
#define TEST_SERIAL_MAX_SIZE \
(INT64_C(1024) * INT64_C(1024) * INT64_C(1024) * INT64_C(1024))
static guestfs_h *g;
static struct timeval start;
static const char *operation;
static int64_t rate;
static void
stop_transfer (int sig)
{
guestfs_user_cancel (g);
}
/* Compute Y - X and return the result in milliseconds.
* Approximately the same as this code:
* http://www.mpp.mpg.de/~huber/util/timevaldiff.c
*/
static int64_t
timeval_diff (const struct timeval *x, const struct timeval *y)
{
int64_t msec;
msec = (y->tv_sec - x->tv_sec) * 1000;
msec += (y->tv_usec - x->tv_usec) / 1000;
return msec;
}
static void
progress_cb (guestfs_h *g, void *vp, uint64_t event,
int eh, int flags,
const char *buf, size_t buflen,
const uint64_t *array, size_t arraylen)
{
uint64_t transferred;
struct timeval now;
int64_t millis;
assert (event == GUESTFS_EVENT_PROGRESS);
assert (arraylen >= 4);
gettimeofday (&now, NULL);
/* Bytes transferred. */
transferred = array[2];
/* Calculate the speed of the upload or download. */
millis = timeval_diff (&start, &now);
assert (millis >= 0);
if (millis != 0) {
rate = 1000 * transferred / millis;
printf ("%s: %" PRIi64 " bytes/sec \r",
operation, rate);
fflush (stdout);
}
}
static void
test_virtio_serial (void)
{
int fd, r, eh;
char tmpfile[] = "/tmp/speedtestXXXXXX";
struct sigaction sa, old_sa;
/* Create a sparse file. We could upload from /dev/zero, but we
* won't get progress messages because libguestfs tests if the
* source file is a regular file.
*/
fd = mkstemp (tmpfile);
if (fd == -1) {
perror ("mkstemp");
exit (EXIT_FAILURE);
}
if (ftruncate (fd, TEST_SERIAL_MAX_SIZE) == -1) {
perror ("ftruncate");
exit (EXIT_FAILURE);
}
if (close (fd) == -1) {
perror ("close");
exit (EXIT_FAILURE);
}
g = guestfs_create ();
if (!g) {
perror ("guestfs_create");
exit (EXIT_FAILURE);
}
if (guestfs_add_drive_scratch (g, INT64_C (100*1024*1024), -1) == -1)
exit (EXIT_FAILURE);
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
/* Make and mount a filesystem which will be used by the download test. */
if (guestfs_mkfs (g, "ext4", "/dev/sda") == -1)
exit (EXIT_FAILURE);
if (guestfs_mount (g, "/dev/sda", "/") == -1)
exit (EXIT_FAILURE);
/* Time out the upload after TEST_SERIAL_MAX_TIME seconds have passed. */
memset (&sa, 0, sizeof sa);
sa.sa_handler = stop_transfer;
sa.sa_flags = SA_RESTART;
sigaction (SIGALRM, &sa, &old_sa);
/* Get progress messages, which will tell us how much data has been
* transferred.
*/
eh = guestfs_set_event_callback (g, progress_cb, GUESTFS_EVENT_PROGRESS,
0, NULL);
if (eh == -1)
exit (EXIT_FAILURE);
gettimeofday (&start, NULL);
rate = -1;
operation = "upload";
alarm (TEST_SERIAL_MAX_TIME);
/* For the upload test, upload the sparse file to /dev/null in the
* appliance. Hopefully this is mostly testing just virtio-serial.
*/
guestfs_push_error_handler (g, NULL, NULL);
r = guestfs_upload (g, tmpfile, "/dev/null");
alarm (0);
unlink (tmpfile);
guestfs_pop_error_handler (g);
/* It's possible that the upload will finish before the alarm fires,
* or that the upload will be stopped by the alarm.
*/
if (r == -1 && guestfs_last_errno (g) != EINTR) {
fprintf (stderr,
"%s: expecting upload command to return EINTR\n%s\n",
program_name, guestfs_last_error (g));
exit (EXIT_FAILURE);
}
if (rate == -1) {
rate_error:
fprintf (stderr, "%s: internal error: progress callback was not called! (r=%d, errno=%d)\n",
program_name,
r, guestfs_last_errno (g));
exit (EXIT_FAILURE);
}
print_rate ("virtio-serial upload rate:", rate);
/* For the download test, download a sparse file within the
* appliance to /dev/null on the host.
*/
if (guestfs_touch (g, "/sparse") == -1)
exit (EXIT_FAILURE);
if (guestfs_truncate_size (g, "/sparse", TEST_SERIAL_MAX_SIZE) == -1)
exit (EXIT_FAILURE);
gettimeofday (&start, NULL);
rate = -1;
operation = "download";
alarm (TEST_SERIAL_MAX_TIME);
guestfs_push_error_handler (g, NULL, NULL);
r = guestfs_download (g, "/sparse", "/dev/null");
alarm (0);
guestfs_pop_error_handler (g);
if (r == -1 && guestfs_last_errno (g) != EINTR) {
fprintf (stderr,
"%s: expecting download command to return EINTR\n%s\n",
program_name, guestfs_last_error (g));
exit (EXIT_FAILURE);
}
if (rate == -1)
goto rate_error;
print_rate ("virtio-serial download rate:", rate);
if (guestfs_shutdown (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
/* Restore SIGALRM signal handler. */
sigaction (SIGALRM, &old_sa, NULL);
}
/* The time we will spend running the test (seconds). */
#define TEST_BLOCK_DEVICE_TIME 30
static void
test_block_device (void)
{
int fd;
char tmpfile[] = "/tmp/speedtestXXXXXX";
CLEANUP_FREE char **devices = NULL;
char *r;
const char *argv[4];
int64_t bytes_written, bytes_read;
g = guestfs_create ();
if (!g) {
perror ("guestfs_create");
exit (EXIT_FAILURE);
}
/* Create a fully allocated backing file. Note we are not testing
* the speed of allocation on the host.
*/
fd = mkstemp (tmpfile);
if (fd == -1) {
perror ("mkstemp");
exit (EXIT_FAILURE);
}
close (fd);
if (guestfs_disk_create (g, tmpfile, "raw",
INT64_C (1024*1024*1024),
GUESTFS_DISK_CREATE_PREALLOCATION, "full",
-1) == -1)
exit (EXIT_FAILURE);
if (guestfs_add_drive (g, tmpfile) == -1)
exit (EXIT_FAILURE);
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
devices = guestfs_list_devices (g);
if (devices == NULL)
exit (EXIT_FAILURE);
if (devices[0] == NULL) {
fprintf (stderr, "%s: expected guestfs_list_devices to return at least 1 device\n",
program_name);
exit (EXIT_FAILURE);
}
/* Test write speed. */
argv[0] = devices[0];
argv[1] = "w";
#define XSTR(x) STR(x)
#define STR(x) #x
argv[2] = XSTR(TEST_BLOCK_DEVICE_TIME);
argv[3] = NULL;
r = guestfs_debug (g, "device_speed", (char **) argv);
if (r == NULL)
exit (EXIT_FAILURE);
if (sscanf (r, "%" SCNi64, &bytes_written) != 1) {
fprintf (stderr, "%s: could not parse device_speed output\n",
program_name);
exit (EXIT_FAILURE);
}
print_rate ("block device writes:", bytes_written / TEST_BLOCK_DEVICE_TIME);
/* Test read speed. */
argv[0] = devices[0];
argv[1] = "r";
#define XSTR(x) STR(x)
#define STR(x) #x
argv[2] = XSTR(TEST_BLOCK_DEVICE_TIME);
argv[3] = NULL;
r = guestfs_debug (g, "device_speed", (char **) argv);
if (r == NULL)
exit (EXIT_FAILURE);
if (sscanf (r, "%" SCNi64, &bytes_read) != 1) {
fprintf (stderr, "%s: could not parse device_speed output\n",
program_name);
exit (EXIT_FAILURE);
}
print_rate ("block device reads:", bytes_read / TEST_BLOCK_DEVICE_TIME);
if (guestfs_shutdown (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
/* Remove temporary file. */
unlink (tmpfile);
}