From 40dfabf9f8b1777d739532a1d12c58e6f54bdbed Mon Sep 17 00:00:00 2001 From: Jebastine E Date: Tue, 8 Sep 2026 21:07:39 +0530 Subject: [PATCH 1/2] feat: add metrics bindings --- .../org_rocstreaming_roctoolkit_RocReceiver.h | 8 ++ .../org_rocstreaming_roctoolkit_RocSender.h | 8 ++ roc_jni/src/main/impl/package.h | 3 + roc_jni/src/main/impl/receiver.c | 87 +++++++++++++++++++ roc_jni/src/main/impl/sender.c | 87 +++++++++++++++++++ .../roctoolkit/RocConnectionMetrics.java | 29 +++++++ .../rocstreaming/roctoolkit/RocReceiver.java | 29 +++++++ .../roctoolkit/RocReceiverMetrics.java | 33 +++++++ .../rocstreaming/roctoolkit/RocSender.java | 29 +++++++ .../roctoolkit/RocSenderMetrics.java | 33 +++++++ .../roctoolkit/RocReceiverTest.java | 23 +++++ .../roctoolkit/RocSenderTest.java | 23 +++++ 12 files changed, 392 insertions(+) create mode 100644 src/main/java/org/rocstreaming/roctoolkit/RocConnectionMetrics.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/RocReceiverMetrics.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/RocSenderMetrics.java diff --git a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h index dcbcd42..4809239 100644 --- a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h +++ b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h @@ -55,6 +55,14 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeUnlink JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeReadFloats (JNIEnv *, jobject, jlong, jfloatArray); +/* + * Class: org_rocstreaming_roctoolkit_RocReceiver + * Method: nativeQuery + * Signature: (JI)Lorg/rocstreaming/roctoolkit/RocReceiverMetrics; + */ +JNIEXPORT jobject JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeQuery + (JNIEnv *, jobject, jlong, jint); + #ifdef __cplusplus } #endif diff --git a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h index 5674e88..b36fc47 100644 --- a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h +++ b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h @@ -55,6 +55,14 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeUnlink JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeWriteFloats (JNIEnv *, jobject, jlong, jfloatArray); +/* + * Class: org_rocstreaming_roctoolkit_RocSender + * Method: nativeQuery + * Signature: (JI)Lorg/rocstreaming/roctoolkit/RocSenderMetrics; + */ +JNIEXPORT jobject JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeQuery + (JNIEnv *, jobject, jlong, jint); + #ifdef __cplusplus } #endif diff --git a/roc_jni/src/main/impl/package.h b/roc_jni/src/main/impl/package.h index 42a22f1..d3ced4d 100644 --- a/roc_jni/src/main/impl/package.h +++ b/roc_jni/src/main/impl/package.h @@ -4,6 +4,7 @@ #define CHANNEL_LAYOUT_CLASS PACKAGE_NAME "/ChannelLayout" #define CLOCK_SOURCE_CLASS PACKAGE_NAME "/ClockSource" +#define CONNECTION_METRICS_CLASS PACKAGE_NAME "/RocConnectionMetrics" #define LATENCY_TUNER_BACKEND_CLASS PACKAGE_NAME "/LatencyTunerBackend" #define LATENCY_TUNER_PROFILE_CLASS PACKAGE_NAME "/LatencyTunerProfile" #define CONTEXT_CONFIG_CLASS PACKAGE_NAME "/RocContextConfig" @@ -16,6 +17,8 @@ #define PACKET_ENCODING_CLASS PACKAGE_NAME "/PacketEncoding" #define PROTOCOL_CLASS PACKAGE_NAME "/Protocol" #define RECEIVER_CONFIG_CLASS PACKAGE_NAME "/RocReceiverConfig" +#define RECEIVER_METRICS_CLASS PACKAGE_NAME "/RocReceiverMetrics" #define RESAMPLER_BACKEND_CLASS PACKAGE_NAME "/ResamplerBackend" #define RESAMPLER_PROFILE_CLASS PACKAGE_NAME "/ResamplerProfile" #define SENDER_CONFIG_CLASS PACKAGE_NAME "/RocSenderConfig" +#define SENDER_METRICS_CLASS PACKAGE_NAME "/RocSenderMetrics" diff --git a/roc_jni/src/main/impl/receiver.c b/roc_jni/src/main/impl/receiver.c index 290068b..c2daebe 100644 --- a/roc_jni/src/main/impl/receiver.c +++ b/roc_jni/src/main/impl/receiver.c @@ -4,6 +4,7 @@ #include "exceptions.h" #include "helpers.h" #include "interface_config.h" +#include "package.h" #include "receiver_config.h" #include @@ -203,3 +204,89 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeReadFl (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); } } + +JNIEXPORT jobject JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeQuery( + JNIEnv* env, jobject jobj, jlong jreceiver, jint jslot) { + assert(env); + + roc_receiver* receiver = (roc_receiver*) jreceiver; + roc_receiver_metrics receiver_metrics = {}; + roc_connection_metrics* conn_metrics = NULL; + size_t conn_count = 0; + jobject jresult = NULL; + jclass conn_metrics_class = NULL; + jmethodID conn_metrics_constructor = NULL; + jobjectArray jconn_metrics_array = NULL; + jclass receiver_metrics_class = NULL; + jmethodID receiver_metrics_constructor = NULL; + + if (!jreceiver) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiver: must not be null"); + goto out; + } + + if (roc_receiver_query(receiver, (roc_slot) jslot, &receiver_metrics, NULL, NULL) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to query RocReceiver metrics"); + goto out; + } + + conn_count = receiver_metrics.connection_count; + if (conn_count > 0) { + conn_metrics = (roc_connection_metrics*) calloc(conn_count, sizeof(roc_connection_metrics)); + if (!conn_metrics) { + throw_exception(env, ASSERTION_ERROR, "Failed to allocate memory for connection metrics"); + goto out; + } + + if (roc_receiver_query(receiver, (roc_slot) jslot, &receiver_metrics, conn_metrics, &conn_count) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to query RocReceiver connection metrics"); + goto out; + } + } + + conn_metrics_class = find_class(env, CONNECTION_METRICS_CLASS); + if (!conn_metrics_class) { + goto out; + } + + conn_metrics_constructor = find_method( + env, conn_metrics_class, "RocConnectionMetrics", "", "(J)V"); + if (!conn_metrics_constructor) { + goto out; + } + + jconn_metrics_array = (*env)->NewObjectArray(env, (jsize) conn_count, conn_metrics_class, NULL); + if (!jconn_metrics_array) { + throw_exception(env, ASSERTION_ERROR, "Failed to create RocConnectionMetrics array"); + goto out; + } + + for (size_t i = 0; i < conn_count; i++) { + jobject jconn_metric = (*env)->NewObject( + env, conn_metrics_class, conn_metrics_constructor, (jlong) conn_metrics[i].e2e_latency); + if (!jconn_metric) { + throw_exception(env, ASSERTION_ERROR, "Failed to create RocConnectionMetrics object"); + goto out; + } + (*env)->SetObjectArrayElement(env, jconn_metrics_array, (jsize) i, jconn_metric); + (*env)->DeleteLocalRef(env, jconn_metric); + } + + receiver_metrics_class = find_class(env, RECEIVER_METRICS_CLASS); + if (!receiver_metrics_class) { + goto out; + } + + receiver_metrics_constructor = find_method( + env, receiver_metrics_class, "RocReceiverMetrics", "", "(I[L" CONNECTION_METRICS_CLASS ";)V"); + if (!receiver_metrics_constructor) { + goto out; + } + + jresult = (*env)->NewObject(env, receiver_metrics_class, receiver_metrics_constructor, + (jint) receiver_metrics.connection_count, jconn_metrics_array); + +out: + free(conn_metrics); + return jresult; +} diff --git a/roc_jni/src/main/impl/sender.c b/roc_jni/src/main/impl/sender.c index 5c95887..d226054 100644 --- a/roc_jni/src/main/impl/sender.c +++ b/roc_jni/src/main/impl/sender.c @@ -4,6 +4,7 @@ #include "exceptions.h" #include "helpers.h" #include "interface_config.h" +#include "package.h" #include "sender_config.h" #include @@ -192,3 +193,89 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeWriteFlo (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); } } + +JNIEXPORT jobject JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeQuery( + JNIEnv* env, jobject jobj, jlong jsender, jint jslot) { + assert(env); + + roc_sender* sender = (roc_sender*) jsender; + roc_sender_metrics sender_metrics = {}; + roc_connection_metrics* conn_metrics = NULL; + size_t conn_count = 0; + jobject jresult = NULL; + jclass conn_metrics_class = NULL; + jmethodID conn_metrics_constructor = NULL; + jobjectArray jconn_metrics_array = NULL; + jclass sender_metrics_class = NULL; + jmethodID sender_metrics_constructor = NULL; + + if (!jsender) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSender: must not be null"); + goto out; + } + + if (roc_sender_query(sender, (roc_slot) jslot, &sender_metrics, NULL, NULL) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to query RocSender metrics"); + goto out; + } + + conn_count = sender_metrics.connection_count; + if (conn_count > 0) { + conn_metrics = (roc_connection_metrics*) calloc(conn_count, sizeof(roc_connection_metrics)); + if (!conn_metrics) { + throw_exception(env, ASSERTION_ERROR, "Failed to allocate memory for connection metrics"); + goto out; + } + + if (roc_sender_query(sender, (roc_slot) jslot, &sender_metrics, conn_metrics, &conn_count) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to query RocSender connection metrics"); + goto out; + } + } + + conn_metrics_class = find_class(env, CONNECTION_METRICS_CLASS); + if (!conn_metrics_class) { + goto out; + } + + conn_metrics_constructor = find_method( + env, conn_metrics_class, "RocConnectionMetrics", "", "(J)V"); + if (!conn_metrics_constructor) { + goto out; + } + + jconn_metrics_array = (*env)->NewObjectArray(env, (jsize) conn_count, conn_metrics_class, NULL); + if (!jconn_metrics_array) { + throw_exception(env, ASSERTION_ERROR, "Failed to create RocConnectionMetrics array"); + goto out; + } + + for (size_t i = 0; i < conn_count; i++) { + jobject jconn_metric = (*env)->NewObject( + env, conn_metrics_class, conn_metrics_constructor, (jlong) conn_metrics[i].e2e_latency); + if (!jconn_metric) { + throw_exception(env, ASSERTION_ERROR, "Failed to create RocConnectionMetrics object"); + goto out; + } + (*env)->SetObjectArrayElement(env, jconn_metrics_array, (jsize) i, jconn_metric); + (*env)->DeleteLocalRef(env, jconn_metric); + } + + sender_metrics_class = find_class(env, SENDER_METRICS_CLASS); + if (!sender_metrics_class) { + goto out; + } + + sender_metrics_constructor = find_method( + env, sender_metrics_class, "RocSenderMetrics", "", "(I[L" CONNECTION_METRICS_CLASS ";)V"); + if (!sender_metrics_constructor) { + goto out; + } + + jresult = (*env)->NewObject(env, sender_metrics_class, sender_metrics_constructor, + (jint) sender_metrics.connection_count, jconn_metrics_array); + +out: + free(conn_metrics); + return jresult; +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocConnectionMetrics.java b/src/main/java/org/rocstreaming/roctoolkit/RocConnectionMetrics.java new file mode 100644 index 0000000..da551a4 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/RocConnectionMetrics.java @@ -0,0 +1,29 @@ +package org.rocstreaming.roctoolkit; + +import java.time.Duration; +import lombok.*; + +/** + * Connection metrics. + *

