diff --git a/build.gradle.kts b/build.gradle.kts index 0c93e6b..95322e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,7 @@ if (!project.hasProperty("isGithubActions")) { } group = "io.newm" -version = "2.2.1-SNAPSHOT" +version = "2.2.2-SNAPSHOT" java.sourceCompatibility = JavaVersion.VERSION_21 java.targetCompatibility = JavaVersion.VERSION_21 diff --git a/src/main/kotlin/io/newm/kogmios/ClientImpl.kt b/src/main/kotlin/io/newm/kogmios/ClientImpl.kt index 42c126e..ac53818 100644 --- a/src/main/kotlin/io/newm/kogmios/ClientImpl.kt +++ b/src/main/kotlin/io/newm/kogmios/ClientImpl.kt @@ -4,6 +4,7 @@ import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.websocket.* import io.ktor.client.request.* +import io.ktor.client.statement.bodyAsText import io.ktor.http.* import io.ktor.serialization.* import io.ktor.util.reflect.* @@ -14,8 +15,15 @@ import io.newm.kogmios.protocols.messages.* import io.newm.kogmios.protocols.model.* import io.newm.kogmios.protocols.model.fault.InternalErrorFault import io.newm.kogmios.protocols.model.fault.StringFaultData +import io.newm.kogmios.protocols.model.result.HealthResult import io.newm.kogmios.serializers.BigFractionSerializer import io.newm.kogmios.serializers.BigIntegerSerializer +import java.io.IOException +import java.math.BigInteger +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.set +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ClosedReceiveChannelException @@ -28,12 +36,6 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import org.apache.commons.numbers.fraction.BigFraction import org.slf4j.LoggerFactory -import java.io.IOException -import java.math.BigInteger -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import kotlin.collections.set -import kotlin.coroutines.CoroutineContext internal class ClientImpl( private val websocketHost: String, @@ -509,6 +511,12 @@ internal class ClientImpl( } } + override suspend fun health(): HealthResult { + val response = httpClient.get("http://$websocketHost:$websocketPort/health") + val jsonBody = response.bodyAsText() + return json.decodeFromString(jsonBody) + } + override suspend fun acquireMempool(timeoutMs: Long): MsgAcquireMempoolResponse { assertConnected() val message = MsgAcquireMempool() diff --git a/src/main/kotlin/io/newm/kogmios/StateQueryClient.kt b/src/main/kotlin/io/newm/kogmios/StateQueryClient.kt index 5a46c35..2f28e59 100644 --- a/src/main/kotlin/io/newm/kogmios/StateQueryClient.kt +++ b/src/main/kotlin/io/newm/kogmios/StateQueryClient.kt @@ -24,6 +24,7 @@ import io.newm.kogmios.protocols.model.Origin import io.newm.kogmios.protocols.model.ParamsProjectedRewards import io.newm.kogmios.protocols.model.ParamsUtxo import io.newm.kogmios.protocols.model.PointOrOrigin +import io.newm.kogmios.protocols.model.result.HealthResult /** * Client interface for querying the state of the node and ledger. @@ -128,6 +129,11 @@ interface StateQueryClient : Client { params: ParamsUtxo, timeoutMs: Long = DEFAULT_REQUEST_TIMEOUT_MS ): MsgQueryUtxoResponse + + /** + * Get the health of the node. + */ + suspend fun health(): HealthResult } fun createStateQueryClient( diff --git a/src/main/kotlin/io/newm/kogmios/protocols/model/CardanoEra.kt b/src/main/kotlin/io/newm/kogmios/protocols/model/CardanoEra.kt new file mode 100644 index 0000000..a0f1d86 --- /dev/null +++ b/src/main/kotlin/io/newm/kogmios/protocols/model/CardanoEra.kt @@ -0,0 +1,28 @@ +package io.newm.kogmios.protocols.model + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonNames + +@Serializable +enum class CardanoEra { + @JsonNames("byron") + BYRON, + + @JsonNames("shelley") + SHELLEY, + + @JsonNames("allegra") + ALLEGRA, + + @JsonNames("mary") + MARY, + + @JsonNames("alonzo") + ALONZO, + + @JsonNames("babbage") + BABBAGE, + + @JsonNames("conway") + CONWAY, +} diff --git a/src/main/kotlin/io/newm/kogmios/protocols/model/result/HealthResult.kt b/src/main/kotlin/io/newm/kogmios/protocols/model/result/HealthResult.kt new file mode 100644 index 0000000..abb5691 --- /dev/null +++ b/src/main/kotlin/io/newm/kogmios/protocols/model/result/HealthResult.kt @@ -0,0 +1,65 @@ +package io.newm.kogmios.protocols.model.result + +import io.newm.kogmios.protocols.model.CardanoEra +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class HealthResult( + @SerialName("connectionStatus") + val connectionStatus: String, + @SerialName("currentEpoch") + val currentEpoch: Long, + @SerialName("currentEra") + val currentEra: CardanoEra, + @SerialName("lastKnownTip") + val lastKnownTip: LastKnownTip, + @SerialName("lastTipUpdate") + val lastTipUpdate: String, + @SerialName("metrics") + val metrics: Metrics, + @SerialName("network") + val network: String, + @SerialName("networkSynchronization") + val networkSynchronization: Double, + @SerialName("slotInEpoch") + val slotInEpoch: Long, + @SerialName("startTime") + val startTime: String, + @SerialName("version") + val version: String +) + +// { +// "metrics": { +// "totalUnrouted": 1, +// "totalMessages": 30029, +// "runtimeStats": { +// "gcCpuTime": 1233009354, +// "cpuTime": 81064672549, +// "maxHeapSize": 41630, +// "currentHeapSize": 1014 +// }, +// "totalConnections": 10, +// "sessionDurations": { +// "max": 57385, +// "mean": 7057, +// "min": 0 +// }, +// "activeConnections": 0 +// }, +// "startTime": "2021-03-15T16:16:41.470782977Z", +// "lastTipUpdate": "2021-03-15T16:28:36.853115034Z", +// "lastKnownTip": { +// "hash": "c29428f386c701c1d1ba1fd259d4be78921ee9ee6c174eac898245ceb55e8061", +// "blockNo": 5034297, +// "slot": 15520688 +// }, +// "networkSynchronization": 0.99, +// "currentEra": "mary", +// "connectionStatus": "disconnected", +// "currentEpoch": 164, +// "slotInEpoch": 324543, +// "version": "6.0.0", +// "network": "mainnet" +// } diff --git a/src/main/kotlin/io/newm/kogmios/protocols/model/result/LastKnownTip.kt b/src/main/kotlin/io/newm/kogmios/protocols/model/result/LastKnownTip.kt new file mode 100644 index 0000000..8a6b201 --- /dev/null +++ b/src/main/kotlin/io/newm/kogmios/protocols/model/result/LastKnownTip.kt @@ -0,0 +1,14 @@ +package io.newm.kogmios.protocols.model.result + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LastKnownTip( + @SerialName("height") + val height: Long, + @SerialName("id") + val id: String, + @SerialName("slot") + val slot: Long, +) diff --git a/src/main/kotlin/io/newm/kogmios/protocols/model/result/Metrics.kt b/src/main/kotlin/io/newm/kogmios/protocols/model/result/Metrics.kt new file mode 100644 index 0000000..03cb4c3 --- /dev/null +++ b/src/main/kotlin/io/newm/kogmios/protocols/model/result/Metrics.kt @@ -0,0 +1,20 @@ +package io.newm.kogmios.protocols.model.result + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Metrics( + @SerialName("activeConnections") + val activeConnections: Int, + @SerialName("runtimeStats") + val runtimeStats: RuntimeStats, + @SerialName("sessionDurations") + val sessionDurations: SessionDurations, + @SerialName("totalConnections") + val totalConnections: Long, + @SerialName("totalMessages") + val totalMessages: Long, + @SerialName("totalUnrouted") + val totalUnrouted: Long, +) diff --git a/src/main/kotlin/io/newm/kogmios/protocols/model/result/RuntimeStats.kt b/src/main/kotlin/io/newm/kogmios/protocols/model/result/RuntimeStats.kt new file mode 100644 index 0000000..be19df9 --- /dev/null +++ b/src/main/kotlin/io/newm/kogmios/protocols/model/result/RuntimeStats.kt @@ -0,0 +1,16 @@ +package io.newm.kogmios.protocols.model.result + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RuntimeStats( + @SerialName("cpuTime") + val cpuTime: Long, + @SerialName("currentHeapSize") + val currentHeapSize: Long, + @SerialName("gcCpuTime") + val gcCpuTime: Long, + @SerialName("maxHeapSize") + val maxHeapSize: Long, +) diff --git a/src/main/kotlin/io/newm/kogmios/protocols/model/result/SessionDurations.kt b/src/main/kotlin/io/newm/kogmios/protocols/model/result/SessionDurations.kt new file mode 100644 index 0000000..edb34da --- /dev/null +++ b/src/main/kotlin/io/newm/kogmios/protocols/model/result/SessionDurations.kt @@ -0,0 +1,14 @@ +package io.newm.kogmios.protocols.model.result + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SessionDurations( + @SerialName("max") + val max: Double, + @SerialName("mean") + val mean: Double, + @SerialName("min") + val min: Double, +) diff --git a/src/test/kotlin/io/newm/kogmios/protocols/statequery/StateQueryTest.kt b/src/test/kotlin/io/newm/kogmios/protocols/statequery/StateQueryTest.kt index 0421f95..e8f17d4 100644 --- a/src/test/kotlin/io/newm/kogmios/protocols/statequery/StateQueryTest.kt +++ b/src/test/kotlin/io/newm/kogmios/protocols/statequery/StateQueryTest.kt @@ -25,6 +25,8 @@ import io.newm.kogmios.protocols.model.result.ProjectedRewardsResult import io.newm.kogmios.protocols.model.result.RewardAccountSummariesResult import io.newm.kogmios.protocols.model.result.StakePoolsResult import io.newm.kogmios.protocols.model.result.UtxoResult +import java.io.IOException +import java.math.BigInteger import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -33,8 +35,6 @@ import kotlinx.coroutines.runBlocking import kotlinx.datetime.Instant import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import java.io.IOException -import java.math.BigInteger class StateQueryTest { companion object { @@ -49,6 +49,25 @@ class StateQueryTest { // private const val TEST_SECURE = true } + @Test + fun `test health`() = + runBlocking { + createStateQueryClient( + websocketHost = TEST_HOST, + websocketPort = TEST_PORT, + secure = TEST_SECURE, + ).use { client -> + val connectResult = client.connect() + assertThat(connectResult).isTrue() + assertThat(client.isConnected).isTrue() + + val response = client.health() + assertThat(response).isNotNull() + assertThat(response.connectionStatus).isEqualTo("connected") + println(response) + } + } + @Test fun `test acquire origin`() = runBlocking {