[Spice-devel] [RfC 4/4] server/tests/replay: introduce

Alon Levy alevy at redhat.com
Fri Jul 1 19:49:45 PDT 2011


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



More information about the Spice-devel mailing list