[Spice-devel] [RFC spice-gtk 3/5] qos: bandwidth computation

Victor Toso victortoso at redhat.com
Fri Apr 7 13:19:28 UTC 2017


From: Victor Toso <me at victortoso.com>

Calculating channels' write and read rate and overall bandwidth of the
session will play an important role on how to apply throttling in
specific channels whenever is necessary.

As the ping and bandwidth data gathered with from server is not shared
with the client, it might take a while to have an accurate session's
bandwidth data.

Related: https://bugs.freedesktop.org/show_bug.cgi?id=96598
Related: https://bugs.freedesktop.org/show_bug.cgi?id=87324

Signed-off-by: Victor Toso <victortoso at redhat.com>
---
 src/spice-session.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 108 insertions(+), 1 deletion(-)

diff --git a/src/spice-session.c b/src/spice-session.c
index f7c35b4..d270c63 100644
--- a/src/spice-session.c
+++ b/src/spice-session.c
@@ -129,11 +129,24 @@ struct _SpiceSessionPrivate {
 
     /* QoS */
     GHashTable        *qos_table;
+    guint             qos_main_id;
+    gdouble           maximum_read_rate;
+    gdouble           maximum_write_rate;
 };
 
+#define QOS_BANDWITH_TIMEOUT    5 /* Seconds */
+
 typedef struct session_channel_qos {
     bool throttling_write_enabled;
     bool throttling_read_enabled;
+    /* Last write/read rate in mega bytes per second */
+    gdouble write_rate;
+    gdouble read_rate;
+
+    /* To calculate the write and read rate for this channel */
+    gint64 time;
+    gsize data_written;
+    gsize data_read;
 } session_channel_qos;
 
 /**
@@ -334,6 +347,9 @@ session_disconnect(SpiceSession *self, gboolean keep_main)
         }
     }
 
+    if (s->qos_main_id > 0)
+        g_source_remove(s->qos_main_id);
+
     s->connection_id = 0;
 
     g_clear_pointer(&s->name, g_free);
@@ -1577,6 +1593,8 @@ SpiceSession *spice_session_new_from_session(SpiceSession *session)
     return copy;
 }
 
+static gboolean qos_main(gpointer user_data);
+
 /**
  * spice_session_connect:
  * @session: a #SpiceSession
@@ -1607,7 +1625,13 @@ gboolean spice_session_connect(SpiceSession *session)
         s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
 
     glz_decoder_window_clear(s->glz_window);
-    return spice_channel_connect(s->cmain);
+    if (!spice_channel_connect(s->cmain))
+        return FALSE;
+
+    if (s->qos_enabled)
+        s->qos_main_id = g_timeout_add_seconds(QOS_BANDWITH_TIMEOUT, qos_main, session);
+
+    return TRUE;
 }
 
 /**
@@ -2271,6 +2295,7 @@ void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel)
 
     qos = g_new0(session_channel_qos, 1);
     qos->throttling_write_enabled = qos->throttling_read_enabled = false;
+    qos->time = g_get_monotonic_time();
     g_hash_table_insert(s->qos_table, channel, qos);
 
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
@@ -2825,6 +2850,88 @@ gboolean spice_session_is_for_migration(SpiceSession *session)
     return session->priv->for_migration;
 }
 
+/* Update data on given session_channel_qos */
+static void update_spice_channel_qos_data(SpiceChannel *channel,
+                                          session_channel_qos *qos)
+{
+    gsize total_read_bytes, total_write_bytes;
+    gint64 time;
+    gdouble wrate, rrate, data, interval;
+
+    g_object_get(channel,
+                 "total-read-bytes", &total_read_bytes,
+                 "total-write-bytes", &total_write_bytes,
+                 NULL);
+
+    /* We should always store the time and data even if the channel is being
+     * throttled by QOS */
+    time = g_get_monotonic_time();
+    interval = time - qos->time;
+    /* Rate are stored in mibit per second */
+    data = total_write_bytes - qos->data_written;
+    wrate = (data * 1000000.0) / (interval * 1024.0 * 1024.0);
+    data = total_read_bytes - qos->data_read;
+    rrate = (data * 1000000.0) / (interval * 1024.0 * 1024.0);
+
+    qos->write_rate = wrate;
+    qos->read_rate = rrate;
+
+    qos->time = time;
+    qos->data_written = total_write_bytes;
+    qos->data_read = total_read_bytes;
+}
+
+/**
+ * qos_main
+ *
+ * This function verifies the status of each SpiceChannel using the
+ * session_channel_qos structure and might try to apply Quality of Service
+ * heuristics for better network usage in SpiceSession.
+ *
+ * If a high priority channel is 'waiting' to perform IO, we will perform
+ * throttling to low priority channels.
+ **/
+static gboolean qos_main(gpointer user_data)
+{
+    SpiceSession *self = user_data;
+    SpiceSessionPrivate *s = self->priv;
+    GList *iter, *list;
+    gdouble session_wrate = 0.0, session_rrate = 0.0;
+
+    list = spice_session_get_channels(self);
+    for (iter = list; iter != NULL; iter = iter->next) {
+        SpiceChannel *channel;
+        session_channel_qos *qos;
+
+        channel = SPICE_CHANNEL(iter->data);
+        qos = g_hash_table_lookup(s->qos_table, channel);
+        g_return_val_if_fail(qos != NULL, G_SOURCE_REMOVE);
+
+        update_spice_channel_qos_data(channel, qos);
+
+        /* For the session */
+        session_wrate += qos->write_rate;
+        session_rrate += qos->read_rate;
+    }
+
+    if (s->maximum_write_rate < session_wrate) {
+        spice_debug("[qos] Maximum write rate changed: %2.4f -> %2.4f MB/s",
+                    s->maximum_write_rate, session_wrate);
+
+        s->maximum_write_rate = session_wrate;
+    }
+
+    if (s->maximum_read_rate < session_rrate) {
+        spice_debug("[qos] Maximum read rate changed: %2.4f -> %2.4f MB/s",
+                    s->maximum_read_rate, session_rrate);
+
+        s->maximum_read_rate = session_rrate;
+    }
+
+    g_list_free(list);
+    return G_SOURCE_CONTINUE;
+}
+
 G_GNUC_INTERNAL
 void spice_session_set_main_channel(SpiceSession *session, SpiceChannel *channel)
 {
-- 
2.9.3



More information about the Spice-devel mailing list