From ea930a4bf8d443678e418bb79a5d3f0263fa5b98 Mon Sep 17 00:00:00 2001 From: Vivia Nikolaidou Date: Mon, 24 Feb 2020 16:44:12 +0200 Subject: [PATCH] tcpclientsrc: Expose connection stats as property Unfortunately the OS takes care of bad connections for us, so we can't get the stats in a platform-independent way. Count total bytes received as well, platform-independently. --- gst/tcp/gsttcpclientsrc.c | 83 ++++++++++++++++++++++++++++++++++++++- gst/tcp/gsttcpclientsrc.h | 3 ++ meson.build | 2 + 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/gst/tcp/gsttcpclientsrc.c b/gst/tcp/gsttcpclientsrc.c index b601f1c212..b236e1a3b1 100644 --- a/gst/tcp/gsttcpclientsrc.c +++ b/gst/tcp/gsttcpclientsrc.c @@ -43,6 +43,16 @@ #include "config.h" #endif +/* macOS and iOS have the .h files but the tcp_info struct is private API */ +#if defined(HAVE_NETINET_TCP_H) && defined(HAVE_NETINET_IN_H) && defined(HAVE_SYS_SOCKET_H) +#include +#include +#include +#if defined(TCP_INFO) +#define HAVE_SOCKET_METRIC_HEADERS +#endif +#endif + #include #include "gsttcpclientsrc.h" #include "gsttcp.h" @@ -65,7 +75,8 @@ enum PROP_0, PROP_HOST, PROP_PORT, - PROP_TIMEOUT + PROP_TIMEOUT, + PROP_STATS, }; #define gst_tcp_client_src_parent_class parent_class @@ -88,6 +99,7 @@ static void gst_tcp_client_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_tcp_client_src_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +static GstStructure *gst_tcp_client_src_get_stats (GstTCPClientSrc * src); static void gst_tcp_client_src_class_init (GstTCPClientSrcClass * klass) @@ -128,6 +140,30 @@ gst_tcp_client_src_class_init (GstTCPClientSrcClass * klass) G_MAXUINT, TCP_DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstTCPClientSrc::stats: + * + * Sends a GstStructure with statistics. We count bytes-received in a + * platform-independent way and the rest via the tcp_info struct, if it's + * available. The OS takes care of the TCP layer for us so we can't know it + * from here. + * + * Struct members: + * + * bytes-received (uint64): Total bytes received (platform-independent) + * reordering (uint): Amount of reordering (linux-specific) + * unacked (uint): Un-acked packets (linux-specific) + * sacked (uint): Selective acked packets (linux-specific) + * lost (uint): Lost packets (linux-specific) + * retrans (uint): Retransmits (linux-specific) + * fackets (uint): Forward acknowledgement (linux-specific) + * + * Since: 1.18 + */ + g_object_class_install_property (gobject_class, PROP_STATS, + g_param_spec_boxed ("stats", "Stats", "Retrieve a statistics structure", + GST_TYPE_STRUCTURE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + gst_element_class_add_static_pad_template (gstelement_class, &srctemplate); gst_element_class_set_static_metadata (gstelement_class, @@ -172,6 +208,7 @@ gst_tcp_client_src_finalize (GObject * gobject) this->socket = NULL; g_free (this->host); this->host = NULL; + gst_clear_structure (&this->stats); G_OBJECT_CLASS (parent_class)->finalize (gobject); } @@ -278,6 +315,7 @@ gst_tcp_client_src_create (GstPushSrc * psrc, GstBuffer ** outbuf) ret = GST_FLOW_OK; gst_buffer_unmap (*outbuf, &map); gst_buffer_resize (*outbuf, 0, rret); + src->bytes_received += read; GST_LOG_OBJECT (src, "Returning buffer from _get of size %" G_GSIZE_FORMAT ", ts %" @@ -363,6 +401,9 @@ gst_tcp_client_src_get_property (GObject * object, guint prop_id, case PROP_TIMEOUT: g_value_set_uint (value, tcpclientsrc->timeout); break; + case PROP_STATS: + g_value_take_boxed (value, gst_tcp_client_src_get_stats (tcpclientsrc)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -379,6 +420,9 @@ gst_tcp_client_src_start (GstBaseSrc * bsrc) GSocketAddress *saddr; GResolver *resolver; + src->bytes_received = 0; + gst_clear_structure (&src->stats); + /* look up name if we need to */ addr = g_inet_address_new_from_string (src->host); if (!addr) { @@ -480,6 +524,8 @@ gst_tcp_client_src_stop (GstBaseSrc * bsrc) if (src->socket) { GST_DEBUG_OBJECT (src, "closing socket"); + src->stats = gst_tcp_client_src_get_stats (src); + if (!g_socket_close (src->socket, &err)) { GST_ERROR_OBJECT (src, "Failed to close socket: %s", err->message); g_clear_error (&err); @@ -517,3 +563,38 @@ gst_tcp_client_src_unlock_stop (GstBaseSrc * bsrc) return TRUE; } + +static GstStructure * +gst_tcp_client_src_get_stats (GstTCPClientSrc * src) +{ + GstStructure *s; + + /* we can't get the values post stop so just return the saved ones */ + if (src->stats) + return gst_structure_copy (src->stats); + + s = gst_structure_new ("GstTCPClientSrcStats", + "bytes-received", G_TYPE_UINT64, src->bytes_received, NULL); + +#ifdef HAVE_SOCKET_METRIC_HEADERS + if (src->socket) { + struct tcp_info info; + socklen_t info_len = sizeof info; + int fd; + + fd = g_socket_get_fd (src->socket); + + if (getsockopt (fd, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0) { + gst_structure_set (s, + "reordering", G_TYPE_UINT, info.tcpi_reordering, + "unacked", G_TYPE_UINT, info.tcpi_unacked, + "sacked", G_TYPE_UINT, info.tcpi_sacked, + "lost", G_TYPE_UINT, info.tcpi_lost, + "retrans", G_TYPE_UINT, info.tcpi_retrans, + "fackets", G_TYPE_UINT, info.tcpi_fackets, NULL); + } + } +#endif + + return s; +} diff --git a/gst/tcp/gsttcpclientsrc.h b/gst/tcp/gsttcpclientsrc.h index 582cb670ff..85fe647950 100644 --- a/gst/tcp/gsttcpclientsrc.h +++ b/gst/tcp/gsttcpclientsrc.h @@ -60,6 +60,9 @@ struct _GstTCPClientSrc { /* socket */ GSocket *socket; GCancellable *cancellable; + + guint64 bytes_received; + GstStructure *stats; }; struct _GstTCPClientSrcClass { diff --git a/meson.build b/meson.build index 789b73cf8b..967fdabb52 100644 --- a/meson.build +++ b/meson.build @@ -112,6 +112,8 @@ check_headers = [ ['HAVE_EMMINTRIN_H', 'emmintrin.h'], ['HAVE_INTTYPES_H', 'inttypes.h'], ['HAVE_MEMORY_H', 'memory.h'], + ['HAVE_NETINET_IN_H', 'netinet/in.h'], + ['HAVE_NETINET_TCP_H', 'netinet/tcp.h'], ['HAVE_PROCESS_H', 'process.h'], ['HAVE_SMMINTRIN_H', 'smmintrin.h'], ['HAVE_STDINT_H', 'stdint.h'],