From: Victor Toso <me@xxxxxxxxxxxxxx> 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@xxxxxxxxxx> --- 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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel