summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Toso <victortoso@redhat.com>2015-10-05 07:44:43 +0200
committerVictor Toso <victortoso@redhat.com>2015-10-09 08:34:14 +0200
commitb020fcbd617890e91030e998de6eb7e809a2de55 (patch)
treea2d3a6dd575f6a57f4ef88272ed619c35fbf551b
parent033acd151fb8e6182ed1d7cf1156badfd1333232 (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.c227
-rw-r--r--src/spice-channel-priv.h10
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__ */