solution: base code for prometheus monitoring

This commit is contained in:
Igor Artamonov
2021-02-02 21:50:06 -05:00
parent 3ec5632f26
commit 93e08f58f6
6 changed files with 144 additions and 1 deletions

View File

@@ -105,4 +105,9 @@ open class Config(
return mainConfig.tokens ?: TokensConfig(emptyList())
}
@Bean
open fun monitoringConfig(@Autowired mainConfig: MainConfig): MonitoringConfig {
return mainConfig.monitoring
}
}

View File

@@ -23,5 +23,6 @@ class MainConfig {
var proxy: ProxyConfig? = null
var upstreams: UpstreamsConfig? = null
var tokens: TokensConfig? = null
var monitoring: MonitoringConfig = MonitoringConfig.default()
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2021 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.config
class MonitoringConfig(
val enabled: Boolean,
val prometheus: PrometheusConfig
) {
companion object {
fun default(): MonitoringConfig {
return MonitoringConfig(true, PrometheusConfig.default())
}
}
data class PrometheusConfig(
val enabled: Boolean,
val path: String,
val port: Int
) {
companion object {
fun default(): PrometheusConfig {
return PrometheusConfig(true, "/prometheus", 8081)
}
}
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2021 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.monitoring
import com.sun.net.httpserver.HttpServer
import io.emeraldpay.dshackle.config.MainConfig
import io.emeraldpay.dshackle.config.MonitoringConfig
import io.micrometer.core.instrument.Meter
import io.micrometer.core.instrument.Metrics
import io.micrometer.core.instrument.config.MeterFilter
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import io.micrometer.prometheus.PrometheusConfig
import io.micrometer.prometheus.PrometheusMeterRegistry
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import java.io.IOException
import java.io.OutputStream
import java.net.InetSocketAddress
import javax.annotation.PostConstruct
@Service
class MonitoringSetup(
@Autowired private val monitoringConfig: MonitoringConfig
) {
companion object {
private val log = LoggerFactory.getLogger(MonitoringSetup::class.java)
}
@PostConstruct
fun setup() {
val prometheusRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
Metrics.globalRegistry.add(prometheusRegistry)
Metrics.globalRegistry.config().meterFilter(object: MeterFilter {
override fun map(id: Meter.Id): Meter.Id {
return id.withName("dshackle." + id.name)
}
})
if (monitoringConfig.prometheus.enabled) {
// use standard JVM server with a single thread blocking processing
// prometheus is a single thread periodic call, no reason to setup anything complex
try {
val server = HttpServer.create(InetSocketAddress(monitoringConfig.prometheus.port), 0);
server.createContext(monitoringConfig.prometheus.path) { httpExchange ->
val response = prometheusRegistry.scrape()
httpExchange.sendResponseHeaders(200, response.toByteArray().size.toLong());
httpExchange.responseBody.use { os ->
os.write(response.toByteArray())
}
}
Thread(server::start).start();
} catch (e: IOException) {
log.error("Failed to start Prometheus Server", e)
}
}
}
}

View File

@@ -24,6 +24,8 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.grpc.Chain
import io.micrometer.core.instrument.Metrics
import io.micrometer.core.instrument.Tag
import org.apache.commons.collections4.Factory
import org.apache.commons.collections4.FunctorException
import org.slf4j.LoggerFactory
@@ -50,6 +52,7 @@ abstract class Multistream(
companion object {
private val log = LoggerFactory.getLogger(Multistream::class.java)
private const val metrics = "upstreams"
}
private var cacheSubscription: Disposable? = null
@@ -63,6 +66,24 @@ abstract class Multistream(
private var subscription: Disposable? = null
private var capabilities: Set<Capability> = emptySet()
init {
UpstreamAvailability.values().forEach { status ->
Metrics.gauge("$metrics.availability",
listOf(Tag.of("chain", chain.chainCode), Tag.of("status", status.name.toLowerCase()))
, this) {
upstreams.count { it.getStatus() == status }.toDouble()
}
}
upstreams.forEach { up ->
Metrics.gauge("$metrics.lag",
listOf(Tag.of("chain", chain.chainCode), Tag.of("upstream", up.getId()))
, this) {
up.getLag().toDouble()
}
}
}
open fun init() {
onUpstreamsUpdated()
}