Remove boot and qemu analysis tools into a separate project.

These have now moved here:
https://github.com/libguestfs/libguestfs-analysis-tools

This commit removes them and updates guestfs-performance(1) to point
to the new location.
This commit is contained in:
Richard W.M. Jones
2019-11-28 15:31:01 +00:00
parent 8ba0457e9e
commit 58ab39ba34
25 changed files with 9 additions and 3843 deletions

6
.gitignore vendored
View File

@@ -596,12 +596,6 @@ Makefile.in
/test-tool/stamp-libguestfs-test-tool.pod
/tools/stamp-virt-*.pod
/tools/virt-*.1
/utils/boot-analysis/boot-analysis
/utils/boot-analysis/boot-analysis.1
/utils/boot-benchmark/boot-benchmark
/utils/boot-benchmark/boot-benchmark.1
/utils/qemu-boot/qemu-boot
/utils/qemu-speed-test/qemu-speed-test
/website/download/builder/*.xz
/website/*.html
/website/README.txt

View File

@@ -185,16 +185,6 @@ if HAVE_FUSE
SUBDIRS += fuse
endif
# Miscellaneous utilities.
if HAVE_BOOT_ANALYSIS
SUBDIRS += utils/boot-analysis
endif
SUBDIRS += \
utils/boot-benchmark \
utils/max-disks \
utils/qemu-boot \
utils/qemu-speed-test
# After all source files were used we can generate the translation strings
SUBDIRS += po
@@ -251,7 +241,6 @@ EXTRA_DIST = \
tests/test-functions.sh \
tmp/.gitignore \
update-bugs.sh \
utils/README \
valgrind-suppressions \
website/bugs.png \
website/communicate.png \

View File

@@ -356,11 +356,6 @@ AC_CONFIG_FILES([Makefile
tests/xml/Makefile
tests/yara/Makefile
tools/Makefile
utils/boot-analysis/Makefile
utils/boot-benchmark/Makefile
utils/max-disks/Makefile
utils/qemu-boot/Makefile
utils/qemu-speed-test/Makefile
website/index.html])
AC_OUTPUT

View File

@@ -388,11 +388,3 @@ ruby/ext/guestfs/module.c
sparsify/dummy.c
sysprep/dummy.c
test-tool/test-tool.c
utils/boot-analysis/boot-analysis-timeline.c
utils/boot-analysis/boot-analysis-utils.c
utils/boot-analysis/boot-analysis-utils.h
utils/boot-analysis/boot-analysis.c
utils/boot-analysis/boot-analysis.h
utils/boot-benchmark/boot-benchmark.c
utils/qemu-boot/qemu-boot.c
utils/qemu-speed-test/qemu-speed-test.c

View File

@@ -29,16 +29,11 @@ appliance:
Run this command several times in a row and discard the first few
runs, so that you are measuring a typical "hot cache" case.
I<Side note for developers:> If you are compiling libguestfs from
source, there is a program called
F<utils/boot-benchmark/boot-benchmark> which does the same thing, but
performs multiple runs and prints the mean and standard deviation. To
run it, do:
make
./run utils/boot-benchmark/boot-benchmark
There is a manual page F<utils/boot-benchmark/boot-benchmark.1>
I<Side note for developers:> There is a program called
F<boot-benchmark> in
L<https://github.com/libguestfs/libguestfs-analysis-tools> which does
the same thing, but performs multiple runs and prints the mean and
standard deviation.
=head3 Explanation
@@ -449,19 +444,12 @@ heading.
=head2 Boot analysis
In the libguestfs source directory, in F<utils/boot-analysis> is a
In L<https://github.com/libguestfs/libguestfs-analysis-tools> is a
program called C<boot-analysis>. This program is able to produce a
very detailed breakdown of the boot steps (eg. qemu, BIOS, kernel,
libguestfs init script), and can measure how long it takes to perform
each step.
To run this program, do:
make
./run utils/boot-analysis/boot-analysis
There is a manual page F<utils/boot-benchmark/boot-analysis.1>
=head2 Detailed timings using ts
Use the L<ts(1)> command (from moreutils) to show detailed
@@ -602,15 +590,15 @@ bit.
Sometimes performance regressions happen in other programs (eg. qemu,
the kernel) that cause problems for libguestfs.
In the libguestfs source,
F<utils/boot-benchmark/boot-benchmark-range.pl> is a script which can
In L<https://github.com/libguestfs/libguestfs-analysis-tools>
F<boot-benchmark/boot-benchmark-range.pl> is a script which can
be used to benchmark libguestfs across a range of git commits in
another project to find out if any commit is causing a slowdown (or
speedup).
To find out how to use this script, consult the manual:
./utils/boot-benchmark/boot-benchmark-range.pl --man
./boot-benchmark/boot-benchmark-range.pl --man
=head1 SEE ALSO

View File

@@ -21,8 +21,3 @@ dnl Replace libtool with a wrapper that clobbers dependency_libs in *.la files
dnl http://lists.fedoraproject.org/pipermail/devel/2010-November/146343.html
LIBTOOL='bash $(top_srcdir)/libtool-kill-dependency_libs.sh $(top_builddir)/libtool'
AC_SUBST([LIBTOOL])
dnl Only build boot-analysis program on x86-64 and aarch64. It
dnl requires custom work to port to each architecture.
AM_CONDITIONAL([HAVE_BOOT_ANALYSIS],
[test "$host_cpu" = "x86_64" || test "$host_cpu" = "aarch64"])

View File

@@ -451,11 +451,3 @@ ruby/ext/guestfs/module.c
sparsify/dummy.c
sysprep/dummy.c
test-tool/test-tool.c
utils/boot-analysis/boot-analysis-timeline.c
utils/boot-analysis/boot-analysis-utils.c
utils/boot-analysis/boot-analysis.c
utils/boot-benchmark/boot-benchmark-range.pl
utils/boot-benchmark/boot-benchmark.c
utils/max-disks/max-disks.pl
utils/qemu-boot/qemu-boot.c
utils/qemu-speed-test/qemu-speed-test.c

View File

@@ -1,7 +0,0 @@
This directory contains miscellaneous utilities for profiling and
testing libguestfs. For more information see guestfs-performance(1).
For libguestfs tools you should look in the specific tool directories
such as cat/, df/, etc.
For an overview of libguestfs directories, see guestfs-hacking(1).

View File

@@ -1,57 +0,0 @@
# libguestfs
# Copyright (C) 2011-2019 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.
include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = boot-analysis.pod
noinst_PROGRAMS = boot-analysis
boot_analysis_SOURCES = \
boot-analysis.c \
boot-analysis.h \
boot-analysis-timeline.c \
boot-analysis-utils.c \
boot-analysis-utils.h
boot_analysis_CPPFLAGS = \
-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
-I$(top_srcdir)/common/utils -I$(top_builddir)/common/utils \
-I$(top_srcdir)/lib -I$(top_builddir)/lib
boot_analysis_CFLAGS = \
-pthread \
$(WARN_CFLAGS) $(WERROR_CFLAGS) \
$(PCRE_CFLAGS)
boot_analysis_LDADD = \
$(top_builddir)/common/utils/libutils.la \
$(top_builddir)/lib/libguestfs.la \
$(PCRE_LIBS) \
$(LIBXML2_LIBS) \
$(LIBVIRT_LIBS) \
$(LTLIBINTL) \
$(top_builddir)/gnulib/lib/libgnu.la \
-lm
# Manual page.
# It should be noinst_MANS but that doesn't work.
noinst_DATA = boot-analysis.1
boot-analysis.1: boot-analysis.pod
$(PODWRAPPER) \
--man $@ \
--license GPLv2+ \
--warning safe \
$<

View File

@@ -1,547 +0,0 @@
/* libguestfs
* Copyright (C) 2016 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.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <assert.h>
#include <pcre.h>
#include "ignore-value.h"
#include "guestfs.h"
#include "guestfs-utils.h"
#include "boot-analysis.h"
COMPILE_REGEXP(re_initcall_calling_module,
"calling ([_A-Za-z0-9]+)\\+.*\\[([_A-Za-z0-9]+)]", 0)
COMPILE_REGEXP(re_initcall_calling,
"calling ([_A-Za-z0-9]+)\\+", 0)
static void construct_initcall_timeline (void);
/* "supermin: internal insmod xx.ko" -> "insmod xx.ko" */
static char *
translate_supermin_insmod_message (const char *message)
{
const char *p, *q;
char *ret;
assert (STRPREFIX (message, "supermin: internal "));
p = message + strlen ("supermin: internal ");
/* Strip off the .ko and anything that follows. */
q = strstr (p, ".ko");
if (q == NULL)
error (EXIT_FAILURE, 0, "cannot find '.ko' suffix in '%s'", message);
ret = strndup (p, q-p);
if (ret == NULL)
error (EXIT_FAILURE, errno, "strndup");
return ret;
}
/* Analyze significant events from the events array, to form a
* timeline of activities.
*/
void
construct_timeline (void)
{
size_t i, j, k;
struct pass_data *data;
struct activity *activity;
const char *first_kernel_message;
for (i = 0; i < NR_TEST_PASSES; ++i) {
data = &pass_data[i];
/* Find an activity, by matching an event with the condition
* `begin_cond' through to the second event `end_cond'. Create an
* activity object in the timeline from the result.
*/
#define FIND(name, flags, begin_cond, end_cond) \
do { \
activity = NULL; \
for (j = 0; j < data->nr_events; ++j) { \
if (begin_cond) { \
for (k = j+1; k < data->nr_events; ++k) { \
if (end_cond) { \
if (i == 0) \
activity = add_activity (name, flags); \
else \
activity = find_activity (name); \
break; \
} \
} \
break; \
} \
} \
if (activity) { \
activity->start_event[i] = j; \
activity->end_event[i] = k; \
} \
else \
error (EXIT_FAILURE, 0, "could not find activity '%s' in pass '%zu'", \
name, i); \
} while (0)
/* Same as FIND() macro, but if no matching events are found,
* ignore it.
*/
#define FIND_OPTIONAL(name, flags, begin_cond, end_cond) \
do { \
activity = NULL; \
for (j = 0; j < data->nr_events; ++j) { \
if (begin_cond) { \
for (k = j+1; k < data->nr_events; ++k) { \
if (end_cond) { \
if (i == 0) \
activity = add_activity (name, flags); \
else \
activity = find_activity (name); \
break; \
} \
} \
break; \
} \
} \
if (activity) { \
activity->start_event[i] = j; \
activity->end_event[i] = k; \
} \
} while (0)
/* Find multiple entries, where we check for:
* next_cond
* next_cond
* next_cond
* end_cond
*/
#define FIND_MULTIPLE(debug_name, flags, next_cond, end_cond, translate_message) \
do { \
activity = NULL; \
for (j = 0; j < data->nr_events; ++j) { \
if (next_cond) { \
CLEANUP_FREE char *message = translate_message (data->events[j].message); \
if (activity) \
activity->end_event[i] = j; \
if (i == 0) \
activity = add_activity (message, flags); \
else \
activity = find_activity (message); \
activity->start_event[i] = j; \
} \
else if (end_cond) \
break; \
} \
if (j < data->nr_events && activity) \
activity->end_event[i] = j; \
else \
error (EXIT_FAILURE, 0, "could not find activity '%s' in pass '%zu'", \
debug_name, i); \
} while (0)
/* Add one activity which is going to cover the whole process
* from launch to close. The launch event is always event 0.
* NB: This activity must be called "run" (see below).
*/
FIND ("run", LONG_ACTIVITY,
j == 0, data->events[k].source == GUESTFS_EVENT_CLOSE);
/* Find where we invoke supermin --build. This should be a null
* operation, but it still takes time to run the external command.
*/
FIND ("supermin:build", 0,
data->events[j].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[j].message,
"begin building supermin appliance"),
data->events[k].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[k].message,
"finished building supermin appliance"));
/* Find where we invoke qemu to test features. */
FIND_OPTIONAL ("qemu:feature-detect", 0,
data->events[j].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[j].message,
"begin testing qemu features"),
data->events[k].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[k].message,
"finished testing qemu features"));
/* Find where we run qemu. */
FIND_OPTIONAL ("qemu", LONG_ACTIVITY,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "-no-user-config"),
data->events[k].source == GUESTFS_EVENT_CLOSE);
/* For the libvirt backend, connecting to libvirt, getting
* capabilities, parsing capabilities etc.
*/
FIND_OPTIONAL ("libvirt:connect", 0,
data->events[j].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[j].message, "connect to libvirt"),
data->events[k].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[k].message, "successfully opened libvirt handle"));
FIND_OPTIONAL ("libvirt:get-libvirt-capabilities", 0,
data->events[j].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[j].message, "get libvirt capabilities"),
data->events[k].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[k].message, "parsing capabilities XML"));
FIND_OPTIONAL ("libguestfs:parse-libvirt-capabilities", 0,
data->events[j].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[j].message, "parsing capabilities XML"),
data->events[k].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[k].message, "get_backend_setting"));
FIND_OPTIONAL ("libguestfs:create-libvirt-xml", 0,
data->events[j].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[j].message, "create libvirt XML"),
data->events[k].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[k].message, "libvirt XML:"));
#if defined(__aarch64__)
#define FIRST_FIRMWARE_MESSAGE "UEFI firmware starting"
#else
#define SGABIOS_STRING "\033[1;256r\033[256;256H\033[6n"
#define FIRST_FIRMWARE_MESSAGE SGABIOS_STRING
#endif
/* Try to determine the first message that the kernel prints. */
#if defined(__aarch64__)
first_kernel_message = "Booting Linux on physical CPU";
#else
first_kernel_message = "Probing EDD";
for (j = 0; j < data->nr_events; ++j)
if (data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, first_kernel_message))
goto found_first_kernel_message;
first_kernel_message = "Linux version ";
for (j = 0; j < data->nr_events; ++j)
if (data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, first_kernel_message))
goto found_first_kernel_message;
error (EXIT_FAILURE, 0, "could not determine first message printed by the kernel");
found_first_kernel_message:
#endif
/* For the libvirt backend, find the overhead of libvirt. */
FIND_OPTIONAL ("libvirt:overhead", 0,
data->events[j].source == GUESTFS_EVENT_LIBRARY &&
strstr (data->events[j].message, "launch libvirt guest"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, FIRST_FIRMWARE_MESSAGE));
/* From starting qemu up to entering the BIOS is the qemu overhead. */
FIND_OPTIONAL ("qemu:overhead", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "-no-user-config"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, FIRST_FIRMWARE_MESSAGE));
/* From entering the BIOS to starting the kernel is the BIOS overhead. */
FIND_OPTIONAL ("bios:overhead", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, FIRST_FIRMWARE_MESSAGE),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, first_kernel_message));
#if defined(__i386__) || defined(__x86_64__)
/* SGABIOS (option ROM). */
FIND_OPTIONAL ("sgabios", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, SGABIOS_STRING),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "SeaBIOS (version"));
#endif
#if defined(__i386__) || defined(__x86_64__)
/* SeaBIOS. */
FIND ("seabios", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "SeaBIOS (version"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, first_kernel_message));
#endif
#if defined(__i386__) || defined(__x86_64__)
/* SeaBIOS - only available when using debug messages. */
FIND_OPTIONAL ("seabios:pci-probe", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "Searching bootorder for: /pci@"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "Scan for option roms"));
#endif
/* Find where we run the guest kernel. */
FIND ("kernel", LONG_ACTIVITY,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, first_kernel_message),
data->events[k].source == GUESTFS_EVENT_CLOSE);
/* Kernel startup to userspace. */
FIND ("kernel:overhead", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, first_kernel_message),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "supermin:") &&
strstr (data->events[k].message, "starting up"));
/* The time taken to get into start_kernel function. */
FIND_OPTIONAL ("kernel:entry", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, first_kernel_message),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "Linux version "));
#if defined(__i386__) || defined(__x86_64__)
/* Alternatives patching instructions (XXX not very accurate we
* really need some debug messages inserted into the code).
*/
FIND ("kernel:alternatives", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "Last level dTLB entries"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "Freeing SMP alternatives"));
#endif
/* ftrace patching instructions. */
FIND_OPTIONAL ("kernel:ftrace", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "ftrace: allocating"),
1);
/* All initcall functions, before we enter userspace. */
FIND ("kernel:initcalls-before-userspace", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "calling "),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "Freeing unused kernel memory"));
/* Find where we run supermin mini-initrd. */
FIND ("supermin:mini-initrd", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "supermin:") &&
strstr (data->events[j].message, "starting up"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "supermin: chroot"));
/* Loading kernel modules from supermin initrd. */
FIND_MULTIPLE
("supermin insmod", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "supermin: internal insmod"),
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "supermin: picked"),
translate_supermin_insmod_message);
/* Find where we run the /init script. */
FIND ("/init", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "supermin: chroot"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "guestfsd --verbose"));
/* Everything from the chroot to the first echo in the /init
* script counts as bash overhead.
*/
FIND ("bash:overhead", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "supermin: chroot"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "Starting /init script"));
/* /init: Mount special filesystems. */
FIND ("/init:mount-special", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "*guestfs_boot_analysis=1*"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "kmod static-nodes"));
/* /init: Run kmod static-nodes */
FIND ("/init:kmod-static-nodes", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "kmod static-nodes"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "systemd-tmpfiles"));
/* /init: systemd-tmpfiles. */
FIND ("/init:systemd-tmpfiles", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "systemd-tmpfiles"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "udev"));
/* /init: start udevd. */
FIND ("/init:udev-overhead", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "udevd --daemon"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "nullglob"));
/* /init: set up network. */
FIND ("/init:network-overhead", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "+ ip addr"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "+ test"));
/* /init: probe MD arrays. */
FIND ("/init:md-probe", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "+ mdadm"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "+ modprobe dm_mod"));
/* /init: probe DM/LVM. */
FIND ("/init:lvm-probe", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "+ modprobe dm_mod"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "+ ldmtool"));
/* /init: probe Windows dynamic disks. */
FIND ("/init:windows-dynamic-disks-probe", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "+ ldmtool"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "+ test"));
/* Find where we run guestfsd. */
FIND ("guestfsd", 0,
data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[j].message, "guestfsd --verbose"),
data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, "fsync /dev/sda"));
/* Shutdown process. */
FIND ("shutdown", 0,
data->events[j].source == GUESTFS_EVENT_TRACE &&
STREQ (data->events[j].message, "close"),
data->events[k].source == GUESTFS_EVENT_CLOSE);
}
construct_initcall_timeline ();
}
/* Handling of initcall is so peculiar that we hide it in a separate
* function from the rest.
*/
static void
construct_initcall_timeline (void)
{
size_t i, j, k;
struct pass_data *data;
struct activity *activity;
for (i = 0; i < NR_TEST_PASSES; ++i) {
data = &pass_data[i];
/* Each kernel initcall is bracketed by:
*
* calling ehci_hcd_init+0x0/0xc1 @ 1"
* initcall ehci_hcd_init+0x0/0xc1 returned 0 after 420 usecs"
*
* For initcall functions in modules:
*
* calling virtio_mmio_init+0x0/0x1000 [virtio_mmio] @ 1"
* initcall virtio_mmio_init+0x0/0x1000 [virtio_mmio] returned 0 after 14 usecs"
*
* Initcall functions can be nested, and do not have unique names.
*/
for (j = 0; j < data->nr_events; ++j) {
int vec[30], r;
const char *message = data->events[j].message;
if (data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
((r = pcre_exec (re_initcall_calling_module, NULL,
message, strlen (message),
0, 0, vec, sizeof vec / sizeof vec[0])) >= 1 ||
(r = pcre_exec (re_initcall_calling, NULL,
message, strlen (message),
0, 0, vec, sizeof vec / sizeof vec[0])) >= 1)) {
CLEANUP_FREE char *fn_name = NULL, *module_name = NULL;
if (r >= 2) /* because pcre_exec returns 1 + number of captures */
fn_name = strndup (message + vec[2], vec[3]-vec[2]);
if (r >= 3)
module_name = strndup (message + vec[4], vec[5]-vec[4]);
CLEANUP_FREE char *fullname;
if (asprintf (&fullname, "%s.%s",
module_name ? module_name : "kernel", fn_name) == -1)
error (EXIT_FAILURE, errno, "asprintf");
CLEANUP_FREE char *initcall_match;
if (asprintf (&initcall_match, "initcall %s", fn_name) == -1)
error (EXIT_FAILURE, errno, "asprintf");
/* Get a unique name for this activity. Unfortunately
* kernel initcall function names are not unique!
*/
CLEANUP_FREE char *activity_name;
if (asprintf (&activity_name, "initcall %s", fullname) == -1)
error (EXIT_FAILURE, errno, "asprintf");
if (i == 0) {
int n = 1;
while (activity_exists (activity_name)) {
free (activity_name);
if (asprintf (&activity_name, "initcall %s:%d", fullname, n) == -1)
error (EXIT_FAILURE, errno, "asprintf");
n++;
}
}
else {
int n = 1;
while (!activity_exists_with_no_data (activity_name, i)) {
free (activity_name);
if (asprintf (&activity_name, "initcall %s:%d", fullname, n) == -1)
error (EXIT_FAILURE, errno, "asprintf");
n++;
}
}
/* Find the matching end event. It might be some time later,
* since it appears initcalls can be nested.
*/
for (k = j+1; k < data->nr_events; ++k) {
if (data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
strstr (data->events[k].message, initcall_match)) {
if (i == 0)
activity = add_activity (activity_name, 0);
else
activity = find_activity (activity_name);
activity->start_event[i] = j;
activity->end_event[i] = k;
break;
}
}
}
}
}
}

