[libsoup/carlosgc/metrics: 1/2] Add support for collecting metrics




commit ac41334a8b2d524a312462bcecf708bbab1c6e13
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Fri Mar 26 15:30:59 2021 +0100

    Add support for collecting metrics

 docs/reference/libsoup-3.0-docs.xml     |   4 +
 docs/reference/libsoup-3.0-sections.txt |  23 +++
 docs/reference/meson.build              |   1 +
 libsoup/cache/soup-cache.c              |   4 +
 libsoup/include/soup-installed.h        |   1 +
 libsoup/meson.build                     |   2 +
 libsoup/soup-message-io.c               |  13 +-
 libsoup/soup-message-metrics-private.h  |  26 ++++
 libsoup/soup-message-metrics.c          | 245 ++++++++++++++++++++++++++++++++
 libsoup/soup-message-metrics.h          |  53 +++++++
 libsoup/soup-message-private.h          |  15 ++
 libsoup/soup-message.c                  | 110 ++++++++++++++
 libsoup/soup-message.h                  |   7 +-
 libsoup/soup-session.c                  |  11 ++
 libsoup/soup-types.h                    |   1 +
 libsoup/soup.h                          |   1 +
 tests/cache-test.c                      | 110 ++++++++++++++
 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 410189b1..8a739f5e 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);
@@ -1313,11 +1317,43 @@ soup_message_get_connection (SoupMessage *msg)
        return priv->connection;
 }
 
+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.
@@ -2334,3 +2371,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 ();
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]