184 lines
5.6 KiB
Nim
184 lines
5.6 KiB
Nim
# Nimbus - Fetch account and storage states from peers efficiently
|
|
#
|
|
# Copyright (c) 2021 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.
|
|
|
|
import
|
|
std/[strformat, strutils, times],
|
|
chronos,
|
|
chronicles,
|
|
eth/[common/eth_types, p2p],
|
|
stint,
|
|
../../../utils/prettify,
|
|
../../timer_helper
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
logScope:
|
|
topics = "snap-ticker"
|
|
|
|
type
|
|
TickerStats* = object
|
|
pivotBlock*: Option[BlockNumber]
|
|
nAccounts*: (float,float) ## mean and standard deviation
|
|
nStorage*: (float,float) ## mean and standard deviation
|
|
accountsFill*: (float,float,float) ## mean, standard deviation, merged total
|
|
accCoverage*: float ## as factor
|
|
activeQueues*: int
|
|
flushedQueues*: int64
|
|
|
|
TickerStatsUpdater* =
|
|
proc: TickerStats {.gcsafe, raises: [Defect].}
|
|
|
|
TickerRef* = ref object
|
|
## Account fetching state that is shared among all peers.
|
|
nBuddies: int
|
|
lastStats: TickerStats
|
|
lastTick: uint64
|
|
statsCb: TickerStatsUpdater
|
|
logTicker: TimerCallback
|
|
tick: uint64 # more than 5*10^11y before wrap when ticking every sec
|
|
|
|
const
|
|
tickerStartDelay = chronos.milliseconds(100)
|
|
tickerLogInterval = chronos.seconds(1)
|
|
tickerLogSuppressMax = 100
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions: pretty printing
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc ppMs*(elapsed: times.Duration): string
|
|
{.gcsafe, raises: [Defect, ValueError]} =
|
|
result = $elapsed.inMilliseconds
|
|
let ns = elapsed.inNanoseconds mod 1_000_000 # fraction of a milli second
|
|
if ns != 0:
|
|
# to rounded deca milli seconds
|
|
let dm = (ns + 5_000i64) div 10_000i64
|
|
result &= &".{dm:02}"
|
|
result &= "ms"
|
|
|
|
proc ppSecs*(elapsed: times.Duration): string
|
|
{.gcsafe, raises: [Defect, ValueError]} =
|
|
result = $elapsed.inSeconds
|
|
let ns = elapsed.inNanoseconds mod 1_000_000_000 # fraction of a second
|
|
if ns != 0:
|
|
# round up
|
|
let ds = (ns + 5_000_000i64) div 10_000_000i64
|
|
result &= &".{ds:02}"
|
|
result &= "s"
|
|
|
|
proc ppMins*(elapsed: times.Duration): string
|
|
{.gcsafe, raises: [Defect, ValueError]} =
|
|
result = $elapsed.inMinutes
|
|
let ns = elapsed.inNanoseconds mod 60_000_000_000 # fraction of a minute
|
|
if ns != 0:
|
|
# round up
|
|
let dm = (ns + 500_000_000i64) div 1_000_000_000i64
|
|
result &= &":{dm:02}"
|
|
result &= "m"
|
|
|
|
proc pp(d: times.Duration): string
|
|
{.gcsafe, raises: [Defect, ValueError]} =
|
|
if 40 < d.inSeconds:
|
|
d.ppMins
|
|
elif 200 < d.inMilliseconds:
|
|
d.ppSecs
|
|
else:
|
|
d.ppMs
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions: ticking log messages
|
|
# ------------------------------------------------------------------------------
|
|
|
|
template noFmtError(info: static[string]; code: untyped) =
|
|
try:
|
|
code
|
|
except ValueError as e:
|
|
raiseAssert "Inconveivable (" & info & "): " & e.msg
|
|
|
|
proc setLogTicker(t: TickerRef; at: Moment) {.gcsafe.}
|
|
|
|
proc runLogTicker(t: TickerRef) {.gcsafe.} =
|
|
let data = t.statsCb()
|
|
|
|
if data != t.lastStats or
|
|
t.lastTick + tickerLogSuppressMax < t.tick:
|
|
t.lastStats = data
|
|
t.lastTick = t.tick
|
|
var
|
|
nAcc, nStore, bulk: string
|
|
pivot = "n/a"
|
|
let
|
|
accCov = data.accountsFill[0].toPC(1) &
|
|
"(" & data.accountsFill[1].toPC(1) & ")" &
|
|
"/" & data.accountsFill[2].toPC(0)
|
|
flushed = data.flushedQueues
|
|
buddies = t.nBuddies
|
|
tick = t.tick.toSI
|
|
mem = getTotalMem().uint.toSI
|
|
|
|
noFmtError("runLogTicker"):
|
|
if data.pivotBlock.isSome:
|
|
pivot = &"#{data.pivotBlock.get}/{data.activeQueues}"
|
|
nAcc = &"{(data.nAccounts[0]+0.5).int64}({(data.nAccounts[1]+0.5).int64})"
|
|
nStore = &"{(data.nStorage[0]+0.5).int64}({(data.nStorage[1]+0.5).int64})"
|
|
|
|
info "Snap sync statistics",
|
|
tick, buddies, pivot, nAcc, accCov, nStore, flushed, mem
|
|
|
|
t.tick.inc
|
|
t.setLogTicker(Moment.fromNow(tickerLogInterval))
|
|
|
|
|
|
proc setLogTicker(t: TickerRef; at: Moment) =
|
|
if not t.logTicker.isNil:
|
|
t.logTicker = safeSetTimer(at, runLogTicker, t)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public constructor and start/stop functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc init*(T: type TickerRef; cb: TickerStatsUpdater): T =
|
|
## Constructor
|
|
T(statsCb: cb)
|
|
|
|
proc start*(t: TickerRef) =
|
|
## Re/start ticker unconditionally
|
|
#debug "Started ticker"
|
|
t.logTicker = safeSetTimer(Moment.fromNow(tickerStartDelay), runLogTicker, t)
|
|
|
|
proc stop*(t: TickerRef) =
|
|
## Stop ticker unconditionally
|
|
t.logTicker = nil
|
|
#debug "Stopped ticker"
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc startBuddy*(t: TickerRef) =
|
|
## Increment buddies counter and start ticker unless running.
|
|
if t.nBuddies <= 0:
|
|
t.nBuddies = 1
|
|
t.start()
|
|
else:
|
|
t.nBuddies.inc
|
|
|
|
proc stopBuddy*(t: TickerRef) =
|
|
## Decrement buddies counter and stop ticker if there are no more registered
|
|
## buddies.
|
|
t.nBuddies.dec
|
|
if t.nBuddies <= 0:
|
|
t.stop()
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|