View File

@@ -1,90 +0,0 @@
/* libguestfs
* Copyright (C) 2016 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.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <error.h>
#include <errno.h>
#include "ignore-value.h"
#include "guestfs.h"
#include "guestfs-utils.h"
#include "boot-analysis-utils.h"
void
get_time (struct timespec *ts)
{
if (clock_gettime (CLOCK_REALTIME, ts) == -1)
error (EXIT_FAILURE, errno, "clock_gettime: CLOCK_REALTIME");
}
int64_t
timespec_diff (const struct timespec *x, const struct timespec *y)
{
int64_t nsec;
nsec = (y->tv_sec - x->tv_sec) * UINT64_C(1000000000);
nsec += y->tv_nsec - x->tv_nsec;
return nsec;
}
void
test_info (guestfs_h *g, int nr_test_passes)
{
const char *qemu = guestfs_get_hv (g);
CLEANUP_FREE char *cmd = NULL;
CLEANUP_FREE char *backend = NULL;
/* Related to the test program. */
printf ("test version: %s %s\n", PACKAGE_NAME, PACKAGE_VERSION_FULL);
printf (" test passes: %d\n", nr_test_passes);
/* Related to the host. */
printf ("host version: ");
fflush (stdout);
ignore_value (system ("uname -a"));
printf (" host CPU: ");
fflush (stdout);
ignore_value (system ("perl -n -e 'if (/^model name.*: (.*)/) { print \"$1\\n\"; exit }' /proc/cpuinfo"));
/* Related to qemu. */
backend = guestfs_get_backend (g);
printf (" backend: %-20s [to change set $LIBGUESTFS_BACKEND]\n",
backend);
printf (" qemu: %-20s [to change set $LIBGUESTFS_HV]\n", qemu);
printf ("qemu version: ");
fflush (stdout);
if (asprintf (&cmd, "%s -version", qemu) == -1)
error (EXIT_FAILURE, errno, "asprintf");
ignore_value (system (cmd));
printf (" smp: %-20d [to change use --smp option]\n",
guestfs_get_smp (g));
printf (" memsize: %-20d [to change use --memsize option]\n",
guestfs_get_memsize (g));
/* Related to the guest kernel. Be nice to get the guest
* kernel version here somehow (XXX).
*/
printf (" append: %-20s [to change use --append option]\n",
guestfs_get_append (g) ? : "");
}

