[Spice-devel] [RFC spice-gtk 2/5] qos: introduce quality of service to channels

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


From: Victor Toso <me at victortoso.com>

The QOS over spice channels is splitted in a few patches in order to
easy review.

This patch introduces 4 new functions that are only internally exposed
to SpiceChannels, they are:

* spice_session_qos_can_channel_write()
* spice_session_qos_can_channel_read()
* spice_session_qos_channel_has_write_nbytes()
* spice_session_qos_channel_has_read_nbytes()

Prior to each read or write, the SpiceChannel should query if it is
allowed to perform the IO with spice_session_qos_can_channel_write (or
read).

After each successful read or write, the SpiceChannel should inform
the QOS system how many bytes it has write/read.

These functions should perform as fast as possible to not introduce
delays in each IO.

The logic behind QOS will be introduced in follow up patches.

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

Signed-off-by: Victor Toso <victortoso at redhat.com>
---
 src/spice-channel.c      |  28 ++++++++++++
 src/spice-option.c       |   5 ++
 src/spice-session-priv.h |   5 ++
 src/spice-session.c      | 117 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 155 insertions(+)

diff --git a/src/spice-channel.c b/src/spice-channel.c
index b3fd521..f49950d 100644
--- a/src/spice-channel.c
+++ b/src/spice-channel.c
@@ -837,6 +837,13 @@ static gint spice_channel_flush_wire_nonblocking(SpiceChannel *channel,
     return ret;
 }
 
+static gboolean wait_qos_can_flush(gpointer data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(data);
+    SpiceChannelPrivate *c = channel->priv;
+    return spice_session_qos_can_channel_write(c->session, channel);
+}
+
 /*
  * Write all 'data' of length 'datalen' bytes out to
  * the wire
@@ -856,6 +863,12 @@ static void spice_channel_flush_wire(SpiceChannel *channel,
 
         if (c->has_error) return;
 
+        if (!g_coroutine_condition_wait(&c->coroutine, wait_qos_can_flush, channel)) {
+            CHANNEL_DEBUG(channel, "Closing the channel: spice_channel_flush failed qos");
+            c->has_error = TRUE;
+            return;
+        }
+
         ret = spice_channel_flush_wire_nonblocking(channel, ptr+offset, datalen-offset, &cond);
         if (ret == -1) {
             if (cond != 0) {
@@ -875,6 +888,7 @@ static void spice_channel_flush_wire(SpiceChannel *channel,
         }
         offset += ret;
         c->total_write_bytes += ret;
+        spice_session_qos_channel_has_write_nbytes(c->session, channel, ret);
     }
 }
 
@@ -1063,6 +1077,13 @@ static int spice_channel_read_wire_nonblocking(SpiceChannel *channel,
     return ret;
 }
 
+static gboolean wait_qos_can_read(gpointer data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(data);
+    SpiceChannelPrivate *c = channel->priv;
+    return spice_session_qos_can_channel_read(c->session, channel);
+}
+
 /*
  * Read at least 1 more byte of data straight off the wire
  * into the requested buffer.
@@ -1081,6 +1102,12 @@ static int spice_channel_read_wire(SpiceChannel *channel, void *data, size_t len
             return 0;
         }
 
+        if (!g_coroutine_condition_wait(&c->coroutine, wait_qos_can_read, channel)) {
+            CHANNEL_DEBUG(channel, "Closing the channel: spice_channel_read failed qos");
+            c->has_error = TRUE;
+            return 0;
+        }
+
         ret = spice_channel_read_wire_nonblocking(channel, data, len, &cond);
 
         if (ret == -1) {
@@ -1099,6 +1126,7 @@ static int spice_channel_read_wire(SpiceChannel *channel, void *data, size_t len
             return 0;
         }
 
+        spice_session_qos_channel_has_read_nbytes(c->session, channel, ret);
         return ret;
     }
 }
diff --git a/src/spice-option.c b/src/spice-option.c
index c04e978..bf81dea 100644
--- a/src/spice-option.c
+++ b/src/spice-option.c
@@ -36,6 +36,7 @@ static char *usbredir_redirect_on_connect = NULL;
 static gboolean smartcard = FALSE;
 static gboolean disable_audio = FALSE;
 static gboolean disable_usbredir = FALSE;
+static gboolean disable_qos = FALSE;
 static gint cache_size = 0;
 static gint glz_window_size = 0;
 static gchar *secure_channels = NULL;
@@ -203,6 +204,8 @@ GOptionGroup* spice_get_option_group(void)
           N_("Subject of the host certificate (field=value pairs separated by commas)"), N_("<host-subject>") },
         { "spice-disable-audio", '\0', 0, G_OPTION_ARG_NONE, &disable_audio,
           N_("Disable audio support"), NULL },
+        { "spice-disable-qos", '\0', 0, G_OPTION_ARG_NONE, &disable_qos,
+          N_("Disable quality of service"), NULL },
         { "spice-smartcard", '\0', 0, G_OPTION_ARG_NONE, &smartcard,
           N_("Enable smartcard support"), NULL },
         { "spice-smartcard-certificates", '\0', 0, G_OPTION_ARG_STRING, &smartcard_certificates,
@@ -312,6 +315,8 @@ void spice_set_session_option(SpiceSession *session)
         g_object_set(session, "enable-usbredir", FALSE, NULL);
     if (disable_audio)
         g_object_set(session, "enable-audio", FALSE, NULL);
+    if (disable_qos)
+        g_object_set(session, "enable-qos", FALSE, NULL);
     if (cache_size)
         g_object_set(session, "cache-size", cache_size, NULL);
     if (glz_window_size)
diff --git a/src/spice-session-priv.h b/src/spice-session-priv.h
index 03005aa..a65a7c0 100644
--- a/src/spice-session-priv.h
+++ b/src/spice-session-priv.h
@@ -100,6 +100,11 @@ void spice_session_set_main_channel(SpiceSession *session, SpiceChannel *channel
 gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session);
 SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context);
 const gchar* spice_audio_data_mode_to_string(gint mode);
+
+gboolean spice_session_qos_can_channel_write(SpiceSession *session, SpiceChannel *channel);
+gboolean spice_session_qos_can_channel_read(SpiceSession *session, SpiceChannel *channel);
+void spice_session_qos_channel_has_write_nbytes(SpiceSession *session, SpiceChannel *channel, gssize nbytes);
+void spice_session_qos_channel_has_read_nbytes(SpiceSession *session, SpiceChannel *channel, gssize nbytes);
 G_END_DECLS
 
 #endif /* __SPICE_CLIENT_SESSION_PRIV_H__ */
