From 64d577ed888ab5d322a2c7093f8969d7dc337317 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Campos Date: Fri, 9 Apr 2021 10:16:56 +0200 Subject: metrics: add support for size metrics --- docs/reference/libsoup-3.0-sections.txt | 7 ++ libsoup/cache/soup-cache.c | 7 +- libsoup/server/soup-server-io.c | 2 +- libsoup/soup-body-output-stream.c | 18 ++++- libsoup/soup-client-input-stream.c | 15 ++++ libsoup/soup-filter-input-stream.c | 65 ++++++++++++----- libsoup/soup-logger.c | 17 +++-- libsoup/soup-message-io-data.c | 10 ++- libsoup/soup-message-io-data.h | 1 + libsoup/soup-message-io.c | 55 ++++++++++++++- libsoup/soup-message-metrics-private.h | 9 ++- libsoup/soup-message-metrics.c | 119 ++++++++++++++++++++++++++++++++ libsoup/soup-message-metrics.h | 18 +++++ tests/cache-test.c | 12 ++++ tests/coding-test.c | 18 +++++ tests/request-body-test.c | 29 ++++++++ tests/streaming-test.c | 55 +++++++++++++++ 17 files changed, 425 insertions(+), 32 deletions(-) diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt index cc5b5710..6d958f5b 100644 --- a/docs/reference/libsoup-3.0-sections.txt +++ b/docs/reference/libsoup-3.0-sections.txt @@ -1010,6 +1010,13 @@ soup_message_metrics_get_tls_start soup_message_metrics_get_request_start soup_message_metrics_get_response_start soup_message_metrics_get_response_end + +soup_message_metrics_get_request_header_bytes_sent +soup_message_metrics_get_request_body_size +soup_message_metrics_get_request_body_bytes_sent +soup_message_metrics_get_response_header_bytes_received +soup_message_metrics_get_response_body_size +soup_message_metrics_get_response_body_bytes_received SOUP_TYPE_MESSAGE_METRICS soup_message_metrics_get_type diff --git a/libsoup/cache/soup-cache.c b/libsoup/cache/soup-cache.c index 969b3a4d..a33be803 100644 --- a/libsoup/cache/soup-cache.c +++ b/libsoup/cache/soup-cache.c @@ -40,7 +40,7 @@ #include "soup-content-processor.h" #include "soup-message-private.h" #include "soup.h" -#include "soup-message-private.h" +#include "soup-message-metrics-private.h" #include "soup-misc.h" #include "soup-session-private.h" #include "soup-session-feature-private.h" @@ -688,6 +688,7 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg) SoupCacheEntry *entry; GInputStream *file_stream, *body_stream, *cache_stream, *client_stream; GFile *file; + SoupMessageMetrics *metrics; g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL); g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); @@ -711,6 +712,10 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg) if (!body_stream) return NULL; + metrics = soup_message_get_metrics (msg); + if (metrics) + metrics->response_body_size = entry->length; + /* If we are told to send a response from cache any validation in course is over by now */ entry->being_validated = FALSE; diff --git a/libsoup/server/soup-server-io.c b/libsoup/server/soup-server-io.c index 5cc240b1..a806d2f3 100644 --- a/libsoup/server/soup-server-io.c +++ b/libsoup/server/soup-server-io.c @@ -661,7 +661,7 @@ io_read (SoupServerMessage *msg, switch (io->read_state) { case SOUP_MESSAGE_IO_STATE_HEADERS: - if (!soup_message_io_data_read_headers (io, FALSE, NULL, error)) { + if (!soup_message_io_data_read_headers (io, FALSE, NULL, NULL, error)) { if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT)) soup_server_message_set_status (msg, SOUP_STATUS_BAD_REQUEST, NULL); return FALSE; diff --git a/libsoup/soup-body-output-stream.c b/libsoup/soup-body-output-stream.c index 499e8e6f..c0aeb458 100644 --- a/libsoup/soup-body-output-stream.c +++ b/libsoup/soup-body-output-stream.c @@ -118,7 +118,15 @@ soup_body_output_stream_wrote_data (SoupBodyOutputStream *bostream, const void *buffer, gsize count) { - g_signal_emit (bostream, signals[WROTE_DATA], 0, buffer, count); + g_signal_emit (bostream, signals[WROTE_DATA], 0, buffer, count, FALSE); +} + +static void +soup_body_output_stream_wrote_metadata (SoupBodyOutputStream *bostream, + const void *buffer, + gsize count) +{ + g_signal_emit (bostream, signals[WROTE_DATA], 0, buffer, count, TRUE); } static gssize @@ -178,6 +186,9 @@ again: nwrote = g_pollable_stream_write (priv->base_stream, buf, len, blocking, cancellable, error); + if (nwrote > 0) + soup_body_output_stream_wrote_metadata (bostream, buf, nwrote); + if (nwrote < 0) return nwrote; memmove (buf, buf + nwrote, len + 1 - nwrote); @@ -341,9 +352,10 @@ soup_body_output_stream_class_init (SoupBodyOutputStreamClass *stream_class) 0, NULL, NULL, NULL, - G_TYPE_NONE, 2, + G_TYPE_NONE, 3, G_TYPE_POINTER, - G_TYPE_UINT); + G_TYPE_UINT, + G_TYPE_BOOLEAN); g_object_class_install_property ( object_class, PROP_ENCODING, diff --git a/libsoup/soup-client-input-stream.c b/libsoup/soup-client-input-stream.c index bf4a866f..a95bfe41 100644 --- a/libsoup/soup-client-input-stream.c +++ b/libsoup/soup-client-input-stream.c @@ -12,6 +12,7 @@ #include "soup-client-input-stream.h" #include "soup.h" #include "soup-message-private.h" +#include "soup-message-metrics-private.h" #include "soup-misc.h" struct _SoupClientInputStream { @@ -20,6 +21,7 @@ struct _SoupClientInputStream { typedef struct { SoupMessage *msg; + SoupMessageMetrics *metrics; } SoupClientInputStreamPrivate; enum { @@ -69,6 +71,7 @@ soup_client_input_stream_set_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_MESSAGE: priv->msg = g_value_dup_object (value); + priv->metrics = soup_message_get_metrics (priv->msg); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -100,11 +103,15 @@ soup_client_input_stream_read_fn (GInputStream *stream, GCancellable *cancellable, GError **error) { + SoupClientInputStreamPrivate *priv = soup_client_input_stream_get_instance_private (SOUP_CLIENT_INPUT_STREAM (stream)); gssize nread; nread = G_INPUT_STREAM_CLASS (soup_client_input_stream_parent_class)-> read_fn (stream, buffer, count, cancellable, error); + if (priv->metrics && nread > 0) + priv->metrics->response_body_size += nread; + if (nread == 0) g_signal_emit (stream, signals[SIGNAL_EOF], 0); @@ -117,11 +124,15 @@ soup_client_input_stream_skip (GInputStream *stream, GCancellable *cancellable, GError **error) { + SoupClientInputStreamPrivate *priv = soup_client_input_stream_get_instance_private (SOUP_CLIENT_INPUT_STREAM (stream)); gssize nread; nread = G_INPUT_STREAM_CLASS (soup_client_input_stream_parent_class)-> skip (stream, count, cancellable, error); + if (priv->metrics && nread > 0) + priv->metrics->response_body_size += nread; + if (nread == 0) g_signal_emit (stream, signals[SIGNAL_EOF], 0); @@ -134,11 +145,15 @@ soup_client_input_stream_read_nonblocking (GPollableInputStream *stream, gsize count, GError **error) { + SoupClientInputStreamPrivate *priv = soup_client_input_stream_get_instance_private (SOUP_CLIENT_INPUT_STREAM (stream)); gssize nread; nread = soup_client_input_stream_parent_pollable_interface-> read_nonblocking (stream, buffer, count, error); + if (priv->metrics && nread > 0) + priv->metrics->response_body_size += nread; + if (nread == 0) g_signal_emit (stream, signals[SIGNAL_EOF], 0); diff --git a/libsoup/soup-filter-input-stream.c b/libsoup/soup-filter-input-stream.c index 448b7c9f..6ef11a05 100644 --- a/libsoup/soup-filter-input-stream.c +++ b/libsoup/soup-filter-input-stream.c @@ -26,6 +26,14 @@ typedef struct { gboolean in_read_until; } SoupFilterInputStreamPrivate; +enum { + READ_DATA, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + static void soup_filter_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); G_DEFINE_TYPE_WITH_CODE (SoupFilterInputStream, soup_filter_input_stream, G_TYPE_FILTER_INPUT_STREAM, @@ -81,17 +89,21 @@ soup_filter_input_stream_read_fn (GInputStream *stream, { SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (stream); SoupFilterInputStreamPrivate *priv = soup_filter_input_stream_get_instance_private (fstream); + gssize bytes_read; if (!priv->in_read_until) priv->need_more = FALSE; - if (priv->buf && !priv->in_read_until) { + if (priv->buf && !priv->in_read_until) return read_from_buf (fstream, buffer, count); - } else { - return g_pollable_stream_read (G_FILTER_INPUT_STREAM (fstream)->base_stream, - buffer, count, - TRUE, cancellable, error); - } + + bytes_read = g_pollable_stream_read (G_FILTER_INPUT_STREAM (fstream)->base_stream, + buffer, count, + TRUE, cancellable, error); + if (bytes_read > 0) + g_signal_emit (fstream, signals[READ_DATA], 0, bytes_read); + + return bytes_read; } static gssize @@ -102,16 +114,20 @@ soup_filter_input_stream_skip (GInputStream *stream, { SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (stream); SoupFilterInputStreamPrivate *priv = soup_filter_input_stream_get_instance_private (fstream); + gssize bytes_skipped; if (!priv->in_read_until) priv->need_more = FALSE; - if (priv->buf && !priv->in_read_until) { + if (priv->buf && !priv->in_read_until) return read_from_buf (fstream, NULL, count); - } else { - return g_input_stream_skip (G_FILTER_INPUT_STREAM (fstream)->base_stream, - count, cancellable, error); - } + + bytes_skipped = g_input_stream_skip (G_FILTER_INPUT_STREAM (fstream)->base_stream, + count, cancellable, error); + if (bytes_skipped > 0) + g_signal_emit (fstream, signals[READ_DATA], 0, bytes_skipped); + + return bytes_skipped; } static gboolean @@ -134,17 +150,21 @@ soup_filter_input_stream_read_nonblocking (GPollableInputStream *stream, { SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (stream); SoupFilterInputStreamPrivate *priv = soup_filter_input_stream_get_instance_private (fstream); + gssize bytes_read; if (!priv->in_read_until) priv->need_more = FALSE; - if (priv->buf && !priv->in_read_until) { + if (priv->buf && !priv->in_read_until) return read_from_buf (fstream, buffer, count); - } else { - return g_pollable_stream_read (G_FILTER_INPUT_STREAM (fstream)->base_stream, - buffer, count, - FALSE, NULL, error); - } + + bytes_read = g_pollable_stream_read (G_FILTER_INPUT_STREAM (fstream)->base_stream, + buffer, count, + FALSE, NULL, error); + if (bytes_read > 0) + g_signal_emit (fstream, signals[READ_DATA], 0, bytes_read); + + return bytes_read; } static GSource * @@ -178,6 +198,17 @@ soup_filter_input_stream_class_init (SoupFilterInputStreamClass *stream_class) input_stream_class->read_fn = soup_filter_input_stream_read_fn; input_stream_class->skip = soup_filter_input_stream_skip; + + signals[READ_DATA] = + g_signal_new ("read-data", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + G_TYPE_UINT); + } static void diff --git a/libsoup/soup-logger.c b/libsoup/soup-logger.c index 3bb7dda3..c9899e22 100644 --- a/libsoup/soup-logger.c +++ b/libsoup/soup-logger.c @@ -815,13 +815,20 @@ got_body (SoupMessage *msg, gpointer user_data) } static void -body_stream_wrote_data_cb (GOutputStream *stream, const void *buffer, - guint count, SoupLogger *logger) +body_stream_wrote_data_cb (GOutputStream *stream, + const void *buffer, + guint count, + gboolean is_metadata, + SoupLogger *logger) { - SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger); - SoupMessage *msg = g_hash_table_lookup (priv->request_messages, - stream); + SoupLoggerPrivate *priv; + SoupMessage *msg; + + if (is_metadata) + return; + priv = soup_logger_get_instance_private (logger); + msg = g_hash_table_lookup (priv->request_messages, stream); write_body (logger, buffer, count, msg, priv->request_bodies); } diff --git a/libsoup/soup-message-io-data.c b/libsoup/soup-message-io-data.c index 61ad83f4..04a9c255 100644 --- a/libsoup/soup-message-io-data.c +++ b/libsoup/soup-message-io-data.c @@ -50,6 +50,7 @@ gboolean soup_message_io_data_read_headers (SoupMessageIOData *io, gboolean blocking, GCancellable *cancellable, + gushort *extra_bytes, GError **error) { gssize nread, old_len; @@ -66,8 +67,11 @@ soup_message_io_data_read_headers (SoupMessageIOData *io, cancellable, error); io->read_header_buf->len = old_len + MAX (nread, 0); if (nread == 0) { - if (io->read_header_buf->len > 0) + if (io->read_header_buf->len > 0) { + if (extra_bytes) + *extra_bytes = 0; break; + } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, @@ -82,12 +86,16 @@ soup_message_io_data_read_headers (SoupMessageIOData *io, io->read_header_buf->len - 2, "\n\n", 2)) { io->read_header_buf->len--; + if (extra_bytes) + *extra_bytes = 1; break; } else if (nread == 2 && old_len >= 3 && !strncmp ((char *)io->read_header_buf->data + io->read_header_buf->len - 3, "\n\r\n", 3)) { io->read_header_buf->len -= 2; + if (extra_bytes) + *extra_bytes = 2; break; } } diff --git a/libsoup/soup-message-io-data.h b/libsoup/soup-message-io-data.h index 868ae8d5..e3feae7a 100644 --- a/libsoup/soup-message-io-data.h +++ b/libsoup/soup-message-io-data.h @@ -80,6 +80,7 @@ void soup_message_io_data_cleanup (SoupMessageIOData *io); gboolean soup_message_io_data_read_headers (SoupMessageIOData *io, gboolean blocking, GCancellable *cancellable, + gushort *extra_bytes, GError **error); typedef gboolean (*SoupMessageIOSourceFunc) (GObject *msg, diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c index 9eab3ef2..68b437e5 100644 --- a/libsoup/soup-message-io.c +++ b/libsoup/soup-message-io.c @@ -25,6 +25,7 @@ #include "soup-filter-input-stream.h" #include "soup-logger-private.h" #include "soup-message-private.h" +#include "soup-message-metrics-private.h" #include "soup-message-queue-item.h" #include "soup-misc.h" #include "soup-uri-utils-private.h" @@ -34,6 +35,8 @@ struct _SoupClientMessageIOData { SoupMessageQueueItem *item; + SoupMessageMetrics *metrics; + #ifdef HAVE_SYSPROF gint64 begin_time_nsec; #endif @@ -164,9 +167,19 @@ soup_message_setup_body_istream (GInputStream *body_stream, static void request_body_stream_wrote_data_cb (SoupMessage *msg, const void *buffer, - guint count) + guint count, + gboolean is_metadata) { - soup_message_wrote_body_data (msg, count); + SoupClientMessageIOData *client_io = soup_message_get_io_data (msg); + + if (client_io->metrics) { + client_io->metrics->request_body_bytes_sent += count; + if (!is_metadata) + client_io->metrics->request_body_size += count; + } + + if (!is_metadata) + soup_message_wrote_body_data (msg, count); } static void @@ -345,6 +358,8 @@ io_write (SoupMessage *msg, gboolean blocking, if (nwrote == -1) return FALSE; io->written += nwrote; + if (client_io->metrics) + client_io->metrics->request_header_bytes_sent += nwrote; } io->written = 0; @@ -497,6 +512,18 @@ parse_headers (SoupMessage *msg, return TRUE; } +static void +response_network_stream_read_data_cb (SoupMessage *msg, + guint count) +{ + SoupClientMessageIOData *client_io = soup_message_get_io_data (msg); + + if (client_io->base.read_state < SOUP_MESSAGE_IO_STATE_BODY_START) + client_io->metrics->response_header_bytes_received += count; + else + client_io->metrics->response_body_bytes_received += count; +} + /* Attempts to push forward the reading side of @msg's I/O. Returns * %TRUE if it manages to make some progress, and it is likely that * further progress can be made. Returns %FALSE if it has reached a @@ -511,15 +538,27 @@ io_read (SoupMessage *msg, gboolean blocking, SoupMessageIOData *io = &client_io->base; gboolean succeeded; gboolean is_first_read; + gushort extra_bytes; switch (io->read_state) { case SOUP_MESSAGE_IO_STATE_HEADERS: is_first_read = io->read_header_buf->len == 0 && soup_message_get_status (msg) == SOUP_STATUS_NONE; - if (!soup_message_io_data_read_headers (io, blocking, cancellable, error)) + if (!soup_message_io_data_read_headers (io, blocking, cancellable, &extra_bytes, error)) return FALSE; + if (client_io->metrics) { + /* Adjust the header and body bytes received, since we might + * have read part of the body already that is queued by the stream. + */ + if (client_io->metrics->response_header_bytes_received > io->read_header_buf->len + extra_bytes) { + client_io->metrics->response_body_bytes_received = + client_io->metrics->response_header_bytes_received - io->read_header_buf->len - extra_bytes; + client_io->metrics->response_header_bytes_received -= client_io->metrics->response_body_bytes_received; + } + } + if (is_first_read) soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_START); @@ -642,6 +681,9 @@ io_read (SoupMessage *msg, gboolean blocking, if (nread == 0) io->read_state = SOUP_MESSAGE_IO_STATE_BODY_DONE; + if (client_io->metrics) + client_io->metrics->response_body_size += nread; + break; } @@ -1000,6 +1042,13 @@ soup_message_send_request (SoupMessageQueueItem *item, io->base.read_state = SOUP_MESSAGE_IO_STATE_NOT_STARTED; io->base.write_state = SOUP_MESSAGE_IO_STATE_HEADERS; + io->metrics = soup_message_get_metrics (io->item->msg); + if (io->metrics) { + g_signal_connect_object (io->base.istream, "read-data", + G_CALLBACK (response_network_stream_read_data_cb), + io->item->msg, G_CONNECT_SWAPPED); + } + #ifdef HAVE_SYSPROF io->begin_time_nsec = SYSPROF_CAPTURE_CURRENT_TIME; #endif diff --git a/libsoup/soup-message-metrics-private.h b/libsoup/soup-message-metrics-private.h index 03d62ad1..6f0869e0 100644 --- a/libsoup/soup-message-metrics-private.h +++ b/libsoup/soup-message-metrics-private.h @@ -19,8 +19,15 @@ struct _SoupMessageMetrics { guint64 request_start; guint64 response_start; guint64 response_end; + + guint64 request_header_bytes_sent; + guint64 request_body_size; + guint64 request_body_bytes_sent; + guint64 response_header_bytes_received; + guint64 response_body_size; + guint64 response_body_bytes_received; }; -SoupMessageMetrics *soup_message_metrics_new (void); +SoupMessageMetrics *soup_message_metrics_new (void); G_END_DECLS diff --git a/libsoup/soup-message-metrics.c b/libsoup/soup-message-metrics.c index e3b642c3..41a9ef49 100644 --- a/libsoup/soup-message-metrics.c +++ b/libsoup/soup-message-metrics.c @@ -32,6 +32,10 @@ * fetch start event and finish with response end. All other events are optional. * An event can be 0 because it hasn't happened yet, because it's optional or * because the load failed before the event reached. + * + * Size metrics are expressed in bytes and aree updated while the #SoupMessage is + * being loaded. You can connect to different #SoupMessage signals to get the + * final result of every value. */ G_DEFINE_BOXED_TYPE (SoupMessageMetrics, soup_message_metrics, soup_message_metrics_copy, soup_message_metrics_free) @@ -243,3 +247,118 @@ soup_message_metrics_get_response_end (SoupMessageMetrics *metrics) return metrics->response_end; } + +/** + * soup_message_metrics_get_request_header_bytes_sent: + * @metrics: a #SoupMessageMetrics + * + * Get the number of bytes sent to the network for the request headers. + * This value is available right before #SoupMessage::wrote-headers signal + * is emitted, but you might get an intermediate value if called before. + * + * Returns: the request headers bytes sent + */ +guint64 +soup_message_metrics_get_request_header_bytes_sent (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->request_header_bytes_sent; +} + +/** + * soup_message_metrics_get_request_body_size: + * @metrics: a #SoupMessageMetrics + * + * Get the request body size in bytes. This is the size of the original body + * given to the request before any encoding is applied. This value is available + * right before #SoupMessage::wrote-body signal is emitted, but you might get + * an intermediate value if called before. + * + * Returns: the request body size + */ +guint64 +soup_message_metrics_get_request_body_size (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->request_body_size; +} + +/** + * soup_message_metrics_get_request_body_bytes_sent: + * @metrics: a #SoupMessageMetrics + * + * Get the number of bytes sent to the network for the request body. This is + * the size of the body sent, after encodings are applied, so it might be + * greater than the value returned by soup_message_metrics_get_request_body_size(). + * This value is available right before #SoupMessage::wrote-body signal is + * emitted, but you might get an intermediate value if called before. + * + * Returns: the request body bytes sent + */ +guint64 +soup_message_metrics_get_request_body_bytes_sent (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->request_body_bytes_sent; +} + +/** + * soup_message_metrics_get_response_header_bytes_received: + * @metrics: a #SoupMessageMetrics + * + * Get the number of bytes received from the network for the response headers. + * This value is available right before #SoupMessage::got-headers signal + * is emitted, but you might get an intermediate value if called before. + * For resources loaded from the disk cache this value is always 0. + * + * Returns: the response headers bytes received + */ +guint64 +soup_message_metrics_get_response_header_bytes_received (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->response_header_bytes_received; +} + +/** + * soup_message_metrics_get_response_body_size: + * @metrics: a #SoupMessageMetrics + * + * Get the response body size in bytes. This is the size of the body as given to the + * user after all encodings are applied, so it might be greater than the value + * returned by soup_message_metrics_get_response_body_bytes_received(). This value is + * available right before #SoupMessage::got-body signal is emitted, but you might get + * an intermediate value if called before. + * + * Returns: the response body size + */ +guint64 +soup_message_metrics_get_response_body_size (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->response_body_size; +} + +/** + * soup_message_metrics_get_response_body_bytes_received: + * @metrics: a #SoupMessageMetrics + * + * Get the number of bytes received from the network for the response body. This value is + * available right before #SoupMessage::got-body signal is emitted, but you might get + * an intermediate value if called before. + * For resources loaded from the disk cache this value is always 0. + * + * Returns: the response body bytes received + */ +guint64 +soup_message_metrics_get_response_body_bytes_received (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->response_body_bytes_received; +} diff --git a/libsoup/soup-message-metrics.h b/libsoup/soup-message-metrics.h index b5d3f9fb..1c12a62d 100644 --- a/libsoup/soup-message-metrics.h +++ b/libsoup/soup-message-metrics.h @@ -48,6 +48,24 @@ guint64 soup_message_metrics_get_response_start (SoupMessageMetrics SOUP_AVAILABLE_IN_ALL guint64 soup_message_metrics_get_response_end (SoupMessageMetrics *metrics); +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_request_header_bytes_sent (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_request_body_size (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_request_body_bytes_sent (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_response_header_bytes_received (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_response_body_size (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_response_body_bytes_received (SoupMessageMetrics *metrics); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(SoupMessageMetrics, soup_message_metrics_free) G_END_DECLS diff --git a/tests/cache-test.c b/tests/cache-test.c index 517201ea..f7b7f6fc 100644 --- a/tests/cache-test.c +++ b/tests/cache-test.c @@ -796,6 +796,12 @@ do_metrics_test (gconstpointer data) soup_test_request_close_stream (stream, NULL, NULL); g_object_unref (stream); g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >=, soup_message_metrics_get_response_start (metrics)); + g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), >, 0); g_object_unref (msg); body = do_request (session, base_uri, "GET", "/2", NULL, @@ -840,6 +846,12 @@ do_metrics_test (gconstpointer data) soup_test_request_close_stream (stream, NULL, NULL); g_object_unref (stream); g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >=, soup_message_metrics_get_response_start (metrics)); + g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), >, 0); g_object_unref (msg); soup_test_session_abort_unref (session); diff --git a/tests/coding-test.c b/tests/coding-test.c index 45e9452c..1897ae75 100644 --- a/tests/coding-test.c +++ b/tests/coding-test.c @@ -211,6 +211,21 @@ do_coding_test_gzip (CodingTestData *data, gconstpointer test_data) g_bytes_unref (body); } +static void +do_coding_test_gzip_metrics (CodingTestData *data, gconstpointer test_data) +{ + GBytes *body; + SoupMessageMetrics *metrics; + + soup_message_add_flags (data->msg, SOUP_MESSAGE_COLLECT_METRICS); + body = soup_session_send_and_read (data->session, data->msg, NULL, NULL); + metrics = soup_message_get_metrics (data->msg); + g_assert_nonnull (metrics); + g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), ==, g_bytes_get_size (body)); + g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), <, soup_message_metrics_get_response_body_size (metrics)); + g_bytes_unref (body); +} + static void do_coding_test_gzip_with_junk (CodingTestData *data, gconstpointer test_data) { @@ -345,6 +360,9 @@ main (int argc, char **argv) g_test_add ("/coding/message/gzip", CodingTestData, GINT_TO_POINTER (CODING_TEST_DEFAULT), setup_coding_test, do_coding_test_gzip, teardown_coding_test); + g_test_add ("/coding/message/gzip/metrics", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_gzip_metrics, teardown_coding_test); g_test_add ("/coding/message/gzip/with-junk", CodingTestData, GINT_TO_POINTER (CODING_TEST_DEFAULT), setup_coding_test, do_coding_test_gzip_with_junk, teardown_coding_test); diff --git a/tests/request-body-test.c b/tests/request-body-test.c index 3ba52e1b..1b9938d3 100644 --- a/tests/request-body-test.c +++ b/tests/request-body-test.c @@ -33,8 +33,23 @@ wrote_body_data (SoupMessage *msg, guint count, PutTestData *ptd) { + SoupMessageMetrics *metrics = soup_message_get_metrics (msg); + debug_printf (2, " wrote_body_data, %u bytes\n", count); ptd->nwrote += count; + + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), >=, ptd->nwrote); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, ptd->nwrote); +} + +static void +wrote_body (SoupMessage *msg, + PutTestData *ptd) +{ + SoupMessageMetrics *metrics = soup_message_get_metrics (msg); + + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), >=, ptd->nwrote); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, ptd->nwrote); } static GChecksum * @@ -107,6 +122,7 @@ do_request_test (gconstpointer data) SoupMessageHeaders *request_headers; const char *client_md5, *server_md5; GChecksum *check; + SoupMessageMetrics *metrics; if (flags & RESTART) uri = g_uri_parse_relative (base_uri, "/redirect", SOUP_HTTP_URI_FLAGS, NULL); @@ -118,6 +134,7 @@ do_request_test (gconstpointer data) client_md5 = g_checksum_get_string (check); msg = soup_message_new_from_uri ("PUT", uri); + soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS); request_headers = soup_message_get_request_headers (msg); if (flags & BYTES) { soup_message_set_request_body_from_bytes (msg, ptd.content_type, ptd.bytes); @@ -137,17 +154,29 @@ do_request_test (gconstpointer data) g_signal_connect (msg, "wrote-body-data", G_CALLBACK (wrote_body_data), &ptd); + g_signal_connect (msg, "wrote-body", + G_CALLBACK (wrote_body), &ptd); if (flags & ASYNC) soup_test_session_async_send (session, msg, NULL, NULL); else soup_test_session_send_message (session, msg); soup_test_assert_message_status (msg, SOUP_STATUS_CREATED); + + metrics = soup_message_get_metrics (msg); if (flags & NULL_STREAM) { g_assert_cmpint (ptd.nwrote, ==, 0); g_assert_cmpstr (soup_message_headers_get_one (request_headers, "Content-Length"), ==, "0"); + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0); } else { g_assert_cmpint (g_bytes_get_size (ptd.bytes), ==, ptd.nwrote); + if (flags & BYTES) { + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, ptd.nwrote); + } else { + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), >, ptd.nwrote); + } + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, ptd.nwrote); } server_md5 = soup_message_headers_get_one (soup_message_get_response_headers (msg), diff --git a/tests/streaming-test.c b/tests/streaming-test.c index 318d8c4a..7b776d4c 100644 --- a/tests/streaming-test.c +++ b/tests/streaming-test.c @@ -81,6 +81,30 @@ server_callback (SoupServer *server, G_CALLBACK (free_offset), offset); } +static void +msg_wrote_headers_cb (SoupMessage *msg, + SoupMessageMetrics *metrics) +{ + g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0); +} + +static void +msg_got_headers_cb (SoupMessage *msg, + SoupMessageMetrics *metrics) +{ + g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), >, 0); +} + +static void +msg_got_body_cb (SoupMessage *msg, + SoupMessageMetrics *metrics) +{ + g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), >, 0); +} + static void do_request (SoupSession *session, GUri *base_uri, char *path) { @@ -88,15 +112,46 @@ do_request (SoupSession *session, GUri *base_uri, char *path) SoupMessage *msg; GBytes *body; char *md5; + SoupMessageMetrics *metrics; uri = g_uri_parse_relative (base_uri, path, SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri ("GET", uri); g_uri_unref (uri); + soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS); + metrics = soup_message_get_metrics (msg); + g_assert_nonnull (metrics); + g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), ==, 0); + + g_signal_connect (msg, "wrote-headers", + G_CALLBACK (msg_wrote_headers_cb), + metrics); + g_signal_connect (msg, "got-headers", + G_CALLBACK (msg_got_headers_cb), + metrics); + g_signal_connect (msg, "got-body", + G_CALLBACK (msg_got_body_cb), + metrics); + body = soup_test_session_async_send (session, msg, NULL, NULL); soup_test_assert_message_status (msg, SOUP_STATUS_OK); g_assert_cmpint (g_bytes_get_size (body), ==, g_bytes_get_size (full_response)); + g_assert_cmpint (soup_message_metrics_get_response_body_size (metrics), ==, g_bytes_get_size (body)); + g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), >, 0); + if (g_str_equal (path, "chunked")) { + g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), >, soup_message_metrics_get_response_body_size (metrics)); + } else { + g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), ==, soup_message_metrics_get_response_body_size (metrics)); + } md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5, (guchar *)g_bytes_get_data (body, NULL), -- cgit v1.2.1