View File

@@ -1,36 +0,0 @@
/* libguestfs
* Copyright (C) 2016 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.
*/
#ifndef GUESTFS_BOOT_ANALYSIS_UTILS_H_
#define GUESTFS_BOOT_ANALYSIS_UTILS_H_
/* Get current time, returning it in *ts. If there is a system call
* failure, this exits.
*/
extern void get_time (struct timespec *ts);
/* Computes Y - X, returning nanoseconds. */
extern int64_t timespec_diff (const struct timespec *x, const struct timespec *y);
/* Display host machine and test parameters (to stdout). 'g' should
* be an open libguestfs handle. It is used for reading hv, memsize
* etc. and is not modified.
*/
extern void test_info (guestfs_h *g, int nr_test_passes);
#endif /* GUESTFS_BOOT_ANALYSIS_UTILS_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -1,108 +0,0 @@
/* libguestfs
* Copyright (C) 2016 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.
*/
#ifndef GUESTFS_BOOT_ANALYSIS_H_
#define GUESTFS_BOOT_ANALYSIS_H_
#define NR_WARMUP_PASSES 3
#define NR_TEST_PASSES 5
/* Per-pass data collected. */
struct pass_data {
#define PASS_MAGIC 0x45545545
uint32_t magic; /* Struct magic. */
size_t pass;
struct timespec start_t;
struct timespec end_t;
int64_t elapsed_ns;
/* Array of timestamped events. */
size_t nr_events;
struct event *events;
/* Was the previous appliance log message incomplete? If so, this
* contains the index of that incomplete message in the events
* array.
*/
ssize_t incomplete_log_message;
/* Have we seen the launch event yet? We don't record events until
* this one has been received. This makes it easy to base the
* timeline at event 0.
*/
int seen_launch;
};
/* The 'source' field in the event is a guestfs event
* (GUESTFS_EVENT_*). We also wish to encode libvirt as a source, so
* we use a magic/impossible value for that here. Note that events
* are bitmasks, and normally no more than one bit may be set.
*/
#define SOURCE_LIBVIRT ((uint64_t)~0)
extern char *source_to_string (uint64_t source);
struct event {
struct timespec t;
uint64_t source;
char *message;
};
extern struct pass_data pass_data[NR_TEST_PASSES];
/* The final timeline consisting of various activities starting and
* ending. We're interested in when the activities start, and how
* long they take (mean, variance, standard deviation of length).
*/
struct activity {
#define ACTIVITY_MAGIC 0xAC1AC1AC
uint32_t magic; /* Struct magic. */
char *name; /* Name of this activity. */
int flags;
#define LONG_ACTIVITY 1 /* Expected to take a long time. */
/* For each pass, record the actual start & end events of this
* activity.
*/
size_t start_event[NR_TEST_PASSES];
size_t end_event[NR_TEST_PASSES];
double t; /* Start (ns offset). */
double end_t; /* t + mean - 1 */
/* Length of this activity. */
double mean; /* Mean time elapsed (ns). */
double variance; /* Variance. */
double sd; /* Standard deviation. */
double percent; /* Percent of total elapsed time. */
int warning; /* Appears in red. */
};
extern size_t nr_activities;
extern struct activity *activities;
extern int activity_exists (const char *name);
extern struct activity *add_activity (const char *name, int flags);
extern struct activity *find_activity (const char *name);
extern int activity_exists_with_no_data (const char *name, size_t pass);
extern void construct_timeline (void);
#endif /* GUESTFS_BOOT_ANALYSIS_H_ */

