diff --git a/.changes/cfc58da7-d2c6-47d7-a40e-72893b49bca7.json b/.changes/cfc58da7-d2c6-47d7-a40e-72893b49bca7.json new file mode 100644 index 00000000..334b897d --- /dev/null +++ b/.changes/cfc58da7-d2c6-47d7-a40e-72893b49bca7.json @@ -0,0 +1,8 @@ +{ + "id": "cfc58da7-d2c6-47d7-a40e-72893b49bca7", + "type": "feature", + "description": "Enable access to metrics for HTTP streams", + "issues": [ + "https://github.com/awslabs/smithy-kotlin/issues/893" + ] +} \ No newline at end of file diff --git a/aws-crt-kotlin/api/android/aws-crt-kotlin.api b/aws-crt-kotlin/api/android/aws-crt-kotlin.api index cf4ecd3d..aaf0b96e 100644 --- a/aws-crt-kotlin/api/android/aws-crt-kotlin.api +++ b/aws-crt-kotlin/api/android/aws-crt-kotlin.api @@ -634,7 +634,35 @@ public abstract interface class aws/sdk/kotlin/crt/http/HttpStream : aws/sdk/kot public abstract fun writeChunk ([BZ)V } +public final class aws/sdk/kotlin/crt/http/HttpStreamMetrics { + public fun (JJJJJJI)V + public final fun component1 ()J + public final fun component2 ()J + public final fun component3 ()J + public final fun component4 ()J + public final fun component5 ()J + public final fun component6 ()J + public final fun component7 ()I + public final fun copy (JJJJJJI)Laws/sdk/kotlin/crt/http/HttpStreamMetrics; + public static synthetic fun copy$default (Laws/sdk/kotlin/crt/http/HttpStreamMetrics;JJJJJJIILjava/lang/Object;)Laws/sdk/kotlin/crt/http/HttpStreamMetrics; + public fun equals (Ljava/lang/Object;)Z + public final fun getReceiveEndTimestampNs ()J + public final fun getReceiveStartTimestampNs ()J + public final fun getReceivingDurationNs ()J + public final fun getSendEndTimestampNs ()J + public final fun getSendStartTimestampNs ()J + public final fun getSendingDurationNs ()J + public final fun getStreamId ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class aws/sdk/kotlin/crt/http/HttpStreamMetricsJVMKt { + public static final fun toKotlin (Lsoftware/amazon/awssdk/crt/http/HttpStreamMetrics;)Laws/sdk/kotlin/crt/http/HttpStreamMetrics; +} + public abstract interface class aws/sdk/kotlin/crt/http/HttpStreamResponseHandler { + public abstract fun onMetrics (Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/http/HttpStreamMetrics;)V public abstract fun onResponseBody (Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/io/Buffer;)I public abstract fun onResponseComplete (Laws/sdk/kotlin/crt/http/HttpStream;I)V public abstract fun onResponseHeaders (Laws/sdk/kotlin/crt/http/HttpStream;IILjava/util/List;)V @@ -642,6 +670,7 @@ public abstract interface class aws/sdk/kotlin/crt/http/HttpStreamResponseHandle } public final class aws/sdk/kotlin/crt/http/HttpStreamResponseHandler$DefaultImpls { + public static fun onMetrics (Laws/sdk/kotlin/crt/http/HttpStreamResponseHandler;Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/http/HttpStreamMetrics;)V public static fun onResponseBody (Laws/sdk/kotlin/crt/http/HttpStreamResponseHandler;Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/io/Buffer;)I public static fun onResponseHeadersDone (Laws/sdk/kotlin/crt/http/HttpStreamResponseHandler;Laws/sdk/kotlin/crt/http/HttpStream;I)V } diff --git a/aws-crt-kotlin/api/jvm/aws-crt-kotlin.api b/aws-crt-kotlin/api/jvm/aws-crt-kotlin.api index cf4ecd3d..aaf0b96e 100644 --- a/aws-crt-kotlin/api/jvm/aws-crt-kotlin.api +++ b/aws-crt-kotlin/api/jvm/aws-crt-kotlin.api @@ -634,7 +634,35 @@ public abstract interface class aws/sdk/kotlin/crt/http/HttpStream : aws/sdk/kot public abstract fun writeChunk ([BZ)V } +public final class aws/sdk/kotlin/crt/http/HttpStreamMetrics { + public fun (JJJJJJI)V + public final fun component1 ()J + public final fun component2 ()J + public final fun component3 ()J + public final fun component4 ()J + public final fun component5 ()J + public final fun component6 ()J + public final fun component7 ()I + public final fun copy (JJJJJJI)Laws/sdk/kotlin/crt/http/HttpStreamMetrics; + public static synthetic fun copy$default (Laws/sdk/kotlin/crt/http/HttpStreamMetrics;JJJJJJIILjava/lang/Object;)Laws/sdk/kotlin/crt/http/HttpStreamMetrics; + public fun equals (Ljava/lang/Object;)Z + public final fun getReceiveEndTimestampNs ()J + public final fun getReceiveStartTimestampNs ()J + public final fun getReceivingDurationNs ()J + public final fun getSendEndTimestampNs ()J + public final fun getSendStartTimestampNs ()J + public final fun getSendingDurationNs ()J + public final fun getStreamId ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class aws/sdk/kotlin/crt/http/HttpStreamMetricsJVMKt { + public static final fun toKotlin (Lsoftware/amazon/awssdk/crt/http/HttpStreamMetrics;)Laws/sdk/kotlin/crt/http/HttpStreamMetrics; +} + public abstract interface class aws/sdk/kotlin/crt/http/HttpStreamResponseHandler { + public abstract fun onMetrics (Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/http/HttpStreamMetrics;)V public abstract fun onResponseBody (Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/io/Buffer;)I public abstract fun onResponseComplete (Laws/sdk/kotlin/crt/http/HttpStream;I)V public abstract fun onResponseHeaders (Laws/sdk/kotlin/crt/http/HttpStream;IILjava/util/List;)V @@ -642,6 +670,7 @@ public abstract interface class aws/sdk/kotlin/crt/http/HttpStreamResponseHandle } public final class aws/sdk/kotlin/crt/http/HttpStreamResponseHandler$DefaultImpls { + public static fun onMetrics (Laws/sdk/kotlin/crt/http/HttpStreamResponseHandler;Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/http/HttpStreamMetrics;)V public static fun onResponseBody (Laws/sdk/kotlin/crt/http/HttpStreamResponseHandler;Laws/sdk/kotlin/crt/http/HttpStream;Laws/sdk/kotlin/crt/io/Buffer;)I public static fun onResponseHeadersDone (Laws/sdk/kotlin/crt/http/HttpStreamResponseHandler;Laws/sdk/kotlin/crt/http/HttpStream;I)V } diff --git a/aws-crt-kotlin/common/src/aws/sdk/kotlin/crt/http/HttpStreamMetrics.kt b/aws-crt-kotlin/common/src/aws/sdk/kotlin/crt/http/HttpStreamMetrics.kt new file mode 100644 index 00000000..70204717 --- /dev/null +++ b/aws-crt-kotlin/common/src/aws/sdk/kotlin/crt/http/HttpStreamMetrics.kt @@ -0,0 +1,15 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.crt.http + +public data class HttpStreamMetrics( + val sendStartTimestampNs: Long, + val sendEndTimestampNs: Long, + val sendingDurationNs: Long, + val receiveStartTimestampNs: Long, + val receiveEndTimestampNs: Long, + val receivingDurationNs: Long, + val streamId: Int, +) diff --git a/aws-crt-kotlin/common/src/aws/sdk/kotlin/crt/http/HttpStreamResponseHandler.kt b/aws-crt-kotlin/common/src/aws/sdk/kotlin/crt/http/HttpStreamResponseHandler.kt index 42368988..a5365171 100644 --- a/aws-crt-kotlin/common/src/aws/sdk/kotlin/crt/http/HttpStreamResponseHandler.kt +++ b/aws-crt-kotlin/common/src/aws/sdk/kotlin/crt/http/HttpStreamResponseHandler.kt @@ -68,6 +68,15 @@ public interface HttpStreamResponseHandler { // window size through `Stream.incrementWindow()`? bodyBytesIn.len + /** + * Called right before stream is complete, whether successful or unsuccessful. + * @param stream The HTTP stream to which the metrics apply + * @param metrics The [HttpStreamMetrics] containing metrics for the given stream + */ + public fun onMetrics(stream: HttpStream, metrics: HttpStreamMetrics) { + /* Optional callback, nothing to do by default */ + } + /** * Called from Native when the Response has completed. * @param stream completed stream diff --git a/aws-crt-kotlin/jvm/src/aws/sdk/kotlin/crt/http/HttpRequestUtil.kt b/aws-crt-kotlin/jvm/src/aws/sdk/kotlin/crt/http/HttpRequestUtil.kt index b9253edf..ad2db7b7 100644 --- a/aws-crt-kotlin/jvm/src/aws/sdk/kotlin/crt/http/HttpRequestUtil.kt +++ b/aws-crt-kotlin/jvm/src/aws/sdk/kotlin/crt/http/HttpRequestUtil.kt @@ -8,8 +8,12 @@ package aws.sdk.kotlin.crt.http import aws.sdk.kotlin.crt.io.MutableBuffer import aws.sdk.kotlin.crt.io.byteArrayBuffer import java.nio.ByteBuffer +import software.amazon.awssdk.crt.http.HttpHeader as HttpHeaderJni import software.amazon.awssdk.crt.http.HttpRequest as HttpRequestJni import software.amazon.awssdk.crt.http.HttpRequestBodyStream as HttpRequestBodyStreamJni +import software.amazon.awssdk.crt.http.HttpStream as HttpStreamJni +import software.amazon.awssdk.crt.http.HttpStreamMetrics as HttpStreamMetricsJni +import software.amazon.awssdk.crt.http.HttpStreamResponseHandler as HttpStreamResponseHandlerJni /** * Convert the KMP version of [HttpRequest] into the JNI equivalent @@ -17,47 +21,50 @@ import software.amazon.awssdk.crt.http.HttpRequestBodyStream as HttpRequestBodyS internal fun HttpRequest.into(): HttpRequestJni { val jniHeaders = headers.entries() .map { entry -> - entry.value.map { - software.amazon.awssdk.crt.http.HttpHeader(entry.key, it) - } + entry.value.map { HttpHeaderJni(entry.key, it) } } .flatten() .toTypedArray() val bodyStream = body?.let { JniRequestBodyStream(it) } - return software.amazon.awssdk.crt.http.HttpRequest(method, encodedPath, jniHeaders, bodyStream) + return HttpRequestJni(method, encodedPath, jniHeaders, bodyStream) } -internal fun HttpStreamResponseHandler.asJniStreamResponseHandler(): software.amazon.awssdk.crt.http.HttpStreamResponseHandler { +internal fun HttpStreamResponseHandler.asJniStreamResponseHandler(): HttpStreamResponseHandlerJni { val handler = this - return object : software.amazon.awssdk.crt.http.HttpStreamResponseHandler { + return object : HttpStreamResponseHandlerJni { override fun onResponseHeaders( - stream: software.amazon.awssdk.crt.http.HttpStream, + stream: HttpStreamJni, statusCode: Int, blockType: Int, - headers: Array?, + headers: Array?, ) { val ktHeaders = headers?.map { HttpHeader(it.name, it.value) } val ktStream = HttpStreamJVM(stream) handler.onResponseHeaders(ktStream, statusCode, blockType, ktHeaders) } - override fun onResponseHeadersDone(stream: software.amazon.awssdk.crt.http.HttpStream, blockType: Int) { + override fun onResponseHeadersDone(stream: HttpStreamJni, blockType: Int) { val ktStream = HttpStreamJVM(stream) handler.onResponseHeadersDone(ktStream, blockType) } - override fun onResponseBody(stream: software.amazon.awssdk.crt.http.HttpStream, bodyBytesIn: ByteArray?): Int { + override fun onResponseBody(stream: HttpStreamJni, bodyBytesIn: ByteArray?): Int { if (bodyBytesIn == null) return 0 val ktStream = HttpStreamJVM(stream) val buffer = byteArrayBuffer(bodyBytesIn) return handler.onResponseBody(ktStream, buffer) } - override fun onResponseComplete(stream: software.amazon.awssdk.crt.http.HttpStream, errorCode: Int) { + override fun onResponseComplete(stream: HttpStreamJni, errorCode: Int) { val ktStream = HttpStreamJVM(stream) handler.onResponseComplete(ktStream, errorCode) } + + override fun onMetrics(stream: HttpStreamJni, metrics: HttpStreamMetricsJni) { + val ktStream = HttpStreamJVM(stream) + handler.onMetrics(ktStream, metrics.toKotlin()) + } } } diff --git a/aws-crt-kotlin/jvm/src/aws/sdk/kotlin/crt/http/HttpStreamMetricsJVM.kt b/aws-crt-kotlin/jvm/src/aws/sdk/kotlin/crt/http/HttpStreamMetricsJVM.kt new file mode 100644 index 00000000..47f1d55f --- /dev/null +++ b/aws-crt-kotlin/jvm/src/aws/sdk/kotlin/crt/http/HttpStreamMetricsJVM.kt @@ -0,0 +1,19 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.crt.http + +/** + * Convert a CRT JNI metrics object into a Kotlin-native one + */ +public fun software.amazon.awssdk.crt.http.HttpStreamMetrics.toKotlin(): HttpStreamMetrics = + HttpStreamMetrics( + sendStartTimestampNs = this.sendStartTimestampNs, + sendEndTimestampNs = this.sendEndTimestampNs, + sendingDurationNs = this.sendingDurationNs, + receiveStartTimestampNs = this.receiveStartTimestampNs, + receiveEndTimestampNs = this.receiveEndTimestampNs, + receivingDurationNs = this.receivingDurationNs, + streamId = this.streamId, + ) diff --git a/build.gradle.kts b/build.gradle.kts index b546079b..942fea26 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget buildscript { repositories { mavenCentral() + mavenLocal() } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 20a38358..7446c35b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin-version = "1.9.21" aws-kotlin-repo-tools-version = "0.3.2" # libs -crt-java-version = "0.29.1" +crt-java-version = "0.29.6" coroutines-version = "1.7.3" # testing