diff options
| author | Carlos Garcia Campos <cgarcia@igalia.com> | 2021-03-26 15:30:59 +0100 |
|---|---|---|
| committer | Patrick Griffis <tingping@tingping.se> | 2021-04-13 21:09:55 +0000 |
| commit | 878809ba6aced2df29931fc461f905865cbcc26c (patch) | |
| tree | d56a62489f5e2b97aaa0e24a3918016c7183f3c0 | |
| parent | 48f07d3fe9254520dc7371666fb517e7aa201aeb (diff) | |
| download | libsoup-878809ba6aced2df29931fc461f905865cbcc26c.tar.gz | |
Add support for collecting metrics
| -rw-r--r-- | docs/reference/libsoup-3.0-docs.xml | 4 | ||||
| -rw-r--r-- | docs/reference/libsoup-3.0-sections.txt | 23 | ||||
| -rw-r--r-- | docs/reference/meson.build | 1 | ||||
| -rw-r--r-- | libsoup/cache/soup-cache.c | 4 | ||||
| -rw-r--r-- | libsoup/include/soup-installed.h | 1 | ||||
| -rw-r--r-- | libsoup/meson.build | 2 | ||||
| -rw-r--r-- | libsoup/soup-message-io.c | 13 | ||||
| -rw-r--r-- | libsoup/soup-message-metrics-private.h | 26 | ||||
| -rw-r--r-- | libsoup/soup-message-metrics.c | 245 | ||||
| -rw-r--r-- | libsoup/soup-message-metrics.h | 53 | ||||
| -rw-r--r-- | libsoup/soup-message-private.h | 15 | ||||
| -rw-r--r-- | libsoup/soup-message.c | 110 | ||||
| -rw-r--r-- | libsoup/soup-message.h | 7 | ||||
| -rw-r--r-- | libsoup/soup-session.c | 11 | ||||
| -rw-r--r-- | libsoup/soup-types.h | 1 | ||||
| -rw-r--r-- | libsoup/soup.h | 1 | ||||
| -rw-r--r-- | tests/cache-test.c | 110 | ||||
| -rw-r--r-- | tests/connection-test.c | 210 |
18 files changed, 827 insertions, 10 deletions
diff --git a/docs/reference/libsoup-3.0-docs.xml b/docs/reference/libsoup-3.0-docs.xml index 3130a35b..b1fa54d6 100644 --- a/docs/reference/libsoup-3.0-docs.xml +++ b/docs/reference/libsoup-3.0-docs.xml @@ -76,6 +76,10 @@ <xi:include href="xml/soup-hsts-enforcer-db.xml"/> <xi:include href="xml/soup-hsts-policy.xml"/> </section> + <section> + <title>Metrics</title> + <xi:include href="xml/soup-message-metrics.xml"/> + </section> </chapter> <chapter> diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt index 26775111..cc5b5710 100644 --- a/docs/reference/libsoup-3.0-sections.txt +++ b/docs/reference/libsoup-3.0-sections.txt @@ -55,6 +55,8 @@ soup_message_get_site_for_cookies soup_message_set_site_for_cookies soup_message_get_is_top_level_navigation soup_message_set_is_top_level_navigation +<SUBSECTION> +soup_message_get_metrics <SUBSECTION Standard> SOUP_MESSAGE SOUP_IS_MESSAGE @@ -991,3 +993,24 @@ SOUP_IS_HSTS_ENFORCER_DB SOUP_IS_HSTS_ENFORCER_DB_CLASS soup_hsts_enforcer_db_get_type </SECTION> + +<SECTION> +<FILE>soup-message-metrics</FILE> +<TITLE>SoupMessageMetrics</TITLE> +SoupMessageMetrics +soup_message_metrics_copy +soup_message_metrics_free +<SUBSECTION> +soup_message_metrics_get_fetch_start +soup_message_metrics_get_dns_start +soup_message_metrics_get_dns_end +soup_message_metrics_get_connect_start +soup_message_metrics_get_connect_end +soup_message_metrics_get_tls_start +soup_message_metrics_get_request_start +soup_message_metrics_get_response_start +soup_message_metrics_get_response_end +<SUBSECTION Standard> +SOUP_TYPE_MESSAGE_METRICS +soup_message_metrics_get_type +</SECTION> diff --git a/docs/reference/meson.build b/docs/reference/meson.build index 40a98944..de67bb20 100644 --- a/docs/reference/meson.build +++ b/docs/reference/meson.build @@ -36,6 +36,7 @@ ignore_headers = [ 'soup-message-io-data.h', 'soup-uri-utils-private.h', 'soup-session-feature-private.h', + 'soup-message-metrics-private.h', ] mkdb_args = [ diff --git a/libsoup/cache/soup-cache.c b/libsoup/cache/soup-cache.c index f9303cec..969b3a4d 100644 --- a/libsoup/cache/soup-cache.c +++ b/libsoup/cache/soup-cache.c @@ -692,6 +692,8 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg) g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL); g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_REQUEST_START); + entry = soup_cache_entry_lookup (cache, msg); g_return_val_if_fail (entry, NULL); @@ -716,6 +718,8 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg) /* Message starting */ soup_message_starting (msg); + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_START); + /* Status */ soup_message_set_status (msg, entry->status_code, NULL); diff --git a/libsoup/include/soup-installed.h b/libsoup/include/soup-installed.h index 85ad6eb5..99954228 100644 --- a/libsoup/include/soup-installed.h +++ b/libsoup/include/soup-installed.h @@ -30,6 +30,7 @@ extern "C" { #include <libsoup/soup-hsts-policy.h> #include <libsoup/soup-logger.h> #include <libsoup/soup-message.h> +#include <libsoup/soup-message-metrics.h> #include <libsoup/soup-method.h> #include <libsoup/soup-multipart.h> #include <libsoup/soup-multipart-input-stream.h> diff --git a/libsoup/meson.build b/libsoup/meson.build index 827c4a3b..adcccc3e 100644 --- a/libsoup/meson.build +++ b/libsoup/meson.build @@ -64,6 +64,7 @@ soup_sources = [ 'soup-message-headers.c', 'soup-message-io.c', 'soup-message-io-data.c', + 'soup-message-metrics.c', 'soup-message-queue-item.c', 'soup-method.c', 'soup-misc.c', @@ -120,6 +121,7 @@ soup_introspection_headers = [ 'soup-logger.h', 'soup-message.h', 'soup-message-headers.h', + 'soup-message-metrics.h', 'soup-method.h', 'soup-multipart.h', 'soup-multipart-input-stream.h', diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c index 8aef3170..9eab3ef2 100644 --- a/libsoup/soup-message-io.c +++ b/libsoup/soup-message-io.c @@ -510,12 +510,19 @@ io_read (SoupMessage *msg, gboolean blocking, SoupClientMessageIOData *client_io = soup_message_get_io_data (msg); SoupMessageIOData *io = &client_io->base; gboolean succeeded; + gboolean is_first_read; 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)) return FALSE; + if (is_first_read) + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_START); + succeeded = parse_headers (msg, (char *)io->read_header_buf->data, io->read_header_buf->len, @@ -533,6 +540,7 @@ io_read (SoupMessage *msg, gboolean blocking, */ soup_message_headers_append (soup_message_get_request_headers (msg), "Connection", "close"); + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_END); io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; break; } @@ -639,6 +647,7 @@ io_read (SoupMessage *msg, gboolean blocking, case SOUP_MESSAGE_IO_STATE_BODY_DONE: io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_END); soup_message_got_body (msg); break; @@ -771,7 +780,9 @@ soup_message_io_finish (SoupMessage *msg, /* Connection got closed, but we can safely try again. */ io->item->state = SOUP_MESSAGE_RESTARTING; - } + } else if (error) { + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_END); + } soup_message_io_finished (msg); } diff --git a/libsoup/soup-message-metrics-private.h b/libsoup/soup-message-metrics-private.h new file mode 100644 index 00000000..03d62ad1 --- /dev/null +++ b/libsoup/soup-message-metrics-private.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright 2021 Igalia S.L. + */ + +#pragma once + +#include "soup-message-metrics.h" + +G_BEGIN_DECLS + +struct _SoupMessageMetrics { + guint64 fetch_start; + guint64 dns_start; + guint64 dns_end; + guint64 connect_start; + guint64 connect_end; + guint64 tls_start; + guint64 request_start; + guint64 response_start; + guint64 response_end; +}; + +SoupMessageMetrics *soup_message_metrics_new (void); + +G_END_DECLS diff --git a/libsoup/soup-message-metrics.c b/libsoup/soup-message-metrics.c new file mode 100644 index 00000000..e3b642c3 --- /dev/null +++ b/libsoup/soup-message-metrics.c @@ -0,0 +1,245 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * soup-message-metrics.c + * + * Copyright (C) 2021 Igalia S.L. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "soup-message-metrics-private.h" + +/** + * SECTION:soup-message-metrics + * @short_description: Message metrics + * @see_also: #SoupMessage + * + * Metrics collected while loading a #SoupMessage. + * + * Metrics are not collected by default for a #SoupMessage, you need to add the + * flag %SOUP_MESSAGE_COLLECT_METRICS to enable the feature. + */ + +/** + * SoupMessageMetrics: + * + * SoupMessageMetrics contains metrics collected while loading a #SoupMessage + * either from the network or the disk cache. + * + * Temporal metrics are expressed as a monotonic time and always start with a + * 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. + */ + +G_DEFINE_BOXED_TYPE (SoupMessageMetrics, soup_message_metrics, soup_message_metrics_copy, soup_message_metrics_free) + +SoupMessageMetrics * +soup_message_metrics_new (void) +{ + return g_slice_new0 (SoupMessageMetrics); +} + +/** + * soup_message_metrics_copy: + * @metrics: a #SoupMessageMetrics + * + * Copies @metrics. + * + * Returns: a copy of @metrics + * + **/ +SoupMessageMetrics * +soup_message_metrics_copy (SoupMessageMetrics *metrics) +{ + SoupMessageMetrics *copy; + + g_return_val_if_fail (metrics != NULL, NULL); + + copy = soup_message_metrics_new (); + *copy = *metrics; + + return copy; +} + +/** + * soup_message_metrics_free: + * @metrics: a #SoupMessageMetrics + * + * Frees @metrics + */ +void +soup_message_metrics_free (SoupMessageMetrics *metrics) +{ + g_return_if_fail (metrics != NULL); + + g_slice_free (SoupMessageMetrics, metrics); +} + +/** + * soup_message_metrics_get_fetch_start: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately before the #SoupMessage started to + * fetch a resource either from a remote server or local disk cache. + * + * Returns: the fetch start time + */ +guint64 +soup_message_metrics_get_fetch_start (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->fetch_start; +} + +/** + * soup_message_metrics_get_dns_start: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately before the #SoupMessage started the + * domain lookup name for the resource. It will be 0 if no domain + * lookup was required to fetch the resource (a persistent connection + * was used or resource was loaded from the local disk cache). + * + * Returns: the domain lookup start time + */ +guint64 +soup_message_metrics_get_dns_start (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->dns_start; +} + +/** + * soup_message_metrics_get_dns_end: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately after the #SoupMessage completed the + * domain lookup name for the resource. It will be 0 if no domain + * lookup was required to fetch the resource (a persistent connection + * was used or resource was loaded from the local disk cache). + * + * Returns: the domain lookup end time + */ +guint64 +soup_message_metrics_get_dns_end (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->dns_end; +} + +/** + * soup_message_metrics_get_connect_start: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately before the #SoupMessage started to + * establish the connection to the server. It will be 0 if no + * network connection was required to fetch the resource (a persistent + * connection was used or resource was loaded from the local disk cache). + * + * Returns: the connection start time + */ +guint64 +soup_message_metrics_get_connect_start (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->connect_start; +} + +/** + * soup_message_metrics_get_connect_end: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately after the #SoupMessage completed the + * connection to the server. This includes the time for the proxy + * negotiation and TLS handshake. It will be 0 if no network connection + * was required to fetch the resource (a persistent connection was used + * or resource was loaded from the local disk cache). + * + * Returns: the connection end time + */ +guint64 +soup_message_metrics_get_connect_end (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->connect_end; +} + +/** + * soup_message_metrics_get_tls_start: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately before the #SoupMessage started the + * TLS handshake. It will be 0 if no TLS handshake was required + * to fetch the resource (connection was not secure, a persistent + * connection was used or resource was loaded from the local disk cache). + * + * Returns: the tls start time + */ +guint64 +soup_message_metrics_get_tls_start (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->tls_start; +} + +/** + * soup_message_metrics_get_request_start: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately before the #SoupMessage started the + * request of the resource from the server or the local disk cache. + * + * Returns: the request start time + */ +guint64 +soup_message_metrics_get_request_start (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->request_start; +} + +/** + * soup_message_metrics_get_response_start: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately after the #SoupMessage received the first + * bytes of the response from the server or the local disk cache. + * + * Returns: the response start time + */ +guint64 +soup_message_metrics_get_response_start (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->response_start; +} + +/** + * soup_message_metrics_get_response_end: + * @metrics: a #SoupMessageMetrics + * + * Get the time immediately after the #SoupMessage received the last + * bytes of the response from the server or the local disk cache. + * In case of load failure, this returns the time immediately before the + * fetch is aborted. + * + * Returns: the response end time + */ +guint64 +soup_message_metrics_get_response_end (SoupMessageMetrics *metrics) +{ + g_return_val_if_fail (metrics != NULL, 0); + + return metrics->response_end; +} diff --git a/libsoup/soup-message-metrics.h b/libsoup/soup-message-metrics.h new file mode 100644 index 00000000..b5d3f9fb --- /dev/null +++ b/libsoup/soup-message-metrics.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright 2021 Igalia S.L. + */ + +#pragma once + +#include "soup-types.h" + +G_BEGIN_DECLS + +typedef struct _SoupMessageMetrics SoupMessageMetrics; + +SOUP_AVAILABLE_IN_ALL +GType soup_message_metrics_get_type (void); +#define SOUP_TYPE_MESSAGE_METRICS (soup_message_metrics_get_type()) + +SOUP_AVAILABLE_IN_ALL +SoupMessageMetrics *soup_message_metrics_copy (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +void soup_message_metrics_free (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_fetch_start (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_dns_start (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_dns_end (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_connect_start (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_connect_end (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_tls_start (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_request_start (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_response_start (SoupMessageMetrics *metrics); + +SOUP_AVAILABLE_IN_ALL +guint64 soup_message_metrics_get_response_end (SoupMessageMetrics *metrics); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(SoupMessageMetrics, soup_message_metrics_free) + +G_END_DECLS diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h index 93e45d9e..05e0dcc9 100644 --- a/libsoup/soup-message-private.h +++ b/libsoup/soup-message-private.h @@ -143,4 +143,19 @@ void soup_message_set_method (SoupMessage *ms void soup_message_set_http_version (SoupMessage *msg, SoupHTTPVersion version); +typedef enum { + SOUP_MESSAGE_METRICS_FETCH_START, + SOUP_MESSAGE_METRICS_DNS_START, + SOUP_MESSAGE_METRICS_DNS_END, + SOUP_MESSAGE_METRICS_CONNECT_START, + SOUP_MESSAGE_METRICS_CONNECT_END, + SOUP_MESSAGE_METRICS_TLS_START, + SOUP_MESSAGE_METRICS_REQUEST_START, + SOUP_MESSAGE_METRICS_RESPONSE_START, + SOUP_MESSAGE_METRICS_RESPONSE_END +} SoupMessageMetricsType; + +void soup_message_set_metrics_timestamp (SoupMessage *msg, + SoupMessageMetricsType type); + #endif /* __SOUP_MESSAGE_PRIVATE_H__ */ diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index d9e9e4f3..87adc6f4 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -15,6 +15,7 @@ #include "soup.h" #include "soup-connection.h" #include "soup-message-private.h" +#include "soup-message-metrics-private.h" #include "soup-uri-utils-private.h" /** @@ -93,6 +94,8 @@ typedef struct { gboolean is_top_level_navigation; gboolean is_options_ping; guint last_connection_id; + + SoupMessageMetrics *metrics; } SoupMessagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (SoupMessage, soup_message, G_TYPE_OBJECT) @@ -168,6 +171,7 @@ soup_message_finalize (GObject *object) g_clear_pointer (&priv->uri, g_uri_unref); g_clear_pointer (&priv->first_party, g_uri_unref); g_clear_pointer (&priv->site_for_cookies, g_uri_unref); + g_clear_pointer (&priv->metrics, soup_message_metrics_free); g_clear_object (&priv->auth); g_clear_object (&priv->proxy_auth); @@ -1314,10 +1318,42 @@ soup_message_get_connection (SoupMessage *msg) } static void +soup_message_set_metrics_timestamp_for_network_event (SoupMessage *msg, + GSocketClientEvent event) +{ + switch (event) { + case G_SOCKET_CLIENT_RESOLVING: + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_DNS_START); + break; + case G_SOCKET_CLIENT_RESOLVED: + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_DNS_END); + break; + case G_SOCKET_CLIENT_CONNECTING: + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_CONNECT_START); + break; + case G_SOCKET_CLIENT_CONNECTED: + /* connect_end happens after proxy and tls */ + case G_SOCKET_CLIENT_PROXY_NEGOTIATING: + case G_SOCKET_CLIENT_PROXY_NEGOTIATED: + break; + case G_SOCKET_CLIENT_TLS_HANDSHAKING: + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_TLS_START); + break; + case G_SOCKET_CLIENT_TLS_HANDSHAKED: + break; + case G_SOCKET_CLIENT_COMPLETE: + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_CONNECT_END); + break; + } +} + +static void re_emit_connection_event (SoupMessage *msg, GSocketClientEvent event, GIOStream *connection) { + soup_message_set_metrics_timestamp_for_network_event (msg, event); + g_signal_emit (msg, signals[NETWORK_EVENT], 0, event, connection); } @@ -1428,6 +1464,7 @@ soup_message_cleanup_response (SoupMessage *msg) * and proxy authentication. Note that #SoupMessage::authenticate signal will * be emitted, if you want to disable authentication for a message use * soup_message_disable_feature() passing #SOUP_TYPE_AUTH_MANAGER instead. + * @SOUP_MESSAGE_COLLECT_METRICS: Metrics will be collected for this message. * * Various flags that can be set on a #SoupMessage to alter its * behavior. @@ -2335,3 +2372,76 @@ soup_message_get_connection_id (SoupMessage *msg) return priv->last_connection_id; } + +/** + * soup_message_get_metrics: + * @msg: The #SoupMessage + * + * Get the #SoupMessageMetrics of @msg. If the flag %SOUP_MESSAGE_COLLECT_METRICS is not + * enabled for @msg this will return %NULL. + * + * Returns: (transfer none) (nullable): a #SoupMessageMetrics, or %NULL + */ +SoupMessageMetrics * +soup_message_get_metrics (SoupMessage *msg) +{ + SoupMessagePrivate *priv; + + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); + + priv = soup_message_get_instance_private (msg); + if (priv->metrics) + return priv->metrics; + + if (priv->msg_flags & SOUP_MESSAGE_COLLECT_METRICS) + priv->metrics = soup_message_metrics_new (); + + return priv->metrics; +} + +void +soup_message_set_metrics_timestamp (SoupMessage *msg, + SoupMessageMetricsType type) +{ + SoupMessageMetrics *metrics = soup_message_get_metrics (msg); + guint64 timestamp; + + if (!metrics) + return; + + timestamp = g_get_monotonic_time (); + switch (type) { + case SOUP_MESSAGE_METRICS_FETCH_START: + memset (metrics, 0, sizeof (SoupMessageMetrics)); + metrics->fetch_start = timestamp; + break; + case SOUP_MESSAGE_METRICS_DNS_START: + metrics->dns_start = timestamp; + break; + case SOUP_MESSAGE_METRICS_DNS_END: + metrics->dns_end = timestamp; + break; + case SOUP_MESSAGE_METRICS_CONNECT_START: + metrics->connect_start = timestamp; + break; + case SOUP_MESSAGE_METRICS_CONNECT_END: + metrics->connect_end = timestamp; + break; + case SOUP_MESSAGE_METRICS_TLS_START: + metrics->tls_start = timestamp; + break; + case SOUP_MESSAGE_METRICS_REQUEST_START: + metrics->request_start = timestamp; + break; + case SOUP_MESSAGE_METRICS_RESPONSE_START: + /* In case of multiple requests due to a informational response + * the response start is the first one. + */ + if (metrics->response_start == 0) + metrics->response_start = timestamp; + break; + case SOUP_MESSAGE_METRICS_RESPONSE_END: + metrics->response_end = timestamp; + break; + } +} diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h index 99ebb766..a6ebf57d 100644 --- a/libsoup/soup-message.h +++ b/libsoup/soup-message.h @@ -77,7 +77,8 @@ typedef enum { SOUP_MESSAGE_NO_REDIRECT = (1 << 1), SOUP_MESSAGE_NEW_CONNECTION = (1 << 2), SOUP_MESSAGE_IDEMPOTENT = (1 << 3), - SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE = (1 << 4) + SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE = (1 << 4), + SOUP_MESSAGE_COLLECT_METRICS = (1 << 5) } SoupMessageFlags; SOUP_AVAILABLE_IN_ALL @@ -171,4 +172,8 @@ void soup_message_set_is_options_ping (SoupMessage *msg, SOUP_AVAILABLE_IN_ALL guint64 soup_message_get_connection_id (SoupMessage *msg); +SOUP_AVAILABLE_IN_ALL +SoupMessageMetrics *soup_message_get_metrics (SoupMessage *msg); + + G_END_DECLS diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index 06374dd5..521d32cb 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -1343,6 +1343,7 @@ soup_session_append_queue_item (SoupSession *session, SoupSessionHost *host; GSList *f; + soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_FETCH_START); soup_message_cleanup_response (msg); item = soup_message_queue_item_new (session, msg, async, cancellable, callback, user_data); @@ -1701,6 +1702,7 @@ tunnel_connect (SoupMessageQueueItem *item) msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, soup_message_get_uri (item->msg)); soup_message_add_flags (msg, SOUP_MESSAGE_NO_REDIRECT); + soup_message_remove_flags (msg, SOUP_MESSAGE_COLLECT_METRICS); tunnel_item = soup_session_append_queue_item (session, msg, item->async, @@ -1727,6 +1729,8 @@ connect_complete (SoupMessageQueueItem *item, SoupConnection *conn, GError *erro return; } + soup_message_set_metrics_timestamp (item->msg, SOUP_MESSAGE_METRICS_RESPONSE_END); + item->error = error; soup_connection_disconnect (conn); if (item->state == SOUP_MESSAGE_CONNECTING) { @@ -2003,6 +2007,8 @@ soup_session_process_queue_item (SoupSession *session, item->state = SOUP_MESSAGE_RUNNING; + soup_message_set_metrics_timestamp (item->msg, SOUP_MESSAGE_METRICS_REQUEST_START); + soup_session_send_queue_item (session, item, (SoupMessageIOCompletionFn)message_completed); @@ -2025,6 +2031,8 @@ soup_session_process_queue_item (SoupSession *session, case SOUP_MESSAGE_RESTARTING: item->state = SOUP_MESSAGE_STARTING; soup_message_restarted (item->msg); + soup_message_set_metrics_timestamp (item->msg, SOUP_MESSAGE_METRICS_FETCH_START); + break; case SOUP_MESSAGE_FINISHING: @@ -3040,6 +3048,8 @@ static void cache_stream_finished (GInputStream *stream, SoupMessageQueueItem *item) { + soup_message_set_metrics_timestamp (item->msg, SOUP_MESSAGE_METRICS_RESPONSE_END); + g_signal_handlers_disconnect_matched (stream, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, item); item->state = SOUP_MESSAGE_FINISHING; @@ -3175,6 +3185,7 @@ async_respond_from_cache (SoupSession *session, /* Cached file was deleted? */ return FALSE; } + g_object_set_data_full (G_OBJECT (item->task), "SoupSession:istream", stream, g_object_unref); diff --git a/libsoup/soup-types.h b/libsoup/soup-types.h index 5e8d4862..8ce0505f 100644 --- a/libsoup/soup-types.h +++ b/libsoup/soup-types.h @@ -22,6 +22,7 @@ typedef struct _SoupCookieJar SoupCookieJar; typedef struct _SoupHSTSEnforcer SoupHSTSEnforcer; typedef struct _SoupHSTSPolicy SoupHSTSPolicy; typedef struct _SoupMessage SoupMessage; +typedef struct _SoupMessageMetrics SoupMessageMetrics; typedef struct _SoupServer SoupServer; typedef struct _SoupServerMessage SoupServerMessage; typedef struct _SoupSession SoupSession; diff --git a/libsoup/soup.h b/libsoup/soup.h index 73c7f9c4..42d758a7 100644 --- a/libsoup/soup.h +++ b/libsoup/soup.h @@ -30,6 +30,7 @@ extern "C" { #include "hsts/soup-hsts-policy.h" #include "soup-logger.h" #include "soup-message.h" +#include "soup-message-metrics.h" #include "soup-method.h" #include "soup-multipart.h" #include "soup-multipart-input-stream.h" diff --git a/tests/cache-test.c b/tests/cache-test.c index 97710047..517201ea 100644 --- a/tests/cache-test.c +++ b/tests/cache-test.c @@ -738,6 +738,115 @@ do_leaks_test (gconstpointer data) g_free (cache_dir); } +static void +do_metrics_test (gconstpointer data) +{ + GUri *base_uri = (GUri *)data; + SoupSession *session; + SoupCache *cache; + char *cache_dir; + char *body; + GUri *uri; + SoupMessage *msg; + SoupMessageHeaders *request_headers; + GInputStream *stream; + char buffer[256]; + gsize nread; + SoupMessageMetrics *metrics; + + cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL); + debug_printf (2, " Caching to %s\n", cache_dir); + cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER); + session = soup_test_session_new (NULL); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (cache)); + + g_signal_connect (session, "request-queued", + G_CALLBACK (request_queued), NULL); + g_signal_connect (session, "request-unqueued", + G_CALLBACK (request_unqueued), NULL); + + body = do_request (session, base_uri, "GET", "/1", NULL, + "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT", + NULL); + g_free (body); + + last_request_validated = last_request_hit_network = last_request_unqueued = FALSE; + uri = g_uri_parse_relative (base_uri, "/1", 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_fetch_start (metrics), ==, 0); + + stream = soup_test_request_send (session, msg, NULL, 0, NULL); + g_assert_true (G_IS_INPUT_STREAM (stream)); + g_assert_false (is_network_stream (stream)); + + g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >=, soup_message_metrics_get_fetch_start (metrics)); + g_input_stream_read_all (stream, buffer, sizeof (buffer), &nread, NULL, NULL); + g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >=, soup_message_metrics_get_request_start (metrics)); + 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_object_unref (msg); + + body = do_request (session, base_uri, "GET", "/2", NULL, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT", + "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT", + "Test-Set-Cache-Control", "must-revalidate", + NULL); + g_free (body); + + last_request_validated = last_request_hit_network = last_request_unqueued = FALSE; + uri = g_uri_parse_relative (base_uri, "/2", SOUP_HTTP_URI_FLAGS, NULL); + msg = soup_message_new_from_uri ("GET", uri); + g_uri_unref (uri); + + request_headers = soup_message_get_request_headers (msg); + soup_message_headers_append (request_headers, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT"); + soup_message_headers_append (request_headers, + "Test-Set-Expires", "Sat, 02 Jan 2011 00:00:00 GMT"); + soup_message_headers_append (request_headers, + "Test-Set-Cache-Control", "must-revalidate"); + + 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_fetch_start (metrics), ==, 0); + + stream = soup_test_request_send (session, msg, NULL, 0, NULL); + g_assert_true (G_IS_INPUT_STREAM (stream)); + g_assert_false (is_network_stream (stream)); + g_assert_true (last_request_validated); + + g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >=, soup_message_metrics_get_fetch_start (metrics)); + g_input_stream_read_all (stream, buffer, sizeof (buffer), &nread, NULL, NULL); + g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >=, soup_message_metrics_get_request_start (metrics)); + 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_object_unref (msg); + + soup_test_session_abort_unref (session); + g_object_unref (cache); + g_free (cache_dir); +} + int main (int argc, char **argv) { @@ -756,6 +865,7 @@ main (int argc, char **argv) g_test_add_data_func ("/cache/refcounting", base_uri, do_refcounting_test); g_test_add_data_func ("/cache/headers", base_uri, do_headers_test); g_test_add_data_func ("/cache/leaks", base_uri, do_leaks_test); + g_test_add_data_func ("/cache/metrics", base_uri, do_metrics_test); ret = g_test_run (); diff --git a/tests/connection-test.c b/tests/connection-test.c index b307cb88..ec40bc5b 100644 --- a/tests/connection-test.c +++ b/tests/connection-test.c @@ -863,17 +863,92 @@ network_event (SoupMessage *msg, GSocketClientEvent event, } } + if (soup_message_query_flags (msg, SOUP_MESSAGE_COLLECT_METRICS)) { + SoupMessageMetrics *metrics = soup_message_get_metrics (msg); + + g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0); + + switch (event) { + case G_SOCKET_CLIENT_RESOLVING: + g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), >, 0); + break; + case G_SOCKET_CLIENT_RESOLVED: + g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), >=, soup_message_metrics_get_dns_start (metrics)); + break; + case G_SOCKET_CLIENT_CONNECTING: + g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), >=, soup_message_metrics_get_dns_end (metrics)); + break; + case G_SOCKET_CLIENT_TLS_HANDSHAKING: + g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), >=, soup_message_metrics_get_connect_start (metrics)); + break; + case G_SOCKET_CLIENT_COMPLETE: + g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >=, soup_message_metrics_get_connect_start (metrics)); + if (soup_message_metrics_get_tls_start (metrics)) + g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >=, soup_message_metrics_get_tls_start (metrics)); + break; + default: + break; + } + } + *events = *events + 1; } static void -do_one_connection_event_test (SoupSession *session, const char *uri, - const char *events) +metrics_test_message_starting_cb (SoupMessage *msg) +{ + SoupMessageMetrics *metrics = soup_message_get_metrics (msg); + + g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >=, soup_message_metrics_get_fetch_start (metrics)); +} + +static void +metrics_test_status_changed_cb (SoupMessage *msg) +{ + SoupMessageMetrics *metrics; + + metrics = soup_message_get_metrics (msg); + g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >=, soup_message_metrics_get_request_start (metrics)); +} + +static void +metrics_test_got_body_cb (SoupMessage *msg) +{ + SoupMessageMetrics *metrics = soup_message_get_metrics (msg); + + g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >=, soup_message_metrics_get_response_start (metrics)); +} + +static void +do_one_connection_event_test (SoupSession *session, + const char *uri, + gboolean collect_metrics, + const char *events) { SoupMessage *msg; GBytes *body; + SoupMessageMetrics *metrics; msg = soup_message_new ("GET", uri); + if (collect_metrics) { + soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS); + g_signal_connect (msg, "starting", + G_CALLBACK (metrics_test_message_starting_cb), + NULL); + g_signal_connect (msg, "notify::status-code", + G_CALLBACK (metrics_test_status_changed_cb), + NULL); + g_signal_connect (msg, "got-body", + G_CALLBACK (metrics_test_got_body_cb), + NULL); + } g_signal_connect (msg, "network-event", G_CALLBACK (network_event), &events); @@ -886,22 +961,128 @@ do_one_connection_event_test (SoupSession *session, const char *uri, events++; } + metrics = soup_message_get_metrics (msg); + if (collect_metrics) { + g_assert_nonnull (metrics); + + g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), >, 0); + if (g_str_equal (uri, HTTPS_SERVER)) + g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >, 0); + } else { + g_assert_null (metrics); + } + g_bytes_unref (body); g_object_unref (msg); soup_session_abort (session); } static void -do_connection_event_test_for_session (SoupSession *session) +do_one_connection_event_fail_test (SoupSession *session, + const char *uri, + gboolean collect_metrics, + GQuark domain, + gint code, + const char *events) +{ + SoupMessage *msg; + GBytes *body; + SoupMessageMetrics *metrics; + GError *error = NULL; + GTlsDatabase *previous_tlsdb = NULL; + + if (tls_available) { + GTlsDatabase *tlsdb; + + previous_tlsdb = g_object_ref (soup_session_get_tls_database (session)); + tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ()); + soup_session_set_tls_database (session, tlsdb); + g_object_unref (tlsdb); + } + + msg = soup_message_new ("GET", uri); + if (collect_metrics) { + soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS); + g_signal_connect (msg, "starting", + G_CALLBACK (metrics_test_message_starting_cb), + NULL); + g_signal_connect (msg, "notify::status-code", + G_CALLBACK (metrics_test_status_changed_cb), + NULL); + g_signal_connect (msg, "got-body", + G_CALLBACK (metrics_test_got_body_cb), + NULL); + } + g_signal_connect (msg, "network-event", + G_CALLBACK (network_event), + &events); + body = soup_session_send_and_read (session, msg, NULL, &error); + soup_test_assert_message_status (msg, SOUP_STATUS_NONE); + g_assert_error (error, domain, code); + g_error_free (error); + + while (*events) { + soup_test_assert (!*events, + "Expected %s", + event_name_from_abbrev (*events)); + events++; + } + + metrics = soup_message_get_metrics (msg); + if (collect_metrics) { + g_assert_nonnull (metrics); + + g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), >, 0); + if (g_str_equal (uri, HTTPS_SERVER)) + g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), >, 0); + g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), ==, 0); + g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >, 0); + } else { + g_assert_null (metrics); + } + + g_bytes_unref (body); + g_object_unref (msg); + soup_session_abort (session); + + if (tls_available) { + soup_session_set_tls_database (session, previous_tlsdb); + g_object_unref (previous_tlsdb); + } +} + +static void +do_connection_event_test_for_session (SoupSession *session, + gboolean collect_metrics) { GProxyResolver *resolver; debug_printf (1, " http\n"); - do_one_connection_event_test (session, HTTP_SERVER, "rRcCx"); + do_one_connection_event_test (session, HTTP_SERVER, collect_metrics, "rRcCx"); + + debug_printf (1, " wrong http (invalid port)\n"); + do_one_connection_event_fail_test (session, HTTP_SERVER_BAD_PORT, collect_metrics, + G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, + "rRc"); if (tls_available) { debug_printf (1, " https\n"); - do_one_connection_event_test (session, HTTPS_SERVER, "rRcCtTx"); + do_one_connection_event_test (session, HTTPS_SERVER, collect_metrics, "rRcCtTx"); + debug_printf (1, " wrong https (invalid certificate)\n"); + do_one_connection_event_fail_test (session, HTTPS_SERVER, collect_metrics, + G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, + "rRcCt"); } else debug_printf (1, " https -- SKIPPING\n"); @@ -910,11 +1091,11 @@ do_connection_event_test_for_session (SoupSession *session) g_object_unref (resolver); debug_printf (1, " http with proxy\n"); - do_one_connection_event_test (session, HTTP_SERVER, "rRcCx"); + do_one_connection_event_test (session, HTTP_SERVER, collect_metrics, "rRcCx"); if (tls_available) { debug_printf (1, " https with proxy\n"); - do_one_connection_event_test (session, HTTPS_SERVER, "rRcCpPtTx"); + do_one_connection_event_test (session, HTTPS_SERVER, collect_metrics, "rRcCpPtTx"); } else debug_printf (1, " https with proxy -- SKIPPING\n"); } @@ -927,7 +1108,7 @@ do_connection_event_test (void) SOUP_TEST_SKIP_IF_NO_APACHE; session = soup_test_session_new (NULL); - do_connection_event_test_for_session (session); + do_connection_event_test_for_session (session, FALSE); soup_test_session_abort_unref (session); } @@ -1261,6 +1442,18 @@ do_connection_preconnect_test (void) debug_printf (1, " https -- SKIPPING\n"); } +static void +do_connection_metrics_test (void) +{ + SoupSession *session; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + session = soup_test_session_new (NULL); + do_connection_event_test_for_session (session, TRUE); + soup_test_session_abort_unref (session); +} + int main (int argc, char **argv) { @@ -1283,6 +1476,7 @@ main (int argc, char **argv) g_test_add_func ("/connection/state", do_connection_state_test); g_test_add_func ("/connection/event", do_connection_event_test); g_test_add_func ("/connection/preconnect", do_connection_preconnect_test); + g_test_add_func ("/connection/metrics", do_connection_metrics_test); ret = g_test_run (); |