View File

@@ -1,99 +0,0 @@
=head1 NAME
boot-analysis - Trace and analyze the appliance boot process
=head1 SYNOPSIS
./run utils/boot-analysis/boot-analysis
=head1 DESCRIPTION
Trace and analyze the appliance boot process to find out which steps
are taking the most time. It is not part of the standard tests.
This needs to be run on a quiet machine, so that other processes
disturb the timing as little as possible. The program is completely
safe to run at any time. It doesn't read or write any external files,
and it doesn't require root.
You can run it from the build directory on the built copy of
libguestfs like this:
make
./run utils/boot-analysis/boot-analysis
If you omit C<./run> then it is run on the installed copy of libguestfs.
=head2 How it works
We create a libguestfs handle and register callback handlers so we
can see appliance messages, trace events and so on.
We then launch the handle and shut it down as quickly as possible.
While the handle is running, events (seen by the callback handlers)
are written verbatim into an in-memory buffer, with timestamps.
Afterwards we analyze the result using regular expressions to try to
identify a "timeline" for the handle (eg. at what time did the BIOS
hand control to the kernel). This analysis is done in
F<utils/boot-analysis/boot-analysis-timeline.c>.
The whole process is repeated across a few runs, and the final
timeline (including statistical analysis of the variation between
runs) gets printed.
The program is very sensitive to the specific messages printed by
BIOS/kernel/supermin/userspace. It only works on x86-64 or aarch64.
It will require periodic adjustment of the regular expressions in
order to keep things up to date.
=head1 OPTIONS
=over 4
=item B<--help>
Display brief help.
=item B<--append> "OPTIONS"
Append C<OPTIONS> to the kernel command line.
=item B<--color>
=item B<--colour>
Output colours (as ANSI escape sequences), even if the output is not a
terminal.
=item B<-m> MB
=item B<--memsize> MB
Set the appliance memory size in MB.
=item B<--smp> N
Enable C<N> virtual CPUs.
=item B<-v>
=item B<--verbose>
More verbose output, useful for debugging problems.
=back
=head1 SEE ALSO
L<guestfs-performance(1)>,
L<http://libguestfs.org/>.
=head1 AUTHOR
Richard W.M. Jones L<http://people.redhat.com/~rjones/>
=head1 COPYRIGHT
Copyright (C) 2016 Red Hat Inc.

View File

@@ -1,56 +0,0 @@
# libguestfs
# Copyright (C) 2011-2019 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.
# Safety and liveness tests of components that libguestfs depends upon
# (not of libguestfs itself). Mainly this is for qemu and the kernel.
# This test is the first to run.
include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = boot-benchmark.pod boot-benchmark-range.pl
noinst_PROGRAMS = boot-benchmark
boot_benchmark_SOURCES = \
boot-benchmark.c \
../boot-analysis/boot-analysis-utils.c \
../boot-analysis/boot-analysis-utils.h
boot_benchmark_CPPFLAGS = \
-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
-I$(top_srcdir)/common/utils -I$(top_builddir)/common/utils \
-I$(top_srcdir)/lib -I$(top_builddir)/lib \
-I$(top_srcdir)/utils/boot-analysis
boot_benchmark_CFLAGS = \
$(WARN_CFLAGS) $(WERROR_CFLAGS)
boot_benchmark_LDADD = \
$(top_builddir)/common/utils/libutils.la \
$(top_builddir)/lib/libguestfs.la \
$(LIBXML2_LIBS) \
$(LTLIBINTL) \
$(top_builddir)/gnulib/lib/libgnu.la \
-lm
# Manual page.
# It should be noinst_MANS but that doesn't work.
noinst_DATA = boot-benchmark.1
boot-benchmark.1: boot-benchmark.pod
$(PODWRAPPER) \
--man $@ \
--license GPLv2+ \
--warning safe \
$<

