On Wed, 2015-08-19 at 15:39 +0100, Frediano Ziglio wrote: > From: Alon Levy <alon@xxxxxxxxx> > > usage: replay -p <port> -c <client command line> <cmdfile> Update commit message for new executable name: usage: spice-server-relay -p ... > > will run the commands from cmdfile ignoring timestamps, right after a > connection is established from the client, and will SIGINT the client > on end of cmdfile, and exit itself after waiting for the client. > > spicy-stats from spice-gtk is useful for testing, it prints the summary > of the traffic on each channel. > > You can also run with no client by doing: > replay <cmdfile> Here as well: "spice-server-replay <cmdfile>" > > For example, the 300 MB file (compressed to 4 MB with xz -9) available > at [1] produces the following output: > > spicy-stats total bytes read: > total bytes read: > inputs: 214 > display: 1968983 > cursor: 390 > main: 256373 > > You could run it directly like so: > curl http://annarchy.freedesktop.org/~alon/win7_boot_shutdown.cmd.xz | \ > xzcat | server/tests/replay -p 12345 -c `which spicy-stats` - > > Known Problems: > * Implementation is wrong. Should do a single device->host conversion > (i.e. get_virt), and then marshall/demarshall that (i.e. RedDrawable). > * segfault on file read done resulting in the above spicy-stats not > being reproducable (well, up to 1% yes). > > [1] http://annarchy.freedesktop.org/~alon/win7_boot_shutdown.cmd.xz > > Now based on glib including using an asyncqueue for reading the playback > file, and proper freeing of the allocated commands, with --slow, > --compression and a progress timer, and doesn't use more then nsurfaces. > > Signed-off-by: Alon Levy <alon@xxxxxxxxx> > Signed-off-by: Marc-André Lureau <marcandre.lureau@xxxxxxxxx> > --- > server/red_replay_qxl.c | 7 +- > server/red_replay_qxl.h | 17 +++ > server/tests/Makefile.am | 9 ++ > server/tests/replay.c | 346 +++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 377 insertions(+), 2 deletions(-) > create mode 100644 server/tests/replay.c > > diff --git a/server/red_replay_qxl.c b/server/red_replay_qxl.c > index 663c6ed..e52ee9c 100644 > --- a/server/red_replay_qxl.c > +++ b/server/red_replay_qxl.c > @@ -186,13 +186,15 @@ static replay_t read_binary(SpiceReplay *replay, const char *prefix, size_t *siz > **buf, size_t base_size) > { > char template[1024]; > - int with_zlib; > + int with_zlib = -1; > int zlib_size; > uint8_t *zlib_buffer; > z_stream strm; > > snprintf(template, sizeof(template), "binary %%d %s %%ld:", prefix); > - replay_fscanf(replay, template, &with_zlib, size); > + if (replay_fscanf(replay, template, &with_zlib, size) == REPLAY_EOF) > + return REPLAY_EOF; > + > if (*buf == NULL) { > *buf = malloc(*size + base_size); > if (*buf == NULL) { > @@ -207,6 +209,7 @@ static replay_t read_binary(SpiceReplay *replay, const char *prefix, size_t *siz > hexdump(*buf + base_size, *size); > } > #else > + spice_return_val_if_fail(with_zlib != -1, REPLAY_EOF); > if (with_zlib) { > int ret; > > diff --git a/server/red_replay_qxl.h b/server/red_replay_qxl.h > index 77a37e0..a89b3d4 100644 > --- a/server/red_replay_qxl.h > +++ b/server/red_replay_qxl.h > @@ -1,3 +1,20 @@ > +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ > +/* > + Copyright (C) 2009-2015 Red Hat, Inc. > + > + This library is free software; you can redistribute it and/or > + modify it under the terms of the GNU Lesser General Public > + License as published by the Free Software Foundation; either > + version 2.1 of the License, or (at your option) any later version. > + > + This library 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 > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public > + License along with this library; if not, see <http://www.gnu.org/licenses/>. > +*/ > #ifndef RED_REPLAY_QXL_H > #define RED_REPLAY_QXL_H > > diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am > index 926b3e9..81bbc29 100644 > --- a/server/tests/Makefile.am > +++ b/server/tests/Makefile.am > @@ -42,6 +42,7 @@ noinst_PROGRAMS = \ > test_two_servers \ > test_vdagent \ > test_display_width_stride \ > + spice-server-replay \ > $(NULL) > > test_vdagent_SOURCES = \ > @@ -103,3 +104,11 @@ test_display_width_stride_SOURCES = \ > test_display_base.h \ > test_display_width_stride.c \ > $(NULL) > + > +spice_server_replay_SOURCES = \ > + replay.c \ > + test_display_base.h \ > + basic_event_loop.c \ > + basic_event_loop.h \ > + test_util.h \ > + $(NULL) > diff --git a/server/tests/replay.c b/server/tests/replay.c > new file mode 100644 > index 0000000..01590c0 > --- /dev/null > +++ b/server/tests/replay.c > @@ -0,0 +1,346 @@ > +/* Replay a previously recorded file (via SPICE_WORKER_RECORD_FILENAME) > + */ > + > +#include <string.h> > +#include <stdlib.h> > +#include <stdio.h> > +#include <strings.h> > +#include <sys/types.h> > +#include <signal.h> > +#include <unistd.h> > +#include <pthread.h> > +#include <sys/wait.h> > +#include <fcntl.h> > +#include <glib.h> > + > +#include <spice/macros.h> > +#include "red_replay_qxl.h" > +#include "test_display_base.h" > +#include "common/log.h" > + > +static SpiceCoreInterface *core; > +static SpiceServer *server; > +static SpiceReplay *replay; > +static QXLWorker *qxl_worker = NULL; > +static gboolean started = FALSE; > +static QXLInstance display_sin = { 0, }; > +static gint slow = 0; > +static pid_t client_pid; > +static GMainLoop *loop = NULL; > +static GAsyncQueue *aqueue = NULL; > +static long total_size; > + > +static GMutex mutex; > +static guint fill_source_id = 0; > + > + > +#define MEM_SLOT_GROUP_ID 0 > + > +/* Parts cribbed from spice-display.h/.c/qxl.c */ > + > +static QXLDevMemSlot slot = { > +.slot_group_id = MEM_SLOT_GROUP_ID, > +.slot_id = 0, > +.generation = 0, > +.virt_start = 0, > +.virt_end = ~0, > +.addr_delta = 0, > +.qxl_ram_size = ~0, > +}; > + > +static void attach_worker(QXLInstance *qin, QXLWorker *_qxl_worker) > +{ > + static int count = 0; > + if (++count > 1) { > + g_warning("%s ignored\n", __func__); > + return; > + } > + g_debug("%s\n", __func__); > + qxl_worker = _qxl_worker; > + spice_qxl_add_memslot(qin, &slot); > + spice_server_vm_start(server); > +} > + > +static void set_compression_level(QXLInstance *qin, int level) > +{ > + g_debug("%s\n", __func__); > +} > + > +static void set_mm_time(QXLInstance *qin, uint32_t mm_time) > +{ > +} > + > +// same as qemu/ui/spice-display.h > +#define MAX_SURFACE_NUM 1024 > + > +static void get_init_info(QXLInstance *qin, QXLDevInitInfo *info) > +{ > + bzero(info, sizeof(*info)); > + info->num_memslots = 1; > + info->num_memslots_groups = 1; > + info->memslot_id_bits = 1; > + info->memslot_gen_bits = 1; > + info->n_surfaces = MAX_SURFACE_NUM; > +} > + > +static gboolean fill_queue_idle(gpointer user_data) > +{ > + gboolean keep = FALSE; > + > + while (g_async_queue_length(aqueue) < 50) { > + QXLCommandExt *cmd = spice_replay_next_cmd(replay, qxl_worker); > + if (!cmd) { > + g_async_queue_push(aqueue, GINT_TO_POINTER(-1)); > + goto end; > + } > + > + if (slow) > + g_usleep(slow); > + > + g_async_queue_push(aqueue, cmd); > + } > + > +end: > + if (!keep) { > + g_mutex_lock(&mutex); > + fill_source_id = 0; > + g_mutex_unlock(&mutex); > + } > + spice_qxl_wakeup(&display_sin); > + > + return keep; > +} > + > +static void fill_queue(void) > +{ > + g_mutex_lock(&mutex); > + > + if (!started) > + goto end; > + > + if (fill_source_id != 0) > + goto end; > + > + fill_source_id = g_idle_add(fill_queue_idle, NULL); > + > +end: > + g_mutex_unlock(&mutex); > +} > + > + > +// called from spice_server thread (i.e. red_worker thread) > +static int get_command(QXLInstance *qin, QXLCommandExt *ext) > +{ > + QXLCommandExt *cmd; > + > + if (g_async_queue_length(aqueue) == 0) { > + /* could use a gcondition ? */ > + fill_queue(); > + return FALSE; > + } > + > + cmd = g_async_queue_try_pop(aqueue); > + if (GPOINTER_TO_INT(cmd) == -1) { > + g_main_loop_quit(loop); > + return FALSE; > + } > + > + *ext = *cmd; > + > + return TRUE; > +} > + > +static int req_cmd_notification(QXLInstance *qin) > +{ > + if (!started) > + return TRUE; > + > + g_printerr("id: %d, queue length: %d", > + fill_source_id, g_async_queue_length(aqueue)); > + > + return TRUE; > +} > + > +static void end_replay(void) > +{ > + int child_status; > + > + /* FIXME: wait threads and end cleanly */ > + spice_replay_free(replay); > + > + if (client_pid) { > + g_debug("kill %d", client_pid); > + kill(client_pid, SIGINT); > + waitpid(client_pid, &child_status, 0); > + } > + exit(0); > +} > + > +static void release_resource(QXLInstance *qin, struct QXLReleaseInfoExt release_info) > +{ > + spice_replay_free_cmd(replay, (QXLCommandExt *)release_info.info->id); > +} > + > +static int get_cursor_command(QXLInstance *qin, struct QXLCommandExt *ext) > +{ > + return FALSE; > +} > + > +static int req_cursor_notification(QXLInstance *qin) > +{ > + return TRUE; > +} > + > +static void notify_update(QXLInstance *qin, uint32_t update_id) > +{ > +} > + > +static int flush_resources(QXLInstance *qin) > +{ > + return TRUE; > +} > + > +static QXLInterface display_sif = { > + .base = { > + .type = SPICE_INTERFACE_QXL, > + .description = "replay", > + .major_version = SPICE_INTERFACE_QXL_MAJOR, > + .minor_version = SPICE_INTERFACE_QXL_MINOR > + }, > + .attache_worker = attach_worker, > + .set_compression_level = set_compression_level, > + .set_mm_time = set_mm_time, > + .get_init_info = get_init_info, > + .get_command = get_command, > + .req_cmd_notification = req_cmd_notification, > + .release_resource = release_resource, > + .get_cursor_command = get_cursor_command, > + .req_cursor_notification = req_cursor_notification, > + .notify_update = notify_update, > + .flush_resources = flush_resources, > +}; > + > +static void replay_channel_event(int event, SpiceChannelEventInfo *info) > +{ > + g_printerr(""); > + > + if (info->type == SPICE_CHANNEL_DISPLAY && > + event == SPICE_CHANNEL_EVENT_INITIALIZED) { > + started = TRUE; > + } > +} > + > +static gboolean start_client(gchar *cmd, GError **error) > +{ > + gboolean retval; > + gint argc; > + gchar **argv = NULL; > + > + > + if (!g_shell_parse_argv(cmd, &argc, &argv, error)) > + return FALSE; > + > + retval = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, > + NULL, NULL, &client_pid, error); > + g_strfreev(argv); > + > + return retval; > +} > + > +static gboolean progress_timer(gpointer user_data) > +{ > + FILE *fd = user_data; > + /* it seems somehow thread safe, move to worker thread? */ > + double pos = (double)ftell(fd); > + > + g_debug("%.2f%%", pos/total_size * 100); > + return TRUE; > +} > + > +int main(int argc, char **argv) > +{ > + GError *error = NULL; > + GOptionContext *context = NULL; > + gchar *client = NULL, **file = NULL; > + gint port = 5000, compression = SPICE_IMAGE_COMPRESSION_AUTO_GLZ; > + gboolean wait = FALSE; > + FILE *fd; > + > + GOptionEntry entries[] = { > + { "client", 'c', 0, G_OPTION_ARG_STRING, &client, "Client", "CMD" }, > + { "compression", 'C', 0, G_OPTION_ARG_INT, &compression, "Compression (default 2)", "INT" }, > + { "port", 'p', 0, G_OPTION_ARG_INT, &port, "Server port (default 5000)", "PORT" }, > + { "wait", 'w', 0, G_OPTION_ARG_NONE, &wait, "Wait for client", NULL }, > + { "slow", 's', 0, G_OPTION_ARG_INT, &slow, "Slow down replay", NULL }, > + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file, "replay file", "FILE" }, > + { NULL } > + }; > + > + context = g_option_context_new("- replay spice server recording"); > + g_option_context_add_main_entries(context, entries, NULL); > + if (!g_option_context_parse(context, &argc, &argv, &error)) { > + g_printerr("Option parsing failed: %s\n", error->message); > + exit(1); > + } > + if (!file) { > + g_printerr("%s\n", g_option_context_get_help(context, TRUE, NULL)); > + exit(1); > + } > + > + if (strncmp(file[0], "-", 1) == 0) { > + fd = stdin; > + } else { > + fd = fopen(file[0], "r"); > + } > + if (fd == NULL) { > + g_printerr("error opening %s\n", argv[1]); > + return 1; > + } > + if (fcntl(fileno(fd), FD_CLOEXEC) < 0) { > + perror("fcntl failed"); > + exit(1); > + } > + fseek(fd, 0L, SEEK_END); > + total_size = ftell(fd); > + fseek(fd, 0L, SEEK_SET); > + if (total_size > 0) > + g_timeout_add_seconds(1, progress_timer, fd); > + replay = spice_replay_new(fd, MAX_SURFACE_NUM); > + > + aqueue = g_async_queue_new(); > + core = basic_event_loop_init(); > + core->channel_event = replay_channel_event; > + > + server = spice_server_new(); > + spice_server_set_image_compression(server, compression); > + spice_server_set_port(server, port); > + spice_server_set_noauth(server); > + > + g_print("listening on port %d (insecure)\n", port); > + spice_server_init(server, core); > + > + display_sin.base.sif = &display_sif.base; > + spice_server_add_interface(server, &display_sin.base); > + > + if (client) { > + start_client(client, &error); > + wait = TRUE; > + } > + > + if (!wait) { > + started = TRUE; > + fill_queue(); > + } > + > + loop = g_main_loop_new(NULL, FALSE); > + g_main_loop_run(loop); > + > + end_replay(); > + g_async_queue_unref(aqueue); > + > + /* FIXME: there should be a way to join server threads before: > + * g_main_loop_unref(loop); > + */ > + > + return 0; > +} _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel