solution: metrics for grpc requests

This commit is contained in:
Igor Artamonov
2021-02-10 17:53:07 -05:00
parent 981a5dc589
commit 81c5f2ef7b
2 changed files with 128 additions and 12 deletions

View File

@@ -19,14 +19,20 @@ package io.emeraldpay.dshackle.rpc
import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common import io.emeraldpay.api.proto.Common
import io.emeraldpay.api.proto.ReactorBlockchainGrpc import io.emeraldpay.api.proto.ReactorBlockchainGrpc
import io.emeraldpay.dshackle.BlockchainType
import io.emeraldpay.dshackle.SilentException import io.emeraldpay.dshackle.SilentException
import io.emeraldpay.grpc.Chain import io.emeraldpay.grpc.Chain
import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.Metrics
import io.micrometer.core.instrument.Timer
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@Service @Service
class BlockchainRpc( class BlockchainRpc(
@@ -40,22 +46,51 @@ class BlockchainRpc(
private val log = LoggerFactory.getLogger(BlockchainRpc::class.java) private val log = LoggerFactory.getLogger(BlockchainRpc::class.java)
private val describeMetric = Counter.builder("request.grpc.request")
.tag("type", "describe")
.register(Metrics.globalRegistry)
private val subscribeStatusMetric = Counter.builder("request.grpc.request")
.tag("type", "subscribeStatus")
.register(Metrics.globalRegistry)
private val errorMetric = Counter.builder("request.grpc.err")
.register(Metrics.globalRegistry)
override fun nativeCall(request: Mono<BlockchainOuterClass.NativeCallRequest>): Flux<BlockchainOuterClass.NativeCallReplyItem> { override fun nativeCall(request: Mono<BlockchainOuterClass.NativeCallRequest>): Flux<BlockchainOuterClass.NativeCallReplyItem> {
return nativeCall.nativeCall(request) var startTime = 0L
var metrics: RequestMetrics? = null
return nativeCall.nativeCall(
request
.doOnNext {
metrics = getMetrics(it.chain)
metrics!!.nativeCallMetric.increment()
startTime = System.currentTimeMillis()
}
).doOnNext {
metrics?.nativeCallRespMetric?.record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS)
}.doOnError { errorMetric.increment() }
} }
override fun subscribeHead(request: Mono<Common.Chain>): Flux<BlockchainOuterClass.ChainHead> { override fun subscribeHead(request: Mono<Common.Chain>): Flux<BlockchainOuterClass.ChainHead> {
return streamHead.add(request) return streamHead.add(
request
.doOnNext { getMetrics(it.type).subscribeHeadMetric.increment() }
).doOnError { errorMetric.increment() }
} }
override fun subscribeTxStatus(request: Mono<BlockchainOuterClass.TxStatusRequest>): Flux<BlockchainOuterClass.TxStatus> { override fun subscribeTxStatus(request: Mono<BlockchainOuterClass.TxStatusRequest>): Flux<BlockchainOuterClass.TxStatus> {
return request.flatMapMany { request -> return request.flatMapMany { request ->
val chain = Chain.byId(request.chainValue) val chain = Chain.byId(request.chainValue)
val metrics = getMetrics(chain)
metrics.subscribeTxMetric.increment()
try { try {
trackTx.find { it.isSupported(chain) }?.subscribe(request) trackTx.find { it.isSupported(chain) }?.let { track ->
?: Flux.error(SilentException.UnsupportedBlockchain(chain)) track.subscribe(request)
.doOnNext { metrics.subscribeHeadRespMetric.increment() }
.doOnError { errorMetric.increment() }
} ?: Flux.error(SilentException.UnsupportedBlockchain(chain))
} catch (t: Throwable) { } catch (t: Throwable) {
log.error("Internal error during Tx Subscription", t) log.error("Internal error during Tx Subscription", t)
errorMetric.increment()
Flux.error<BlockchainOuterClass.TxStatus>(IllegalStateException("Internal Error")) Flux.error<BlockchainOuterClass.TxStatus>(IllegalStateException("Internal Error"))
} }
} }
@@ -64,15 +99,21 @@ class BlockchainRpc(
override fun subscribeBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> { override fun subscribeBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
return requestMono.flatMapMany { request -> return requestMono.flatMapMany { request ->
val chain = Chain.byId(request.asset.chainValue) val chain = Chain.byId(request.asset.chainValue)
val metrics = getMetrics(chain)
metrics.subscribeBalanceMetric.increment()
val asset = request.asset.code.toLowerCase() val asset = request.asset.code.toLowerCase()
try { try {
trackAddress.find { it.isSupported(chain, asset) }?.subscribe(request) trackAddress.find { it.isSupported(chain, asset) }?.let { track ->
?: Flux.error<BlockchainOuterClass.AddressBalance>(SilentException.UnsupportedBlockchain(chain)) track.subscribe(request)
.doOnNext { metrics.subscribeBalanceRespMetric.increment() }
.doOnError { errorMetric.increment() }
} ?: Flux.error<BlockchainOuterClass.AddressBalance>(SilentException.UnsupportedBlockchain(chain))
.doOnSubscribe { .doOnSubscribe {
log.error("Balance for $chain:$asset is not supported") log.error("Balance for $chain:$asset is not supported")
} }
} catch (t: Throwable) { } catch (t: Throwable) {
log.error("Internal error during Balance Subscription", t) log.error("Internal error during Balance Subscription", t)
errorMetric.increment()
Flux.error<BlockchainOuterClass.AddressBalance>(IllegalStateException("Internal Error")) Flux.error<BlockchainOuterClass.AddressBalance>(IllegalStateException("Internal Error"))
} }
} }
@@ -81,25 +122,102 @@ class BlockchainRpc(
override fun getBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> { override fun getBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
return requestMono.flatMapMany { request -> return requestMono.flatMapMany { request ->
val chain = Chain.byId(request.asset.chainValue) val chain = Chain.byId(request.asset.chainValue)
val metrics = getMetrics(chain)
metrics.getBalanceMetric.increment()
val asset = request.asset.code.toLowerCase() val asset = request.asset.code.toLowerCase()
val startTime = System.currentTimeMillis()
try { try {
trackAddress.find { it.isSupported(chain, asset) }?.getBalance(request) trackAddress.find { it.isSupported(chain, asset) }?.let { track ->
?: Flux.error<BlockchainOuterClass.AddressBalance>(SilentException.UnsupportedBlockchain(chain)) track.getBalance(request)
.doOnNext {
metrics.getBalanceRespMetric.record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS)
}
} ?: Flux.error<BlockchainOuterClass.AddressBalance>(SilentException.UnsupportedBlockchain(chain))
.doOnSubscribe { .doOnSubscribe {
log.error("Balance for $chain:$asset is not supported") log.error("Balance for $chain:$asset is not supported")
} }
} catch (t: Throwable) { } catch (t: Throwable) {
log.error("Internal error during Balance Request", t) log.error("Internal error during Balance Request", t)
errorMetric.increment()
Flux.error<BlockchainOuterClass.AddressBalance>(IllegalStateException("Internal Error")) Flux.error<BlockchainOuterClass.AddressBalance>(IllegalStateException("Internal Error"))
} }
} }
} }
override fun describe(request: Mono<BlockchainOuterClass.DescribeRequest>): Mono<BlockchainOuterClass.DescribeResponse> { override fun describe(request: Mono<BlockchainOuterClass.DescribeRequest>): Mono<BlockchainOuterClass.DescribeResponse> {
describeMetric.increment()
return describe.describe(request) return describe.describe(request)
.doOnError { errorMetric.increment() }
} }
override fun subscribeStatus(request: Mono<BlockchainOuterClass.StatusRequest>): Flux<BlockchainOuterClass.ChainStatus> { override fun subscribeStatus(request: Mono<BlockchainOuterClass.StatusRequest>): Flux<BlockchainOuterClass.ChainStatus> {
subscribeStatusMetric.increment()
return subscribeStatus.subscribeStatus(request) return subscribeStatus.subscribeStatus(request)
.doOnError { errorMetric.increment() }
}
private val metrics = EnumMap<Chain, RequestMetrics>(Chain::class.java)
private val metricsSetupLock = ReentrantLock()
private fun getMetrics(chain: Common.ChainRef): RequestMetrics {
return getMetrics(Chain.byId(chain.number))
}
private fun getMetrics(chain: Chain): RequestMetrics {
val existing = metrics[chain]
if (existing != null) {
return existing
}
return metricsSetupLock.withLock {
// second check in case it was updated while getting the lock
val existing2 = metrics[chain]
if (existing2 != null) {
existing2
} else {
val created = RequestMetrics(chain)
metrics[chain] = created
created
}
}
}
class RequestMetrics(chain: Chain) {
val nativeCallMetric = Counter.builder("request.grpc.request")
.tag("type", "nativeCall")
.tag("chain", chain.chainCode)
.register(Metrics.globalRegistry)
val nativeCallRespMetric = Timer.builder("request.grpc.response")
.tag("type", "nativeCall")
.tag("chain", chain.chainCode)
.publishPercentileHistogram()
.register(Metrics.globalRegistry)
val subscribeHeadMetric = Counter.builder("request.grpc.request")
.tag("type", "subscribeHead")
.tag("chain", chain.chainCode)
.register(Metrics.globalRegistry)
val subscribeHeadRespMetric = Counter.builder("request.grpc.reply")
.tag("type", "subscribeHead")
.tag("chain", chain.chainCode)
.register(Metrics.globalRegistry)
val subscribeTxMetric = Counter.builder("request.grpc.request")
.tag("type", "subscribeTx")
.tag("chain", chain.chainCode)
.register(Metrics.globalRegistry)
val subscribeBalanceMetric = Counter.builder("request.grpc.request")
.tag("type", "subscribeBalance")
.tag("chain", chain.chainCode)
.register(Metrics.globalRegistry)
val subscribeBalanceRespMetric = Counter.builder("request.grpc.reply")
.tag("type", "subscribeBalance")
.tag("chain", chain.chainCode)
.register(Metrics.globalRegistry)
val getBalanceMetric = Counter.builder("request.grpc.request")
.tag("type", "getBalance")
.tag("chain", chain.chainCode)
.register(Metrics.globalRegistry)
val getBalanceRespMetric = Timer.builder("request.grpc.response")
.tag("type", "getBalance")
.tag("chain", chain.chainCode)
.publishPercentileHistogram()
.register(Metrics.globalRegistry)
} }
} }

View File

@@ -163,9 +163,7 @@ class GrpcUpstreams(
fun getOrCreate(chain: Chain): UpstreamChange { fun getOrCreate(chain: Chain): UpstreamChange {
val metricsTags = listOf( val metricsTags = listOf(
// "unknown" is not supposed to happen Tag.of("upstream", id),
Tag.of("upstream", id ?: "unknown"),
// UNSPECIFIED shouldn't happen too
Tag.of("chain", chain.chainCode) Tag.of("chain", chain.chainCode)
) )