diff --git a/src/spice-session.c b/src/spice-session.c
index 6f8cf5e..f7c35b4 100644
--- a/src/spice-session.c
+++ b/src/spice-session.c
@@ -68,6 +68,9 @@ struct _SpiceSessionPrivate {
     /* whether to enable smartcard event forwarding to the server */
     gboolean          smartcard;
 
+    /* whether to enable quality of service */
+    gboolean          qos_enabled;
+
     /* list of certificates to use for the software smartcard reader if
      * enabled. For now, it has to contain exactly 3 certificates for
      * the software reader to be functional
@@ -123,8 +126,15 @@ struct _SpiceSessionPrivate {
     SpiceUsbDeviceManager *usb_manager;
     SpicePlaybackChannel *playback_channel;
     PhodavServer      *webdav;
+
+    /* QoS */
+    GHashTable        *qos_table;
 };
 
+typedef struct session_channel_qos {
+    bool throttling_write_enabled;
+    bool throttling_read_enabled;
+} session_channel_qos;
 
 /**
  * SECTION:spice-session
@@ -204,6 +214,7 @@ enum {
     PROP_USERNAME,
     PROP_UNIX_PATH,
     PROP_PREF_COMPRESSION,
+    PROP_QOS,
 };
 
 /* signals */
@@ -295,6 +306,7 @@ static void spice_session_init(SpiceSession *session)
     s->glz_window = glz_decoder_window_new();
     update_proxy(session, NULL);
 
