summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Levy <alevy@redhat.com>2011-07-01 19:49:42 +0300
committerAlon Levy <alevy@redhat.com>2012-06-07 14:09:20 +0300
commit576c7a4bdd6cd0aa79d53d1681e3a79d9751587b (patch)
tree967be8072dcba97ba2d01423b74957ade8c666fc
parenta4b904bdd34942a59d4e7022525006ecb71c869a (diff)
server/tests/replay: introducefeatures/record-replay-qxl-hack
usage: replay <cmdfile> <port> <client command line> 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> 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 - 12345 `which spicy-stats` -h localhost -p 12345 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
-rw-r--r--server/spice-server.syms2
-rw-r--r--server/tests/Makefile.am9
-rw-r--r--server/tests/replay.c296
3 files changed, 307 insertions, 0 deletions
diff --git a/server/spice-server.syms b/server/spice-server.syms
index 99a72718..eea7952f 100644
--- a/server/spice-server.syms
+++ b/server/spice-server.syms
@@ -111,4 +111,6 @@ global:
SPICE_SERVER_0.10.3 {
spice_server_is_server_mouse;
+ replay_next_cmd;
+ replay_from_fd;
} SPICE_SERVER_0.10.2;
diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
index e77865c7..9ddd22c1 100644
--- a/server/tests/Makefile.am
+++ b/server/tests/Makefile.am
@@ -33,6 +33,7 @@ noinst_PROGRAMS = \
test_just_sockets_no_ssl \
test_playback \
test_display_resolution_changes \
+ replay \
$(NULL)
test_display_streaming_SOURCES = \
@@ -73,3 +74,11 @@ test_empty_success_SOURCES = \
test_fail_on_null_core_interface_SOURCES = \
test_fail_on_null_core_interface.c \
$(NULL)
+
+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 00000000..ba4a32d2
--- /dev/null
+++ b/server/tests/replay.c
@@ -0,0 +1,296 @@
+/* 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 <spice/macros.h>
+#include "red_replay_qxl.h"
+#include "test_display_base.h"
+
+static SpiceCoreInterface *core;
+static SpiceServer *server;
+static Replay *replay;
+
+static void do_wakeup(void *opaque);
+
+#define MEM_SLOT_GROUP_ID 0
+
+/* Parts cribbed from spice-display.h/.c/qxl.c */
+
+static QXLWorker *qxl_worker = NULL;
+
+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 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;
+pid_t client_pid;
+
+// called from spice_server thread (i.e. red_worker thread)
+static int get_command(QXLInstance *qin, struct QXLCommandExt *ext)
+{
+ if (!wakeup_timer) {
+ 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 void create_wakeup_timer(void)
+{
+ wakeup_timer = core->timer_add(do_wakeup, NULL);
+ core->timer_start(wakeup_timer, 1);
+}
+
+static int req_cmd_notification(QXLInstance *qin)
+{
+ if (!wakeup_timer) {
+ create_wakeup_timer();
+ } else {
+ 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;
+}
+
+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 = 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,
+};
+
+static QXLInstance display_sin = {
+ .base = {
+ .sif = &display_sif.base,
+ },
+ .id = 0,
+};
+
+static void replay_channel_event(int event, SpiceChannelEventInfo *info)
+{
+ static int channels = 0;
+
+ if (info->type == SPICE_CHANNEL_DISPLAY) {
+ create_wakeup_timer();
+ }
+}
+
+static void start_client(char **argv)
+{
+ pid_t pid;
+
+ if ((pid = fork()) == 0) {
+ fclose(replay->fd); // close the command file
+ /* child */
+ fprintf(stderr, "launching %s\n", argv[0]);
+ execv(argv[0], argv);
+ } else {
+ client_pid = pid;
+ }
+}
+
+void usage(void *program)
+{
+ fprintf(stderr, "usage: %s <cmdlog_file> [<port> <client args>]\n", program);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int port;
+ char **client_argv = NULL;
+ FILE *fd;
+
+ core = basic_event_loop_init();
+ core->channel_event = replay_channel_event;
+ SpiceServer* server = spice_server_new();
+
+ ext_cmd_last.cmd.data = 0;
+ pthread_mutex_init(&ext_cmd_mutex, NULL);
+ if (argc == 3) {
+ usage(argv[0]);
+ return 1; /* never reached */
+ }
+ if (argc >= 4) {
+ port = atoi(argv[2]);
+ if (port == 0) {
+ usage(argv[0]);
+ }
+ client_argv = &argv[3];
+ }
+ if (strncmp(argv[1], "-", 1) == 0) {
+ fd = stdin;
+ } else {
+ fd = fopen(argv[1], "r");
+ }
+ if (fd == NULL) {
+ fprintf(stderr, "error opening %s\n", argv[1]);
+ return 1;
+ }
+ replay = replay_from_fd(fd);
+ 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);
+ if (client_argv) {
+ start_client(client_argv);
+ } else {
+ create_wakeup_timer();
+ }
+ basic_event_loop_mainloop();
+ end_replay();
+ return 0;
+}