View File

@@ -1,240 +0,0 @@
#!/usr/bin/env perl
# Copyright (C) 2016 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.
use warnings;
use strict;
use Pod::Usage;
use Getopt::Long;
=head1 NAME
boot-benchmark-range.pl - Benchmark libguestfs across a range of commits
from another project
=head1 SYNOPSIS
LIBGUESTFS_BACKEND=direct \
LIBGUESTFS_HV=/path/to/qemu/x86_64-softmmu/qemu-system-x86_64 \
./run \
utils/boot-benchmark/boot-benchmark-range.pl /path/to/qemu HEAD~50..HEAD
=head1
Run F<utils/boot-benchmark/boot-benchmark> across a range of commits
in another project. This is useful for finding performance
regressions in other programs such as qemu or the Linux kernel which
might be affecting libguestfs.
For example, suppose you suspect there has been a performance
regression in qemu, somewhere between C<HEAD~50..HEAD>. You could run
the script like this:
LIBGUESTFS_BACKEND=direct \
LIBGUESTFS_HV=/path/to/qemu/x86_64-softmmu/qemu-system-x86_64 \
./run \
utils/boot-benchmark/boot-benchmark-range.pl /path/to/qemu HEAD~50..HEAD
where F</path/to/qemu> is the path to the qemu git repository.
The output is a list of the qemu commits, annotated by the benchmark
time and some other information about how the time compares to the
previous commit.
You should run these tests on an unloaded machine. In particular
running a desktop environment, web browser and so on can make the
benchmarks useless.
=head1 OPTIONS
=over 4
=cut
my $help;
=item B<--help>
Display brief help.
=cut
my $man;
=item B<--man>
Display full documentation (man page).
=cut
my $benchmark_command;
=item B<--benchmark> C<boot-benchmark>
Set the name of the benchmark to run. You only need to use this if
the script cannot find the right path to the libguestfs
F<utils/boot-benchmark/boot-benchmark> program. By default the script
looks for this file in the same directory as its executable.
=cut
my $make_command = "make";
=item B<--make> C<make>
Set the command used to build the other project. The default is
to run C<make>.
If the command fails, then the commit is skipped.
=back
=cut
# Clean up the program name.
my $progname = $0;
$progname =~ s{.*/}{};
# Parse options.
GetOptions ("help|?" => \$help,
"man" => \$man,
"benchmark=s" => \$benchmark_command,
"make=s" => \$make_command,
) or pod2usage (2);
pod2usage (-exitval => 0) if $help;
pod2usage (-exitval => 0, -verbose => 2) if $man;
die "$progname: missing argument: requires path to git repository and range of commits\n" unless @ARGV == 2;
my $dir = $ARGV[0];
my $range = $ARGV[1];
die "$progname: $dir is not a git repository\n"
unless -d $dir && -d "$dir/.git";
sub silently_run
{
open my $saveout, ">&STDOUT";
open my $saveerr, ">&STDERR";
open STDOUT, ">/dev/null";
open STDERR, ">/dev/null";
my $ret = system (@_);
open STDOUT, ">&", $saveout;
open STDERR, ">&", $saveerr;
return $ret;
}
# Find the benchmark program and check it works.
unless (defined $benchmark_command) {
$benchmark_command = $0;
$benchmark_command =~ s{/[^/]+$}{};
$benchmark_command .= "/boot-benchmark";
my $r = silently_run ("$benchmark_command", "--help");
die "$progname: cannot locate boot-benchmark program, try using --benchmark\n" unless $r == 0;
}
# Get the top-most commit from the remote, and restore it on exit.
my $top_commit = `git -C '$dir' rev-parse HEAD`;
chomp $top_commit;
sub checkout
{
my $sha = shift;
my $ret = silently_run ("git", "-C", $dir, "checkout", $sha);
return $ret;
}
END {
checkout ($top_commit);
}
# Get the range of commits and log messages.
my @range = ();
open RANGE, "git -C '$dir' log --reverse --oneline $range |" or die;
while (<RANGE>) {
if (m/^([0-9a-f]+) (.*)/) {
my $sha = $1;
my $msg = $2;
push @range, [ $sha, $msg ];
}
}
close RANGE or die;
# Run the test.
my $prev_ms;
foreach (@range) {
my ($sha, $msg) = @$_;
my $r;
print "\n";
print "$sha $msg\n";
# Checkout this commit in the other repo.
$r = checkout ($sha);
if ($r != 0) {
print "git checkout failed\n";
next;
}
# Build the repo, silently.
$r = silently_run ("cd $dir && $make_command");
if ($r != 0) {
print "build failed\n";
next;
}
# Run the benchmark program and get the timing.
my ($time_ms, $time_str);
open BENCHMARK, "$benchmark_command | grep '^Result:' |" or die;
while (<BENCHMARK>) {
die unless m/^Result: (([\d.]+)ms ±[\d.]+ms)/;
$time_ms = $2;
$time_str = $1;
}
close BENCHMARK;
print "\t", $time_str;
if (defined $prev_ms) {
if ($prev_ms > $time_ms) {
my $pc = 100 * ($prev_ms-$time_ms) / $time_ms;
if ($pc >= 1) {
printf (" ↑ improves performance by %0.1f%%", $pc);
}
} elsif ($prev_ms < $time_ms) {
my $pc = 100 * ($time_ms-$prev_ms) / $prev_ms;
if ($pc >= 1) {
printf (" ↓ degrades performance by %0.1f%%", $pc);
}
}
}
print "\n";
$prev_ms = $time_ms;
}
=head1 SEE ALSO
L<git(1)>,
L<guestfs-performance(1)>.
=head1 AUTHOR
Richard W.M. Jones.
=head1 COPYRIGHT
Copyright (C) 2016 Red Hat Inc.

View File

