nimbus-eth1/nimbus/db/aristo/aristo_profile.nim

212 lines
6.7 KiB
Nim

# Nimbus
# Copyright (c) 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/[algorithm, math, sequtils, strformat, strutils, tables, times],
eth/common
type
AristoDbProfData* = tuple[sum: float, sqSum: float, count: int, masked: bool]
AristoDbProfListRef* = ref object of RootRef
## Statistic table synced with name indexes from `AristoDbProfNames`. Here
## a `ref` is used so it can be modified when part of another object.
##
list*: seq[AristoDbProfData]
AristoDbProfEla* = seq[(Duration,seq[uint])]
AristoDbProfMean* = seq[(Duration,seq[uint])]
AristoDbProfCount* = seq[(int,seq[uint])]
AristoDbProfStats* = tuple
count: int
total: Duration
mean: Duration
stdDev: Duration
devRatio: float
masked: bool
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
proc toDuration(fl: float): Duration =
## Convert the nanoseconds argument `ns` to a `Duration`.
let (s, ns) = fl.splitDecimal
initDuration(seconds = s.int, nanoseconds = (ns * 1_000_000_000).int)
func toFloat(ela: Duration): float =
## Convert the argument `ela` to a floating point seconds result.
let
elaS = ela.inSeconds
elaNs = (ela - initDuration(seconds=elaS)).inNanoSeconds
elaS.float + elaNs.float / 1_000_000_000
proc updateTotal(t: AristoDbProfListRef; fnInx: uint) =
## Summary update helper
if fnInx == 0:
t.list[0].reset
elif not t.list[fnInx].masked:
t.list[0].sum += t.list[fnInx].sum
t.list[0].sqSum += t.list[fnInx].sqSum
t.list[0].count += t.list[fnInx].count
# ---------------------
func ppUs(elapsed: Duration): string {.gcsafe, raises: [ValueError].} =
result = $elapsed.inMicroseconds
let ns = elapsed.inNanoseconds mod 1_000 # fraction of a micro second
if ns != 0:
# to rounded deca milli seconds
let du = (ns + 5i64) div 10i64
result &= &".{du:02}"
result &= "us"
func ppMs(elapsed: Duration): string {.gcsafe, raises: [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"
func ppSecs(elapsed: Duration): string {.gcsafe, raises: [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"
func ppMins(elapsed: Duration): string {.gcsafe, raises: [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"
# ------------------------------------------------------------------------------
# Public helpers
# ------------------------------------------------------------------------------
func toStr*(elapsed: Duration): string =
try:
if 0 < times.inMinutes(elapsed):
result = elapsed.ppMins
elif 0 < times.inSeconds(elapsed):
result = elapsed.ppSecs
elif 0 < times.inMilliSeconds(elapsed):
result = elapsed.ppMs
elif 0 < times.inMicroSeconds(elapsed):
result = elapsed.ppUs
else:
result = $elapsed.inNanoSeconds & "ns"
except ValueError:
result = $elapsed
proc update*(t: AristoDbProfListRef; inx: uint; ela: Duration) =
## Register time `ela` spent while executing function `fn`
let s = ela.toFloat
t.list[inx].sum += s
t.list[inx].sqSum += s * s
t.list[inx].count.inc
proc byElapsed*(t: AristoDbProfListRef): AristoDbProfEla =
## Collate `CoreDb` function symbols by elapsed times, sorted with largest
## `Duration` first. Zero `Duration` entries are discarded.
var u: Table[Duration,seq[uint]]
for inx in 0u ..< t.list.len.uint:
t.updateTotal inx
let (secs,_,count,_) = t.list[inx]
if 0 < count:
let ela = secs.toDuration
u.withValue(ela,val):
val[].add inx
do:
u[ela] = @[inx]
result.add (t.list[0u].sum.toDuration, @[0u])
for ela in u.keys.toSeq.sorted Descending:
u.withValue(ela,val):
result.add (ela, val[])
proc byMean*(t: AristoDbProfListRef): AristoDbProfMean =
## Collate `CoreDb` function symbols by elapsed mean times, sorted with
## largest `Duration` first. Zero `Duration` entries are discarded.
var u: Table[Duration,seq[uint]]
for inx in 0u ..< t.list.len.uint:
t.updateTotal inx
let (secs,_,count,_) = t.list[inx]
if 0 < count:
let ela = (secs / count.float).toDuration
u.withValue(ela,val):
val[].add inx
do:
u[ela] = @[inx]
result.add ((t.list[0u].sum / t.list[0u].count.float).toDuration, @[0u])
for mean in u.keys.toSeq.sorted Descending:
u.withValue(mean,val):
result.add (mean, val[])
proc byVisits*(t: AristoDbProfListRef): AristoDbProfCount =
## Collate `CoreDb` function symbols by number of visits, sorted with
## largest number first.
var u: Table[int,seq[uint]]
for fnInx in 0 ..< t.list.len:
t.updateTotal fnInx.uint
let (_,_,count,_) = t.list[fnInx]
if 0 < count:
u.withValue(count,val):
val[].add fnInx.uint
do:
u[count] = @[fnInx.uint]
result.add (t.list[0u].count, @[0u])
for count in u.keys.toSeq.sorted Descending:
u.withValue(count,val):
result.add (count, val[])
func stats*(
t: AristoDbProfListRef;
inx: uint;
): AristoDbProfStats =
## Print mean and strandard deviation of timing
let data = t.list[inx]
result.count = data.count
result.masked = data.masked
if 0 < result.count:
let
mean = data.sum / result.count.float
sqMean = data.sqSum / result.count.float
meanSq = mean * mean
# Mathematically, `meanSq <= sqMean` but there might be rounding errors
# if `meanSq` and `sqMean` are approximately the same.
sigma = sqMean - min(meanSq,sqMean)
stdDev = sigma.sqrt
result.total = data.sum.toDuration
result.mean = mean.toDuration
result.stdDev = stdDev.sqrt.toDuration
if 0 < mean:
result.devRatio = stdDev / mean
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------