[Spice-devel] [RFC spice-gtk 5/5] spicy: save data measurements for each channel
Victor Toso
victortoso at redhat.com
Fri Apr 7 13:19:30 UTC 2017
From: Victor Toso <me at victortoso.com>
In order to verify the network usage per channel, we introduce the
--save-network-data option to spicy which will store the network data
in the following format:
index[1], s-write, s-read, ch1-write, ch1-read, ..., chN-write, chN-read
index[2], s-write, s-read, ch1-write, ch1-read, ..., chN-write, chN-read
index[3], s-write, s-read, ch1-write, ch1-read, ..., chN-write, chN-read
...
#column 2 session write
#column 3 session read
#column 4 channel1-name write
...
With this format, we can use the data to plot charts of the overall
network usage per channel. A tiny script is provided that uses gnuplot
for plotting, it should be used as:
$ ./tools/spicy-gnuplot.sh spicy-network-data-HHMMSS
Signed-off-by: Victor Toso <victortoso at redhat.com>
---
tools/spicy-gnuplot.sh | 19 ++++
tools/spicy.c | 259 ++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 277 insertions(+), 1 deletion(-)
create mode 100755 tools/spicy-gnuplot.sh
diff --git a/tools/spicy-gnuplot.sh b/tools/spicy-gnuplot.sh
new file mode 100755
index 0000000..87acb8a
--- /dev/null
+++ b/tools/spicy-gnuplot.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# FIXME:
+# Not putting too much effort on this for now. It should be a python
+# script with more arguments to generate different tipes of charts
+function plot_spicy_file()
+{
+ local data=$(grep "#column" $1 | \
+ awk -v file=$1 '{print "\""file "\" using 1:"$2" title \""$3" "$4"\", "}')
+ data="set key outside; set style data lines; plot $data"
+ gnuplot -p -e "$data"
+}
+
+if [ "$#" -ne 1 ] || ! [ -f "$1" ]; then
+ echo "Usage: $0 <spicy-network-data-file>" >&2
+ exit 1
+fi
+
+plot_spicy_file $1
diff --git a/tools/spicy.c b/tools/spicy.c
index eeb640d..82d5134 100644
--- a/tools/spicy.c
+++ b/tools/spicy.c
@@ -39,6 +39,7 @@
#include "spicy-connect.h"
typedef struct spice_connection spice_connection;
+typedef struct spice_channel_network_data spice_channel_network_data;
enum {
STATE_SCROLL_LOCK,
@@ -91,6 +92,13 @@ G_DEFINE_TYPE (SpiceWindow, spice_window, G_TYPE_OBJECT);
#define CHANNELID_MAX 4
#define MONITORID_MAX 4
+struct spice_channel_network_data {
+ gint64 time; /* Time when measurements were taken */
+ gsize wdata; /* in mega bytes */
+ gsize rdata;
+ gdouble wrate; /* in mega bytes per second */
+ gdouble rrate;
+};
// FIXME: turn this into an object, get rid of fixed wins array, use
// signals to replace the various callback that iterate over wins array
@@ -105,12 +113,29 @@ struct spice_connection {
gboolean agent_connected;
int channels;
int disconnecting;
+ int network_data_id;
+
+ struct {
+ GHashTable *table; /* Map channels to spice_channel_network_data */
+ GList *list; /* List of channels in a fixed order for reference */
+ GFile *file;
+ GFileOutputStream *stream;
+ guint index;
+ GQueue *network_data_queue;
+ bool ongoing_saving_async;
+ } netdata;
/* key: SpiceFileTransferTask, value: TransferTaskWidgets */
GHashTable *transfers;
GtkWidget *transfer_dialog;
};
+typedef struct {
+ spice_connection *conn;
+ gchar *data;
+ gsize size;
+} save_network_data_op;
+
static spice_connection *connection_new(void);
static void connection_connect(spice_connection *conn);
static void connection_disconnect(spice_connection *conn);
@@ -121,10 +146,13 @@ static void usb_connect_failed(GObject *object,
gpointer data);
static gboolean is_gtk_session_property(const gchar *property);
static void del_window(spice_connection *conn, SpiceWindow *win);
+static void write_network_data_op_async(spice_connection *conn,
+ save_network_data_op *op);
/* options */
static gboolean fullscreen = false;
static gboolean version = false;
+static gboolean should_save_network_data = false;
static char *spicy_title = NULL;
/* globals */
static GMainLoop *mainloop = NULL;
@@ -1724,11 +1752,17 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
{
spice_connection *conn = data;
int id;
+ spice_channel_network_data *netdata;
g_object_get(channel, "channel-id", &id, NULL);
conn->channels++;
SPICE_DEBUG("new channel (#%d)", id);
+ netdata = g_new0(spice_channel_network_data, 1);
+ netdata->time = g_get_monotonic_time();
+ g_hash_table_insert(conn->netdata.table, channel, netdata);
+ conn->netdata.list = g_list_append(conn->netdata.list, channel);
+
if (SPICE_IS_MAIN_CHANNEL(channel)) {
SPICE_DEBUG("new main channel");
conn->main = SPICE_MAIN_CHANNEL(channel);
@@ -1854,21 +1888,237 @@ static spice_connection *connection_new(void)
conn->transfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
g_object_unref,
(GDestroyNotify)transfer_task_widgets_free);
+ conn->netdata.table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+ conn->netdata.network_data_queue = g_queue_new();
connections++;
SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
return conn;
}
+static void write_network_data_op_cb(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gboolean ret;
+ gsize bytes_written;
+ save_network_data_op *op = user_data;
+ GError *error = NULL;
+ spice_connection *conn = op->conn;
+
+ ret = g_output_stream_write_all_finish(G_OUTPUT_STREAM(source_object),
+ res, &bytes_written, &error);
+ g_assert_no_error(error);
+ g_assert_true(ret);
+ g_assert_cmpint(op->size, ==, bytes_written);
+ g_free(op->data);
+ g_free(op);
+
+ /* Keep writing till the queue is empty */
+ conn->netdata.ongoing_saving_async = false;
+ write_network_data_op_async(conn, NULL);
+}
+
+static void write_network_data_op_async(spice_connection *conn,
+ save_network_data_op *op)
+{
+ if (conn->netdata.ongoing_saving_async) {
+ g_assert_nonnull(op);
+ g_queue_push_tail(conn->netdata.network_data_queue, op);
+ return;
+ }
+
+ if (op == NULL)
+ op = g_queue_pop_head(conn->netdata.network_data_queue);
+
+ if (op == NULL)
+ return;
+
+ conn->netdata.ongoing_saving_async = true;
+ g_output_stream_write_all_async(G_OUTPUT_STREAM(conn->netdata.stream),
+ op->data, op->size, G_PRIORITY_DEFAULT,
+ NULL, write_network_data_op_cb, op);
+}
+
+static void save_network_data_comments_sync(spice_connection *conn)
+{
+ GString *str;
+ GList *it;
+ gint i;
+ GError *error = NULL;
+
+ str = g_string_new(NULL);
+ /* Index note: gnuplot start with 1 */
+ g_string_printf(str,
+ "#column 2 bandwidth\n"
+ "#column 3 session write\n"
+ "#column 4 session read\n");
+ i = 5;
+ for (it = conn->netdata.list; it != NULL; it = it->next) {
+ SpiceChannel *channel = SPICE_CHANNEL(it->data);
+ gint type;
+ const gchar *name;
+
+ g_object_get(channel, "channel-type", &type, NULL);
+ name = spice_channel_type_to_string(type);
+ g_string_append_printf(str,
+ "#column %d %s write\n"
+ "#column %d %s read\n",
+ i, name, (i + 1), name);
+ i += 2;
+ }
+
+ /* sync: it should block */
+ g_output_stream_write(G_OUTPUT_STREAM(conn->netdata.stream),
+ str->str, str->len,
+ NULL, &error);
+ g_string_free(str, TRUE);
+ g_assert_no_error(error);
+}
+
+static void save_network_data(spice_connection *conn,
+ gdouble session_write_rate,
+ gdouble session_read_rate)
+{
+ save_network_data_op *op;
+ GString *str;
+ GList *it;
+
+ /* Format to be saved is:
+ * index, session write+read rate, session_write_rate, session_read_rate,
+ * channel1_write_rate, channel1_read_rate, ..., channelN_read_rate
+ * When the file is closed, we include a comment with mapping pair of
+ * columns to the channel's name (i.e 1-main, n-webdav) */
+
+ conn->netdata.index++;
+ str = g_string_new(NULL);
+ g_string_printf(str, "%u, %.5f, %.5f, %.5f", conn->netdata.index,
+ (session_write_rate + session_read_rate),
+ session_write_rate, session_read_rate);
+ for (it = conn->netdata.list; it != NULL; it = it->next) {
+ spice_channel_network_data *d;
+
+ d = g_hash_table_lookup(conn->netdata.table, it->data);
+ g_string_append_printf(str, ", %.5f, %.5f", d->wrate, d->rrate);
+ }
+ g_string_append_printf(str, "\n");
+
+ op = g_new0(save_network_data_op, 1);
+ op->conn = conn;
+ op->data = g_string_free(str, FALSE);
+ op->size = strlen(op->data);
+
+ write_network_data_op_async(conn, op);
+}
+
+static gboolean network_data_cb(gpointer user_data)
+{
+ spice_connection *conn;
+ GList *it, *list;
+ gdouble session_wrate, session_rrate;
+ gint64 time;
+
+ conn = user_data;
+ time = g_get_monotonic_time();
+
+ session_wrate = session_rrate = 0.0;
+ list = spice_session_get_channels(conn->session);
+ for (it = list; it != NULL; it = it->next) {
+ SpiceChannel *channel;
+ gsize total_read_bytes, total_write_bytes;
+ gdouble wrate, rrate, data, interval;
+ spice_channel_network_data *netdata;
+
+ channel = SPICE_CHANNEL(it->data);
+ g_object_get(channel,
+ "total-read-bytes", &total_read_bytes,
+ "total-write-bytes", &total_write_bytes,
+ NULL);
+
+ netdata = g_hash_table_lookup(conn->netdata.table, channel);
+ g_return_val_if_fail(netdata != NULL, G_SOURCE_REMOVE);
+
+ interval = time - netdata->time;
+ data = total_write_bytes - netdata->wdata;
+ wrate = (data * 1000000.0) / (interval * 1024.0 * 1024.0);
+ data = total_read_bytes - netdata->rdata;
+ rrate = (data * 1000000.0) / (interval * 1024.0 * 1024.0);
+
+ netdata->time = time;
+ netdata->wdata = total_write_bytes;
+ netdata->wrate = wrate;
+ netdata->rdata = total_read_bytes;
+ netdata->rrate = rrate;
+
+ /* For the session */
+ session_wrate += wrate;
+ session_rrate += rrate;
+ }
+ g_list_free(list);
+ save_network_data(conn, session_wrate, session_rrate);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void save_network_stop(spice_connection *conn)
+{
+ gchar *path;
+
+ if (conn->network_data_id != 0)
+ g_source_remove(conn->network_data_id);
+
+ save_network_data_comments_sync(conn);
+
+ path = g_file_get_path(conn->netdata.file);
+ SPICE_DEBUG("Data saved, file is being closed now %s", path);
+ g_free(path);
+ g_object_unref(conn->netdata.file);
+ g_object_unref(conn->netdata.stream);
+}
+
+static void save_network_start(spice_connection *conn)
+{
+ GDateTime *date;
+ gchar *current_dir, *date_str, *filename, *path;
+ GError *error = NULL;
+
+ date = g_date_time_new_now_local();
+ date_str = g_date_time_format(date, "%H%M%S");
+ filename = g_strdup_printf("spicy-network-data-%s", date_str);
+ g_free(date_str);
+ g_date_time_unref(date);
+
+ current_dir = g_get_current_dir();
+ path = g_build_filename(current_dir, filename, NULL);
+ g_free(current_dir);
+ g_free(filename);
+
+ conn->netdata.file = g_file_new_for_path(path);
+ SPICE_DEBUG("Network data will be stored at %s", path);
+ g_free(path);
+
+ conn->netdata.stream = g_file_create(conn->netdata.file, G_FILE_CREATE_NONE, NULL, &error);
+ g_assert_no_error(error);
+
+ conn->network_data_id = g_timeout_add_seconds(5, network_data_cb, conn);
+}
+
static void connection_connect(spice_connection *conn)
{
conn->disconnecting = false;
- spice_session_connect(conn->session);
+ if (!spice_session_connect(conn->session))
+ return;
+
+ if (should_save_network_data)
+ save_network_start(conn);
}
static void connection_disconnect(spice_connection *conn)
{
if (conn->disconnecting)
return;
+
+ save_network_stop(conn);
+
conn->disconnecting = true;
spice_session_disconnect(conn->session);
}
@@ -1877,6 +2127,8 @@ static void connection_destroy(spice_connection *conn)
{
g_object_unref(conn->session);
g_hash_table_unref(conn->transfers);
+ g_hash_table_unref(conn->netdata.table);
+ g_list_free(conn->netdata.list);
free(conn);
connections--;
@@ -1909,6 +2161,11 @@ static GOptionEntry cmd_entries[] = {
.description = "Set the window title",
.arg_description = "<title>",
},{
+ .long_name = "save-network-data",
+ .arg = G_OPTION_ARG_NONE,
+ .arg_data = &should_save_network_data,
+ .description = "Save network data in csv format",
+ },{
/* end of list */
}
};
--
2.9.3
More information about the Spice-devel
mailing list