@@ -1,227 +0,0 @@
/* libguestfs
* Copyright (C) 2016 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.
*/
/* See instructions in boot-benchmark.1 */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <getopt.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
#include <error.h>
#include <assert.h>
#include <math.h>
#include "guestfs.h"
#include "guestfs-utils.h"
#include "boot-analysis-utils.h"
#include "getprogname.h"
#define NR_WARMUP_PASSES 3
#define NR_TEST_PASSES 10
static const char *append = NULL;
static int memsize = 0;
static int smp = 1;
static void run_test (void);
static guestfs_h *create_handle (void);
static void add_drive (guestfs_h *g);
static void __attribute__((noreturn))
usage (int exitcode)
{
guestfs_h *g;
int default_memsize = -1;
g = guestfs_create ();
if (g) {
default_memsize = guestfs_get_memsize (g);
guestfs_close (g);
}
fprintf (stderr,
"boot-benchmark: Benchmark the time taken to boot the libguestfs appliance.\n"
"Usage:\n"
" boot-benchmark [--options]\n"
"Options:\n"
" --help Display this usage text and exit.\n"
" --append OPTS Append OPTS to kernel command line.\n"
" -m MB\n"
" --memsize MB Set memory size in MB (default: %d).\n"
" --smp N Enable N virtual CPUs (default: 1).\n",
default_memsize);
exit (exitcode);
}
int
main (int argc, char *argv[])
{
enum { HELP_OPTION = CHAR_MAX + 1 };
static const char options[] = "m:";
static const struct option long_options[] = {
{ "help", 0, 0, HELP_OPTION },
{ "append", 1, 0, 0 },
{ "memsize", 1, 0, 'm' },
{ "smp", 1, 0, 0 },
{ 0, 0, 0, 0 }
};
int c, option_index;
for (;;) {
c = getopt_long (argc, argv, options, long_options, &option_index);
if (c == -1) break;
switch (c) {
case 0: /* Options which are long only. */
if (STREQ (long_options[option_index].name, "append")) {
append = optarg;
break;
}
else if (STREQ (long_options[option_index].name, "smp")) {
if (sscanf (optarg, "%d", &smp) != 1) {
fprintf (stderr, "%s: could not parse smp parameter: %s\n",
getprogname (), optarg);
exit (EXIT_FAILURE);
}
break;
}
fprintf (stderr, "%s: unknown long option: %s (%d)\n",
getprogname (), long_options[option_index].name, option_index);
exit (EXIT_FAILURE);
case 'm':
if (sscanf (optarg, "%d", &memsize) != 1) {
fprintf (stderr, "%s: could not parse memsize parameter: %s\n",
getprogname (), optarg);
exit (EXIT_FAILURE);
}
break;
case HELP_OPTION:
usage (EXIT_SUCCESS);
default:
usage (EXIT_FAILURE);
}
}
run_test ();
}
static void
run_test (void)
{
guestfs_h *g;
size_t i;
int64_t ns[NR_TEST_PASSES];
double mean;
double variance;
double sd;
printf ("Warming up the libguestfs cache ...\n");
for (i = 0; i < NR_WARMUP_PASSES; ++i) {
g = create_handle ();
add_drive (g);
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
}
printf ("Running the tests ...\n");
for (i = 0; i < NR_TEST_PASSES; ++i) {
struct timespec start_t, end_t;
g = create_handle ();
add_drive (g);
get_time (&start_t);
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
get_time (&end_t);
ns[i] = timespec_diff (&start_t, &end_t);
}
/* Calculate the mean. */
mean = 0;
for (i = 0; i < NR_TEST_PASSES; ++i)
mean += ns[i];
mean /= NR_TEST_PASSES;
/* Calculate the variance and standard deviation. */
variance = 0;
for (i = 0; i < NR_TEST_PASSES; ++i)
variance = pow (ns[i] - mean, 2);
variance /= NR_TEST_PASSES;
sd = sqrt (variance);
/* Print the test parameters. */
printf ("\n");
g = create_handle ();
test_info (g, NR_TEST_PASSES);
guestfs_close (g);
/* Print the result. */
printf ("\n");
printf ("Result: %.1fms ±%.1fms\n", mean / 1000000, sd / 1000000);
}
/* Common function to create the handle and set various defaults. */
static guestfs_h *
create_handle (void)
{
guestfs_h *g;
CLEANUP_FREE char *full_append = NULL;
g = guestfs_create ();
if (!g) error (EXIT_FAILURE, errno, "guestfs_create");
if (memsize != 0)
if (guestfs_set_memsize (g, memsize) == -1)
exit (EXIT_FAILURE);
if (smp >= 2)
if (guestfs_set_smp (g, smp) == -1)
exit (EXIT_FAILURE);
if (append != NULL)
if (guestfs_set_append (g, full_append) == -1)
exit (EXIT_FAILURE);
return g;
}
/* Common function to add the /dev/null drive. */
static void
add_drive (guestfs_h *g)
{
if (guestfs_add_drive_opts (g, "/dev/null",
GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
-1) == -1)
exit (EXIT_FAILURE);
}

View File

@@ -1,68 +0,0 @@
=head1 NAME
boot-benchmark - Benchmark the time taken to boot the libguestfs appliance
=head1 SYNOPSIS
./run utils/boot-benchmark/boot-benchmark
=head1 DESCRIPTION
Benchmark the time taken to boot the libguestfs appliance.
It is essentially the same as doing:
time guestfish -a /dev/null run
except that it warms up the caches and repeats the test many times,
printing out the mean time and standard deviation.
This needs to be run on a quiet machine, so that other processes
disturb the timing as little as possible. The program is completely
safe to run at any time. It doesn't read or write any external files,
and it doesn't require root.
You can run it from the build directory on the built copy of
libguestfs like this:
make
./run utils/boot-benchmark/boot-benchmark
If you omit C<./run> then it is run on the installed copy of libguestfs.
=head1 OPTIONS
=over 4
=item B<--help>
Display brief help.
=item B<--append> "OPTIONS"
Append C<OPTIONS> to the kernel command line.
=item B<-m> MB
=item B<--memsize> MB
Set the appliance memory size in MB.
=item B<--smp> N
Enable C<N> virtual CPUs.
=back
=head1 SEE ALSO
L<guestfs-performance(1)>,
L<http://libguestfs.org/>.
=head1 AUTHOR
Richard W.M. Jones L<http://people.redhat.com/~rjones/>
=head1 COPYRIGHT
Copyright (C) 2016 Red Hat Inc.

View File

@@ -1,22 +0,0 @@
# libguestfs
# Copyright (C) 2018 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.
include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = max-disks.pl
noinst_SCRIPTS = max-disks.pl

View File

@@ -1,94 +0,0 @@
#!/usr/bin/perl -w
# libguestfs
# Copyright (C) 2018 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.
# From painful experience we know that changes to the kernel can
# suddenly change the maximum number of disks we can add to the
# appliance. This script uses a simple binary search to find the
# current maximum.
#
# To test different kernels you may want to do:
# rm -rf /var/tmp/.guestfs-*
# export SUPERMIN_KERNEL=/boot/vmlinuz-...
# export SUPERMIN_MODULES=/lib/modules/...
# ./max-disks.pl
#
# See also:
# https://bugzilla.redhat.com/show_bug.cgi?id=1478201#c4
use strict;
use Sys::Guestfs;
$| = 1;
my $low = 1;
my $high = 2048; # Will never test higher than this.
my $mid = 1024;
# Get the kernel under test.
my $g = Sys::Guestfs->new ();
$g->launch ();
my %kernel = $g->utsname;
$g->close ();
sub test_mid
{
my ($ret, %k);
eval {
my $g = Sys::Guestfs->new ();
for (my $i = 0; $i < $mid; ++$i) {
$g->add_drive_scratch (1024*1024);
}
$g->launch ();
%k = $g->utsname;
$g->shutdown ();
};
if ($@) {
printf ("%d => bad\n", $mid);
$ret = 0;
}
else {
printf ("%d => good\n", $mid);
$ret = 1;
}
die "kernel version changed during test!\n"
if exists $k{uts_release} && $k{uts_release} ne $kernel{uts_release};
return $ret;
}
for (;;) {
if (test_mid ()) {
# good, so try higher
$low = $mid;
}
else {
# bad, so try lower
$high = $mid;
}
$mid = int (($high+$low) / 2);
if ($mid == $high || $mid == $low) {
printf("kernel: %s %s (%s)\n",
$kernel{uts_sysname}, $kernel{uts_release},
$kernel{uts_machine});
# +1 because of the appliance disk.
printf ("max disks = %d\n", $mid+1);
exit 0;
}
}

View File

@@ -1,40 +0,0 @@
# libguestfs
# Copyright (C) 2011-2019 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.
include $(top_srcdir)/subdir-rules.mk
noinst_PROGRAMS = qemu-boot
qemu_boot_SOURCES = \
qemu-boot.c
qemu_boot_CPPFLAGS = \
-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
-I$(top_srcdir)/common/utils -I$(top_builddir)/common/utils \
-I$(top_srcdir)/lib -I$(top_builddir)/lib \
-I$(top_srcdir)/common/parallel -I$(top_builddir)/common/parallel \
-I$(top_srcdir)/df
qemu_boot_CFLAGS = \
-pthread \
$(WARN_CFLAGS) $(WERROR_CFLAGS)
qemu_boot_LDADD = \
$(top_builddir)/common/parallel/libparallel.la \
$(top_builddir)/common/utils/libutils.la \
$(top_builddir)/lib/libguestfs.la \
$(LIBXML2_LIBS) \
$(LIBVIRT_LIBS) \
$(LTLIBINTL) \
$(top_builddir)/gnulib/lib/libgnu.la

