diff options
author | Victor Toso <victortoso@redhat.com> | 2015-10-05 07:44:43 +0200 |
---|---|---|
committer | Victor Toso <victortoso@redhat.com> | 2015-10-09 08:34:14 +0200 |
commit | b020fcbd617890e91030e998de6eb7e809a2de55 (patch) | |
tree | a2d3a6dd575f6a57f4ef88272ed619c35fbf551b | |
parent | 033acd151fb8e6182ed1d7cf1156badfd1333232 (diff) |
wip: ping-pong from client is workingclient-side-bandwidth-monitoring
This patch creates an internal function to test the ping-pong protocol
from channel-main. This could be an internal api or the base for
bandwidth monitoring in client side.
-rw-r--r-- | src/channel-main.c | 227 | ||||
-rw-r--r-- | src/spice-channel-priv.h | 10 |
2 files changed, 224 insertions, 13 deletions
diff --git a/src/channel-main.c b/src/channel-main.c index d47c188..6487b18 100644 --- a/src/channel-main.c +++ b/src/channel-main.c @@ -55,7 +55,8 @@ #define ZERO_PAGE_SIZE 4096 #define NET_STATS_NUM_SAMPLES 10 -#define NET_STATS_BANDWIDTH_MAX_FAIL 5 +#define NET_STATS_PING_DELAY_MS 100 +#define NET_STATS_NUM_LATENCY_TESTS 3 typedef struct spice_migrate spice_migrate; @@ -86,12 +87,24 @@ typedef enum { DISPLAY_ENABLED, } SpiceDisplayState; +struct network_async_task { + SpiceMainChannel *main_channel; + GSimpleAsyncResult *res; + GAsyncReadyCallback callback; + gpointer user_data; + gulong cancel_id; + GCancellable *cancellable; + guint current_test; + guint ping_timer_id; +}; + typedef struct _SpiceNetworkStatus { /* TODO: we might want to improve this with something - * like EggCounter (GNOME Builder) in the future. */ + * like EggCounter (GNOME Builder) in the future... + * Improving the network status is desirable: + * https://bugs.freedesktop.org/show_bug.cgi?id=87324 */ guint num_tries; guint num_fails; - gdouble latency; gdouble bitrate; } SpiceNetworkStatus; @@ -153,6 +166,7 @@ struct _SpiceMainChannelPrivate { GCancellable *cancellable_volume_info; SpiceNetworkStatus net_stats; + gboolean net_stats_ongoing_op; }; struct spice_migrate { @@ -1243,6 +1257,7 @@ static void audio_record_volume_info_cb(GObject *object, GAsyncResult *res, gpoi __func__, spice_yes_no(mute), nchannels, volume[0]); g_free(volume); agent_msg_queue(main_channel, VD_AGENT_AUDIO_VOLUME_SYNC, + sizeof(VDAgentAudioVolumeSync) + array_size, avs); } @@ -1664,8 +1679,7 @@ static void main_handle_pong(SpiceChannel *channel, SpiceMsgIn *in) 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); + c->net_stats.latency = MIN(c->net_stats.latency, roundtrip); CHANNEL_DEBUG(channel, "(network-stats)[%d] latency: %.5f ms (%lu us) - " "average is %.5f ms (%.5f us)", ping->id, roundtrip/1000.0, roundtrip, @@ -1673,20 +1687,16 @@ static void main_handle_pong(SpiceChannel *channel, SpiceMsgIn *in) break; case SPICE_PING_ID_RATE_1KB: case SPICE_PING_ID_RATE_10KB: - case SPICE_PING_ID_RATE_20KB: + case SPICE_PING_ID_RATE_20KB: { + gdouble bps; 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 = (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); @@ -1694,15 +1704,19 @@ static void main_handle_pong(SpiceChannel *channel, SpiceMsgIn *in) "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; + + if (c->net_stats_ongoing_op + && ping->id == SPICE_PING_ID_LAST - 1) + c->net_stats_ongoing_op = FALSE; } typedef struct channel_new { @@ -2028,6 +2042,24 @@ static void spice_main_set_max_clipboard(SpiceMainChannel *self, gint max) spice_channel_wakeup(SPICE_CHANNEL(self), FALSE); } +static void fetch_network_status_cb(GObject *object, GAsyncResult *res, gpointer user_data) +{ + SpiceChannel *channel = user_data; + gdouble latency, bitrate; + GError *error = NULL; + + CHANNEL_DEBUG(channel, "-----------------------------------------------------"); + spice_network_status_finish(channel, res, &latency, &bitrate, &error); + CHANNEL_DEBUG(channel, "LATENCY: %.5f ms", latency/1000.0); + CHANNEL_DEBUG(channel, "BITRATE: %.5f Mbps", bitrate/1024.0/1024.0); + CHANNEL_DEBUG(channel, "-----------------------------------------------------"); +} + +static void fetch_network_status(SpiceChannel *channel) +{ + spice_network_status_async(channel, NULL, fetch_network_status_cb, channel); +} + /* coroutine context */ static void main_agent_handle_msg(SpiceChannel *channel, VDAgentMessage *msg, gpointer payload) @@ -2640,6 +2672,174 @@ static gboolean spice_main_channel_send_ping(SpiceMainChannel *main_channel, Spi return TRUE; } +static gboolean free_network_async_task(gpointer user_data) +{ + struct network_async_task *task = user_data; + + if (task == NULL) + return G_SOURCE_REMOVE; + + if (task->res) + g_object_unref(task->res); + + if (task->main_channel) + g_object_unref(task->main_channel); + + if (task->cancel_id != 0) { + g_cancellable_disconnect(task->cancellable, task->cancel_id); + g_clear_object(&task->cancellable); + } + + g_free(task); + return G_SOURCE_REMOVE; +} + +static gboolean network_status_execute(gpointer task_data) +{ + struct network_async_task *task = task_data; + SpiceMainChannelPrivate *c = task->main_channel->priv; + + if (task->current_test == SPICE_PING_ID_LATENCY) { + static guint counter = 0; + spice_main_channel_send_ping(task->main_channel, task->current_test); + counter++; + if ((counter % NET_STATS_NUM_LATENCY_TESTS) == 0) { + task->current_test += 1; + } + return G_SOURCE_CONTINUE; + } + + if (task->current_test < SPICE_PING_ID_LAST) { + spice_main_channel_send_ping(task->main_channel, task->current_test); + task->current_test++; + return G_SOURCE_CONTINUE; + } + + if (c->net_stats_ongoing_op) + return G_SOURCE_CONTINUE; + + if (c->net_stats.bitrate == 0) { + /* Failed to calculate the bitrate, possible due high latency */ + g_simple_async_result_set_op_res_gboolean(task->res, FALSE); + } else { + g_simple_async_result_set_op_res_gboolean(task->res, TRUE); + } + + CHANNEL_DEBUG(task->main_channel, "(network-stats) Operation finished - removing source"); + g_simple_async_result_complete(task->res); + free_network_async_task(task); + return G_SOURCE_REMOVE; +} + +static void network_cancel_task(GCancellable *cancellable, gpointer user_data) +{ + struct network_async_task *task = user_data; + g_return_if_fail(task != NULL); + + if (task->ping_timer_id != 0) { + /* We don't need to send more pings */ + g_source_remove(task->ping_timer_id); + } + +#if GLIB_CHECK_VERSION(2,40,0) + free_network_async_task(task); +#else + +# if !GLIB_CHECK_VERSION(2,32,0) + /* g_simple_async_result_set_check_cancellable is not present. Set an error + * in the GSimpleAsyncResult in case of _finish functions is called */ + g_simple_async_result_set_error(task->res, + SPICE_CLIENT_ERROR, + SPICE_CLIENT_ERROR_FAILED, + "Operation was cancelled"); +# endif + /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=705395 + * Free the memory in idle */ + g_idle_add(free_network_async_task, task); +#endif +} + +G_GNUC_INTERNAL +gboolean spice_network_status_finish(SpiceChannel *channel, + GAsyncResult *res, + gdouble *latency, + gdouble *bitrate, + GError **error) +{ + SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv; + GSimpleAsyncResult *simple = (GSimpleAsyncResult *) res; + + g_return_val_if_fail(g_simple_async_result_is_valid(res, + G_OBJECT(channel), spice_network_status_async), FALSE); + + if (g_simple_async_result_propagate_error(simple, error)) { + return FALSE; + } + + if (latency != NULL) { + *latency = c->net_stats.latency; + } + + if (bitrate != NULL) { + *bitrate = c->net_stats.bitrate; + } + + return g_simple_async_result_get_op_res_gboolean(simple); +} + +G_GNUC_INTERNAL +void spice_network_status_async(SpiceChannel *channel, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + struct network_async_task *task; + SpiceMainChannelPrivate *c; + + spice_assert(SPICE_IS_MAIN_CHANNEL(channel)); + c = SPICE_MAIN_CHANNEL(channel)->priv; + + if (c->net_stats_ongoing_op) { + /* FIXME: In case we want to expand net-stat to public api, + * we should handle multiple tasks i.e. by queuing them and + * when first task finish, we call the callback of all */ + CHANNEL_DEBUG(channel, "(network-stats) ongoing network status " + "operation. Only one request supported."); + return; + } + + task = g_malloc0(sizeof(struct network_async_task)); + simple = g_simple_async_result_new(G_OBJECT(channel), + callback, + user_data, + spice_network_status_async); +#if GLIB_CHECK_VERSION(2,32,0) + g_simple_async_result_set_check_cancellable (simple, cancellable); +#endif + + task->res = simple; + task->callback = callback; + task->user_data = user_data; + task->main_channel = g_object_ref(channel); + + if (cancellable) { + task->cancellable = g_object_ref(cancellable); + task->cancel_id = g_cancellable_connect(cancellable, + G_CALLBACK(network_cancel_task), + task, + NULL); + } + + /* Always start with empty stats */ + c->net_stats_ongoing_op = TRUE; + memset(&c->net_stats, 0, sizeof(SpiceNetworkStatus)); + c->net_stats.latency = G_MAXUINT64; + task->ping_timer_id = g_timeout_add(NET_STATS_PING_DELAY_MS, + network_status_execute, + task); +} + static void channel_set_handlers(SpiceChannelClass *klass) { static const spice_msg_handler handlers[] = { @@ -2839,6 +3039,7 @@ void spice_main_clipboard_selection_release(SpiceMainChannel *channel, guint sel agent_clipboard_release(channel, selection); spice_channel_wakeup(SPICE_CHANNEL(channel), FALSE); + fetch_network_status(SPICE_CHANNEL(channel)); } /** diff --git a/src/spice-channel-priv.h b/src/spice-channel-priv.h index 436a521..470fd5c 100644 --- a/src/spice-channel-priv.h +++ b/src/spice-channel-priv.h @@ -203,6 +203,16 @@ void spice_vmc_write_async(SpiceChannel *self, gssize spice_vmc_write_finish(SpiceChannel *self, GAsyncResult *result, GError **error); +void spice_network_status_async(SpiceChannel *channel, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean spice_network_status_finish(SpiceChannel *channel, + GAsyncResult *res, + gdouble *latency, + gdouble *bitrate, + GError **error); + G_END_DECLS #endif /* __SPICE_CLIENT_CHANNEL_PRIV_H__ */ |