mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
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:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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
|
||||
|
||||
11
Makefile.am
11
Makefile.am
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
@@ -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 \
|
||||
$<
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) ? : "");
|
||||
}
|
||||
@@ -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
@@ -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_ */
|
||||
@@ -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.
|
||||
@@ -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 \
|
||||
$<
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user