|
|
e76f14 |
From 07954ad0663456014c56ed4db0c1ab3f71846678 Mon Sep 17 00:00:00 2001
|
|
|
e76f14 |
From: "Richard W.M. Jones" <rjones@redhat.com>
|
|
|
e76f14 |
Date: Fri, 18 Mar 2016 18:00:51 +0000
|
|
|
e76f14 |
Subject: [PATCH] tests/qemu: Add program for tracing and analyzing boot times.
|
|
|
e76f14 |
|
|
|
e76f14 |
(cherry picked from commit 0ba59db611fc2620bf47b50c6d0a71242a0800d7)
|
|
|
e76f14 |
---
|
|
|
e76f14 |
.gitignore | 1 +
|
|
|
e76f14 |
appliance/init | 5 +-
|
|
|
e76f14 |
tests/qemu/Makefile.am | 26 +-
|
|
|
e76f14 |
tests/qemu/boot-analysis-timeline.c | 468 ++++++++++++++++
|
|
|
e76f14 |
tests/qemu/boot-analysis.c | 1030 +++++++++++++++++++++++++++++++++++
|
|
|
e76f14 |
tests/qemu/boot-analysis.h | 94 ++++
|
|
|
e76f14 |
6 files changed, 1620 insertions(+), 4 deletions(-)
|
|
|
e76f14 |
create mode 100644 tests/qemu/boot-analysis-timeline.c
|
|
|
e76f14 |
create mode 100644 tests/qemu/boot-analysis.c
|
|
|
e76f14 |
create mode 100644 tests/qemu/boot-analysis.h
|
|
|
e76f14 |
|
|
|
e76f14 |
diff --git a/.gitignore b/.gitignore
|
|
|
e76f14 |
index 6436eeb..9c45df7 100644
|
|
|
e76f14 |
--- a/.gitignore
|
|
|
e76f14 |
+++ b/.gitignore
|
|
|
e76f14 |
@@ -509,6 +509,7 @@ Makefile.in
|
|
|
e76f14 |
/tests/mountable/test-internal-parse-mountable
|
|
|
e76f14 |
/tests/parallel/test-parallel
|
|
|
e76f14 |
/tests/protocol/test-error-messages
|
|
|
e76f14 |
+/tests/qemu/boot-analysis
|
|
|
e76f14 |
/tests/qemu/qemu-boot
|
|
|
e76f14 |
/tests/qemu/qemu-speed-test
|
|
|
e76f14 |
/tests/regressions/rhbz501893
|
|
|
e76f14 |
diff --git a/appliance/init b/appliance/init
|
|
|
e76f14 |
index 4407bbb..4a04bce 100755
|
|
|
e76f14 |
--- a/appliance/init
|
|
|
e76f14 |
+++ b/appliance/init
|
|
|
e76f14 |
@@ -54,6 +54,9 @@ fi
|
|
|
e76f14 |
if [[ $cmdline == *guestfs_noreboot=1* ]]; then
|
|
|
e76f14 |
guestfs_noreboot=1
|
|
|
e76f14 |
fi
|
|
|
e76f14 |
+if [[ $cmdline == *guestfs_boot_analysis=1* ]]; then
|
|
|
e76f14 |
+ guestfs_boot_analysis=1
|
|
|
e76f14 |
+fi
|
|
|
e76f14 |
|
|
|
e76f14 |
# Mount the other special filesystems.
|
|
|
e76f14 |
if [ ! -d /sys ]; then rm -f /sys; fi
|
|
|
e76f14 |
@@ -133,7 +136,7 @@ lvm vgchange -aay --sysinit
|
|
|
e76f14 |
ldmtool create all
|
|
|
e76f14 |
|
|
|
e76f14 |
# These are useful when debugging.
|
|
|
e76f14 |
-if test "$guestfs_verbose" = 1; then
|
|
|
e76f14 |
+if test "$guestfs_verbose" = 1 && test "$guestfs_boot_analysis" != 1; then
|
|
|
e76f14 |
uname -a
|
|
|
e76f14 |
ls -lR /dev
|
|
|
e76f14 |
cat /proc/mounts
|
|
|
e76f14 |
diff --git a/tests/qemu/Makefile.am b/tests/qemu/Makefile.am
|
|
|
e76f14 |
index fe45d88..bea1c85 100644
|
|
|
e76f14 |
--- a/tests/qemu/Makefile.am
|
|
|
e76f14 |
+++ b/tests/qemu/Makefile.am
|
|
|
e76f14 |
@@ -33,10 +33,10 @@ EXTRA_DIST = \
|
|
|
e76f14 |
qemu-boot.c \
|
|
|
e76f14 |
qemu-speed-test.c
|
|
|
e76f14 |
|
|
|
e76f14 |
-# qemu-boot & qemu-speed-test are built but not run by default as they
|
|
|
e76f14 |
-# are mainly qemu & kernel diagnostic tools.
|
|
|
e76f14 |
+# qemu-boot, qemu-speed-test and boot-analysis are built but not run
|
|
|
e76f14 |
+# by default as they are mainly qemu & kernel diagnostic tools.
|
|
|
e76f14 |
|
|
|
e76f14 |
-check_PROGRAMS = qemu-boot qemu-speed-test
|
|
|
e76f14 |
+check_PROGRAMS = qemu-boot qemu-speed-test boot-analysis
|
|
|
e76f14 |
|
|
|
e76f14 |
qemu_boot_SOURCES = \
|
|
|
e76f14 |
../../df/estimate-max-threads.c \
|
|
|
e76f14 |
@@ -73,6 +73,26 @@ qemu_speed_test_LDADD = \
|
|
|
e76f14 |
$(LTLIBINTL) \
|
|
|
e76f14 |
$(top_builddir)/gnulib/lib/libgnu.la
|
|
|
e76f14 |
|
|
|
e76f14 |
+boot_analysis_SOURCES = \
|
|
|
e76f14 |
+ boot-analysis.c \
|
|
|
e76f14 |
+ boot-analysis.h \
|
|
|
e76f14 |
+ boot-analysis-timeline.c
|
|
|
e76f14 |
+boot_analysis_CPPFLAGS = \
|
|
|
e76f14 |
+ -I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \
|
|
|
e76f14 |
+ -I$(top_srcdir)/src -I$(top_builddir)/src
|
|
|
e76f14 |
+boot_analysis_CFLAGS = \
|
|
|
e76f14 |
+ $(WARN_CFLAGS) $(WERROR_CFLAGS) \
|
|
|
e76f14 |
+ $(PCRE_CFLAGS)
|
|
|
e76f14 |
+boot_analysis_LDADD = \
|
|
|
e76f14 |
+ $(top_builddir)/src/libutils.la \
|
|
|
e76f14 |
+ $(top_builddir)/src/libguestfs.la \
|
|
|
e76f14 |
+ $(PCRE_LIBS) \
|
|
|
e76f14 |
+ $(LIBXML2_LIBS) \
|
|
|
e76f14 |
+ $(LIBVIRT_LIBS) \
|
|
|
e76f14 |
+ $(LTLIBINTL) \
|
|
|
e76f14 |
+ $(top_builddir)/gnulib/lib/libgnu.la \
|
|
|
e76f14 |
+ -lm
|
|
|
e76f14 |
+
|
|
|
e76f14 |
# Don't run these tests in parallel, since they are designed to check
|
|
|
e76f14 |
# the integrity of qemu.
|
|
|
e76f14 |
.NOTPARALLEL:
|
|
|
e76f14 |
diff --git a/tests/qemu/boot-analysis-timeline.c b/tests/qemu/boot-analysis-timeline.c
|
|
|
e76f14 |
new file mode 100644
|
|
|
e76f14 |
index 0000000..8753805
|
|
|
e76f14 |
--- /dev/null
|
|
|
e76f14 |
+++ b/tests/qemu/boot-analysis-timeline.c
|
|
|
e76f14 |
@@ -0,0 +1,468 @@
|
|
|
e76f14 |
+/* libguestfs
|
|
|
e76f14 |
+ * Copyright (C) 2016 Red Hat Inc.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
e76f14 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
e76f14 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
e76f14 |
+ * (at your option) any later version.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
e76f14 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
e76f14 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
e76f14 |
+ * GNU General Public License for more details.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
e76f14 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
e76f14 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include <config.h>
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include <stdio.h>
|
|
|
e76f14 |
+#include <stdlib.h>
|
|
|
e76f14 |
+#include <stdint.h>
|
|
|
e76f14 |
+#include <inttypes.h>
|
|
|
e76f14 |
+#include <string.h>
|
|
|
e76f14 |
+#include <unistd.h>
|
|
|
e76f14 |
+#include <error.h>
|
|
|
e76f14 |
+#include <errno.h>
|
|
|
e76f14 |
+#include <assert.h>
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include <pcre.h>
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include "ignore-value.h"
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include "guestfs.h"
|
|
|
e76f14 |
+#include "guestfs-internal-frontend.h"
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include "boot-analysis.h"
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+COMPILE_REGEXP(re_initcall_calling_module,
|
|
|
e76f14 |
+ "calling ([_A-Za-z0-9]+)\\+.*\\[([_A-Za-z0-9]+)]", 0)
|
|
|
e76f14 |
+COMPILE_REGEXP(re_initcall_calling,
|
|
|
e76f14 |
+ "calling ([_A-Za-z0-9]+)\\+", 0)
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void construct_initcall_timeline (void);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* "supermin: internal insmod xx.ko" -> "insmod xx.ko" */
|
|
|
e76f14 |
+static char *
|
|
|
e76f14 |
+translate_supermin_insmod_message (const char *message)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ char *ret;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ assert (STRPREFIX (message, "supermin: internal "));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ ret = strdup (message + strlen ("supermin: internal "));
|
|
|
e76f14 |
+ if (ret == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strdup");
|
|
|
e76f14 |
+ return ret;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Analyze significant events from the events array, to form a
|
|
|
e76f14 |
+ * timeline of activities.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+void
|
|
|
e76f14 |
+construct_timeline (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i, j, k;
|
|
|
e76f14 |
+ struct pass_data *data;
|
|
|
e76f14 |
+ struct activity *activity;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ data = &pass_data[i];
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find an activity, by matching an event with the condition
|
|
|
e76f14 |
+ * `begin_cond' through to the second event `end_cond'. Create an
|
|
|
e76f14 |
+ * activity object in the timeline from the result.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+#define FIND(name, flags, begin_cond, end_cond) \
|
|
|
e76f14 |
+ do { \
|
|
|
e76f14 |
+ activity = NULL; \
|
|
|
e76f14 |
+ for (j = 0; j < data->nr_events; ++j) { \
|
|
|
e76f14 |
+ if (begin_cond) { \
|
|
|
e76f14 |
+ for (k = j+1; k < data->nr_events; ++k) { \
|
|
|
e76f14 |
+ if (end_cond) { \
|
|
|
e76f14 |
+ if (i == 0) \
|
|
|
e76f14 |
+ activity = add_activity (name, flags); \
|
|
|
e76f14 |
+ else \
|
|
|
e76f14 |
+ activity = find_activity (name); \
|
|
|
e76f14 |
+ break; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ break; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ if (activity) { \
|
|
|
e76f14 |
+ activity->start_event[i] = j; \
|
|
|
e76f14 |
+ activity->end_event[i] = k; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ else \
|
|
|
e76f14 |
+ error (EXIT_FAILURE, 0, "could not find activity '%s' in pass '%zu'", \
|
|
|
e76f14 |
+ name, i); \
|
|
|
e76f14 |
+ } while (0)
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Same as FIND() macro, but if no matching events are found,
|
|
|
e76f14 |
+ * ignore it.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+#define FIND_OPTIONAL(name, flags, begin_cond, end_cond) \
|
|
|
e76f14 |
+ do { \
|
|
|
e76f14 |
+ activity = NULL; \
|
|
|
e76f14 |
+ for (j = 0; j < data->nr_events; ++j) { \
|
|
|
e76f14 |
+ if (begin_cond) { \
|
|
|
e76f14 |
+ for (k = j+1; k < data->nr_events; ++k) { \
|
|
|
e76f14 |
+ if (end_cond) { \
|
|
|
e76f14 |
+ if (i == 0) \
|
|
|
e76f14 |
+ activity = add_activity (name, flags); \
|
|
|
e76f14 |
+ else \
|
|
|
e76f14 |
+ activity = find_activity (name); \
|
|
|
e76f14 |
+ break; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ break; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ if (activity) { \
|
|
|
e76f14 |
+ activity->start_event[i] = j; \
|
|
|
e76f14 |
+ activity->end_event[i] = k; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ } while (0)
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find multiple entries, where we check for:
|
|
|
e76f14 |
+ * next_cond
|
|
|
e76f14 |
+ * next_cond
|
|
|
e76f14 |
+ * next_cond
|
|
|
e76f14 |
+ * end_cond
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+#define FIND_MULTIPLE(debug_name, flags, next_cond, end_cond, translate_message) \
|
|
|
e76f14 |
+ do { \
|
|
|
e76f14 |
+ activity = NULL; \
|
|
|
e76f14 |
+ for (j = 0; j < data->nr_events; ++j) { \
|
|
|
e76f14 |
+ if (next_cond) { \
|
|
|
e76f14 |
+ CLEANUP_FREE char *message = translate_message (data->events[j].message); \
|
|
|
e76f14 |
+ if (activity) \
|
|
|
e76f14 |
+ activity->end_event[i] = j; \
|
|
|
e76f14 |
+ if (i == 0) \
|
|
|
e76f14 |
+ activity = add_activity (message, flags); \
|
|
|
e76f14 |
+ else \
|
|
|
e76f14 |
+ activity = find_activity (message); \
|
|
|
e76f14 |
+ activity->start_event[i] = j; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ else if (end_cond) \
|
|
|
e76f14 |
+ break; \
|
|
|
e76f14 |
+ } \
|
|
|
e76f14 |
+ if (j < data->nr_events && activity) \
|
|
|
e76f14 |
+ activity->end_event[i] = j; \
|
|
|
e76f14 |
+ else \
|
|
|
e76f14 |
+ error (EXIT_FAILURE, 0, "could not find activity '%s' in pass '%zu'", \
|
|
|
e76f14 |
+ debug_name, i); \
|
|
|
e76f14 |
+ } while (0)
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Add one activity which is going to cover the whole process
|
|
|
e76f14 |
+ * from launch to close. The launch event is always event 0.
|
|
|
e76f14 |
+ * NB: This activity must be called "run" (see below).
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ FIND ("run", LONG_ACTIVITY,
|
|
|
e76f14 |
+ j == 0, data->events[k].source == GUESTFS_EVENT_CLOSE);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find where we invoke supermin --build. This should be a null
|
|
|
e76f14 |
+ * operation, but it still takes time to run the external command.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ FIND ("supermin:build", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_LIBRARY &&
|
|
|
e76f14 |
+ strstr (data->events[j].message,
|
|
|
e76f14 |
+ "begin building supermin appliance"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_LIBRARY &&
|
|
|
e76f14 |
+ strstr (data->events[k].message,
|
|
|
e76f14 |
+ "finished building supermin appliance"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find where we invoke qemu to test features. */
|
|
|
e76f14 |
+ FIND ("qemu:feature-detect", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_LIBRARY &&
|
|
|
e76f14 |
+ strstr (data->events[j].message,
|
|
|
e76f14 |
+ "begin testing qemu features"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_LIBRARY &&
|
|
|
e76f14 |
+ strstr (data->events[k].message,
|
|
|
e76f14 |
+ "finished testing qemu features"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find where we run qemu. */
|
|
|
e76f14 |
+ FIND ("qemu", LONG_ACTIVITY,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "-nodefconfig"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_CLOSE);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#define SGABIOS_STRING "\033[1;256r\033[256;256H\033[6n"
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* From starting qemu up to entering the BIOS is the qemu overhead. */
|
|
|
e76f14 |
+ FIND ("qemu:overhead", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "-nodefconfig"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, SGABIOS_STRING));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* From entering the BIOS to starting the kernel is the BIOS overhead. */
|
|
|
e76f14 |
+ FIND_OPTIONAL ("bios:overhead", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, SGABIOS_STRING),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "Probing EDD"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* SGABIOS (option ROM). */
|
|
|
e76f14 |
+ FIND_OPTIONAL ("sgabios", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, SGABIOS_STRING),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "SeaBIOS (version"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* SeaBIOS. */
|
|
|
e76f14 |
+ FIND ("seabios", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "SeaBIOS (version"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "Probing EDD"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* SeaBIOS - only available when using debug messages. */
|
|
|
e76f14 |
+ FIND_OPTIONAL ("seabios:pci-probe", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "Searching bootorder for: /pci@"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "Scan for option roms"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find where we run the guest kernel. */
|
|
|
e76f14 |
+ FIND ("kernel", LONG_ACTIVITY,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "Probing EDD"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_CLOSE);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Kernel startup to userspace. */
|
|
|
e76f14 |
+ FIND ("kernel:overhead", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "Probing EDD"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "supermin:") &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "starting up"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* The time taken to get into start_kernel function. */
|
|
|
e76f14 |
+ FIND ("kernel:entry", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "Probing EDD"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "Linux version"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Alternatives patching instructions (XXX not very accurate we
|
|
|
e76f14 |
+ * really need some debug messages inserted into the code).
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ FIND ("kernel:alternatives", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "Last level dTLB entries"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "Freeing SMP alternatives"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* ftrace patching instructions. */
|
|
|
e76f14 |
+ FIND ("kernel:ftrace", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "ftrace: allocating"),
|
|
|
e76f14 |
+ 1);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find where we run supermin mini-initrd. */
|
|
|
e76f14 |
+ FIND ("supermin:mini-initrd", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "supermin:") &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "starting up"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "supermin: chroot"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Loading kernel modules from supermin initrd. */
|
|
|
e76f14 |
+ FIND_MULTIPLE
|
|
|
e76f14 |
+ ("supermin insmod", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "supermin: internal insmod"),
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "supermin: picked"),
|
|
|
e76f14 |
+ translate_supermin_insmod_message);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find where we run the /init script. */
|
|
|
e76f14 |
+ FIND ("/init", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "supermin: chroot"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "guestfsd --verbose"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Everything from the chroot to the first echo in the /init
|
|
|
e76f14 |
+ * script counts as bash overhead.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ FIND ("bash:overhead", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "supermin: chroot"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "Starting /init script"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: Mount special filesystems. */
|
|
|
e76f14 |
+ FIND ("/init:mount-special", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "*guestfs_boot_analysis=1*"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "kmod static-nodes"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: Run kmod static-nodes */
|
|
|
e76f14 |
+ FIND ("/init:kmod-static-nodes", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "kmod static-nodes"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "systemd-tmpfiles"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: systemd-tmpfiles. */
|
|
|
e76f14 |
+ FIND ("/init:systemd-tmpfiles", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "systemd-tmpfiles"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "udev"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: start udevd. */
|
|
|
e76f14 |
+ FIND ("/init:udev-overhead", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "udevd --daemon"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "nullglob"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: set up network. */
|
|
|
e76f14 |
+ FIND ("/init:network-overhead", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "+ ip addr"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "+ test"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: probe MD arrays. */
|
|
|
e76f14 |
+ FIND ("/init:md-probe", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "+ mdadm"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "+ modprobe dm_mod"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: probe DM/LVM. */
|
|
|
e76f14 |
+ FIND ("/init:lvm-probe", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "+ modprobe dm_mod"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "+ ldmtool"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* /init: probe Windows dynamic disks. */
|
|
|
e76f14 |
+ FIND ("/init:windows-dynamic-disks-probe", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "+ ldmtool"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "+ test"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find where we run guestfsd. */
|
|
|
e76f14 |
+ FIND ("guestfsd", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[j].message, "guestfsd --verbose"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, "fsync /dev/sda"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Shutdown process. */
|
|
|
e76f14 |
+ FIND ("shutdown", 0,
|
|
|
e76f14 |
+ data->events[j].source == GUESTFS_EVENT_TRACE &&
|
|
|
e76f14 |
+ STREQ (data->events[j].message, "close"),
|
|
|
e76f14 |
+ data->events[k].source == GUESTFS_EVENT_CLOSE);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ construct_initcall_timeline ();
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Handling of initcall is so peculiar that we hide it in a separate
|
|
|
e76f14 |
+ * function from the rest.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+construct_initcall_timeline (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i, j, k;
|
|
|
e76f14 |
+ struct pass_data *data;
|
|
|
e76f14 |
+ struct activity *activity;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ data = &pass_data[i];
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Each kernel initcall is bracketed by:
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * calling ehci_hcd_init+0x0/0xc1 @ 1"
|
|
|
e76f14 |
+ * initcall ehci_hcd_init+0x0/0xc1 returned 0 after 420 usecs"
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * For initcall functions in modules:
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * calling virtio_mmio_init+0x0/0x1000 [virtio_mmio] @ 1"
|
|
|
e76f14 |
+ * initcall virtio_mmio_init+0x0/0x1000 [virtio_mmio] returned 0 after 14 usecs"
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * Initcall functions can be nested, and do not have unique names.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ for (j = 0; j < data->nr_events; ++j) {
|
|
|
e76f14 |
+ int vec[30], r;
|
|
|
e76f14 |
+ const char *message = data->events[j].message;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (data->events[j].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ ((r = pcre_exec (re_initcall_calling_module, NULL,
|
|
|
e76f14 |
+ message, strlen (message),
|
|
|
e76f14 |
+ 0, 0, vec, sizeof vec / sizeof vec[0])) >= 1 ||
|
|
|
e76f14 |
+ (r = pcre_exec (re_initcall_calling, NULL,
|
|
|
e76f14 |
+ message, strlen (message),
|
|
|
e76f14 |
+ 0, 0, vec, sizeof vec / sizeof vec[0])) >= 1)) {
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ CLEANUP_FREE char *fn_name = NULL, *module_name = NULL;
|
|
|
e76f14 |
+ if (r >= 2) /* because pcre_exec returns 1 + number of captures */
|
|
|
e76f14 |
+ fn_name = strndup (message + vec[2], vec[3]-vec[2]);
|
|
|
e76f14 |
+ if (r >= 3)
|
|
|
e76f14 |
+ module_name = strndup (message + vec[4], vec[5]-vec[4]);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ CLEANUP_FREE char *fullname;
|
|
|
e76f14 |
+ if (asprintf (&fullname, "%s.%s",
|
|
|
e76f14 |
+ module_name ? module_name : "kernel", fn_name) == -1)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "asprintf");
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ CLEANUP_FREE char *initcall_match;
|
|
|
e76f14 |
+ if (asprintf (&initcall_match, "initcall %s", fn_name) == -1)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "asprintf");
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Get a unique name for this activity. Unfortunately
|
|
|
e76f14 |
+ * kernel initcall function names are not unique!
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ CLEANUP_FREE char *activity_name;
|
|
|
e76f14 |
+ if (asprintf (&activity_name, "initcall %s", fullname) == -1)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "asprintf");
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (i == 0) {
|
|
|
e76f14 |
+ int n = 1;
|
|
|
e76f14 |
+ while (activity_exists (activity_name)) {
|
|
|
e76f14 |
+ free (activity_name);
|
|
|
e76f14 |
+ if (asprintf (&activity_name, "initcall %s:%d", fullname, n) == -1)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "asprintf");
|
|
|
e76f14 |
+ n++;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ else {
|
|
|
e76f14 |
+ int n = 1;
|
|
|
e76f14 |
+ while (!activity_exists_with_no_data (activity_name, i)) {
|
|
|
e76f14 |
+ free (activity_name);
|
|
|
e76f14 |
+ if (asprintf (&activity_name, "initcall %s:%d", fullname, n) == -1)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "asprintf");
|
|
|
e76f14 |
+ n++;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Find the matching end event. It might be some time later,
|
|
|
e76f14 |
+ * since it appears initcalls can be nested.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ for (k = j+1; k < data->nr_events; ++k) {
|
|
|
e76f14 |
+ if (data->events[k].source == GUESTFS_EVENT_APPLIANCE &&
|
|
|
e76f14 |
+ strstr (data->events[k].message, initcall_match)) {
|
|
|
e76f14 |
+ if (i == 0)
|
|
|
e76f14 |
+ activity = add_activity (activity_name, 0);
|
|
|
e76f14 |
+ else
|
|
|
e76f14 |
+ activity = find_activity (activity_name);
|
|
|
e76f14 |
+ activity->start_event[i] = j;
|
|
|
e76f14 |
+ activity->end_event[i] = k;
|
|
|
e76f14 |
+ break;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
diff --git a/tests/qemu/boot-analysis.c b/tests/qemu/boot-analysis.c
|
|
|
e76f14 |
new file mode 100644
|
|
|
e76f14 |
index 0000000..71b265a
|
|
|
e76f14 |
--- /dev/null
|
|
|
e76f14 |
+++ b/tests/qemu/boot-analysis.c
|
|
|
e76f14 |
@@ -0,0 +1,1030 @@
|
|
|
e76f14 |
+/* libguestfs
|
|
|
e76f14 |
+ * Copyright (C) 2016 Red Hat Inc.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
e76f14 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
e76f14 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
e76f14 |
+ * (at your option) any later version.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
e76f14 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
e76f14 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
e76f14 |
+ * GNU General Public License for more details.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
e76f14 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
e76f14 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Trace and analyze the appliance boot process to find out which
|
|
|
e76f14 |
+ * steps are taking the most time. It is not part of the standard
|
|
|
e76f14 |
+ * tests.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * This needs to be run on a quiet machine, so that other processes
|
|
|
e76f14 |
+ * disturb the timing as little as possible. The program is
|
|
|
e76f14 |
+ * completely safe to run at any time. It doesn't read or write any
|
|
|
e76f14 |
+ * external files, and it doesn't require root.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * You can run it from the build directory like this:
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * make
|
|
|
e76f14 |
+ * make -C tests/qemu boot-analysis
|
|
|
e76f14 |
+ * ./run tests/qemu/boot-analysis
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * The way it works is roughly like this:
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * We create a libguestfs handle and register callback handlers so we
|
|
|
e76f14 |
+ * can see appliance messages, trace events and so on.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * We then launch the handle and shut it down as quickly as possible.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * While the handle is running, events (seen by the callback handlers)
|
|
|
e76f14 |
+ * are written verbatim into an in-memory buffer, with timestamps.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * Afterwards we analyze the result using regular expressions to try
|
|
|
e76f14 |
+ * to identify a "timeline" for the handle (eg. at what time did the
|
|
|
e76f14 |
+ * BIOS hand control to the kernel). This analysis is done in
|
|
|
e76f14 |
+ * 'boot-analysis-timeline.c'.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * The whole process is repeated across a few runs, and the final
|
|
|
e76f14 |
+ * timeline (including statistical analysis of the variation between
|
|
|
e76f14 |
+ * runs) gets printed.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * The program is very sensitive to the specific messages printed by
|
|
|
e76f14 |
+ * BIOS/kernel/supermin/userspace, so it won't work on non-x86, and it
|
|
|
e76f14 |
+ * will require periodic adjustment of the regular expressions in
|
|
|
e76f14 |
+ * order to keep things up to date.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include <config.h>
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include <stdio.h>
|
|
|
e76f14 |
+#include <stdlib.h>
|
|
|
e76f14 |
+#include <stdint.h>
|
|
|
e76f14 |
+#include <inttypes.h>
|
|
|
e76f14 |
+#include <string.h>
|
|
|
e76f14 |
+#include <unistd.h>
|
|
|
e76f14 |
+#include <getopt.h>
|
|
|
e76f14 |
+#include <limits.h>
|
|
|
e76f14 |
+#include <time.h>
|
|
|
e76f14 |
+#include <errno.h>
|
|
|
e76f14 |
+#include <error.h>
|
|
|
e76f14 |
+#include <ctype.h>
|
|
|
e76f14 |
+#include <assert.h>
|
|
|
e76f14 |
+#include <math.h>
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include "ignore-value.h"
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include "guestfs.h"
|
|
|
e76f14 |
+#include "guestfs-internal-frontend.h"
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#include "boot-analysis.h"
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Activities taking longer than this % of the total time, except
|
|
|
e76f14 |
+ * those flagged as LONG_ACTIVITY, are highlighted in red.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+#define WARNING_THRESHOLD 1.0
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+struct pass_data pass_data[NR_TEST_PASSES];
|
|
|
e76f14 |
+size_t nr_activities;
|
|
|
e76f14 |
+struct activity *activities;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static int force_colour = 0;
|
|
|
e76f14 |
+static int verbose = 0;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void run_test (void);
|
|
|
e76f14 |
+static void get_time (struct timespec *ts);
|
|
|
e76f14 |
+static int64_t timespec_diff (const struct timespec *x, const struct timespec *y);
|
|
|
e76f14 |
+static struct event *add_event (struct pass_data *, uint64_t source);
|
|
|
e76f14 |
+static guestfs_h *create_handle (void);
|
|
|
e76f14 |
+static void set_up_event_handlers (guestfs_h *g, size_t pass);
|
|
|
e76f14 |
+static void add_drive (guestfs_h *g);
|
|
|
e76f14 |
+static void check_pass_data (void);
|
|
|
e76f14 |
+static void dump_pass_data (void);
|
|
|
e76f14 |
+static void analyze_timeline (void);
|
|
|
e76f14 |
+static void dump_timeline (void);
|
|
|
e76f14 |
+static void print_info (void);
|
|
|
e76f14 |
+static void print_analysis (void);
|
|
|
e76f14 |
+static void print_longest_to_shortest (void);
|
|
|
e76f14 |
+static void free_pass_data (void);
|
|
|
e76f14 |
+static void free_final_timeline (void);
|
|
|
e76f14 |
+static void ansi_green (void);
|
|
|
e76f14 |
+static void ansi_red (void);
|
|
|
e76f14 |
+static void ansi_blue (void);
|
|
|
e76f14 |
+static void ansi_magenta (void);
|
|
|
e76f14 |
+static void ansi_restore (void);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+usage (int exitcode)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ fprintf (stderr,
|
|
|
e76f14 |
+ "boot-analysis: Trace and analyze the appliance boot process.\n"
|
|
|
e76f14 |
+ "Usage:\n"
|
|
|
e76f14 |
+ " boot-analysis [--options]\n"
|
|
|
e76f14 |
+ "Options:\n"
|
|
|
e76f14 |
+ " --help Display this usage text and exit.\n"
|
|
|
e76f14 |
+ " --colour Output colours, even if not a terminal.\n"
|
|
|
e76f14 |
+ " -v|--verbose Verbose output, useful for debugging.\n");
|
|
|
e76f14 |
+ exit (exitcode);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+int
|
|
|
e76f14 |
+main (int argc, char *argv[])
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ enum { HELP_OPTION = CHAR_MAX + 1 };
|
|
|
e76f14 |
+ static const char *options = "v";
|
|
|
e76f14 |
+ static const struct option long_options[] = {
|
|
|
e76f14 |
+ { "help", 0, 0, HELP_OPTION },
|
|
|
e76f14 |
+ { "color", 0, 0, 0 },
|
|
|
e76f14 |
+ { "colour", 0, 0, 0 },
|
|
|
e76f14 |
+ { "verbose", 0, 0, 'v' },
|
|
|
e76f14 |
+ { 0, 0, 0, 0 }
|
|
|
e76f14 |
+ };
|
|
|
e76f14 |
+ int c, option_index;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (;;) {
|
|
|
e76f14 |
+ c = getopt_long (argc, argv, options, long_options, &option_index);
|
|
|
e76f14 |
+ if (c == -1) break;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ switch (c) {
|
|
|
e76f14 |
+ case 0: /* Options which are long only. */
|
|
|
e76f14 |
+ if (STREQ (long_options[option_index].name, "color") ||
|
|
|
e76f14 |
+ STREQ (long_options[option_index].name, "colour")) {
|
|
|
e76f14 |
+ force_colour = 1;
|
|
|
e76f14 |
+ break;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ fprintf (stderr, "%s: unknown long option: %s (%d)\n",
|
|
|
e76f14 |
+ guestfs_int_program_name, long_options[option_index].name, option_index);
|
|
|
e76f14 |
+ exit (EXIT_FAILURE);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ case 'v':
|
|
|
e76f14 |
+ verbose = 1;
|
|
|
e76f14 |
+ break;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ case HELP_OPTION:
|
|
|
e76f14 |
+ usage (EXIT_SUCCESS);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ default:
|
|
|
e76f14 |
+ usage (EXIT_FAILURE);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (STRNEQ (host_cpu, "x86_64"))
|
|
|
e76f14 |
+ fprintf (stderr, "WARNING: host_cpu != x86_64: This program may not work or give bogus results.\n");
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ run_test ();
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+run_test (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ guestfs_h *g;
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf ("Warming up the libguestfs cache ...\n");
|
|
|
e76f14 |
+ for (i = 0; i < NR_WARMUP_PASSES; ++i) {
|
|
|
e76f14 |
+ g = create_handle ();
|
|
|
e76f14 |
+ add_drive (g);
|
|
|
e76f14 |
+ if (guestfs_launch (g) == -1)
|
|
|
e76f14 |
+ exit (EXIT_FAILURE);
|
|
|
e76f14 |
+ guestfs_close (g);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf ("Running the tests in %d passes ...\n", NR_TEST_PASSES);
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ g = create_handle ();
|
|
|
e76f14 |
+ set_up_event_handlers (g, i);
|
|
|
e76f14 |
+ add_drive (g);
|
|
|
e76f14 |
+ if (guestfs_launch (g) == -1)
|
|
|
e76f14 |
+ exit (EXIT_FAILURE);
|
|
|
e76f14 |
+ guestfs_close (g);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf (" pass %zu: %zu events collected in %" PRIi64 " ns\n",
|
|
|
e76f14 |
+ i+1, pass_data[i].nr_events, pass_data[i].elapsed_ns);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (verbose)
|
|
|
e76f14 |
+ dump_pass_data ();
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf ("Analyzing the results ...\n");
|
|
|
e76f14 |
+ check_pass_data ();
|
|
|
e76f14 |
+ construct_timeline ();
|
|
|
e76f14 |
+ analyze_timeline ();
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (verbose)
|
|
|
e76f14 |
+ dump_timeline ();
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf ("\n");
|
|
|
e76f14 |
+ print_info ();
|
|
|
e76f14 |
+ printf ("\n");
|
|
|
e76f14 |
+ print_analysis ();
|
|
|
e76f14 |
+ printf ("\n");
|
|
|
e76f14 |
+ printf ("Longest activities:\n");
|
|
|
e76f14 |
+ printf ("\n");
|
|
|
e76f14 |
+ print_longest_to_shortest ();
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ free_pass_data ();
|
|
|
e76f14 |
+ free_final_timeline ();
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+get_time (struct timespec *ts)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (clock_gettime (CLOCK_REALTIME, ts) == -1)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "clock_gettime: CLOCK_REALTIME");
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Computes Y - X, returning nanoseconds. */
|
|
|
e76f14 |
+static int64_t
|
|
|
e76f14 |
+timespec_diff (const struct timespec *x, const struct timespec *y)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ int64_t nsec;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ nsec = (y->tv_sec - x->tv_sec) * UINT64_C(1000000000);
|
|
|
e76f14 |
+ nsec += y->tv_nsec - x->tv_nsec;
|
|
|
e76f14 |
+ return nsec;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static struct event *
|
|
|
e76f14 |
+add_event (struct pass_data *data, uint64_t source)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct event *ret;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ data->nr_events++;
|
|
|
e76f14 |
+ data->events = realloc (data->events,
|
|
|
e76f14 |
+ sizeof (struct event) * data->nr_events);
|
|
|
e76f14 |
+ if (data->events == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "realloc");
|
|
|
e76f14 |
+ ret = &data->events[data->nr_events-1];
|
|
|
e76f14 |
+ get_time (&ret->t);
|
|
|
e76f14 |
+ ret->source = source;
|
|
|
e76f14 |
+ ret->message = NULL;
|
|
|
e76f14 |
+ return ret;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Common function to create the handle and set various defaults. */
|
|
|
e76f14 |
+static guestfs_h *
|
|
|
e76f14 |
+create_handle (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ guestfs_h *g;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ g = guestfs_create ();
|
|
|
e76f14 |
+ if (!g) error (EXIT_FAILURE, errno, "guestfs_create");
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* We always run these tests using LIBGUESTFS_BACKEND=direct. It
|
|
|
e76f14 |
+ * may be in future we need to test libvirt as well, in case
|
|
|
e76f14 |
+ * performance issues are suspected there, but so far libvirt has
|
|
|
e76f14 |
+ * not been a bottleneck.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ if (guestfs_set_backend (g, "direct") == -1)
|
|
|
e76f14 |
+ exit (EXIT_FAILURE);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* This changes some details in appliance/init and enables a
|
|
|
e76f14 |
+ * detailed trace of calls to initcall functions in the kernel.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ if (guestfs_set_append (g,
|
|
|
e76f14 |
+ "guestfs_boot_analysis=1 "
|
|
|
e76f14 |
+ "ignore_loglevel initcall_debug") == -1)
|
|
|
e76f14 |
+ exit (EXIT_FAILURE);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ return g;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Common function to add the /dev/null drive. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+add_drive (guestfs_h *g)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (guestfs_add_drive_opts (g, "/dev/null",
|
|
|
e76f14 |
+ GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
|
|
|
e76f14 |
+ GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
|
|
|
e76f14 |
+ -1) == -1)
|
|
|
e76f14 |
+ exit (EXIT_FAILURE);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Called when the handle is closed. Perform any cleanups required in
|
|
|
e76f14 |
+ * the pass_data here.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+close_callback (guestfs_h *g, void *datavp, uint64_t source,
|
|
|
e76f14 |
+ int eh, int flags,
|
|
|
e76f14 |
+ const char *buf, size_t buf_len,
|
|
|
e76f14 |
+ const uint64_t *array, size_t array_len)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct pass_data *data = datavp;
|
|
|
e76f14 |
+ struct event *event;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (!data->seen_launch)
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ event = add_event (data, source);
|
|
|
e76f14 |
+ event->message = strdup ("close callback");
|
|
|
e76f14 |
+ if (event->message == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strdup");
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ get_time (&data->end_t);
|
|
|
e76f14 |
+ data->elapsed_ns = timespec_diff (&data->start_t, &data->end_t);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Called when the qemu subprocess exits.
|
|
|
e76f14 |
+ * XXX This is never called - why?
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+subprocess_quit_callback (guestfs_h *g, void *datavp, uint64_t source,
|
|
|
e76f14 |
+ int eh, int flags,
|
|
|
e76f14 |
+ const char *buf, size_t buf_len,
|
|
|
e76f14 |
+ const uint64_t *array, size_t array_len)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct pass_data *data = datavp;
|
|
|
e76f14 |
+ struct event *event;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (!data->seen_launch)
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ event = add_event (data, source);
|
|
|
e76f14 |
+ event->message = strdup ("subprocess quit callback");
|
|
|
e76f14 |
+ if (event->message == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strdup");
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Called when the launch operation is complete (the library and the
|
|
|
e76f14 |
+ * guestfs daemon and talking to each other).
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+launch_done_callback (guestfs_h *g, void *datavp, uint64_t source,
|
|
|
e76f14 |
+ int eh, int flags,
|
|
|
e76f14 |
+ const char *buf, size_t buf_len,
|
|
|
e76f14 |
+ const uint64_t *array, size_t array_len)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct pass_data *data = datavp;
|
|
|
e76f14 |
+ struct event *event;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (!data->seen_launch)
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ event = add_event (data, source);
|
|
|
e76f14 |
+ event->message = strdup ("launch done callback");
|
|
|
e76f14 |
+ if (event->message == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strdup");
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Trim \r (multiple) from the end of a string. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+trim_r (char *message)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t len = strlen (message);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ while (len > 0 && message[len-1] == '\r') {
|
|
|
e76f14 |
+ message[len-1] = '\0';
|
|
|
e76f14 |
+ len--;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Called when we get (possibly part of) a log message (or more than
|
|
|
e76f14 |
+ * one log message) from the appliance (which may include qemu, the
|
|
|
e76f14 |
+ * BIOS, kernel, etc).
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+appliance_callback (guestfs_h *g, void *datavp, uint64_t source,
|
|
|
e76f14 |
+ int eh, int flags,
|
|
|
e76f14 |
+ const char *buf, size_t buf_len,
|
|
|
e76f14 |
+ const uint64_t *array, size_t array_len)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct pass_data *data = datavp;
|
|
|
e76f14 |
+ struct event *event;
|
|
|
e76f14 |
+ size_t i, len, slen;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (!data->seen_launch)
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* If the previous log message was incomplete, but time has moved on
|
|
|
e76f14 |
+ * a lot, record a new log message anyway, so it gets a new
|
|
|
e76f14 |
+ * timestamp.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ if (data->incomplete_log_message >= 0) {
|
|
|
e76f14 |
+ struct timespec ts;
|
|
|
e76f14 |
+ get_time (&ts);
|
|
|
e76f14 |
+ if (timespec_diff (&data->events[data->incomplete_log_message].t,
|
|
|
e76f14 |
+ &ts) >= 10000000 /* 10ms */)
|
|
|
e76f14 |
+ data->incomplete_log_message = -1;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* If the previous log message was incomplete then we may need to
|
|
|
e76f14 |
+ * append part of the current log message to a previous one.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ if (data->incomplete_log_message >= 0) {
|
|
|
e76f14 |
+ len = buf_len;
|
|
|
e76f14 |
+ for (i = 0; i < buf_len; ++i) {
|
|
|
e76f14 |
+ if (buf[i] == '\n') {
|
|
|
e76f14 |
+ len = i;
|
|
|
e76f14 |
+ break;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ event = &data->events[data->incomplete_log_message];
|
|
|
e76f14 |
+ slen = strlen (event->message);
|
|
|
e76f14 |
+ event->message = realloc (event->message, slen + len + 1);
|
|
|
e76f14 |
+ if (event->message == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "realloc");
|
|
|
e76f14 |
+ memcpy (event->message + slen, buf, len);
|
|
|
e76f14 |
+ event->message[slen + len] = '\0';
|
|
|
e76f14 |
+ trim_r (event->message);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Skip what we just added to the previous incomplete message. */
|
|
|
e76f14 |
+ buf += len;
|
|
|
e76f14 |
+ buf_len -= len;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (buf_len == 0) /* still not complete, more to come! */
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Skip the \n in the buffer. */
|
|
|
e76f14 |
+ buf++;
|
|
|
e76f14 |
+ buf_len--;
|
|
|
e76f14 |
+ data->incomplete_log_message = -1;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Add the event, or perhaps multiple events if the message
|
|
|
e76f14 |
+ * contains \n characters.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ while (buf_len > 0) {
|
|
|
e76f14 |
+ len = buf_len;
|
|
|
e76f14 |
+ for (i = 0; i < buf_len; ++i) {
|
|
|
e76f14 |
+ if (buf[i] == '\n') {
|
|
|
e76f14 |
+ len = i;
|
|
|
e76f14 |
+ break;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ event = add_event (data, source);
|
|
|
e76f14 |
+ event->message = strndup (buf, len);
|
|
|
e76f14 |
+ if (event->message == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strndup");
|
|
|
e76f14 |
+ trim_r (event->message);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Skip what we just added to the event. */
|
|
|
e76f14 |
+ buf += len;
|
|
|
e76f14 |
+ buf_len -= len;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (buf_len == 0) {
|
|
|
e76f14 |
+ /* Event is incomplete (doesn't end with \n). We'll finish it
|
|
|
e76f14 |
+ * in the next callback.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ data->incomplete_log_message = event - data->events;
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Skip the \n in the buffer. */
|
|
|
e76f14 |
+ buf++;
|
|
|
e76f14 |
+ buf_len--;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Called when we get a debug message from the library side. These
|
|
|
e76f14 |
+ * are always delivered as complete messages.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+library_callback (guestfs_h *g, void *datavp, uint64_t source,
|
|
|
e76f14 |
+ int eh, int flags,
|
|
|
e76f14 |
+ const char *buf, size_t buf_len,
|
|
|
e76f14 |
+ const uint64_t *array, size_t array_len)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct pass_data *data = datavp;
|
|
|
e76f14 |
+ struct event *event;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (!data->seen_launch)
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ event = add_event (data, source);
|
|
|
e76f14 |
+ event->message = strndup (buf, buf_len);
|
|
|
e76f14 |
+ if (event->message == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strndup");
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Called when we get a call trace message (a libguestfs API function
|
|
|
e76f14 |
+ * has been called or is returning). These are always delivered as
|
|
|
e76f14 |
+ * complete messages.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+trace_callback (guestfs_h *g, void *datavp, uint64_t source,
|
|
|
e76f14 |
+ int eh, int flags,
|
|
|
e76f14 |
+ const char *buf, size_t buf_len,
|
|
|
e76f14 |
+ const uint64_t *array, size_t array_len)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct pass_data *data = datavp;
|
|
|
e76f14 |
+ struct event *event;
|
|
|
e76f14 |
+ char *message;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ message = strndup (buf, buf_len);
|
|
|
e76f14 |
+ if (message == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strndup");
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (STREQ (message, "launch"))
|
|
|
e76f14 |
+ data->seen_launch = 1;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ if (!data->seen_launch) {
|
|
|
e76f14 |
+ free (message);
|
|
|
e76f14 |
+ return;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ event = add_event (data, source);
|
|
|
e76f14 |
+ event->message = message;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Common function to set up event callbacks and record data in memory
|
|
|
e76f14 |
+ * for a particular pass (0 <= pass < NR_TEST_PASSES).
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+set_up_event_handlers (guestfs_h *g, size_t pass)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct pass_data *data;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ assert (/* 0 <= pass && */ pass < NR_TEST_PASSES);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ data = &pass_data[pass];
|
|
|
e76f14 |
+ data->pass = pass;
|
|
|
e76f14 |
+ data->nr_events = 0;
|
|
|
e76f14 |
+ data->events = NULL;
|
|
|
e76f14 |
+ get_time (&data->start_t);
|
|
|
e76f14 |
+ data->incomplete_log_message = -1;
|
|
|
e76f14 |
+ data->seen_launch = 0;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ guestfs_set_event_callback (g, close_callback,
|
|
|
e76f14 |
+ GUESTFS_EVENT_CLOSE, 0, data);
|
|
|
e76f14 |
+ guestfs_set_event_callback (g, subprocess_quit_callback,
|
|
|
e76f14 |
+ GUESTFS_EVENT_SUBPROCESS_QUIT, 0, data);
|
|
|
e76f14 |
+ guestfs_set_event_callback (g, launch_done_callback,
|
|
|
e76f14 |
+ GUESTFS_EVENT_LAUNCH_DONE, 0, data);
|
|
|
e76f14 |
+ guestfs_set_event_callback (g, appliance_callback,
|
|
|
e76f14 |
+ GUESTFS_EVENT_APPLIANCE, 0, data);
|
|
|
e76f14 |
+ guestfs_set_event_callback (g, library_callback,
|
|
|
e76f14 |
+ GUESTFS_EVENT_LIBRARY, 0, data);
|
|
|
e76f14 |
+ guestfs_set_event_callback (g, trace_callback,
|
|
|
e76f14 |
+ GUESTFS_EVENT_TRACE, 0, data);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ guestfs_set_verbose (g, 1);
|
|
|
e76f14 |
+ guestfs_set_trace (g, 1);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Sanity check the collected events. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+check_pass_data (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i, j, len;
|
|
|
e76f14 |
+ int64_t ns;
|
|
|
e76f14 |
+ const char *message;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ assert (pass_data[i].pass == i);
|
|
|
e76f14 |
+ assert (pass_data[i].elapsed_ns > 1000);
|
|
|
e76f14 |
+ assert (pass_data[i].nr_events > 0);
|
|
|
e76f14 |
+ assert (pass_data[i].events != NULL);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (j = 0; j < pass_data[i].nr_events; ++j) {
|
|
|
e76f14 |
+ assert (pass_data[i].events[j].t.tv_sec > 0);
|
|
|
e76f14 |
+ if (j > 0) {
|
|
|
e76f14 |
+ ns = timespec_diff (&pass_data[i].events[j-1].t,
|
|
|
e76f14 |
+ &pass_data[i].events[j].t);
|
|
|
e76f14 |
+ assert (ns >= 0);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ assert (pass_data[i].events[j].source != 0);
|
|
|
e76f14 |
+ message = pass_data[i].events[j].message;
|
|
|
e76f14 |
+ assert (message != NULL);
|
|
|
e76f14 |
+ assert (strchr (message, '\n') == NULL);
|
|
|
e76f14 |
+ len = strlen (message);
|
|
|
e76f14 |
+ assert (len == 0 || message[len-1] != '\r');
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+print_escaped_string (const char *message)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ while (*message) {
|
|
|
e76f14 |
+ if (isprint (*message))
|
|
|
e76f14 |
+ putchar (*message);
|
|
|
e76f14 |
+ else
|
|
|
e76f14 |
+ printf ("\\x%02x", (unsigned int) *message);
|
|
|
e76f14 |
+ message++;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Dump the events to stdout, if verbose is set. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+dump_pass_data (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i, j;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ printf ("pass %zu\n", pass_data[i].pass);
|
|
|
e76f14 |
+ printf (" number of events collected %zu\n", pass_data[i].nr_events);
|
|
|
e76f14 |
+ printf (" elapsed time %" PRIi64 " ns\n", pass_data[i].elapsed_ns);
|
|
|
e76f14 |
+ for (j = 0; j < pass_data[i].nr_events; ++j) {
|
|
|
e76f14 |
+ int64_t ns;
|
|
|
e76f14 |
+ CLEANUP_FREE char *event_str = NULL;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ ns = timespec_diff (&pass_data[i].start_t, &pass_data[i].events[j].t);
|
|
|
e76f14 |
+ event_str = guestfs_event_to_string (pass_data[i].events[j].source);
|
|
|
e76f14 |
+ printf (" #%zu: +%" PRIi64 " [%s] \"", j, ns, event_str);
|
|
|
e76f14 |
+ print_escaped_string (pass_data[i].events[j].message);
|
|
|
e76f14 |
+ printf ("\"\n");
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+int
|
|
|
e76f14 |
+activity_exists (const char *name)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i)
|
|
|
e76f14 |
+ if (STREQ (activities[i].name, name))
|
|
|
e76f14 |
+ return 1;
|
|
|
e76f14 |
+ return 0;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Add an activity to the global list. */
|
|
|
e76f14 |
+struct activity *
|
|
|
e76f14 |
+add_activity (const char *name, int flags)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct activity *ret;
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* You shouldn't have two activities with the same name. */
|
|
|
e76f14 |
+ assert (!activity_exists (name));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ nr_activities++;
|
|
|
e76f14 |
+ activities = realloc (activities, sizeof (struct activity) * nr_activities);
|
|
|
e76f14 |
+ if (activities == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "realloc");
|
|
|
e76f14 |
+ ret = &activities[nr_activities-1];
|
|
|
e76f14 |
+ ret->name = strdup (name);
|
|
|
e76f14 |
+ if (ret->name == NULL)
|
|
|
e76f14 |
+ error (EXIT_FAILURE, errno, "strdup");
|
|
|
e76f14 |
+ ret->flags = flags;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i)
|
|
|
e76f14 |
+ ret->start_event[i] = ret->end_event[i] = 0;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ return ret;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+struct activity *
|
|
|
e76f14 |
+find_activity (const char *name)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i)
|
|
|
e76f14 |
+ if (STREQ (activities[i].name, name))
|
|
|
e76f14 |
+ return &activities[i];
|
|
|
e76f14 |
+ error (EXIT_FAILURE, 0,
|
|
|
e76f14 |
+ "internal error: could not find activity '%s'", name);
|
|
|
e76f14 |
+ /*NOTREACHED*/
|
|
|
e76f14 |
+ abort ();
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+int
|
|
|
e76f14 |
+activity_exists_with_no_data (const char *name, size_t pass)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i)
|
|
|
e76f14 |
+ if (STREQ (activities[i].name, name) &&
|
|
|
e76f14 |
+ activities[i].start_event[pass] == 0 &&
|
|
|
e76f14 |
+ activities[i].end_event[pass] == 0)
|
|
|
e76f14 |
+ return 1;
|
|
|
e76f14 |
+ return 0;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static int
|
|
|
e76f14 |
+compare_activities_by_t (const void *av, const void *bv)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ const struct activity *a = av;
|
|
|
e76f14 |
+ const struct activity *b = bv;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ return a->t - b->t;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Go through the activities, computing the start and elapsed time. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+analyze_timeline (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ struct activity *activity;
|
|
|
e76f14 |
+ size_t i, j;
|
|
|
e76f14 |
+ int64_t delta_ns;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (j = 0; j < nr_activities; ++j) {
|
|
|
e76f14 |
+ activity = &activities[j];
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ activity->t = 0;
|
|
|
e76f14 |
+ activity->mean = 0;
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ delta_ns =
|
|
|
e76f14 |
+ timespec_diff (&pass_data[i].events[0].t,
|
|
|
e76f14 |
+ &pass_data[i].events[activity->start_event[i]].t);
|
|
|
e76f14 |
+ activity->t += delta_ns;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ delta_ns =
|
|
|
e76f14 |
+ timespec_diff (&pass_data[i].events[activity->start_event[i]].t,
|
|
|
e76f14 |
+ &pass_data[i].events[activity->end_event[i]].t);
|
|
|
e76f14 |
+ activity->mean += delta_ns;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Divide through to get real start time and mean of each activity. */
|
|
|
e76f14 |
+ activity->t /= NR_TEST_PASSES;
|
|
|
e76f14 |
+ activity->mean /= NR_TEST_PASSES;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Calculate the end time of this activity. It's convenient when
|
|
|
e76f14 |
+ * drawing the timeline for one activity to finish just before the
|
|
|
e76f14 |
+ * next activity starts, rather than having them end and start at
|
|
|
e76f14 |
+ * the same time, hence ``- 1'' here.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ activity->end_t = activity->t + activity->mean - 1;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* The above only calculated mean. Now we are able to
|
|
|
e76f14 |
+ * calculate from the mean the variance and the standard
|
|
|
e76f14 |
+ * deviation.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ activity->variance = 0;
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ delta_ns =
|
|
|
e76f14 |
+ timespec_diff (&pass_data[i].events[activity->start_event[i]].t,
|
|
|
e76f14 |
+ &pass_data[i].events[activity->end_event[i]].t);
|
|
|
e76f14 |
+ activity->variance += pow (delta_ns - activity->mean, 2);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ activity->variance /= NR_TEST_PASSES;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ activity->sd = sqrt (activity->variance);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Get the total mean elapsed time from the special "run" activity. */
|
|
|
e76f14 |
+ activity = find_activity ("run");
|
|
|
e76f14 |
+ for (j = 0; j < nr_activities; ++j) {
|
|
|
e76f14 |
+ activities[j].percent = 100.0 * activities[j].mean / activity->mean;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ activities[j].warning =
|
|
|
e76f14 |
+ !(activities[j].flags & LONG_ACTIVITY) &&
|
|
|
e76f14 |
+ activities[j].percent >= WARNING_THRESHOLD;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Sort the activities by start time. */
|
|
|
e76f14 |
+ qsort (activities, nr_activities, sizeof (struct activity),
|
|
|
e76f14 |
+ compare_activities_by_t);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Dump the timeline to stdout, if verbose is set. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+dump_timeline (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i) {
|
|
|
e76f14 |
+ printf ("activity %zu:\n", i);
|
|
|
e76f14 |
+ printf (" name = %s\n", activities[i].name);
|
|
|
e76f14 |
+ printf (" start - end = %.1f - %.1f\n",
|
|
|
e76f14 |
+ activities[i].t, activities[i].end_t);
|
|
|
e76f14 |
+ printf (" mean elapsed = %.1f\n", activities[i].mean);
|
|
|
e76f14 |
+ printf (" variance = %.1f\n", activities[i].variance);
|
|
|
e76f14 |
+ printf (" s.d = %.1f\n", activities[i].sd);
|
|
|
e76f14 |
+ printf (" percent = %.1f\n", activities[i].percent);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Print some information that will allow us to determine the test
|
|
|
e76f14 |
+ * system when reviewing the results in future.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+print_info (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf ("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION_FULL);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf ("Host:\n");
|
|
|
e76f14 |
+ ignore_value (system ("uname -a"));
|
|
|
e76f14 |
+ ignore_value (system ("grep '^model name' /proc/cpuinfo | head -1"));
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* We can dig some information about qemu and the appliance out of
|
|
|
e76f14 |
+ * the events.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ printf ("Appliance:\n");
|
|
|
e76f14 |
+ assert (NR_TEST_PASSES > 0);
|
|
|
e76f14 |
+ for (i = 0; i < pass_data[0].nr_events; ++i) {
|
|
|
e76f14 |
+ const char *message = pass_data[0].events[i].message;
|
|
|
e76f14 |
+ if (strstr (message, "qemu version") ||
|
|
|
e76f14 |
+ (strstr (message, "SeaBIOS") && strstr (message, "version")) ||
|
|
|
e76f14 |
+ strstr (message, "Linux version") ||
|
|
|
e76f14 |
+ (strstr (message, "supermin") && strstr (message, "starting up"))) {
|
|
|
e76f14 |
+ print_escaped_string (message);
|
|
|
e76f14 |
+ putchar ('\n');
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+print_activity (struct activity *activity)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (activity->warning) ansi_red (); else ansi_green ();
|
|
|
e76f14 |
+ print_escaped_string (activity->name);
|
|
|
e76f14 |
+ ansi_restore ();
|
|
|
e76f14 |
+ printf (" %1.6fs ±%.1fms ",
|
|
|
e76f14 |
+ activity->mean / 1000000000, activity->sd / 1000000);
|
|
|
e76f14 |
+ if (activity->warning) ansi_red (); else ansi_green ();
|
|
|
e76f14 |
+ printf ("(%.1f%%) ", activity->percent);
|
|
|
e76f14 |
+ ansi_restore ();
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+print_analysis (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ double t = -1; /* Current time. */
|
|
|
e76f14 |
+ /* Which columns contain activities that we are displaying now?
|
|
|
e76f14 |
+ * -1 == unused column, else index of an activity
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ CLEANUP_FREE ssize_t *columns = NULL;
|
|
|
e76f14 |
+ const size_t nr_columns = nr_activities;
|
|
|
e76f14 |
+ size_t last_free_column = 0;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ size_t i, j;
|
|
|
e76f14 |
+ double last_t, smallest_next_t;
|
|
|
e76f14 |
+ const double MAX_T = 1e20;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ columns = malloc (nr_columns * sizeof (ssize_t));
|
|
|
e76f14 |
+ if (columns == NULL) error (EXIT_FAILURE, errno, "malloc");
|
|
|
e76f14 |
+ for (j = 0; j < nr_columns; ++j)
|
|
|
e76f14 |
+ columns[j] = -1;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (;;) {
|
|
|
e76f14 |
+ /* Find the next significant time to display, which is a time when
|
|
|
e76f14 |
+ * some activity started or ended.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ smallest_next_t = MAX_T;
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i) {
|
|
|
e76f14 |
+ if (t < activities[i].t && activities[i].t < smallest_next_t)
|
|
|
e76f14 |
+ smallest_next_t = activities[i].t;
|
|
|
e76f14 |
+ else if (t < activities[i].end_t && activities[i].end_t < smallest_next_t)
|
|
|
e76f14 |
+ smallest_next_t = activities[i].end_t;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ if (smallest_next_t == MAX_T)
|
|
|
e76f14 |
+ break; /* Finished. */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ last_t = t;
|
|
|
e76f14 |
+ t = smallest_next_t;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Draw a spacer line, but only if last_t -> t is a large jump. */
|
|
|
e76f14 |
+ if (t - last_t >= 1000000 /* ns */) {
|
|
|
e76f14 |
+ printf (" ");
|
|
|
e76f14 |
+ ansi_magenta ();
|
|
|
e76f14 |
+ for (j = 0; j < last_free_column; ++j) {
|
|
|
e76f14 |
+ if (columns[j] >= 0 &&
|
|
|
e76f14 |
+ activities[columns[j]].end_t != last_t /* !â–¼ */)
|
|
|
e76f14 |
+ printf ("│ ");
|
|
|
e76f14 |
+ else
|
|
|
e76f14 |
+ printf (" ");
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ ansi_restore ();
|
|
|
e76f14 |
+ printf ("\n");
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* If there are any activities that ended before this time, drop
|
|
|
e76f14 |
+ * them from the columns list.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i) {
|
|
|
e76f14 |
+ if (activities[i].end_t < t) {
|
|
|
e76f14 |
+ for (j = 0; j < nr_columns; ++j)
|
|
|
e76f14 |
+ if (columns[j] == (ssize_t) i) {
|
|
|
e76f14 |
+ columns[j] = -1;
|
|
|
e76f14 |
+ break;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* May need to adjust last_free_column after previous operation. */
|
|
|
e76f14 |
+ while (last_free_column > 0 && columns[last_free_column-1] == -1)
|
|
|
e76f14 |
+ last_free_column--;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* If there are any activities starting at this time, add them to
|
|
|
e76f14 |
+ * the right hand end of the columns list.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i) {
|
|
|
e76f14 |
+ if (activities[i].t == t)
|
|
|
e76f14 |
+ columns[last_free_column++] = i;
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Draw the line. */
|
|
|
e76f14 |
+ ansi_blue ();
|
|
|
e76f14 |
+ printf ("%1.6fs: ", t / 1000000000);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ ansi_magenta ();
|
|
|
e76f14 |
+ for (j = 0; j < last_free_column; ++j) {
|
|
|
e76f14 |
+ if (columns[j] >= 0) {
|
|
|
e76f14 |
+ if (activities[columns[j]].t == t)
|
|
|
e76f14 |
+ printf ("â–² ");
|
|
|
e76f14 |
+ else if (activities[columns[j]].end_t == t)
|
|
|
e76f14 |
+ printf ("â–¼ ");
|
|
|
e76f14 |
+ else
|
|
|
e76f14 |
+ printf ("│ ");
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ else
|
|
|
e76f14 |
+ printf (" ");
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+ ansi_restore ();
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (j = 0; j < last_free_column; ++j) {
|
|
|
e76f14 |
+ if (columns[j] >= 0 && activities[columns[j]].t == t) /* â–² */
|
|
|
e76f14 |
+ print_activity (&activities[columns[j]]);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ printf ("\n");
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static int
|
|
|
e76f14 |
+compare_activities_pointers_by_mean (const void *av, const void *bv)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ const struct activity * const *a = av;
|
|
|
e76f14 |
+ const struct activity * const *b = bv;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ return (*b)->mean - (*a)->mean;
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+print_longest_to_shortest (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+ CLEANUP_FREE struct activity **longest;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Sort the activities longest first. In order not to affect the
|
|
|
e76f14 |
+ * global activities array, sort an array of pointers to the
|
|
|
e76f14 |
+ * activities instead.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ longest = malloc (sizeof (struct activity *) * nr_activities);
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i)
|
|
|
e76f14 |
+ longest[i] = &activities[i];
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ qsort (longest, nr_activities, sizeof (struct activity *),
|
|
|
e76f14 |
+ compare_activities_pointers_by_mean);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Display the activities, longest first. */
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i) {
|
|
|
e76f14 |
+ print_activity (longest[i]);
|
|
|
e76f14 |
+ printf ("\n");
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Free the non-static part of the pass_data structures. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+free_pass_data (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i, j;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < NR_TEST_PASSES; ++i) {
|
|
|
e76f14 |
+ for (j = 0; j < pass_data[i].nr_events; ++j)
|
|
|
e76f14 |
+ free (pass_data[i].events[j].message);
|
|
|
e76f14 |
+ free (pass_data[i].events);
|
|
|
e76f14 |
+ }
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+free_final_timeline (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ size_t i;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ for (i = 0; i < nr_activities; ++i)
|
|
|
e76f14 |
+ free (activities[i].name);
|
|
|
e76f14 |
+ free (activities);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Colours. */
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+ansi_green (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (force_colour || isatty (1))
|
|
|
e76f14 |
+ fputs ("\033[0;32m", stdout);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+ansi_red (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (force_colour || isatty (1))
|
|
|
e76f14 |
+ fputs ("\033[1;31m", stdout);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+ansi_blue (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (force_colour || isatty (1))
|
|
|
e76f14 |
+ fputs ("\033[1;34m", stdout);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+ansi_magenta (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (force_colour || isatty (1))
|
|
|
e76f14 |
+ fputs ("\033[1;35m", stdout);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+static void
|
|
|
e76f14 |
+ansi_restore (void)
|
|
|
e76f14 |
+{
|
|
|
e76f14 |
+ if (force_colour || isatty (1))
|
|
|
e76f14 |
+ fputs ("\033[0m", stdout);
|
|
|
e76f14 |
+}
|
|
|
e76f14 |
diff --git a/tests/qemu/boot-analysis.h b/tests/qemu/boot-analysis.h
|
|
|
e76f14 |
new file mode 100644
|
|
|
e76f14 |
index 0000000..86d403e
|
|
|
e76f14 |
--- /dev/null
|
|
|
e76f14 |
+++ b/tests/qemu/boot-analysis.h
|
|
|
e76f14 |
@@ -0,0 +1,94 @@
|
|
|
e76f14 |
+/* libguestfs
|
|
|
e76f14 |
+ * Copyright (C) 2016 Red Hat Inc.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * This program is free software; you can redistribute it and/or modify
|
|
|
e76f14 |
+ * it under the terms of the GNU General Public License as published by
|
|
|
e76f14 |
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
e76f14 |
+ * (at your option) any later version.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
e76f14 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
e76f14 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
e76f14 |
+ * GNU General Public License for more details.
|
|
|
e76f14 |
+ *
|
|
|
e76f14 |
+ * You should have received a copy of the GNU General Public License along
|
|
|
e76f14 |
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
e76f14 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#ifndef GUESTFS_BOOT_ANALYSIS_H_
|
|
|
e76f14 |
+#define GUESTFS_BOOT_ANALYSIS_H_
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#define NR_WARMUP_PASSES 3
|
|
|
e76f14 |
+#define NR_TEST_PASSES 5
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* Per-pass data collected. */
|
|
|
e76f14 |
+struct pass_data {
|
|
|
e76f14 |
+ size_t pass;
|
|
|
e76f14 |
+ struct timespec start_t;
|
|
|
e76f14 |
+ struct timespec end_t;
|
|
|
e76f14 |
+ int64_t elapsed_ns;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Array of timestamped events. */
|
|
|
e76f14 |
+ size_t nr_events;
|
|
|
e76f14 |
+ struct event *events;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Was the previous appliance log message incomplete? If so, this
|
|
|
e76f14 |
+ * contains the index of that incomplete message in the events
|
|
|
e76f14 |
+ * array.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ ssize_t incomplete_log_message;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Have we seen the launch event yet? We don't record events until
|
|
|
e76f14 |
+ * this one has been received. This makes it easy to base the
|
|
|
e76f14 |
+ * timeline at event 0.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ int seen_launch;
|
|
|
e76f14 |
+};
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+struct event {
|
|
|
e76f14 |
+ struct timespec t;
|
|
|
e76f14 |
+ uint64_t source;
|
|
|
e76f14 |
+ char *message;
|
|
|
e76f14 |
+};
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+extern struct pass_data pass_data[NR_TEST_PASSES];
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+/* The final timeline consisting of various activities starting and
|
|
|
e76f14 |
+ * ending. We're interested in when the activities start, and how
|
|
|
e76f14 |
+ * long they take (mean, variance, standard deviation of length).
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+struct activity {
|
|
|
e76f14 |
+ char *name; /* Name of this activity. */
|
|
|
e76f14 |
+ int flags;
|
|
|
e76f14 |
+#define LONG_ACTIVITY 1 /* Expected to take a long time. */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* For each pass, record the actual start & end events of this
|
|
|
e76f14 |
+ * activity.
|
|
|
e76f14 |
+ */
|
|
|
e76f14 |
+ size_t start_event[NR_TEST_PASSES];
|
|
|
e76f14 |
+ size_t end_event[NR_TEST_PASSES];
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ double t; /* Start (ns offset). */
|
|
|
e76f14 |
+ double end_t; /* t + mean - 1 */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ /* Length of this activity. */
|
|
|
e76f14 |
+ double mean; /* Mean time elapsed (ns). */
|
|
|
e76f14 |
+ double variance; /* Variance. */
|
|
|
e76f14 |
+ double sd; /* Standard deviation. */
|
|
|
e76f14 |
+ double percent; /* Percent of total elapsed time. */
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+ int warning; /* Appears in red. */
|
|
|
e76f14 |
+};
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+extern size_t nr_activities;
|
|
|
e76f14 |
+extern struct activity *activities;
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+extern int activity_exists (const char *name);
|
|
|
e76f14 |
+extern struct activity *add_activity (const char *name, int flags);
|
|
|
e76f14 |
+extern struct activity *find_activity (const char *name);
|
|
|
e76f14 |
+extern int activity_exists_with_no_data (const char *name, size_t pass);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+extern void construct_timeline (void);
|
|
|
e76f14 |
+
|
|
|
e76f14 |
+#endif /* GUESTFS_BOOT_ANALYSIS_H_ */
|
|
|
e76f14 |
--
|
|
|
e76f14 |
1.8.3.1
|
|
|
e76f14 |
|