+ * Holds metrics for a single connection between sender and receiver. + */ +@Getter +@Builder(builderClassName = "Builder", toBuilder = true) +@ToString +@EqualsAndHashCode +public class RocConnectionMetrics { + + /** + * Estimated end-to-end latency. + */ + private final Duration e2eLatency; + + public RocConnectionMetrics(Duration e2eLatency) { + this.e2eLatency = e2eLatency; + } + + RocConnectionMetrics(long e2eLatencyNanos) { + this(Duration.ofNanos(e2eLatencyNanos)); + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java index 9e4e936..d2609eb 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java @@ -385,6 +385,34 @@ public void read(float[] samples) throws RocException { nativeReadFloats(getPtr(), samples); } + /** + * Query receiver slot metrics. + * + * @param slot specifies the receiver slot to query. + * @return receiver metrics for the specified slot. + * + * @throws IllegalArgumentException if the arguments are invalid. + * @throws RocException if the slot does not exist or operation failed. + */ + public RocReceiverMetrics query(Slot slot) throws RocException { + Check.notNull(slot, "Slot"); + + try { + LOGGER.log(Level.FINE, "entering RocReceiver.query(), ptr={0}, slot={1}", + new Object[]{toHex(getPtr()), slot}); + + RocReceiverMetrics metrics = nativeQuery(getPtr(), slot.getValue()); + + LOGGER.log(Level.FINE, "leaving RocReceiver.query(), ptr={0}, metrics={1}", + new Object[]{toHex(getPtr()), metrics}); + return metrics; + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocReceiver.query(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } + } + private static native long nativeOpen(long contextPtr, RocReceiverConfig config) throws RocException; private static native void nativeClose(long receiverPtr); @@ -393,4 +421,5 @@ public void read(float[] samples) throws RocException { private native void nativeUnlink(long receiverPtr, int slot) throws RocException; private native void nativeReadFloats(long receiverPtr, float[] samples) throws RocException; + private native RocReceiverMetrics nativeQuery(long receiverPtr, int slot) throws RocException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverMetrics.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverMetrics.java new file mode 100644 index 0000000..94936dd --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverMetrics.java @@ -0,0 +1,33 @@ +package org.rocstreaming.roctoolkit; + +import lombok.*; + +/** + * Receiver metrics. + *

+ * Holds receiver-side metrics that are not specific to connection. + * If multiple slots are used, each slot has its own metrics. + * + * @see RocReceiver + */ +@Getter +@Builder(builderClassName = "Builder", toBuilder = true) +@ToString +@EqualsAndHashCode +public class RocReceiverMetrics { + + /** + * Number of active connections. + */ + private final int connectionCount; + + /** + * Metrics for individual active connections. + */ + private final RocConnectionMetrics[] connectionMetrics; + + public RocReceiverMetrics(int connectionCount, RocConnectionMetrics[] connectionMetrics) { + this.connectionCount = connectionCount; + this.connectionMetrics = connectionMetrics != null ? connectionMetrics : new RocConnectionMetrics[0]; + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java index cd84df4..88bd8c7 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java @@ -358,6 +358,34 @@ public void write(float[] samples) throws RocException { nativeWriteFloats(getPtr(), samples); } + /** + * Query sender slot metrics. + * + * @param slot specifies the sender slot to query. + * @return sender metrics for the specified slot. + * + * @throws IllegalArgumentException if the arguments are invalid. + * @throws RocException if the slot does not exist or operation failed. + */ + public RocSenderMetrics query(Slot slot) throws RocException { + Check.notNull(slot, "Slot"); + + try { + LOGGER.log(Level.FINE, "entering RocSender.query(), ptr={0}, slot={1}", + new Object[]{toHex(getPtr()), slot}); + + RocSenderMetrics metrics = nativeQuery(getPtr(), slot.getValue()); + + LOGGER.log(Level.FINE, "leaving RocSender.query(), ptr={0}, metrics={1}", + new Object[]{toHex(getPtr()), metrics}); + return metrics; + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocSender.query(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } + } + private static native long nativeOpen(long contextPtr, RocSenderConfig config) throws RocException; private static native void nativeClose(long senderPtr); @@ -366,4 +394,5 @@ public void write(float[] samples) throws RocException { private native void nativeUnlink(long senderPtr, int slot) throws RocException; private native void nativeWriteFloats(long senderPtr, float[] samples) throws RocException; + private native RocSenderMetrics nativeQuery(long senderPtr, int slot) throws RocException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSenderMetrics.java b/src/main/java/org/rocstreaming/roctoolkit/RocSenderMetrics.java new file mode 100644 index 0000000..b82b137 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSenderMetrics.java @@ -0,0 +1,33 @@ +package org.rocstreaming.roctoolkit; + +import lombok.*; + +/** + * Sender metrics. + *

+ * Holds sender-side metrics that are not specific to connection. + * If multiple slots are used, each slot has its own metrics. + * + * @see RocSender + */ +@Getter +@Builder(builderClassName = "Builder", toBuilder = true) +@ToString +@EqualsAndHashCode +public class RocSenderMetrics { + + /** + * Number of active connections. + */ + private final int connectionCount; + + /** + * Metrics for individual active connections. + */ + private final RocConnectionMetrics[] connectionMetrics; + + public RocSenderMetrics(int connectionCount, RocConnectionMetrics[] connectionMetrics) { + this.connectionCount = connectionCount; + this.connectionMetrics = connectionMetrics != null ? connectionMetrics : new RocConnectionMetrics[0]; + } +} diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java index ba1ac08..20e75f0 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java @@ -262,4 +262,27 @@ public void testInvalidRead() throws Exception { assertEquals("Invalid samples: must not be null", exception.getMessage()); } } + + @Test + public void testQuery() throws Exception { + try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { + receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:0")); + RocReceiverMetrics metrics = receiver.query(Slot.DEFAULT); + assertNotNull(metrics); + assertEquals(0, metrics.getConnectionCount()); + assertNotNull(metrics.getConnectionMetrics()); + assertEquals(0, metrics.getConnectionMetrics().length); + } + } + + @Test + public void testInvalidQuery() throws Exception { + try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { + Slot slot = new Slot(1); + receiver.bind(slot, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); + assertThrows(IllegalArgumentException.class, () -> receiver.query(null)); + receiver.unlink(slot); + assertThrows(RocException.class, () -> receiver.query(slot)); + } + } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java index 14cf2bf..5c1dad8 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java @@ -286,4 +286,27 @@ public void testInvalidConnectAfterWrite() throws Exception { } } + @Test + public void testQuery() throws Exception { + try (RocSender sender = new RocSender(context, CONFIG)) { + sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10001")); + RocSenderMetrics metrics = sender.query(Slot.DEFAULT); + assertNotNull(metrics); + assertEquals(0, metrics.getConnectionCount()); + assertNotNull(metrics.getConnectionMetrics()); + assertEquals(0, metrics.getConnectionMetrics().length); + } + } + + @Test + public void testInvalidQuery() throws Exception { + try (RocSender sender = new RocSender(context, CONFIG)) { + Slot slot = new Slot(1); + sender.connect(slot, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10001")); + assertThrows(IllegalArgumentException.class, () -> sender.query(null)); + sender.unlink(slot); + assertThrows(RocException.class, () -> sender.query(slot)); + } + } + } From ece2d70725e9d118ea2e85b0ac35f8eb9a84b59d Mon Sep 17 00:00:00 2001 From: Jebastine E Date: Tue, 8 Sep 2026 21:59:32 +0530 Subject: [PATCH 2/2] test: cover metrics classes --- .../roctoolkit/RocMetricsTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/test/java/org/rocstreaming/roctoolkit/RocMetricsTest.java diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocMetricsTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocMetricsTest.java new file mode 100644 index 0000000..b83dc92 --- /dev/null +++ b/src/test/java/org/rocstreaming/roctoolkit/RocMetricsTest.java @@ -0,0 +1,48 @@ +package org.rocstreaming.roctoolkit; + +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.*; + +class RocMetricsTest { + + @Test + void testConnectionMetrics() { + Duration latency = Duration.ofMillis(50); + RocConnectionMetrics fromDuration = new RocConnectionMetrics(latency); + assertEquals(latency, fromDuration.getE2eLatency()); + + RocConnectionMetrics fromNanos = new RocConnectionMetrics(50_000_000L); + assertEquals(Duration.ofNanos(50_000_000L), fromNanos.getE2eLatency()); + } + + @Test + void testSenderMetrics() { + RocConnectionMetrics connMetric = new RocConnectionMetrics(Duration.ofMillis(10)); + RocSenderMetrics metrics = new RocSenderMetrics(1, new RocConnectionMetrics[]{connMetric}); + assertEquals(1, metrics.getConnectionCount()); + assertEquals(1, metrics.getConnectionMetrics().length); + assertEquals(connMetric, metrics.getConnectionMetrics()[0]); + + RocSenderMetrics nullMetrics = new RocSenderMetrics(0, null); + assertEquals(0, nullMetrics.getConnectionCount()); + assertNotNull(nullMetrics.getConnectionMetrics()); + assertEquals(0, nullMetrics.getConnectionMetrics().length); + } + + @Test + void testReceiverMetrics() { + RocConnectionMetrics connMetric = new RocConnectionMetrics(Duration.ofMillis(10)); + RocReceiverMetrics metrics = new RocReceiverMetrics(1, new RocConnectionMetrics[]{connMetric}); + assertEquals(1, metrics.getConnectionCount()); + assertEquals(1, metrics.getConnectionMetrics().length); + assertEquals(connMetric, metrics.getConnectionMetrics()[0]); + + RocReceiverMetrics nullMetrics = new RocReceiverMetrics(0, null); + assertEquals(0, nullMetrics.getConnectionCount()); + assertNotNull(nullMetrics.getConnectionMetrics()); + assertEquals(0, nullMetrics.getConnectionMetrics().length); + } +}