View File

@@ -1,366 +0,0 @@
/* libguestfs
* Copyright (C) 2014-2019 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.
*/
/* Ancient libguestfs had a script called test-bootbootboot which just
* booted up the appliance in a loop. This was necessary back in the
* bad old days when qemu was not very reliable. This is the
* spiritual successor of that script, designed to find bugs in
* aarch64 KVM. You can control the number of boots that are done and
* the amount of parallelism.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <limits.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include "guestfs.h"
#include "guestfs-utils.h"
#include "estimate-max-threads.h"
#include "getprogname.h"
#define MIN(a,b) ((a)<(b)?(a):(b))
/* Maximum number of threads we would ever run. Note this should not
* be > 20, unless libvirt is modified to increase the maximum number
* of clients. User can override this limit using -P.
*/
#define MAX_THREADS 12
static size_t n; /* Number of qemu processes to run in total. */
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int ignore_errors = 0;
static const char *log_template = NULL;
static size_t log_file_size;
static int trace = 0;
static int verbose = 0;
/* Events captured by the --log option. */
static const uint64_t event_bitmask =
GUESTFS_EVENT_LIBRARY |
GUESTFS_EVENT_WARNING |
GUESTFS_EVENT_APPLIANCE |
GUESTFS_EVENT_TRACE;
struct thread_data {
int thread_num;
int r;
};
static void run_test (size_t P);
static void *start_thread (void *thread_data_vp);
static void message_callback (guestfs_h *g, void *opaque, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len);
static void __attribute__((noreturn))
usage (int exitcode)
{
fprintf (stderr,
"qemu-boot: A program for repeatedly running the libguestfs appliance.\n"
"qemu-boot [-i] [--log output.%%] [-P <nr-threads>] -n <nr-appliances>\n"
" -i Ignore errors\n"
" --log <file.%%>\n"
" Write per-appliance logs to file (%% in name replaced by boot number)\n"
" -P <n> Set number of parallel threads\n"
" (default is based on the amount of free memory)\n"
" -n <n> Set number of appliances to run before exiting\n"
" -v Verbose appliance\n"
" -x Enable libguestfs tracing\n");
exit (exitcode);
}
int
main (int argc, char *argv[])
{
enum { HELP_OPTION = CHAR_MAX + 1 };
static const char options[] = "in:P:vx";
static const struct option long_options[] = {
{ "help", 0, 0, HELP_OPTION },
{ "ignore", 0, 0, 'i' },
{ "log", 1, 0, 0 },
{ "number", 1, 0, 'n' },
{ "processes", 1, 0, 'P' },
{ "trace", 0, 0, 'x' },
{ "verbose", 0, 0, 'v' },
{ 0, 0, 0, 0 }
};
size_t P = 0, i;
int c, option_index;
for (;;) {
c = getopt_long (argc, argv, options, long_options, &option_index);
if (c == -1) break;
switch (c) {
case 0:
/* Options which are long only. */
if (STREQ (long_options[option_index].name, "log")) {
log_template = optarg;
log_file_size = strlen (log_template);
for (i = 0; i < strlen (log_template); ++i) {
if (log_template[i] == '%')
log_file_size += 64;
}
}
else
error (EXIT_FAILURE, 0,
"unknown long option: %s (%d)",
long_options[option_index].name, option_index);
break;
case 'i':
ignore_errors = 1;
break;
case 'n':
if (sscanf (optarg, "%zu", &n) != 1 || n == 0)
error (EXIT_FAILURE, 0, "-n option not numeric and greater than 0");
break;
case 'P':
if (sscanf (optarg, "%zu", &P) != 1)
error (EXIT_FAILURE, 0, "-P option not numeric");
break;
case 'v':
verbose = 1;
break;
case 'x':
trace = 1;
break;
case HELP_OPTION:
usage (EXIT_SUCCESS);
default:
usage (EXIT_FAILURE);
}
}
if (n == 0)
error (EXIT_FAILURE, 0,
"must specify number of processes to run (-n option)");
if (optind != argc)
error (EXIT_FAILURE, 0,
"extra arguments found on the command line");
/* Calculate the number of threads to use. */
if (P > 0)
P = MIN (n, P);
else
P = MIN (n, MIN (MAX_THREADS, estimate_max_threads ()));
run_test (P);
exit (EXIT_SUCCESS);
}
static void
run_test (size_t P)
{
void *status;
int err;
size_t i, errors;
CLEANUP_FREE struct thread_data *thread_data = NULL;
CLEANUP_FREE pthread_t *threads = NULL;
thread_data = malloc (sizeof (struct thread_data) * P);
threads = malloc (sizeof (pthread_t) * P);
if (thread_data == NULL || threads == NULL)
error (EXIT_FAILURE, errno, "malloc");
/* Start the worker threads. */
for (i = 0; i < P; ++i) {
thread_data[i].thread_num = i;
err = pthread_create (&threads[i], NULL, start_thread, &thread_data[i]);
if (err != 0)
error (EXIT_FAILURE, err, "pthread_create[%zu]\n", i);
}
/* Wait for the threads to exit. */
errors = 0;
for (i = 0; i < P; ++i) {
err = pthread_join (threads[i], &status);
if (err != 0) {
fprintf (stderr, "%s: pthread_join[%zu]: %s\n",
getprogname (), i, strerror (err));
errors++;
}
if (*(int *)status == -1)
errors++;
}
if (errors > 0)
exit (EXIT_FAILURE);
}
/* Worker thread. */
static void *
start_thread (void *thread_data_vp)
{
struct thread_data *thread_data = thread_data_vp;
int quit = 0;
int err;
size_t i;
guestfs_h *g;
unsigned errors = 0;
char id[64];
for (;;) {
CLEANUP_FREE char *log_file = NULL;
CLEANUP_FCLOSE FILE *log_fp = NULL;
/* Take the next process. */
err = pthread_mutex_lock (&mutex);
if (err != 0) {
fprintf (stderr, "%s: pthread_mutex_lock: %s",
getprogname (), strerror (err));
goto error;
}
i = n;
if (i > 0) {
printf ("%zu to go ... \r", n);
fflush (stdout);
n--;
}
else
quit = 1;
err = pthread_mutex_unlock (&mutex);
if (err != 0) {
fprintf (stderr, "%s: pthread_mutex_unlock: %s",
getprogname (), strerror (err));
goto error;
}
if (quit) /* Work finished. */
break;
g = guestfs_create ();
if (g == NULL) {
perror ("guestfs_create");
errors++;
if (!ignore_errors)
goto error;
}
/* Only if using --log, set up a callback. See examples/debug-logging.c */
if (log_template != NULL) {
size_t j, k;
log_file = malloc (log_file_size + 1);
if (log_file == NULL) abort ();
for (j = 0, k = 0; j < strlen (log_template); ++j) {
if (log_template[j] == '%') {
snprintf (&log_file[k], log_file_size - k, "%zu", i);
k += strlen (&log_file[k]);
}
else
log_file[k++] = log_template[j];
}
log_file[k] = '\0';
log_fp = fopen (log_file, "w");
if (log_fp == NULL) {
perror (log_file);
abort ();
}
guestfs_set_event_callback (g, message_callback,
event_bitmask, 0, log_fp);
}
snprintf (id, sizeof id, "%zu", i);
guestfs_set_identifier (g, id);
guestfs_set_trace (g, trace);
guestfs_set_verbose (g, verbose);
if (guestfs_add_drive_ro (g, "/dev/null") == -1) {
errors++;
if (!ignore_errors)
goto error;
}
if (guestfs_launch (g) == -1) {
errors++;
if (!ignore_errors)
goto error;
}
if (guestfs_shutdown (g) == -1) {
errors++;
if (!ignore_errors)
goto error;
}
guestfs_close (g);
}
if (errors > 0) {
fprintf (stderr, "%s: thread %d: %u errors were ignored\n",
getprogname (), thread_data->thread_num, errors);
goto error;
}
thread_data->r = 0;
return &thread_data->r;
error:
thread_data->r = -1;
return &thread_data->r;
}
/* If using --log, this is called to write messages to the log file. */
static void
message_callback (guestfs_h *g, void *opaque,
uint64_t event, int event_handle,
int flags,
const char *buf, size_t buf_len,
const uint64_t *array, size_t array_len)
{
FILE *fp = opaque;
if (buf_len > 0) {
CLEANUP_FREE char *msg = strndup (buf, buf_len);
switch (event) {
case GUESTFS_EVENT_APPLIANCE:
fprintf (fp, "%s", msg);
break;
case GUESTFS_EVENT_LIBRARY:
fprintf (fp, "libguestfs: %s\n", msg);
break;
case GUESTFS_EVENT_WARNING:
fprintf (fp, "libguestfs: warning: %s\n", msg);
break;
case GUESTFS_EVENT_TRACE:
fprintf (fp, "libguestfs: trace: %s\n", msg);
break;
}
fflush (fp);
}
}

