From e00ebca63fc6b98d58903d63ec7185f80682da30 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Fri, 14 Jun 2024 13:25:39 +0530 Subject: [PATCH] net/quinn: Add stats property for connection statistics Part-of: --- Cargo.lock | 1 + docs/plugins/gst_plugins_cache.json | 24 ++++++++++ net/quinn/Cargo.toml | 1 + net/quinn/src/quinnquicsink/imp.rs | 19 +++++++- net/quinn/src/quinnquicsrc/imp.rs | 19 +++++++- net/quinn/src/utils.rs | 73 ++++++++++++++++++++++++++++- 6 files changed, 132 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c9c641d..89efb69b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2704,6 +2704,7 @@ dependencies = [ "gstreamer-check", "once_cell", "quinn", + "quinn-proto", "rcgen", "rustls 0.23.10", "rustls-pemfile 2.1.2", diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index 13164fbf..8eced897 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -4243,6 +4243,18 @@ "type": "guint", "writable": true }, + "stats": { + "blurb": "Connection statistics", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "stats;", + "mutable": "null", + "readable": true, + "type": "GstStructure", + "writable": false + }, "timeout": { "blurb": "Value in seconds to timeout QUIC endpoint requests (0 = No timeout).", "conditionally-available": false, @@ -4551,6 +4563,18 @@ "type": "guint", "writable": true }, + "stats": { + "blurb": "Connection statistics", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "stats;", + "mutable": "null", + "readable": true, + "type": "GstStructure", + "writable": false + }, "timeout": { "blurb": "Value in seconds to timeout QUIC endpoint requests (0 = No timeout).", "conditionally-available": false, diff --git a/net/quinn/Cargo.toml b/net/quinn/Cargo.toml index 4daddb6b..21c5f44f 100644 --- a/net/quinn/Cargo.toml +++ b/net/quinn/Cargo.toml @@ -17,6 +17,7 @@ once_cell.workspace = true tokio = { version = "1.36.0", default-features = false, features = ["time", "rt-multi-thread"] } futures = "0.3.30" quinn = { version = "0.11.2", default-features = true, features = ["ring"]} +quinn-proto = "0.11.2" rustls = { version = "0.23", default-features = false, features = ["ring", "std"]} rustls-pemfile = "2" rustls-pki-types = "1" diff --git a/net/quinn/src/quinnquicsink/imp.rs b/net/quinn/src/quinnquicsink/imp.rs index 59ebf567..2d9611a3 100644 --- a/net/quinn/src/quinnquicsink/imp.rs +++ b/net/quinn/src/quinnquicsink/imp.rs @@ -8,8 +8,8 @@ // SPDX-License-Identifier: MPL-2.0 use crate::utils::{ - client_endpoint, make_socket_addr, server_endpoint, wait, QuinnQuicEndpointConfig, WaitError, - CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG, + client_endpoint, get_stats, make_socket_addr, server_endpoint, wait, QuinnQuicEndpointConfig, + WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG, }; use crate::{common::*, utils}; use bytes::Bytes; @@ -264,6 +264,11 @@ impl ObjectImpl for QuinnQuicSink { .nick("Datagram Send Buffer Size") .blurb("Maximum number of outgoing application datagram bytes to buffer") .build(), + glib::ParamSpecBoxed::builder::("stats") + .nick("Connection statistics") + .blurb("Connection statistics") + .read_only() + .build() ] }); @@ -399,6 +404,16 @@ impl ObjectImpl for QuinnQuicSink { "datagram-send-buffer-size" => { (settings.transport_config.datagram_send_buffer_size as u64).to_value() } + "stats" => { + let state = self.state.lock().unwrap(); + match *state { + State::Started(ref state) => { + let connection = state.connection.clone(); + get_stats(Some(connection)).to_value() + } + State::Stopped => get_stats(None).to_value(), + } + } _ => unimplemented!(), } } diff --git a/net/quinn/src/quinnquicsrc/imp.rs b/net/quinn/src/quinnquicsrc/imp.rs index 627de638..d754cde8 100644 --- a/net/quinn/src/quinnquicsrc/imp.rs +++ b/net/quinn/src/quinnquicsrc/imp.rs @@ -8,8 +8,8 @@ // SPDX-License-Identifier: MPL-2.0 use crate::utils::{ - client_endpoint, make_socket_addr, server_endpoint, wait, Canceller, QuinnQuicEndpointConfig, - WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG, + client_endpoint, get_stats, make_socket_addr, server_endpoint, wait, Canceller, + QuinnQuicEndpointConfig, WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG, }; use crate::{common::*, utils}; use bytes::Bytes; @@ -273,6 +273,11 @@ impl ObjectImpl for QuinnQuicSrc { .nick("Datagram Send Buffer Size") .blurb("Maximum number of outgoing application datagram bytes to buffer") .build(), + glib::ParamSpecBoxed::builder::("stats") + .nick("Connection statistics") + .blurb("Connection statistics") + .read_only() + .build() ] }); @@ -418,6 +423,16 @@ impl ObjectImpl for QuinnQuicSrc { "datagram-send-buffer-size" => { (settings.transport_config.datagram_send_buffer_size as u64).to_value() } + "stats" => { + let state = self.state.lock().unwrap(); + match *state { + State::Started(ref state) => { + let connection = state.connection.clone(); + get_stats(Some(connection)).to_value() + } + State::Stopped => get_stats(None).to_value(), + } + } _ => unimplemented!(), } } diff --git a/net/quinn/src/utils.rs b/net/quinn/src/utils.rs index e5a1533e..9aa57fc4 100644 --- a/net/quinn/src/utils.rs +++ b/net/quinn/src/utils.rs @@ -14,8 +14,10 @@ use gst::ErrorMessage; use once_cell::sync::Lazy; use quinn::{ crypto::rustls::QuicClientConfig, crypto::rustls::QuicServerConfig, default_runtime, - ClientConfig, Endpoint, EndpointConfig, MtuDiscoveryConfig, ServerConfig, TransportConfig, + ClientConfig, Connection, Endpoint, EndpointConfig, MtuDiscoveryConfig, ServerConfig, + TransportConfig, }; +use quinn_proto::{ConnectionStats, FrameStats, PathStats, UdpStats}; use std::error::Error; use std::fs::File; use std::io::BufReader; @@ -416,3 +418,72 @@ pub fn client_endpoint(ep_config: &QuinnQuicEndpointConfig) -> Result) -> gst::Structure { + match connection { + Some(conn) => { + // See quinn_proto::ConnectionStats + let stats = conn.stats(); + let udp_stats = |udp: UdpStats, name: String| -> gst::Structure { + gst::Structure::builder(name) + .field("datagrams", udp.datagrams) + .field("bytes", udp.bytes) + .field("ios", udp.ios) + .build() + }; + let frame_stats = |frame: FrameStats, name: String| -> gst::Structure { + gst::Structure::builder(name) + .field("acks", frame.acks) + .field("ack-frequency", frame.ack_frequency) + .field("crypto", frame.crypto) + .field("connection-close", frame.connection_close) + .field("data-blocked", frame.data_blocked) + .field("datagram", frame.datagram) + .field("handshake-done", frame.handshake_done) + .field("immediate-ack", frame.immediate_ack) + .field("max-data", frame.max_data) + .field("max-stream-data", frame.max_stream_data) + .field("max-streams-bidi", frame.max_streams_bidi) + .field("max-streams-uni", frame.max_streams_uni) + .field("new-connection-id", frame.new_connection_id) + .field("new-token", frame.new_token) + .field("path-challenge", frame.path_challenge) + .field("path-response", frame.path_response) + .field("ping", frame.ping) + .field("reset-stream", frame.reset_stream) + .field("retire-connection-id", frame.retire_connection_id) + .field("stream-data-blocked", frame.stream_data_blocked) + .field("streams-blocked-bidi", frame.streams_blocked_bidi) + .field("streams-blocked-uni", frame.streams_blocked_uni) + .field("stop-sending", frame.stop_sending) + .field("stream", frame.stream) + .build() + }; + let path_stats = gst::Structure::builder("path") + .field("cwnd", stats.path.cwnd) + .field("congestion-events", stats.path.congestion_events) + .field("lost-packets", stats.path.lost_packets) + .field("lost-bytes", stats.path.lost_bytes) + .field("sent-packets", stats.path.sent_packets) + .field("sent-plpmtud-probes", stats.path.sent_plpmtud_probes) + .field("lost-plpmtud-probes", stats.path.lost_plpmtud_probes) + .field("black-holes-detected", stats.path.black_holes_detected) + .build(); + + gst::Structure::builder("stats") + .field("udp-tx", udp_stats(stats.udp_tx, "udp-tx".to_string())) + .field("udp-rx", udp_stats(stats.udp_rx, "udp-rx".to_string())) + .field("path", path_stats) + .field( + "frame-tx", + frame_stats(stats.frame_tx, "frame-tx".to_string()), + ) + .field( + "frame-rx", + frame_stats(stats.frame_rx, "frame-rx".to_string()), + ) + .build() + } + None => gst::Structure::new_empty("stats"), + } +}