nim-chroprof/tests/testprofiler.nim

337 lines
10 KiB
Nim
Raw Permalink Normal View History

2024-02-29 20:54:00 -03:00
import std/math
import std/sequtils
2024-02-29 17:55:45 -03:00
2024-02-29 20:54:00 -03:00
import unittest2
2024-02-29 17:55:45 -03:00
import chronos
2024-02-29 20:54:00 -03:00
import ../chroprof/profiler
2024-02-29 17:55:45 -03:00
import ./utils
2024-02-29 20:54:00 -03:00
suite "profiler metrics":
2024-03-01 13:44:06 -03:00
setup:
startRecording()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
teardown:
stopRecording()
resetTime()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
proc recordedMetrics(): MetricsTotals =
var profiler: ProfilerState
profiler.processAllEvents(rawRecording)
profiler.metrics
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute correct times for a simple blocking future":
proc simple() {.async.} =
advanceTime(50.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor simple()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let simpleMetrics = metrics.forProc("simple")
check simpleMetrics.execTime == 50.milliseconds
check simpleMetrics.wallClockTime == 50.milliseconds
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute correct times for a simple non-blocking future":
proc simple() {.async.} =
advanceTime(10.milliseconds)
await advanceTimeAsync(50.milliseconds)
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor simple()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let simpleMetrics = metrics.forProc("simple")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check simpleMetrics.execTime == 20.milliseconds
check simpleMetrics.wallClockTime == 70.milliseconds
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute correct times for a non-blocking future with multiple pauses":
proc simple() {.async.} =
advanceTime(10.milliseconds)
await advanceTimeAsync(50.milliseconds)
advanceTime(10.milliseconds)
await advanceTimeAsync(50.milliseconds)
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor simple()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let simpleMetrics = metrics.forProc("simple")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check simpleMetrics.execTime == 30.milliseconds
check simpleMetrics.wallClockTime == 130.milliseconds
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute correct times when there is a single blocking child":
proc child() {.async.} =
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
proc parent() {.async.} =
advanceTime(10.milliseconds)
await child()
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor parent()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check parentMetrics.execTime == 20.milliseconds
check parentMetrics.childrenExecTime == 10.milliseconds
check parentMetrics.wallClockTime == 30.milliseconds
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check childMetrics.execTime == 10.milliseconds
check childMetrics.wallClockTime == 10.milliseconds
check childMetrics.childrenExecTime == ZeroDuration
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute correct times when there is a single non-blocking child":
proc child() {.async.} =
advanceTime(10.milliseconds)
await advanceTimeAsync(50.milliseconds)
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
proc parent() {.async.} =
advanceTime(10.milliseconds)
await child()
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor parent()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check parentMetrics.execTime == 20.milliseconds
check parentMetrics.childrenExecTime == 20.milliseconds
check parentMetrics.wallClockTime == 90.milliseconds
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check childMetrics.execTime == 20.milliseconds
check childMetrics.wallClockTime == 70.milliseconds
check childMetrics.childrenExecTime == ZeroDuration
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute correct times when there are multiple blocking and non-blocking children":
proc blockingChild() {.async.} =
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
proc nonblockingChild() {.async.} =
advanceTime(10.milliseconds)
await advanceTimeAsync(20.milliseconds)
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
proc parent() {.async.} =
advanceTime(10.milliseconds)
await blockingChild()
advanceTime(10.milliseconds)
await nonblockingChild()
advanceTime(10.milliseconds)
await blockingChild()
advanceTime(10.milliseconds)
await nonblockingChild()
advanceTime(10.milliseconds)
waitFor parent()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let blockingChildMetrics = metrics.forProc("blockingChild")
let nonblockingChildMetrics = metrics.forProc("nonblockingChild")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check parentMetrics.execTime == 50.milliseconds
check parentMetrics.childrenExecTime == 60.milliseconds
check parentMetrics.wallClockTime == 150.milliseconds
check blockingChildMetrics.execTime == 20.milliseconds
check blockingChildMetrics.wallClockTime == 20.milliseconds
check blockingChildMetrics.childrenExecTime == ZeroDuration
check nonblockingChildMetrics.execTime == 40.milliseconds
check nonblockingChildMetrics.wallClockTime == 80.milliseconds
check nonblockingChildMetrics.childrenExecTime == ZeroDuration
test "should compute correct times when a child throws an exception":
proc child() {.async: (raises: [CatchableError]).} =
advanceTime(10.milliseconds)
raise newException(CatchableError, "child exception")
proc parent() {.async: (raises: [CatchableError]).} =
advanceTime(10.milliseconds)
try:
await child()
except:
discard
advanceTime(10.milliseconds)
waitFor parent()
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
check parentMetrics.execTime == 20.milliseconds
check parentMetrics.childrenExecTime == 10.milliseconds
check parentMetrics.wallClockTime == 30.milliseconds
check childMetrics.execTime == 10.milliseconds
check childMetrics.wallClockTime == 10.milliseconds
check childMetrics.childrenExecTime == ZeroDuration
test "should compute correct times when a child gets cancelled":
proc child() {.async.} =
advanceTime(10.milliseconds)
await sleepAsync(1.hours)
proc parent() {.async.} =
advanceTime(10.milliseconds)
# This is sort of subtle: we simulate that parent runs for 10
# milliseconds before actually cancelling the child. This renders the
# test case less trivial as those 10 milliseconds should be billed as
# wallclock time at the child, causing the child's exec time and its
# wallclock time to differ.
let child = child()
advanceTime(10.milliseconds)
await child.cancelAndWait()
advanceTime(10.milliseconds)
waitFor parent()
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
check parentMetrics.execTime == 30.milliseconds
check parentMetrics.childrenExecTime == 10.milliseconds
check parentMetrics.wallClockTime == 40.milliseconds
check childMetrics.execTime == 10.milliseconds
check childMetrics.wallClockTime == 20.milliseconds
check childMetrics.childrenExecTime == ZeroDuration
test "should compute the correct number of times a proc gets called":
proc child() {.async.} =
discard
proc parent() {.async.} =
for i in 1 .. 10:
await child()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor parent()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check parentMetrics.callCount == 1
check childMetrics.callCount == 10
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute the maximum execution time for a proc, out of all calls":
var execTimes = @[10.milliseconds, 50.milliseconds, 10.milliseconds]
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
proc child(d: Duration) {.async.} =
advanceTime(d)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
proc parent() {.async.} =
for d in execTimes:
await child(d)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor parent()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check parentMetrics.execTimeMax == ZeroDuration
check childMetrics.execTimeMax == 50.milliseconds
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should compute the correct execution time within finally blocks":
proc withFinally() {.async.} =
try:
advanceTime(10.milliseconds)
return
finally:
advanceTime(10.milliseconds)
await advanceTimeAsync(10.milliseconds)
advanceTime(10.milliseconds)
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
waitFor withFinally()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
var withFinallyMetrics = metrics.forProc("withFinally")
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check withFinallyMetrics.execTime == 30.milliseconds
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
test "should count futures which start in a completion state":
let completed {.used.} = Future.completed(42)
let failed {.used.} = Future[int].failed((ref ValueError)(msg: "msg"))
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
var metrics = recordedMetrics()
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
let stillborns = metrics.pairs.toSeq.map(
proc(item: (SrcLoc, AggregateMetrics)): uint =
item[1].stillbornCount
).sum
2024-02-29 17:55:45 -03:00
2024-03-01 13:44:06 -03:00
check stillborns == 2
test "should properly account for children's execution time even when those finish after the parent":
let barrier = newFuture[void]("barrier")
proc child() {.async.} =
await barrier
advanceTime(10.milliseconds)
2024-03-07 14:34:13 -03:00
proc parent(): Future[seq[Future[void]]] {.async.} =
advanceTime(10.milliseconds)
2024-03-07 14:34:13 -03:00
return @[child(), child()]
let childFut = waitFor parent()
barrier.complete()
2024-03-07 14:34:13 -03:00
waitFor allFutures(childFut)
var metrics = recordedMetrics()
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
check parentMetrics.execTime == 10.milliseconds
2024-03-07 14:34:13 -03:00
check parentMetrics.childrenExecTime == 20.milliseconds
check parentMetrics.wallClockTime == 10.milliseconds
2024-03-07 14:34:13 -03:00
check childMetrics.execTime == 20.milliseconds
check childMetrics.wallClockTime == 30.milliseconds
check childMetrics.childrenExecTime == ZeroDuration
test "should aggregate child execution times across nested children":
proc child() {.async.} =
advanceTime(10.milliseconds)
proc parent() {.async.} =
advanceTime(10.milliseconds)
await child()
advanceTime(10.milliseconds)
proc grandparent() {.async.} =
advanceTime(10.milliseconds)
await parent()
advanceTime(10.milliseconds)
waitFor grandparent()
var metrics = recordedMetrics()
let grandparentMetrics = metrics.forProc("grandparent")
let parentMetrics = metrics.forProc("parent")
let childMetrics = metrics.forProc("child")
check grandparentMetrics.execTime == 20.milliseconds
check grandparentMetrics.childrenExecTime == 30.milliseconds
check grandparentMetrics.wallClockTime == 50.milliseconds
check parentMetrics.execTime == 20.milliseconds
check parentMetrics.childrenExecTime == 10.milliseconds
check parentMetrics.wallClockTime == 30.milliseconds
check childMetrics.execTime == 10.milliseconds
check childMetrics.wallClockTime == 10.milliseconds
check childMetrics.childrenExecTime == ZeroDuration