mirror of
https://github.com/logos-storage/nim-chronos.git
synced 2026-01-06 15:33:08 +00:00
remove profiler from Chronos, keep only instrumentation code
This commit is contained in:
parent
a68de9fe17
commit
c8899eb134
@ -1,28 +0,0 @@
|
|||||||
import ./config
|
|
||||||
|
|
||||||
when chronosProfiling:
|
|
||||||
import futures
|
|
||||||
import ./profiler/[events, metrics]
|
|
||||||
|
|
||||||
export futures, events, metrics
|
|
||||||
|
|
||||||
when not chronosFutureId:
|
|
||||||
{.error: "chronosProfiling requires chronosFutureId to be enabled".}
|
|
||||||
|
|
||||||
var futureMetrics {.threadvar.}: ProfilerMetrics
|
|
||||||
|
|
||||||
proc getMetrics*(): ProfilerMetrics =
|
|
||||||
## Returns metrics for the current event loop.
|
|
||||||
result = futureMetrics
|
|
||||||
|
|
||||||
proc enableEventCallbacks*(): void =
|
|
||||||
onBaseFutureEvent = handleBaseFutureEvent
|
|
||||||
onAsyncFutureEvent = handleAsyncFutureEvent
|
|
||||||
|
|
||||||
proc enableProfiling*() =
|
|
||||||
## Enables profiling on the current event loop.
|
|
||||||
if not isNil(handleFutureEvent): return
|
|
||||||
|
|
||||||
enableEventCallbacks()
|
|
||||||
handleFutureEvent = proc (e: Event) {.nimcall.} =
|
|
||||||
futureMetrics.processEvent(e)
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
## This module defines the lower-level callback implementations that hook into
|
|
||||||
## the Chronos scheduler when profiling is enabled. The main goal is to provide
|
|
||||||
## timestamped state changes for futures, which form the input for the profiler.
|
|
||||||
|
|
||||||
import ../timer
|
|
||||||
import ../futures
|
|
||||||
import ../srcloc
|
|
||||||
|
|
||||||
type
|
|
||||||
ExtendedFutureState* {.pure.} = enum
|
|
||||||
Pending,
|
|
||||||
Running,
|
|
||||||
Paused,
|
|
||||||
Completed,
|
|
||||||
Cancelled,
|
|
||||||
Failed,
|
|
||||||
|
|
||||||
Event* = object
|
|
||||||
future: FutureBase
|
|
||||||
newState*: ExtendedFutureState
|
|
||||||
timestamp*: Moment
|
|
||||||
|
|
||||||
var handleFutureEvent* {.threadvar.}: proc (event: Event) {.nimcall, gcsafe, raises: [].}
|
|
||||||
|
|
||||||
proc `location`*(self: Event): SrcLoc =
|
|
||||||
self.future.internalLocation[Create][]
|
|
||||||
|
|
||||||
proc `futureId`*(self: Event): uint =
|
|
||||||
self.future.id
|
|
||||||
|
|
||||||
proc mkEvent(future: FutureBase, state: ExtendedFutureState): Event =
|
|
||||||
Event(
|
|
||||||
future: future,
|
|
||||||
newState: state,
|
|
||||||
timestamp: Moment.now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
proc handleBaseFutureEvent*(future: FutureBase,
|
|
||||||
state: FutureState): void {.nimcall.} =
|
|
||||||
{.cast(gcsafe).}:
|
|
||||||
let extendedState = case state:
|
|
||||||
of FutureState.Pending: ExtendedFutureState.Pending
|
|
||||||
of FutureState.Completed: ExtendedFutureState.Completed
|
|
||||||
of FutureState.Cancelled: ExtendedFutureState.Cancelled
|
|
||||||
of FutureState.Failed: ExtendedFutureState.Failed
|
|
||||||
|
|
||||||
if not isNil(handleFutureEvent):
|
|
||||||
handleFutureEvent(mkEvent(future, extendedState))
|
|
||||||
|
|
||||||
proc handleAsyncFutureEvent*(future: FutureBase,
|
|
||||||
state: AsyncFutureState): void {.nimcall.} =
|
|
||||||
{.cast(gcsafe).}:
|
|
||||||
let extendedState = case state:
|
|
||||||
of AsyncFutureState.Running: ExtendedFutureState.Running
|
|
||||||
of AsyncFutureState.Paused: ExtendedFutureState.Paused
|
|
||||||
|
|
||||||
if not isNil(handleFutureEvent):
|
|
||||||
handleFutureEvent(mkEvent(future, extendedState))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
import std/tables
|
|
||||||
import std/options
|
|
||||||
import std/sets
|
|
||||||
|
|
||||||
import ./events
|
|
||||||
import ../[timer, srcloc]
|
|
||||||
|
|
||||||
export timer, tables, sets, srcloc
|
|
||||||
|
|
||||||
type
|
|
||||||
AggregateFutureMetrics* = object
|
|
||||||
execTime*: Duration
|
|
||||||
execTimeMax*: Duration
|
|
||||||
childrenExecTime*: Duration
|
|
||||||
wallClockTime*: Duration
|
|
||||||
stillbornCount*: uint
|
|
||||||
callCount*: uint
|
|
||||||
|
|
||||||
RunningFuture* = object
|
|
||||||
state*: ExtendedFutureState
|
|
||||||
created*: Moment
|
|
||||||
lastStarted*: Moment
|
|
||||||
timeToFirstPause*: Duration
|
|
||||||
partialExecTime*: Duration
|
|
||||||
partialChildrenExecTime*: Duration
|
|
||||||
partialChildrenExecOverlap*: Duration
|
|
||||||
parent*: Option[uint]
|
|
||||||
pauses*: uint
|
|
||||||
|
|
||||||
MetricsTotals* = Table[SrcLoc, AggregateFutureMetrics]
|
|
||||||
|
|
||||||
ProfilerMetrics* = object
|
|
||||||
callStack: seq[uint]
|
|
||||||
partials: Table[uint, RunningFuture]
|
|
||||||
totals*: MetricsTotals
|
|
||||||
|
|
||||||
proc `execTimeWithChildren`*(self: AggregateFutureMetrics): Duration =
|
|
||||||
self.execTime + self.childrenExecTime
|
|
||||||
|
|
||||||
proc push(self: var seq[uint], value: uint): void = self.add(value)
|
|
||||||
|
|
||||||
proc pop(self: var seq[uint]): uint =
|
|
||||||
let value = self[^1]
|
|
||||||
self.setLen(self.len - 1)
|
|
||||||
value
|
|
||||||
|
|
||||||
proc peek(self: var seq[uint]): Option[uint] =
|
|
||||||
if self.len == 0: none(uint) else: self[^1].some
|
|
||||||
|
|
||||||
proc `$`(location: SrcLoc): string =
|
|
||||||
$location.procedure & "[" & $location.file & ":" & $location.line & "]"
|
|
||||||
|
|
||||||
proc futureCreated(self: var ProfilerMetrics, event: Event): void =
|
|
||||||
assert not self.partials.hasKey(event.futureId), $event.location
|
|
||||||
|
|
||||||
self.partials[event.futureId] = RunningFuture(
|
|
||||||
created: event.timestamp,
|
|
||||||
state: Pending,
|
|
||||||
)
|
|
||||||
|
|
||||||
proc bindParent(self: var ProfilerMetrics, metrics: ptr RunningFuture): void =
|
|
||||||
let current = self.callStack.peek()
|
|
||||||
if current.isNone:
|
|
||||||
return
|
|
||||||
|
|
||||||
if metrics.parent.isSome:
|
|
||||||
assert metrics.parent.get == current.get
|
|
||||||
metrics.parent = current
|
|
||||||
|
|
||||||
proc futureRunning(self: var ProfilerMetrics, event: Event): void =
|
|
||||||
assert self.partials.hasKey(event.futureId), $event.location
|
|
||||||
|
|
||||||
self.partials.withValue(event.futureId, metrics):
|
|
||||||
assert metrics.state == Pending or metrics.state == Paused,
|
|
||||||
$event.location & " " & $metrics.state
|
|
||||||
|
|
||||||
self.bindParent(metrics)
|
|
||||||
self.callStack.push(event.futureId)
|
|
||||||
|
|
||||||
metrics.lastStarted = event.timestamp
|
|
||||||
metrics.state = Running
|
|
||||||
|
|
||||||
proc futurePaused(self: var ProfilerMetrics, event: Event): void =
|
|
||||||
assert event.futureId == self.callStack.pop(), $event.location
|
|
||||||
assert self.partials.hasKey(event.futureId), $event.location
|
|
||||||
|
|
||||||
self.partials.withValue(event.futureId, metrics):
|
|
||||||
assert metrics.state == Running, $event.location & " " & $metrics.state
|
|
||||||
|
|
||||||
let segmentExecTime = event.timestamp - metrics.lastStarted
|
|
||||||
|
|
||||||
if metrics.pauses == 0:
|
|
||||||
metrics.timeToFirstPause = segmentExecTime
|
|
||||||
metrics.partialExecTime += segmentExecTime
|
|
||||||
metrics.pauses += 1
|
|
||||||
metrics.state = Paused
|
|
||||||
|
|
||||||
proc futureCompleted(self: var ProfilerMetrics, event: Event): void =
|
|
||||||
let location = event.location
|
|
||||||
if not self.totals.hasKey(location):
|
|
||||||
self.totals[location] = AggregateFutureMetrics()
|
|
||||||
|
|
||||||
self.totals.withValue(location, aggMetrics):
|
|
||||||
if not self.partials.hasKey(event.futureId):
|
|
||||||
# Stillborn futures are those born in a finish state. We count those cause
|
|
||||||
# they may also be a byproduct of a bug.
|
|
||||||
aggMetrics.stillbornCount.inc()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.partials.withValue(event.futureId, metrics):
|
|
||||||
if metrics.state == Running:
|
|
||||||
self.futurePaused(event)
|
|
||||||
|
|
||||||
let execTime = metrics.partialExecTime - metrics.partialChildrenExecOverlap
|
|
||||||
|
|
||||||
aggMetrics.callCount.inc()
|
|
||||||
aggMetrics.execTime += execTime
|
|
||||||
aggMetrics.execTimeMax = max(aggMetrics.execTimeMax, execTime)
|
|
||||||
aggMetrics.childrenExecTime += metrics.partialChildrenExecTime
|
|
||||||
aggMetrics.wallClockTime += event.timestamp - metrics.created
|
|
||||||
|
|
||||||
if metrics.parent.isSome:
|
|
||||||
self.partials.withValue(metrics.parent.get, parentMetrics):
|
|
||||||
parentMetrics.partialChildrenExecTime += metrics.partialExecTime
|
|
||||||
parentMetrics.partialChildrenExecOverlap += metrics.timeToFirstPause
|
|
||||||
|
|
||||||
self.partials.del(event.futureId)
|
|
||||||
|
|
||||||
proc processEvent*(self: var ProfilerMetrics, event: Event): void =
|
|
||||||
case event.newState:
|
|
||||||
of Pending: self.futureCreated(event)
|
|
||||||
of Running: self.futureRunning(event)
|
|
||||||
of Paused: self.futurePaused(event)
|
|
||||||
# Completion, failure and cancellation are currently handled the same way.
|
|
||||||
of Completed: self.futureCompleted(event)
|
|
||||||
of Failed: self.futureCompleted(event)
|
|
||||||
of Cancelled: self.futureCompleted(event)
|
|
||||||
|
|
||||||
proc processAllEvents*(self: var ProfilerMetrics, events: seq[Event]): void =
|
|
||||||
for event in events:
|
|
||||||
self.processEvent(event)
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
import std/os
|
|
||||||
|
|
||||||
import unittest2
|
|
||||||
|
|
||||||
import ".."/".."/chronos
|
|
||||||
import ".."/".."/chronos/profiler
|
|
||||||
|
|
||||||
import ./utils
|
|
||||||
|
|
||||||
suite "Profiler hooks test suite":
|
|
||||||
|
|
||||||
setup:
|
|
||||||
installCallbacks()
|
|
||||||
|
|
||||||
teardown:
|
|
||||||
clearRecording()
|
|
||||||
revertCallbacks()
|
|
||||||
|
|
||||||
test "should emit correct events for a simple future":
|
|
||||||
|
|
||||||
proc simple() {.async.} =
|
|
||||||
os.sleep(1)
|
|
||||||
|
|
||||||
waitFor simple()
|
|
||||||
|
|
||||||
check recording == @[
|
|
||||||
SimpleEvent(state: Pending, procedure: "simple"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "simple"),
|
|
||||||
SimpleEvent(state: Completed, procedure: "simple"),
|
|
||||||
]
|
|
||||||
|
|
||||||
test "should emit correct events when a single child runs as part of the parent":
|
|
||||||
|
|
||||||
proc withChildren() {.async.} =
|
|
||||||
recordSegment("segment 1")
|
|
||||||
await sleepAsync(10.milliseconds)
|
|
||||||
recordSegment("segment 2")
|
|
||||||
|
|
||||||
waitFor withChildren()
|
|
||||||
|
|
||||||
check recording == @[
|
|
||||||
SimpleEvent(state: Pending, procedure: "withChildren"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "withChildren"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 1"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Pending, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: Paused, procedure: "withChildren"),
|
|
||||||
SimpleEvent(state: Completed, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "withChildren"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 2"),
|
|
||||||
SimpleEvent(state: Completed, procedure: "withChildren"),
|
|
||||||
]
|
|
||||||
|
|
||||||
test "should emit correct events when a nested child pauses execution":
|
|
||||||
proc child2() {.async.} =
|
|
||||||
recordSegment("segment 21")
|
|
||||||
await sleepAsync(10.milliseconds)
|
|
||||||
recordSegment("segment 22")
|
|
||||||
await sleepAsync(10.milliseconds)
|
|
||||||
recordSegment("segment 23")
|
|
||||||
|
|
||||||
proc child1() {.async.} =
|
|
||||||
recordSegment("segment 11")
|
|
||||||
await child2()
|
|
||||||
recordSegment("segment 12")
|
|
||||||
|
|
||||||
proc withChildren() {.async.} =
|
|
||||||
recordSegment("segment 1")
|
|
||||||
await child1()
|
|
||||||
recordSegment("segment 2")
|
|
||||||
|
|
||||||
waitFor withChildren()
|
|
||||||
|
|
||||||
check recording == @[
|
|
||||||
# First iteration of parent and each child
|
|
||||||
SimpleEvent(state: Pending, procedure: "withChildren"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "withChildren"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 1"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Pending, procedure: "child1"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "child1"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 11"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Pending, procedure: "child2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "child2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 21"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Pending, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Paused, procedure: "child2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Paused, procedure: "child1"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Paused, procedure: "withChildren"),
|
|
||||||
|
|
||||||
# Second iteration of child2
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Completed, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "child2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 22"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Pending, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Paused, procedure: "child2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Completed, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "child2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 23"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Completed, procedure: "child2"),
|
|
||||||
|
|
||||||
# Second iteration child1
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "child1"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 12"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Completed, procedure: "child1"),
|
|
||||||
|
|
||||||
# Second iteration of parent
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "withChildren"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Completed, procedure: "withChildren"),
|
|
||||||
]
|
|
||||||
|
|
||||||
test "should not say a future is completed before children in finally blocks are run":
|
|
||||||
proc withFinally(): Future[void] {.async.} =
|
|
||||||
try:
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
recordSegment("segment 1")
|
|
||||||
await sleepAsync(10.milliseconds)
|
|
||||||
# both segments must run
|
|
||||||
recordSegment("segment 2")
|
|
||||||
|
|
||||||
waitFor withFinally()
|
|
||||||
|
|
||||||
check recording == @[
|
|
||||||
SimpleEvent(state: Pending, procedure: "withFinally"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "withFinally"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 1"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Pending, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Paused, procedure: "withFinally"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Completed, procedure: "chronos.sleepAsync(Duration)"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "withFinally"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Running, procedure: "segment 2"),
|
|
||||||
SimpleEvent(state: ExtendedFutureState.Completed, procedure: "withFinally"),
|
|
||||||
]
|
|
||||||
@ -1,273 +0,0 @@
|
|||||||
import math
|
|
||||||
import sequtils
|
|
||||||
import unittest2
|
|
||||||
|
|
||||||
import ".."/".."/chronos
|
|
||||||
import ".."/".."/chronos/profiler/metrics
|
|
||||||
|
|
||||||
import ./utils
|
|
||||||
|
|
||||||
suite "Profiler metrics test suite":
|
|
||||||
|
|
||||||
setup:
|
|
||||||
installCallbacks()
|
|
||||||
|
|
||||||
teardown:
|
|
||||||
clearRecording()
|
|
||||||
revertCallbacks()
|
|
||||||
resetTime()
|
|
||||||
|
|
||||||
proc recordedMetrics(): ProfilerMetrics =
|
|
||||||
result.processAllEvents(rawRecording)
|
|
||||||
|
|
||||||
test "should compute correct times for a simple blocking future":
|
|
||||||
proc simple() {.async.} =
|
|
||||||
advanceTime(50.milliseconds)
|
|
||||||
|
|
||||||
waitFor simple()
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
let simpleMetrics = metrics.forProc("simple")
|
|
||||||
|
|
||||||
check simpleMetrics.execTime == 50.milliseconds
|
|
||||||
check simpleMetrics.wallClockTime == 50.milliseconds
|
|
||||||
|
|
||||||
test "should compute correct times for a simple non-blocking future":
|
|
||||||
proc simple {.async.} =
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
await advanceTimeAsync(50.milliseconds)
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
|
|
||||||
waitFor simple()
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
let simpleMetrics = metrics.forProc("simple")
|
|
||||||
|
|
||||||
check simpleMetrics.execTime == 20.milliseconds
|
|
||||||
check simpleMetrics.wallClockTime == 70.milliseconds
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
waitFor simple()
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
let simpleMetrics = metrics.forProc("simple")
|
|
||||||
|
|
||||||
check simpleMetrics.execTime == 30.milliseconds
|
|
||||||
check simpleMetrics.wallClockTime == 130.milliseconds
|
|
||||||
|
|
||||||
test "should compute correct times when there is a single blocking child":
|
|
||||||
proc child() {.async.} =
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
|
|
||||||
proc parent() {.async.} =
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
await child()
|
|
||||||
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 there is a single non-blocking child":
|
|
||||||
proc child() {.async.} =
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
await advanceTimeAsync(50.milliseconds)
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
|
|
||||||
proc parent() {.async.} =
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
await child()
|
|
||||||
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 == 20.milliseconds
|
|
||||||
check parentMetrics.wallClockTime == 90.milliseconds
|
|
||||||
|
|
||||||
check childMetrics.execTime == 20.milliseconds
|
|
||||||
check childMetrics.wallClockTime == 70.milliseconds
|
|
||||||
check childMetrics.childrenExecTime == ZeroDuration
|
|
||||||
|
|
||||||
test "should compute correct times when there are multiple blocking and non-blocking children":
|
|
||||||
proc blockingChild() {.async.} =
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
|
|
||||||
proc nonblockingChild() {.async.} =
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
await advanceTimeAsync(20.milliseconds)
|
|
||||||
advanceTime(10.milliseconds)
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
|
|
||||||
let parentMetrics = metrics.forProc("parent")
|
|
||||||
let blockingChildMetrics = metrics.forProc("blockingChild")
|
|
||||||
let nonblockingChildMetrics = metrics.forProc("nonblockingChild")
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
waitFor parent()
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
let parentMetrics = metrics.forProc("parent")
|
|
||||||
let childMetrics = metrics.forProc("child")
|
|
||||||
|
|
||||||
check parentMetrics.callCount == 1
|
|
||||||
check childMetrics.callCount == 10
|
|
||||||
|
|
||||||
test "should compute the maximum execution time for a proc, out of all calls":
|
|
||||||
var execTimes = @[10.milliseconds, 50.milliseconds, 10.milliseconds]
|
|
||||||
|
|
||||||
proc child(d: Duration) {.async.} =
|
|
||||||
advanceTime(d)
|
|
||||||
|
|
||||||
proc parent() {.async.} =
|
|
||||||
for d in execTimes:
|
|
||||||
await child(d)
|
|
||||||
|
|
||||||
waitFor parent()
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
let parentMetrics = metrics.forProc("parent")
|
|
||||||
let childMetrics = metrics.forProc("child")
|
|
||||||
|
|
||||||
check parentMetrics.execTimeMax == ZeroDuration
|
|
||||||
check childMetrics.execTimeMax == 50.milliseconds
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
waitFor withFinally()
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
var withFinallyMetrics = metrics.forProc("withFinally")
|
|
||||||
|
|
||||||
check withFinallyMetrics.execTime == 30.milliseconds
|
|
||||||
|
|
||||||
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"))
|
|
||||||
|
|
||||||
var metrics = recordedMetrics()
|
|
||||||
|
|
||||||
let stillborns = metrics.totals.pairs.toSeq.map(
|
|
||||||
proc (item: (SrcLoc, AggregateFutureMetrics)): uint =
|
|
||||||
item[1].stillbornCount).sum
|
|
||||||
|
|
||||||
check stillborns == 2
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
import ".."/".."/chronos
|
|
||||||
import ".."/".."/chronos/profiler
|
|
||||||
|
|
||||||
type
|
|
||||||
SimpleEvent* = object
|
|
||||||
procedure*: string
|
|
||||||
state*: ExtendedFutureState
|
|
||||||
|
|
||||||
# XXX this is sort of bad cause we get global state all over, but the fact we
|
|
||||||
# can't use closures on callbacks and that callbacks themselves are just
|
|
||||||
# global vars means we can't really do much better for now.
|
|
||||||
var recording*: seq[SimpleEvent]
|
|
||||||
var rawRecording*: seq[Event]
|
|
||||||
var fakeTime*: Moment = Moment.now()
|
|
||||||
|
|
||||||
# FIXME bad, this needs to be refactored into a callback interface for the profiler.
|
|
||||||
var oldHandleFutureEvent: proc(event: Event) {.nimcall, gcsafe, raises: [].} = nil
|
|
||||||
var installed: bool = false
|
|
||||||
|
|
||||||
proc recordEvent(event: Event) {.nimcall, gcsafe, raises: [].} =
|
|
||||||
{.cast(gcsafe).}:
|
|
||||||
recording.add(
|
|
||||||
SimpleEvent(procedure: $event.location.procedure, state: event.newState))
|
|
||||||
|
|
||||||
var timeShifted = event
|
|
||||||
timeShifted.timestamp = fakeTime
|
|
||||||
|
|
||||||
rawRecording.add(timeShifted)
|
|
||||||
|
|
||||||
proc recordSegment*(segment: string) =
|
|
||||||
{.cast(gcsafe).}:
|
|
||||||
recording.add(SimpleEvent(
|
|
||||||
procedure: segment,
|
|
||||||
state: ExtendedFutureState.Running
|
|
||||||
))
|
|
||||||
|
|
||||||
proc clearRecording*(): void =
|
|
||||||
recording = @[]
|
|
||||||
rawRecording = @[]
|
|
||||||
|
|
||||||
proc installCallbacks*() =
|
|
||||||
assert not installed, "Callbacks already installed"
|
|
||||||
|
|
||||||
enableEventCallbacks()
|
|
||||||
|
|
||||||
oldHandleFutureEvent = handleFutureEvent
|
|
||||||
handleFutureEvent = recordEvent
|
|
||||||
|
|
||||||
installed = true
|
|
||||||
|
|
||||||
proc revertCallbacks*() =
|
|
||||||
assert installed, "Callbacks already uninstalled"
|
|
||||||
|
|
||||||
handleFutureEvent = oldHandleFutureEvent
|
|
||||||
installed = false
|
|
||||||
|
|
||||||
proc forProc*(self: var ProfilerMetrics, procedure: string): AggregateFutureMetrics =
|
|
||||||
for (key, aggMetrics) in self.totals.mpairs:
|
|
||||||
if key.procedure == procedure:
|
|
||||||
return aggMetrics
|
|
||||||
|
|
||||||
proc resetTime*() =
|
|
||||||
fakeTime = Moment.now()
|
|
||||||
|
|
||||||
proc advanceTime*(duration: Duration) =
|
|
||||||
fakeTime += duration
|
|
||||||
|
|
||||||
proc advanceTimeAsync*(duration: Duration): Future[void] =
|
|
||||||
# Simulates a non-blocking operation that takes the provided duration to
|
|
||||||
# complete.
|
|
||||||
var retFuture = newFuture[void]("advanceTimeAsync")
|
|
||||||
var timer: TimerCallback
|
|
||||||
|
|
||||||
proc completion(data: pointer) {.gcsafe.} =
|
|
||||||
if not(retFuture.finished()):
|
|
||||||
advanceTime(duration)
|
|
||||||
retFuture.complete()
|
|
||||||
|
|
||||||
# The actual value for the timer is irrelevant, the important thing is that
|
|
||||||
# this causes the parent to pause before we advance time.
|
|
||||||
timer = setTimer(Moment.fromNow(10.milliseconds),
|
|
||||||
completion, cast[pointer](retFuture))
|
|
||||||
|
|
||||||
return retFuture
|
|
||||||
@ -11,7 +11,7 @@ when (chronosEventEngine in ["epoll", "kqueue"]) or defined(windows):
|
|||||||
import testmacro, testsync, testsoon, testtime, testfut, testsignal,
|
import testmacro, testsync, testsoon, testtime, testfut, testsignal,
|
||||||
testaddress, testdatagram, teststream, testserver, testbugs, testnet,
|
testaddress, testdatagram, teststream, testserver, testbugs, testnet,
|
||||||
testasyncstream, testhttpserver, testshttpserver, testhttpclient,
|
testasyncstream, testhttpserver, testshttpserver, testhttpclient,
|
||||||
testproc, testratelimit, testfutures, testthreadsync, testprofiler
|
testproc, testratelimit, testfutures, testthreadsync
|
||||||
|
|
||||||
# Must be imported last to check for Pending futures
|
# Must be imported last to check for Pending futures
|
||||||
import testutils
|
import testutils
|
||||||
@ -20,7 +20,7 @@ elif chronosEventEngine == "poll":
|
|||||||
import testmacro, testsync, testsoon, testtime, testfut, testaddress,
|
import testmacro, testsync, testsoon, testtime, testfut, testaddress,
|
||||||
testdatagram, teststream, testserver, testbugs, testnet,
|
testdatagram, teststream, testserver, testbugs, testnet,
|
||||||
testasyncstream, testhttpserver, testshttpserver, testhttpclient,
|
testasyncstream, testhttpserver, testshttpserver, testhttpclient,
|
||||||
testratelimit, testfutures, testthreadsync, testprofiler
|
testratelimit, testfutures, testthreadsync
|
||||||
|
|
||||||
# Must be imported last to check for Pending futures
|
# Must be imported last to check for Pending futures
|
||||||
import testutils
|
import testutils
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import ../chronos/config
|
|
||||||
|
|
||||||
when chronosProfiling:
|
|
||||||
import ../chronos/profiler
|
|
||||||
|
|
||||||
import ./profiler/testevents
|
|
||||||
import ./profiler/testmetrics
|
|
||||||
|
|
||||||
enableProfiling()
|
|
||||||
|
|
||||||
{.used.}
|
|
||||||
{.warning[UnusedImport]:off.}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user