mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
128
daemon/debug.c
128
daemon/debug.c
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
356
tests/qemu/qemu-speed-test.c
Normal file
356
tests/qemu/qemu-speed-test.c
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user