diff options
author | Alon Levy <alevy@redhat.com> | 2011-07-01 19:49:42 +0300 |
---|---|---|
committer | Alon Levy <alevy@redhat.com> | 2012-06-07 14:09:20 +0300 |
commit | 576c7a4bdd6cd0aa79d53d1681e3a79d9751587b (patch) | |
tree | 967be8072dcba97ba2d01423b74957ade8c666fc | |
parent | a4b904bdd34942a59d4e7022525006ecb71c869a (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.syms | 2 | ||||
-rw-r--r-- | server/tests/Makefile.am | 9 | ||||
-rw-r--r-- | server/tests/replay.c | 296 |
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; +} |