View File

@@ -1,37 +0,0 @@
# libguestfs
# Copyright (C) 2011-2019 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.
include $(top_srcdir)/subdir-rules.mk
noinst_PROGRAMS = qemu-speed-test
qemu_speed_test_SOURCES = \
qemu-speed-test.c
qemu_speed_test_CPPFLAGS = \
-I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
-I$(top_srcdir)/common/utils -I$(top_builddir)/common/utils \
-I$(top_srcdir)/lib -I$(top_builddir)/lib \
-I$(top_srcdir)/df
qemu_speed_test_CFLAGS = \
$(WARN_CFLAGS) $(WERROR_CFLAGS)
qemu_speed_test_LDADD = \
$(top_builddir)/common/utils/libutils.la \
$(top_builddir)/lib/libguestfs.la \
$(LIBXML2_LIBS) \
$(LIBVIRT_LIBS) \
$(LTLIBINTL) \
$(top_builddir)/gnulib/lib/libgnu.la

View File

@@ -1,471 +0,0 @@
/* 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 <error.h>
#include <getopt.h>
#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include <sys/time.h>
#include "guestfs.h"
#include "guestfs-utils.h"
#include "getprogname.h"
static void test_virtio_serial (void);
static void test_block_device (void);
/* Which tests are enabled? -- All by default. */
static int virtio_serial_upload = 1;
static int virtio_serial_download = 1;
static int block_device_write = 1;
static int block_device_read = 1;
static int max_time_override = 0;
static void
reset_default_tests (int *flag)
{
if (*flag) {
virtio_serial_upload = 0;
virtio_serial_download = 0;
block_device_write = 0;
block_device_read = 0;
*flag = 0;
}
}
static void __attribute__((noreturn))
usage (int exitcode)
{
fprintf (stderr,
"qemu-speed-test: Test the speed of qemu features.\n"
"\n"
"To run all tests (recommended), do:\n"
" qemu-speed-test\n"
"\n"
"To run only specific tests, do:\n"
" qemu-speed-test --option [--option ...]\n"
"where the test options are:\n"
" --virtio-serial-upload\n"
" --virtio-serial-download\n"
" --block-device-write\n"
" --block-device-read\n"
"\n"
"Other options:\n"
" --help Display help output and exit\n"
" -t <SECS> | --time=<SECS> Set max length of test in seconds\n"
);
exit (exitcode);
}
int
main (int argc, char *argv[])
{
enum { HELP_OPTION = CHAR_MAX + 1 };
static const char options[] = "t:";
static const struct option long_options[] = {
{ "help", 0, 0, HELP_OPTION },
{ "time", 1, 0, 't' },
/* Tests. */
{ "virtio-serial-upload", 0, 0, 0 },
{ "virtio-serial-download", 0, 0, 0 },
{ "block-device-write", 0, 0, 0 },
{ "block-device-read", 0, 0, 0 },
{ 0, 0, 0, 0 }
};
int c, option_index;
int reset_flag = 1;
for (;;) {
c = getopt_long (argc, argv, options, long_options, &option_index);
if (c == -1) break;
switch (c) {
case 0:
/* Options which are long only. */
if (STREQ (long_options[option_index].name, "virtio-serial-upload")) {
reset_default_tests (&reset_flag);
virtio_serial_upload = 1;
}
else if (STREQ (long_options[option_index].name, "virtio-serial-download")) {
reset_default_tests (&reset_flag);
virtio_serial_download = 1;
}
else if (STREQ (long_options[option_index].name, "block-device-write")) {
reset_default_tests (&reset_flag);
block_device_write = 1;
}
else if (STREQ (long_options[option_index].name, "block-device-read")) {
reset_default_tests (&reset_flag);
block_device_read = 1;
}
else {
fprintf (stderr, "%s: unknown long option: %s (%d)\n",
getprogname (), long_options[option_index].name, option_index);
exit (EXIT_FAILURE);
}
break;
case 't':
if (sscanf (optarg, "%d", &max_time_override) != 1 ||
max_time_override < 0) {
fprintf (stderr, "%s: -t: argument is not a positive integer\n",
getprogname ());
exit (EXIT_FAILURE);
}
break;
case HELP_OPTION:
usage (EXIT_SUCCESS);
default:
usage (EXIT_FAILURE);
}
}
if (optind != argc) {
fprintf (stderr, "%s: extra arguments found on the command line\n",
getprogname ());
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;
if (!virtio_serial_upload && !virtio_serial_download)
return;
/* 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)
error (EXIT_FAILURE, errno, "mkstemp: %s", tmpfile);
if (ftruncate (fd, TEST_SERIAL_MAX_SIZE) == -1)
error (EXIT_FAILURE, errno, "ftruncate");
if (close (fd) == -1)
error (EXIT_FAILURE, errno, "close");
g = guestfs_create ();
if (!g)
error (EXIT_FAILURE, errno, "guestfs_create");
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);
if (virtio_serial_upload) {
gettimeofday (&start, NULL);
rate = -1;
operation = "upload";
alarm (max_time_override > 0 ? max_time_override : 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",
getprogname (), 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",
getprogname (),
r, guestfs_last_errno (g));
exit (EXIT_FAILURE);
}
print_rate ("virtio-serial upload rate:", rate);
}
if (virtio_serial_download) {
/* 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 (max_time_override > 0 ? max_time_override : 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",
getprogname (), 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];
const int t =
max_time_override > 0 ? max_time_override : TEST_BLOCK_DEVICE_TIME;
char tbuf[64];
int64_t bytes_written, bytes_read;
if (!block_device_write && !block_device_read)
return;
snprintf (tbuf, sizeof tbuf, "%d", t);
g = guestfs_create ();
if (!g)
error (EXIT_FAILURE, errno, "guestfs_create");
/* Create a fully allocated backing file. Note we are not testing
* the speed of allocation on the host.
*/
fd = mkstemp (tmpfile);
if (fd == -1)
error (EXIT_FAILURE, errno, "mkstemp: %s", tmpfile);
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",
getprogname ());
exit (EXIT_FAILURE);
}
if (block_device_write) {
/* Test write speed. */
argv[0] = devices[0];
argv[1] = "w";
argv[2] = tbuf;
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",
getprogname ());
exit (EXIT_FAILURE);
}
print_rate ("block device writes:", bytes_written / t);
}
if (block_device_read) {
/* Test read speed. */
argv[0] = devices[0];
argv[1] = "r";
argv[2] = tbuf;
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",
getprogname ());
exit (EXIT_FAILURE);
}
print_rate ("block device reads:", bytes_read / t);
}
if (guestfs_shutdown (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
/* Remove temporary file. */
unlink (tmpfile);
}