diff options
author | Victor Toso <victortoso@redhat.com> | 2015-10-02 09:52:28 +0200 |
---|---|---|
committer | Victor Toso <victortoso@redhat.com> | 2015-10-09 08:27:02 +0200 |
commit | 033acd151fb8e6182ed1d7cf1156badfd1333232 (patch) | |
tree | b65fdee760d7bb4ed5b4d0cb03280aec0fa3d874 | |
parent | 6c323ca9fd13937e8557d55cec8898a6259a125f (diff) |
channel-main: Implement client side ping protocol
As a good way to provide spice-gtk components information regarding the
current connection as in the case of usbredir to know if a usb device
will can work with current bandwith.
This could be improved to provide generic information to clients.
Note: This patch alone breaks the build with -Werror=unused-function:
'spice_main_channel_send_ping' defined but not used
Related: https://bugzilla.redhat.com/show_bug.cgi?id=1264156
Related: https://bugs.freedesktop.org/show_bug.cgi?id=87324
-rw-r--r-- | src/channel-main.c | 127 |
1 files changed, 126 insertions, 1 deletions
diff --git a/src/channel-main.c b/src/channel-main.c index 6e66401..d47c188 100644 --- a/src/channel-main.c +++ b/src/channel-main.c @@ -31,7 +31,6 @@ #include "spice-channel-priv.h" #include "spice-session-priv.h" #include "spice-audio-priv.h" - /** * SECTION:channel-main * @short_description: the main Spice channel @@ -53,6 +52,11 @@ #define MAX_DISPLAY 16 /* Note must fit in a guint32, see monitors_align */ +#define ZERO_PAGE_SIZE 4096 + +#define NET_STATS_NUM_SAMPLES 10 +#define NET_STATS_BANDWIDTH_MAX_FAIL 5 + typedef struct spice_migrate spice_migrate; #define FILE_XFER_CHUNK_SIZE (VD_AGENT_MAX_DATA_SIZE * 32) @@ -82,6 +86,32 @@ typedef enum { DISPLAY_ENABLED, } SpiceDisplayState; +typedef struct _SpiceNetworkStatus { + /* TODO: we might want to improve this with something + * like EggCounter (GNOME Builder) in the future. */ + guint num_tries; + guint num_fails; + + gdouble latency; + gdouble bitrate; +} SpiceNetworkStatus; + +typedef enum { + SPICE_PING_ID_LATENCY, + SPICE_PING_ID_RATE_1KB, + SPICE_PING_ID_RATE_10KB, + SPICE_PING_ID_RATE_20KB, + SPICE_PING_ID_LAST, +} SpiceMsgcPingID; + +static const guint64 ping_id_data_size[] = { + [SPICE_PING_ID_LATENCY] = 0, + [SPICE_PING_ID_RATE_1KB] = 1024, + [SPICE_PING_ID_RATE_10KB] = 1024 * 10, + [SPICE_PING_ID_RATE_20KB] = 1024 * 20, + [SPICE_PING_ID_LAST] = G_MAXUINT64 +}; + struct _SpiceMainChannelPrivate { enum SpiceMouseMode mouse_mode; bool agent_connected; @@ -121,6 +151,8 @@ struct _SpiceMainChannelPrivate { gboolean agent_volume_playback_sync; gboolean agent_volume_record_sync; GCancellable *cancellable_volume_info; + + SpiceNetworkStatus net_stats; }; struct spice_migrate { @@ -186,6 +218,7 @@ static void file_xfer_completed(SpiceFileXferTask *task, GError *error); static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success); static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max); static void set_agent_connected(SpiceMainChannel *channel, gboolean connected); +static gboolean spice_main_channel_send_ping(SpiceMainChannel *channel, SpiceMsgcPingID ping_id); /* ------------------------------------------------------------------ */ @@ -1616,6 +1649,62 @@ static void main_handle_mm_time(SpiceChannel *channel, SpiceMsgIn *in) spice_session_set_mm_time(session, msg->time); } +/* coroutine context */ +static void main_handle_pong(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv; + gint64 roundtrip, now; + SpiceMsgPing *ping; + + now = g_get_monotonic_time(); + ping = spice_msg_in_parsed(in); + roundtrip = now - ping->timestamp; + g_assert_cmpint(ping->id, <, SPICE_PING_ID_LAST); + g_assert_cmpint(roundtrip, >, 0); + + switch (ping->id) { + case SPICE_PING_ID_LATENCY: + c->net_stats.latency = (c->net_stats.num_tries * c->net_stats.latency + roundtrip); + c->net_stats.latency /= (c->net_stats.num_tries + 1); + CHANNEL_DEBUG(channel, "(network-stats)[%d] latency: %.5f ms (%lu us) - " + "average is %.5f ms (%.5f us)", + ping->id, roundtrip/1000.0, roundtrip, + c->net_stats.latency/1000.0, c->net_stats.latency); + break; + case SPICE_PING_ID_RATE_1KB: + case SPICE_PING_ID_RATE_10KB: + case SPICE_PING_ID_RATE_20KB: + if (roundtrip <= c->net_stats.latency) { + c->net_stats.num_fails++; + CHANNEL_DEBUG(channel, "(network-stats)[%d] Bandwidth measurment disconsidered: " + "roundtrip (%lu us) < latency (%.5f us) [fails: %u]", + ping->id, roundtrip, c->net_stats.latency, + c->net_stats.num_fails); + if (c->net_stats.num_fails >= NET_STATS_BANDWIDTH_MAX_FAIL) { + /* Network may have changed, too many failures in a row. Start over. */ + memset(&c->net_stats, 0, sizeof(SpiceNetworkStatus)); + CHANNEL_DEBUG(channel, "(network-stats) -- restarting network status"); + } + } else { + gdouble bps = (guint64)(ping_id_data_size[ping->id] * 8) * G_USEC_PER_SEC; + bps /= (roundtrip - c->net_stats.latency); + c->net_stats.bitrate = (c->net_stats.num_tries * c->net_stats.bitrate + bps); + c->net_stats.bitrate /= (c->net_stats.num_tries + 1); + CHANNEL_DEBUG (channel, "(network-stats)[%d] bandwidth: %.5f Mbps (%.5f bps) - " + "average is %.5f Mbps (%.5f bps)", + ping->id, bps/1024.0/1024.0, bps, + c->net_stats.bitrate/1024.0/1024.0, c->net_stats.bitrate); + c->net_stats.num_fails = 0; + } + break; + default: + g_warn_if_reached(); + } + + if (c->net_stats.num_tries < NET_STATS_NUM_SAMPLES) + c->net_stats.num_tries += 1; +} + typedef struct channel_new { SpiceSession *session; int type; @@ -2516,6 +2605,41 @@ static void main_handle_migrate_cancel(SpiceChannel *channel, spice_session_abort_migration(session); } +/* main context */ +static gboolean spice_main_channel_send_ping(SpiceMainChannel *main_channel, SpiceMsgcPingID ping_id) +{ + SpiceMsgPing ping; + SpiceMsgOut *msg_out; + SpiceChannel *channel = SPICE_CHANNEL(main_channel); + guint8 zero_page[ZERO_PAGE_SIZE] = {0}; + guint size_left = 0; + g_assert_cmpint(ping_id, <, SPICE_PING_ID_LAST); + + if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_PING)) { + CHANNEL_DEBUG(main_channel, "Spice Server does not handle client's PING"); + return FALSE; + } + + msg_out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_PING); + + ping.id = ping_id; + ping.timestamp = g_get_monotonic_time(); + msg_out->marshallers->msgc_main_ping(msg_out->marshaller, &ping); + /* Depending of ping_id we will send more data */ + size_left = ping_id_data_size[ping_id]; + if (size_left > 0) { + size_left -= spice_marshaller_get_total_size(msg_out->marshaller); + while (size_left > 0) { + guint size = MIN(ZERO_PAGE_SIZE, size_left); + size_left -= size; + spice_marshaller_add_ref(msg_out->marshaller, zero_page, size); + } + } + + spice_msg_out_send(msg_out); + return TRUE; +} + static void channel_set_handlers(SpiceChannelClass *klass) { static const spice_msg_handler handlers[] = { @@ -2525,6 +2649,7 @@ static void channel_set_handlers(SpiceChannelClass *klass) [ SPICE_MSG_MAIN_CHANNELS_LIST ] = main_handle_channels_list, [ SPICE_MSG_MAIN_MOUSE_MODE ] = main_handle_mouse_mode, [ SPICE_MSG_MAIN_MULTI_MEDIA_TIME ] = main_handle_mm_time, + [ SPICE_MSG_MAIN_PONG ] = main_handle_pong, [ SPICE_MSG_MAIN_AGENT_CONNECTED ] = main_handle_agent_connected, [ SPICE_MSG_MAIN_AGENT_DISCONNECTED ] = main_handle_agent_disconnected, |