summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Toso <victortoso@redhat.com>2015-10-02 09:52:28 +0200
committerVictor Toso <victortoso@redhat.com>2015-10-09 08:27:02 +0200
commit033acd151fb8e6182ed1d7cf1156badfd1333232 (patch)
treeb65fdee760d7bb4ed5b4d0cb03280aec0fa3d874
parent6c323ca9fd13937e8557d55cec8898a6259a125f (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.c127
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,