nimbus-eth1/nimbus/sync/misc/ticker.nim

328 lines
10 KiB
Nim

# Nimbus - Fetch account and storage states from peers efficiently
#
# Copyright (c) 2021-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
{.push raises: [].}
import
std/[strformat, strutils, sequtils],
chronos,
chronicles,
eth/[common, p2p],
stint,
../../utils/prettify,
../types,
./timer_helper
logScope:
topics = "tick"
type
TickerSnapStatsUpdater* = proc: TickerSnapStats {.gcsafe, raises: [].}
## Snap sync state update function
TickerFullStatsUpdater* = proc: TickerFullStats {.gcsafe, raises: [].}
## Full sync state update function
SnapDescDetails = object
## Private state descriptor
snapCb: TickerSnapStatsUpdater
recovery: bool
lastRecov: bool
lastStats: TickerSnapStats
FullDescDetails = object
## Private state descriptor
fullCb: TickerFullStatsUpdater
lastStats: TickerFullStats
TickerSnapStats* = object
## Snap sync state (see `TickerSnapStatsUpdater`)
beaconBlock*: Option[BlockNumber]
pivotBlock*: Option[BlockNumber]
nAccounts*: (float,float) ## Mean and standard deviation
accountsFill*: (float,float,float) ## Mean, standard deviation, merged total
nAccountStats*: int ## #chunks
nSlotLists*: (float,float) ## Mean and standard deviation
nContracts*: (float,float) ## Mean and standard deviation
nStorageQueue*: Option[int]
nContractQueue*: Option[int]
nQueues*: int
TickerFullStats* = object
## Full sync state (see `TickerFullStatsUpdater`)
pivotBlock*: Option[BlockNumber]
topPersistent*: BlockNumber
nextUnprocessed*: Option[BlockNumber]
nextStaged*: Option[BlockNumber]
nStagedQueue*: int
suspended*: bool
reOrg*: bool
journal*: seq[int]
TickerRef* = ref object
## Ticker descriptor object
nBuddies: int
logTicker: TimerCallback
started: Moment
visited: Moment
prettyPrint: proc(t: TickerRef) {.gcsafe, raises: [].}
case fullMode: bool
of false:
snap: SnapDescDetails
of true:
full: FullDescDetails
const
extraTraceMessages = false # or true
## Enabled additional logging noise
tickerStartDelay = chronos.milliseconds(100)
tickerLogInterval = chronos.seconds(1)
tickerLogSuppressMax = chronos.seconds(100)
# ------------------------------------------------------------------------------
# Private functions: pretty printing
# ------------------------------------------------------------------------------
proc pc99(val: float): string =
if 0.99 <= val and val < 1.0: "99%"
elif 0.0 < val and val <= 0.01: "1%"
else: val.toPC(0)
proc toStr(a: Option[int]): string =
if a.isNone: "n/a"
else: $a.unsafeGet
# ------------------------------------------------------------------------------
# Private functions: printing ticker messages
# ------------------------------------------------------------------------------
when false:
template logTxt(info: static[string]): static[string] =
"Ticker " & info
template noFmtError(info: static[string]; code: untyped) =
try:
code
except ValueError as e:
raiseAssert "Inconveivable (" & info & "): " & e.msg
proc snapTicker(t: TickerRef) {.gcsafe.} =
let
data = t.snap.snapCb()
now = Moment.now()
if data != t.snap.lastStats or
t.snap.recovery != t.snap.lastRecov or
tickerLogSuppressMax < (now - t.visited):
var
nAcc, nSto, nCon: string
pv = "n/a"
let
nStoQ = data.nStorageQueue.toStr
nConQ = data.nContractQueue.toStr
bc = data.beaconBlock.toStr
recoveryDone = t.snap.lastRecov
accCov = data.accountsFill[0].pc99 &
"(" & data.accountsFill[1].pc99 & ")" &
"/" & data.accountsFill[2].pc99 &
"~" & data.nAccountStats.uint.toSI
nInst = t.nBuddies
# With `int64`, there are more than 29*10^10 years range for seconds
up = (now - t.started).seconds.uint64.toSI
mem = getTotalMem().uint.toSI
t.snap.lastStats = data
t.visited = now
t.snap.lastRecov = t.snap.recovery
if data.pivotBlock.isSome:
pv = data.pivotBlock.toStr & "/" & $data.nQueues
noFmtError("runLogTicker"):
nAcc = (&"{(data.nAccounts[0]+0.5).int64}" &
&"({(data.nAccounts[1]+0.5).int64})")
nSto = (&"{(data.nSlotLists[0]+0.5).int64}" &
&"({(data.nSlotLists[1]+0.5).int64})")
nCon = (&"{(data.nContracts[0]+0.5).int64}" &
&"({(data.nContracts[1]+0.5).int64})")
if t.snap.recovery:
info "Snap sync ticker (recovery)",
up, nInst, bc, pv, nAcc, accCov, nSto, nStoQ, nCon, nConQ, mem
elif recoveryDone:
info "Snap sync ticker (recovery done)",
up, nInst, bc, pv, nAcc, accCov, nSto, nStoQ, nCon, nConQ, mem
else:
info "Snap sync ticker",
up, nInst, bc, pv, nAcc, accCov, nSto, nStoQ, nCon, nConQ, mem
proc fullTicker(t: TickerRef) {.gcsafe.} =
let
data = t.full.fullCb()
now = Moment.now()
if data != t.full.lastStats or
tickerLogSuppressMax < (now - t.visited):
let
persistent = data.topPersistent.toStr
staged = data.nextStaged.toStr
unprocessed = data.nextUnprocessed.toStr
queued = data.nStagedQueue
reOrg = if data.reOrg: "t" else: "f"
pv = data.pivotBlock.toStr
nInst = t.nBuddies
# With `int64`, there are more than 29*10^10 years range for seconds
up = (now - t.started).seconds.uint64.toSI
mem = getTotalMem().uint.toSI
jSeq = data.journal
jrn = if 0 < jSeq.len: jSeq.mapIt($it).join("/") else: "n/a"
t.full.lastStats = data
t.visited = now
if data.suspended:
info "Full sync ticker (suspended)", up, nInst, pv,
persistent, staged, unprocessed, queued, reOrg, mem, jrn
else:
info "Full sync ticker", up, nInst, pv,
persistent, staged, unprocessed, queued, reOrg, mem, jrn
# ------------------------------------------------------------------------------
# Private functions: ticking log messages
# ------------------------------------------------------------------------------
proc setLogTicker(t: TickerRef; at: Moment) {.gcsafe.}
proc runLogTicker(t: TickerRef) {.gcsafe.} =
t.prettyPrint(t)
t.setLogTicker(Moment.fromNow(tickerLogInterval))
proc setLogTicker(t: TickerRef; at: Moment) =
if t.logTicker.isNil:
when extraTraceMessages:
debug logTxt "was stopped", fullMode=t.fullMode, nBuddies=t.nBuddies
else:
t.logTicker = safeSetTimer(at, runLogTicker, t)
proc initImpl(t: TickerRef; cb: TickerSnapStatsUpdater) =
t.fullMode = false
t.prettyPrint = snapTicker
t.snap = SnapDescDetails(snapCb: cb)
proc initImpl(t: TickerRef; cb: TickerFullStatsUpdater) =
t.fullMode = true
t.prettyPrint = fullTicker
t.full = FullDescDetails(fullCb: cb)
proc startImpl(t: TickerRef) =
t.logTicker = safeSetTimer(Moment.fromNow(tickerStartDelay),runLogTicker,t)
if t.started == Moment.default:
t.started = Moment.now()
proc stopImpl(t: TickerRef) =
## Stop ticker unconditionally
t.logTicker = nil
# ------------------------------------------------------------------------------
# Public constructor and start/stop functions
# ------------------------------------------------------------------------------
proc init*(
T: type TickerRef;
cb: TickerSnapStatsUpdater|TickerFullStatsUpdater;
): T =
## Constructor
new result
result.initImpl(cb)
proc init*(t: TickerRef; cb: TickerSnapStatsUpdater) =
## Re-initialise ticket
if not t.isNil:
t.visited.reset
if t.fullMode:
t.prettyPrint(t) # print final state for full sync
t.initImpl(cb)
proc init*(t: TickerRef; cb: TickerFullStatsUpdater) =
## Re-initialise ticket
if not t.isNil:
t.visited.reset
if not t.fullMode:
t.prettyPrint(t) # print final state for snap sync
t.initImpl(cb)
proc start*(t: TickerRef) =
## Re/start ticker unconditionally
if not t.isNil:
when extraTraceMessages:
debug logTxt "start", fullMode=t.fullMode, nBuddies=t.nBuddies
t.startImpl()
proc stop*(t: TickerRef) =
## Stop ticker unconditionally
if not t.isNil:
t.stopImpl()
when extraTraceMessages:
debug logTxt "stop", fullMode=t.fullMode, nBuddies=t.nBuddies
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc startBuddy*(t: TickerRef) =
## Increment buddies counter and start ticker unless running.
if not t.isNil:
if t.nBuddies <= 0:
t.nBuddies = 1
if t.fullMode or not t.snap.recovery:
t.startImpl()
when extraTraceMessages:
debug logTxt "start buddy", fullMode=t.fullMode, nBuddies=t.nBuddies
else:
t.nBuddies.inc
proc startRecovery*(t: TickerRef) =
## Ditto for recovery mode
if not t.isNil and not t.fullMode and not t.snap.recovery:
t.snap.recovery = true
if t.nBuddies <= 0:
t.startImpl()
when extraTraceMessages:
debug logTxt "start recovery", fullMode=t.fullMode, nBuddies=t.nBuddies
proc stopBuddy*(t: TickerRef) =
## Decrement buddies counter and stop ticker if there are no more registered
## buddies.
if not t.isNil:
t.nBuddies.dec
if t.nBuddies <= 0 and not t.fullMode and not t.snap.recovery:
t.nBuddies = 0
t.stopImpl()
when extraTraceMessages:
debug logTxt "stop (buddy)", fullMode=t.fullMode, nBuddies=t.nBuddies
proc stopRecovery*(t: TickerRef) =
## Ditto for recovery mode
if not t.isNil and not t.fullMode and t.snap.recovery:
t.snap.recovery = false
t.stopImpl()
when extraTraceMessages:
debug logTxt "stop (recovery)", fullMode=t.fullMode, nBuddies=t.nBuddies
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------