Merge pull request #85 from p2p-org/fix_daemon_heads

fix daemon heads + head metrics
This commit is contained in:
MaxFomenkov
2022-12-14 13:49:50 +03:00
committed by GitHub
2 changed files with 72 additions and 36 deletions

View File

@@ -28,20 +28,26 @@ import reactor.core.publisher.Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER
import reactor.core.publisher.Sinks.EmitResult.OK import reactor.core.publisher.Sinks.EmitResult.OK
import reactor.core.scheduler.Schedulers import reactor.core.scheduler.Schedulers
import reactor.kotlin.core.publisher.toMono import reactor.kotlin.core.publisher.toMono
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
abstract class AbstractHead @JvmOverloads constructor( abstract class AbstractHead @JvmOverloads constructor(
private val forkChoice: ForkChoice, private val forkChoice: ForkChoice,
private val blockValidator: BlockValidator = BlockValidator.ALWAYS_VALID, private val blockValidator: BlockValidator = BlockValidator.ALWAYS_VALID,
awaitHeadTimeoutMs: Long = 60_000, private val awaitHeadTimeoutMs: Long = 60_000,
private val upstreamId: String = "" private val upstreamId: String = ""
) : Head { ) : Head {
companion object { companion object {
private val log = LoggerFactory.getLogger(AbstractHead::class.java) private val log = LoggerFactory.getLogger(AbstractHead::class.java)
private val instances: MutableMap<String, AtomicInteger> = ConcurrentHashMap()
private val running: MutableMap<String, AtomicInteger> = ConcurrentHashMap()
private val executor = Executors.newSingleThreadScheduledExecutor()
} }
private var stream = Sinks.many().multicast().directBestEffort<BlockContainer>() private var stream = Sinks.many().multicast().directBestEffort<BlockContainer>()
@@ -50,25 +56,27 @@ abstract class AbstractHead @JvmOverloads constructor(
private var stopping = false private var stopping = false
private var lastHeadUpdated = 0L private var lastHeadUpdated = 0L
private val lock = ReentrantLock() private val lock = ReentrantLock()
private var future: Future<*>? = null
private val delayed = AtomicBoolean(false)
init { init {
val state = AtomicBoolean(false)
Gauge.builder("stuck_head", state) { val className = this.javaClass.simpleName
Gauge.builder("stuck_head", delayed) {
if (it.get()) 1.0 else 0.0 if (it.get()) 1.0 else 0.0
}.tag("upstream", upstreamId).tag("class", this.javaClass.simpleName).register(Metrics.globalRegistry) }.tag("upstream", upstreamId).tag("class", className).register(Metrics.globalRegistry)
Gauge.builder("current_head", forkChoice) { Gauge.builder("current_head", forkChoice) {
it.getHead()?.height?.toDouble() ?: 0.0 it.getHead()?.height?.toDouble() ?: 0.0
}.tag("upstream", upstreamId).tag("class", this.javaClass.simpleName).register(Metrics.globalRegistry) }.tag("upstream", upstreamId).tag("class", className).register(Metrics.globalRegistry)
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
{ instances.computeIfAbsent(className) {
val delay = System.currentTimeMillis() - lastHeadUpdated AtomicInteger(0).also { toHeadCountMetric(it, "allocated") }
val delayed = delay > awaitHeadTimeoutMs }.incrementAndGet()
state.set(delayed) }
if (delayed) {
log.warn("No head updates $upstreamId for $delay ms @ ${this.javaClass}") protected fun finalize() {
} instances[this.javaClass.simpleName]?.decrementAndGet()
}, 300, 30, TimeUnit.SECONDS
)
} }
fun follow(source: Flux<BlockContainer>): Disposable { fun follow(source: Flux<BlockContainer>): Disposable {
@@ -154,9 +162,37 @@ abstract class AbstractHead @JvmOverloads constructor(
override fun stop() { override fun stop() {
stopping = true stopping = true
log.debug("Stop ${this.javaClass.simpleName} $upstreamId")
future?.let {
running[this.javaClass.simpleName]?.decrementAndGet()
it.cancel(true)
}
future = null
} }
override fun start() { override fun start() {
stopping = false stopping = false
log.debug("Start ${this.javaClass.simpleName} $upstreamId")
if (future == null) {
future = executor.scheduleAtFixedRate(
{
val delay = System.currentTimeMillis() - lastHeadUpdated
delayed.set(delay > awaitHeadTimeoutMs)
if (delayed.get()) {
log.warn("No head updates $upstreamId for $delay ms @ ${this.javaClass.simpleName}")
}
}, 180, 30, TimeUnit.SECONDS
)
running.computeIfAbsent(this.javaClass.simpleName) {
AtomicInteger(0).also { toHeadCountMetric(it, "running") }
}.incrementAndGet()
}
}
private fun toHeadCountMetric(counter: AtomicInteger, status: String) {
Gauge.builder("head_count", counter) {
it.get().toDouble()
}.tag("class", this.javaClass.simpleName).tag("status", status).register(Metrics.globalRegistry)
} }
} }

View File

@@ -112,33 +112,33 @@ open class EthereumPosMultiStream(
} }
override fun updateHead(): Head { override fun updateHead(): Head {
head?.let { this.head?.takeIf { it is Lifecycle }?.apply { stop() }
if (it is Lifecycle) {
it.stop()
}
}
lagObserver?.stop() lagObserver?.stop()
lagObserver = null lagObserver = null
val head = if (upstreams.size == 1) {
val upstream = upstreams.first() return when (upstreams.size) {
upstream.setLag(0) 0 -> EmptyHead()
upstream.getHead().apply { 1 -> upstreams.first().let {
if (this is Lifecycle) { it.setLag(0)
this.start() it.getHead().apply {
if (this is Lifecycle) {
start()
}
} }
} }
} else {
val heads = upstreams.map { it.getHead() } else -> upstreams.map { it.getHead() }.let { heads ->
val newHead = MergedHead(heads, PriorityForkChoice(), "ETH Pos Multistream").apply { MergedHead(heads, PriorityForkChoice(), "ETH Pos Multistream").apply {
this.start() start()
}.also {
this.lagObserver = EthereumPosHeadLagObserver(it, ArrayList(upstreams)).apply {
start()
}
}
} }
val lagObserver = EthereumPosHeadLagObserver(newHead, ArrayList(upstreams)) }.apply {
this.lagObserver = lagObserver onHeadUpdated(this)
lagObserver.start()
newHead
} }
onHeadUpdated(head)
return head
} }
override fun getLabels(): Collection<UpstreamsConfig.Labels> { override fun getLabels(): Collection<UpstreamsConfig.Labels> {