usage: replay <cmdfile> <path_to_client> 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.
dummyload from spice-gtk is useful for testing, it prints the summary of the traffic on each channel. For example, the 300 MB file (compressed to 4 MB with xz -9) available at [1] produces the following output: dummyload total bytes read: inputs: 0 smartcard: 0 display: 1915049 cursor: 0 main: 256372 These values are not totally reproducible, probably due to some error in the saved file. [1] http://annarchy.freedesktop.org/~alon/win7_boot_shutdown.cmd.xz --- server/tests/Makefile.am | 4 +- server/tests/basic_event_loop.c | 1 - server/tests/replay.c | 281 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 server/tests/replay.c diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am index 8ec4094..d973de4 100644 --- a/server/tests/Makefile.am +++ b/server/tests/Makefile.am @@ -11,7 +11,7 @@ INCLUDES = \ AM_LDFLAGS = $(top_builddir)/server/libspice-server.la -noinst_PROGRAMS = test_just_sockets_no_ssl test_empty_success test_fail_on_null_core_interface test_display_no_ssl test_display_streaming test_playback +noinst_PROGRAMS = test_just_sockets_no_ssl test_empty_success test_fail_on_null_core_interface test_display_no_ssl test_display_streaming test_playback replay test_display_streaming_SOURCES = test_display_streaming.c test_display_base.c test_display_base.h basic_event_loop.c basic_event_loop.h test_util.h @@ -24,3 +24,5 @@ test_empty_success_SOURCES = test_empty_success.c test_fail_on_null_core_interface_SOURCES = test_fail_on_null_core_interface.c test_playback_SOURCES = test_playback.c basic_event_loop.c basic_event_loop.h test_util.h + +replay_SOURCES = replay.c test_display_base.h basic_event_loop.c basic_event_loop.h test_util.h diff --git a/server/tests/basic_event_loop.c b/server/tests/basic_event_loop.c index 8db4426..bdeba21 100644 --- a/server/tests/basic_event_loop.c +++ b/server/tests/basic_event_loop.c @@ -372,4 +372,3 @@ SpiceCoreInterface *basic_event_loop_init(void) core.channel_event = channel_event; return &core; } - diff --git a/server/tests/replay.c b/server/tests/replay.c new file mode 100644 index 0000000..1329391 --- /dev/null +++ b/server/tests/replay.c @@ -0,0 +1,281 @@ +/* Replay a previously recorded file (via SPICE_WORKER_RECORD_FILENAME) + */ + +#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 <spice/macros.h> +#include "red_replay_qxl.h" +#include "test_display_base.h" + +SpiceCoreInterface *core; +SpiceServer *server; +Replay *replay; + +#define MEM_SLOT_GROUP_ID 0 + +/* Parts cribbed from spice-display.h/.c/qxl.c */ + +static QXLWorker *qxl_worker = NULL; + +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 attache_worker(QXLInstance *qin, QXLWorker *_qxl_worker) +{ + static int count = 0; + if (++count > 1) { + printf("%s ignored\n", __func__); + return; + } + printf("%s\n", __func__); + qxl_worker = _qxl_worker; + qxl_worker->add_memslot(qxl_worker, &slot); + qxl_worker->start(qxl_worker); +} + +static void set_compression_level(QXLInstance *qin, int level) +{ + printf("%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; +} + +QXLCommandExt ext_cmd_last; +SpiceTimer *wakeup_timer; +pthread_mutex_t ext_cmd_mutex; +int client_up; +pid_t client_pid; + +// called from spice_server thread (i.e. red_worker thread) +static int get_command(QXLInstance *qin, struct QXLCommandExt *ext) +{ + if (!client_up) { + core->timer_start(wakeup_timer, 50); + return FALSE; + } + pthread_mutex_lock(&ext_cmd_mutex); + if (replay->eof || ext_cmd_last.cmd.data == 0) { + pthread_mutex_unlock(&ext_cmd_mutex); + return FALSE; + } + *ext = ext_cmd_last; + ext_cmd_last.cmd.data = 0; + pthread_mutex_unlock(&ext_cmd_mutex); + core->timer_start(wakeup_timer, 1); + return TRUE; +} + +static int req_cmd_notification(QXLInstance *qin) +{ + core->timer_start(wakeup_timer, 1); + return TRUE; +} + +static void end_replay(void) +{ + int child_status; + + fclose(replay->fd); + free(replay); + pthread_mutex_destroy(&ext_cmd_mutex); + if (client_pid) { + kill(client_pid, SIGINT); + waitpid(client_pid, &child_status, 0); + } + exit(0); +} + +static void do_wakeup(void *opaque) +{ + QXLCommandExt ext_cmd_cur; + + // We read the file here since this is not the worker thread, so we + // can issue dispatcher calls like create_primary/destroy_primary + pthread_mutex_lock(&ext_cmd_mutex); + if (ext_cmd_last.cmd.data != 0) { + pthread_mutex_unlock(&ext_cmd_mutex); + // last command not read yet, sleep some more + core->timer_start(wakeup_timer, 50); + return; + } + pthread_mutex_unlock(&ext_cmd_mutex); + replay_next_cmd(replay, qxl_worker, &ext_cmd_cur); + if (replay->eof || ext_cmd_cur.cmd.data == 0) { + if (ext_cmd_cur.cmd.data == 0) { + fprintf(stderr, "null command returned from create_cmd_from_file\n"); + } else { + fprintf(stderr, "end of file reached\n"); + } + end_replay(); + } + pthread_mutex_lock(&ext_cmd_mutex); + ext_cmd_last = ext_cmd_cur; + pthread_mutex_unlock(&ext_cmd_mutex); + core->timer_start(wakeup_timer, 1); + qxl_worker->wakeup(qxl_worker); +} + +static void release_resource(QXLInstance *qin, struct QXLReleaseInfoExt release_info) +{ + QXLCommandExt *ext = (void*)release_info.info->id; + return; // TODO - release resources from + //printf("%s\n", __func__); + switch (ext->cmd.type) { + case QXL_CMD_DRAW: + break; + case QXL_CMD_SURFACE: + free(ext); + break; + default: + abort(); + } +} + +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; +} + +QXLInterface display_sif = { + .base = { + .type = SPICE_INTERFACE_QXL, + .description = "replay", + .major_version = SPICE_INTERFACE_QXL_MAJOR, + .minor_version = SPICE_INTERFACE_QXL_MINOR + }, + .attache_worker = attache_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, +}; + +QXLInstance display_sin = { + .base = { + .sif = &display_sif.base, + }, + .id = 0, +}; + +void reply_channel_event(int event, SpiceChannelEventInfo *info) +{ + static int channels = 0; + // TODO: wait for the display channel, not just the first two channels. + if (++channels == 2) { + client_up = 1; + } +} + +void start_client(const char *client, int port) +{ + pid_t pid; + char port_s[10]; + char *argv[] = { + (char*)client, + "-p", + NULL, + "-h", + "localhost", + NULL, + }; + snprintf(port_s, sizeof(port_s), "%d", port); + argv[2] = port_s; + + if ((pid = fork()) == 0) { + fclose(replay->fd); // close the command file + /* child */ + fprintf(stderr, "launching %s\n", client); + execv(client, argv); + } else { + client_pid = pid; + } +} + +int main(int argc, char **argv) +{ + int port = 7123; + const char *client; + FILE *fd; + + core = basic_event_loop_init(); + core->channel_event = reply_channel_event; + SpiceServer* server = spice_server_new(); + client_up = 0; + + ext_cmd_last.cmd.data = 0; + pthread_mutex_init(&ext_cmd_mutex, NULL); + if (argc != 3) { + fprintf(stderr, "usage: %s <cmdlog_file> <client_path>\n", argv[0]); + return 1; + } + client = argv[2]; + fd = fopen(argv[1], "r"); + if (fd == NULL) { + fprintf(stderr, "error opening %s\n", argv[1]); + return 1; + } + replay = replay_from_fd(fd); + // some common initialization for all display tests + printf("REPLAY: listening on port %d (unsecure)\n", port); + spice_server_set_port(server, port); + spice_server_set_noauth(server); + spice_server_init(server, core); + + spice_server_add_interface(server, &display_sin.base); + wakeup_timer = core->timer_add(do_wakeup, NULL); + // TODO - wakeup just when a client connects. (todo - wakeup with 0 time) + core->timer_start(wakeup_timer, 1); + start_client(client, port); + basic_event_loop_mainloop(); + end_replay(); + return 0; +} -- 1.7.5.4 _______________________________________________ Spice-devel mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/spice-devel
