212 lines
6.7 KiB
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
|
|
# ------------------------------------------------------------------------------
|