+    s->qos_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
     s->usb_manager = spice_usb_device_manager_get(session, &err);
     if (err != NULL) {
         SPICE_DEBUG("Could not initialize SpiceUsbDeviceManager - %s", err->message);
@@ -382,6 +394,7 @@ spice_session_finalize(GObject *gobject)
 
     g_clear_pointer(&s->pubkey, g_byte_array_unref);
     g_clear_pointer(&s->ca, g_byte_array_unref);
+    g_clear_pointer(&s->qos_table, g_hash_table_unref);
 
     /* Chain up to the parent class */
     if (G_OBJECT_CLASS(spice_session_parent_class)->finalize)
@@ -696,6 +709,9 @@ static void spice_session_get_property(GObject    *gobject,
     case PROP_PREF_COMPRESSION:
         g_value_set_enum(value, s->preferred_compression);
         break;
+    case PROP_QOS:
+        g_value_set_boolean(value, s->qos_enabled);
+        break;
     default:
 	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
 	break;
@@ -835,6 +851,9 @@ static void spice_session_set_property(GObject      *gobject,
     case PROP_PREF_COMPRESSION:
         s->preferred_compression = g_value_get_enum(value);
         break;
+    case PROP_QOS:
+        s->qos_enabled = g_value_get_boolean(value);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
         break;
@@ -1260,6 +1279,22 @@ static void spice_session_class_init(SpiceSessionClass *klass)
                              G_PARAM_READWRITE |
                              G_PARAM_STATIC_STRINGS));
 
+    /**
+     * SpiceSession:enable-qos:
+     *
+     * If set to TRUE, the quality of service for channels will be enabled.
+     *
+     * Since: 0.34
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_QOS,
+         g_param_spec_boolean("enable-qos",
+                          "Enable quality of service",
+                          "Enable quality of service",
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
 
     /**
      * SpiceSession::channel-new:
@@ -1529,6 +1564,7 @@ SpiceSession *spice_session_new_from_session(SpiceSession *session)
                  "enable-smartcard", &c->smartcard,
                  "enable-audio", &c->audio,
                  "enable-usbredir", &c->usbredir,
+                 "enable-qos", &c->qos_enabled,
                  "ca", &c->ca,
                  NULL);
 
@@ -2226,12 +2262,17 @@ void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel)
 
     SpiceSessionPrivate *s = session->priv;
     struct channel *item;
+    session_channel_qos *qos;
 
 
     item = g_new0(struct channel, 1);
     item->channel = channel;
     ring_add(&s->channels, &item->link);
 
+    qos = g_new0(session_channel_qos, 1);
+    qos->throttling_write_enabled = qos->throttling_read_enabled = false;
+    g_hash_table_insert(s->qos_table, channel, qos);
+
     if (SPICE_IS_MAIN_CHANNEL(channel)) {
         gboolean all = spice_strv_contains(s->disable_effects, "all");
 
@@ -2805,3 +2846,79 @@ gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession
 
     return TRUE;
 }
+
+G_GNUC_INTERNAL
+gboolean spice_session_qos_can_channel_write(SpiceSession *session,
+                                             SpiceChannel *channel)
+{
+    session_channel_qos *qos;
+    SpiceSessionPrivate *s;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
+
+    s = session->priv;
+    if (!s->qos_enabled)
+        return true;
+
+    qos = g_hash_table_lookup(s->qos_table, channel);
+    g_return_val_if_fail(qos != NULL, FALSE);
+
+    return !qos->throttling_write_enabled;
+}
+
+G_GNUC_INTERNAL
+gboolean spice_session_qos_can_channel_read(SpiceSession *session,
+                                            SpiceChannel *channel)
+{
+    session_channel_qos *qos;
+    SpiceSessionPrivate *s;
+
+    g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
+    g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);
+
+    s = session->priv;
+    if (!s->qos_enabled)
+        return true;
+
+    qos = g_hash_table_lookup(s->qos_table, channel);
+    g_return_val_if_fail(qos != NULL, FALSE);
+
+    return !qos->throttling_read_enabled;
+}
+
+G_GNUC_INTERNAL
+void spice_session_qos_channel_has_read_nbytes(SpiceSession *session,
+                                               SpiceChannel *channel,
+                                               gssize nbytes)
+{
+    SpiceSessionPrivate *s;
+
+    g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+
+    s = session->priv;
+    if (!s->qos_enabled)
+        return;
+
+    /* TODO */
+    return;
+}
+
+G_GNUC_INTERNAL
+void spice_session_qos_channel_has_write_nbytes(SpiceSession *session,
+                                                SpiceChannel *channel,
+                                                gssize nbytes)
+{
+    SpiceSessionPrivate *s;
+
+    g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(SPICE_IS_CHANNEL(channel));
+
+    s = session->priv;
+    if (!s->qos_enabled)
+        return;
+
+    /* TODO */
+    return;
+}
-- 
2.9.3



More information about the Spice-devel mailing list