Skip to content

Commit 2fe1a39

Browse files
authored
Live status table (#481)
* init * replaced progress bar on live table for console * cosmetic changes
1 parent b31eacd commit 2fe1a39

File tree

12 files changed

+216
-376
lines changed

12 files changed

+216
-376
lines changed

src/NBomber/Constants.fs

-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ let SchedulerTimerDriftMs = 10.0
5151
let SchedulerTickIntervalMs = 1_000.0
5252
[<Literal>]
5353
let ReportingTimerCompleteMs = 3_000
54-
[<Literal>]
55-
let WarmUpFinishPause = 1_000
5654

5755
/// Default status codes
5856

src/NBomber/Domain/ClientPool.fs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open NBomber.Extensions.Internal
1010
open NBomber.Contracts
1111

1212
type ClientPoolEvent =
13-
| StartedInit of poolName:string
13+
| StartedInit of poolName:string * clientCount:int
1414
| StartedDispose of poolName:string
1515
| ClientInitialized of poolName:string * clientNumber:int
1616
| ClientDisposed of poolName:string * clientNumber:int * error:exn option
@@ -46,7 +46,7 @@ type ClientPool(factory: ClientFactory<obj>) =
4646
| Error ex -> yield Error ex
4747
}
4848

49-
_eventStream.OnNext(StartedInit factory.FactoryName)
49+
_eventStream.OnNext(StartedInit(factory.FactoryName, factory.ClientCount))
5050

5151
let result = initClients(0, factory.ClientCount, context) |> Result.sequence
5252
match result with

src/NBomber/Domain/Concurrency/Scheduler/ScenarioScheduler.fs

+9-31
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ module internal NBomber.Domain.Concurrency.Scheduler.ScenarioScheduler
33
open System
44
open System.Threading.Tasks
55

6-
open FSharp.Control.Reactive
7-
86
open NBomber.Contracts
97
open NBomber.Contracts.Stats
108
open NBomber.Domain
@@ -21,16 +19,6 @@ type SchedulerCommand =
2119
| InjectOneTimeActors of scheduledCount:int
2220
| DoNothing
2321

24-
type ScenarioProgressInfo = {
25-
ConstantActorCount: int
26-
OneTimeActorCount: int
27-
CurrentSimulation: LoadSimulation
28-
}
29-
30-
type SchedulerEvent =
31-
| ScenarioStarted
32-
| ProgressUpdated of ScenarioProgressInfo
33-
3422
let calcScheduleByTime (copiesCount: int, prevSegmentCopiesCount: int, timeSegmentProgress: int) =
3523
let value = copiesCount - prevSegmentCopiesCount
3624
let result = (float value / 100.0 * float timeSegmentProgress) + float prevSegmentCopiesCount
@@ -90,7 +78,6 @@ type ScenarioScheduler(dep: ActorDep, scenarioClusterCount: int) =
9078
if _scenario.IsEnabled then new OneTimeActorScheduler(dep, OneTimeActorScheduler.exec)
9179
else new OneTimeActorScheduler(dep, emptyExec)
9280

93-
let _eventStream = Subject.broadcast
9481
let _tcs = TaskCompletionSource()
9582
let _randomGen = Random()
9683

@@ -104,7 +91,7 @@ type ScenarioScheduler(dep: ActorDep, scenarioClusterCount: int) =
10491
_cachedSimulationStats <- getCurrentSimulationStats()
10592
_scnDep.ScenarioStatsActor.Publish StartUseTempBuffer
10693

107-
let buildRealtimeStats (duration) =
94+
let buildRealtimeStats (duration: TimeSpan) =
10895
let simulationStats = getCurrentSimulationStats()
10996
let reply = TaskCompletionSource<ScenarioStats>()
11097
_scnDep.ScenarioStatsActor.Publish(BuildRealtimeStats(reply, simulationStats, duration))
@@ -116,9 +103,13 @@ type ScenarioScheduler(dep: ActorDep, scenarioClusterCount: int) =
116103
_scnDep.ScenarioStatsActor.Publish FlushTempBuffer
117104
reply.Task
118105

119-
let getFinalStats () =
106+
let getStats (isFinal: bool) =
120107
let simulationStats = getCurrentSimulationStats()
121-
let duration = Scenario.getExecutedDuration _scenario
108+
109+
let duration =
110+
if isFinal then Scenario.getExecutedDuration _scenario
111+
else _scnDep.ScenarioTimer.Elapsed
112+
122113
let reply = TaskCompletionSource<ScenarioStats>()
123114
_scnDep.ScenarioStatsActor.Publish(GetFinalStats(reply, simulationStats, duration))
124115
reply.Task
@@ -135,7 +126,6 @@ type ScenarioScheduler(dep: ActorDep, scenarioClusterCount: int) =
135126
_scnDep.ScenarioTimer.Stop()
136127
_constantScheduler.Stop()
137128
_oneTimeScheduler.Stop()
138-
_eventStream.OnCompleted()
139129

140130
let execScheduler () =
141131
if _isWorking && _scnDep.ScenarioStatsActor.FailCount > _scnDep.MaxFailCount then
@@ -169,40 +159,28 @@ type ScenarioScheduler(dep: ActorDep, scenarioClusterCount: int) =
169159

170160
| None -> stop()
171161

172-
let updateProgress () =
173-
let progressInfo = {
174-
ConstantActorCount = getConstantActorCount()
175-
OneTimeActorCount = getOneTimeActorCount()
176-
CurrentSimulation = _currentSimulation
177-
}
178-
_eventStream.OnNext(ProgressUpdated progressInfo)
179-
180162
let start () =
181163
_isWorking <- true
182164
_scnDep.ScenarioTimer.Restart()
183165
execScheduler()
184-
_eventStream.OnNext(ScenarioStarted)
185166
_tcs.Task :> Task
186167

187168
member _.Working = _isWorking
188-
member _.EventStream = _eventStream :> IObservable<_>
189169
member _.Scenario = _scenario
190170
member _.AllRealtimeStats = _scnDep.ScenarioStatsActor.AllRealtimeStats
191171

192172
member _.Start() = start()
193173
member _.Stop() = stop()
194174
member _.ExecScheduler() = execScheduler()
195-
member _.UpdateProgress() = updateProgress()
196175

197176
member _.AddStatsFromAgent(stats) = _scnDep.ScenarioStatsActor.Publish(AddFromAgent stats)
198177
member _.PrepareForRealtimeStats() = prepareForRealtimeStats()
199178
member _.CommitRealtimeStats(duration) = commitRealtimeStats duration
200179
member _.BuildRealtimeStats(duration) = buildRealtimeStats duration
201-
member _.GetFinalStats() = getFinalStats()
180+
member _.GetFinalStats() = getStats true
181+
member _.GetCurrentStats() = getStats false
202182

203183
interface IDisposable with
204184
member _.Dispose() =
205185
stop()
206-
_eventStream.Dispose()
207186
_log.Verbose $"{nameof ScenarioScheduler} disposed"
208-

src/NBomber/Domain/Scenario.fs

+6
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,9 @@ let defaultClusterCount = fun _ -> 1
288288

289289
let getScenariosForWarmUp (scenarios: Scenario list) =
290290
scenarios |> List.filter(fun x -> x.WarmUpDuration.IsSome)
291+
292+
let getMaxDuration (scenarios: Scenario list) =
293+
scenarios |> List.map(fun x -> x.PlanedDuration) |> List.max
294+
295+
let getMaxWarmUpDuration (scenarios: Scenario list) =
296+
scenarios |> List.choose(fun x -> x.WarmUpDuration) |> List.max

src/NBomber/DomainServices/NBomberContext.fs

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
open System
44
open System.IO
5-
open System.Globalization
65

76
open FsToolkit.ErrorHandling
87

src/NBomber/DomainServices/TestHost/TestHost.fs

+54-73
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ open System.Runtime.InteropServices
88
open Microsoft.FSharp.Collections
99

1010
open Serilog
11+
open Spectre.Console
1112
open FsToolkit.ErrorHandling
1213

1314
open NBomber
@@ -43,7 +44,6 @@ type internal TestHost(dep: IGlobalDependency,
4344
let mutable _targetScenarios = List.empty<Scenario>
4445
let mutable _sessionArgs = SessionArgs.empty
4546
let mutable _currentOperation = OperationType.None
46-
let mutable _globalCancelToken = new CancellationTokenSource()
4747
let _defaultNodeInfo = NodeInfo.init None
4848

4949
let getCurrentNodeInfo () =
@@ -92,85 +92,59 @@ type internal TestHost(dep: IGlobalDependency,
9292
let stopSchedulers (schedulers: ScenarioScheduler list) =
9393
schedulers |> List.iter(fun x -> x.Stop())
9494

95-
let initScenarios (sessionArgs: SessionArgs) (cancelToken: CancellationToken) = taskResult {
96-
97-
let targetScenarios = regScenarios |> TestHostScenario.getTargetScenarios sessionArgs
95+
let initScenarios (consoleStatus: StatusContext) (cancelToken: CancellationToken) (sessionArgs: SessionArgs) = taskResult {
9896

9997
let baseContext = NBomberContext.createBaseContext(sessionArgs.TestInfo, getCurrentNodeInfo, cancelToken, _log)
100-
let defaultScnContext = Scenario.ScenarioContext.create baseContext
10198

10299
do! dep.WorkerPlugins |> WorkerPlugins.init dep baseContext
103100
do! dep.ReportingSinks |> ReportingSinks.init dep baseContext
104101

105-
return! TestHostScenario.initScenarios(dep, baseContext, defaultScnContext, sessionArgs, targetScenarios)
102+
return! TestHostScenario.initScenarios(dep, consoleStatus, baseContext, sessionArgs, regScenarios)
106103
}
107104

108-
let startWarmUp (schedulers: ScenarioScheduler list) = backgroundTask {
109-
let isWarmUp = true
110-
TestHostConsole.displayBombingProgress(dep.ApplicationType, schedulers, isWarmUp)
111-
112-
let schedulersArray = schedulers |> List.toArray
113-
use schedulerTimer = new System.Timers.Timer(Constants.SchedulerTickIntervalMs)
114-
schedulerTimer.Elapsed.Add(fun _ ->
115-
schedulersArray
116-
|> Array.Parallel.iter(fun x ->
117-
x.ExecScheduler()
118-
x.UpdateProgress()
119-
)
120-
)
121-
122-
schedulerTimer.Start()
123-
do! schedulers |> List.map(fun x -> x.Start()) |> Task.WhenAll
124-
schedulerTimer.Stop()
125-
126-
// wait on warmup progress bar to finish rendering
127-
do! Task.Delay Constants.WarmUpFinishPause
128-
}
105+
let startScenarios (schedulers: ScenarioScheduler list) (reportingManager: IReportingManager option) = backgroundTask {
129106

130-
let startBombing (schedulers: ScenarioScheduler list)
131-
(reportingManager: IReportingManager) = backgroundTask {
107+
let isWarmUp = reportingManager.IsNone
132108

133-
let isWarmUp = false
134-
TestHostConsole.displayBombingProgress(dep.ApplicationType, schedulers, isWarmUp)
109+
if not isWarmUp then
110+
dep.WorkerPlugins |> WorkerPlugins.start _log
111+
dep.ReportingSinks |> ReportingSinks.start _log
135112

136-
dep.WorkerPlugins |> WorkerPlugins.start _log
137-
dep.ReportingSinks |> ReportingSinks.start _log
113+
use cancelToken = new CancellationTokenSource()
114+
schedulers |> TestHostConsole.LiveStatusTable.display cancelToken.Token isWarmUp
138115

139-
reportingManager.Start()
116+
if reportingManager.IsSome then reportingManager.Value.Start()
140117

141118
// waiting on all scenarios to finish
142119
let schedulersArray = schedulers |> List.toArray
143120
use schedulerTimer = new System.Timers.Timer(Constants.SchedulerTickIntervalMs)
144-
schedulerTimer.Elapsed.Add(fun _ ->
145-
schedulersArray
146-
|> Array.Parallel.iter(fun x ->
147-
x.ExecScheduler()
148-
x.UpdateProgress()
149-
)
150-
)
121+
schedulerTimer.Elapsed.Add(fun _ -> schedulersArray |> Array.Parallel.iter(fun x -> x.ExecScheduler()))
151122

152123
schedulerTimer.Start()
153124
do! schedulers |> List.map(fun x -> x.Start()) |> Task.WhenAll
125+
cancelToken.Cancel()
154126
schedulerTimer.Stop()
155127

156-
// wait on final metrics and reporting tick
157-
do! Task.Delay Constants.ReportingTimerCompleteMs
128+
if not isWarmUp then
129+
// wait on final metrics and reporting tick
130+
do! Task.Delay Constants.ReportingTimerCompleteMs
158131

159-
// waiting (in case of cluster) on all raw stats
160-
do! reportingManager.Stop()
132+
// waiting (in case of cluster) on all raw stats
133+
do! reportingManager.Value.Stop()
161134

162-
do! dep.WorkerPlugins |> WorkerPlugins.stop _log
163-
do! dep.ReportingSinks |> ReportingSinks.stop _log
135+
do! dep.WorkerPlugins |> WorkerPlugins.stop _log
136+
do! dep.ReportingSinks |> ReportingSinks.stop _log
164137
}
165138

166139
let cleanScenarios (sessionArgs: SessionArgs,
140+
consoleStatus: StatusContext,
167141
cancelToken: CancellationToken,
168142
scenarios: Scenario list) =
169143

170144
let baseContext = NBomberContext.createBaseContext(sessionArgs.TestInfo, getCurrentNodeInfo, cancelToken, _log)
171145
let defaultScnContext = Scenario.ScenarioContext.create baseContext
172146
let enabledScenarios = scenarios |> List.filter(fun x -> x.IsEnabled)
173-
TestHostScenario.cleanScenarios dep baseContext defaultScnContext enabledScenarios
147+
TestHostScenario.cleanScenarios dep consoleStatus baseContext defaultScnContext enabledScenarios
174148

175149
member _.SessionArgs = _sessionArgs
176150
member _.CurrentOperation = _currentOperation
@@ -179,28 +153,30 @@ type internal TestHost(dep: IGlobalDependency,
179153
member _.TargetScenarios = _targetScenarios
180154
member _.CurrentSchedulers = _currentSchedulers
181155

182-
member _.StartInit(sessionArgs: SessionArgs) = backgroundTask {
156+
member _.StartInit(sessionArgs: SessionArgs) =
183157
_stopped <- false
184158
_currentOperation <- OperationType.Init
185159

186160
TestHostConsole.printContextInfo dep
187161
_log.Information "Starting init..."
188-
_globalCancelToken.Dispose()
189-
_globalCancelToken <- new CancellationTokenSource()
190-
191-
match! initScenarios sessionArgs _globalCancelToken.Token with
192-
| Ok initializedScenarios ->
193-
_log.Information "Init finished"
194-
_targetScenarios <- initializedScenarios
195-
_sessionArgs <- sessionArgs
196-
_currentOperation <- OperationType.None
197-
return Ok _targetScenarios
198-
199-
| Error appError ->
200-
_log.Error "Init failed"
201-
_currentOperation <- OperationType.Stop
202-
return AppError.createResult appError
203-
}
162+
163+
TestHostConsole.displayStatus "Initializing scenarios..." (fun consoleStatus -> backgroundTask {
164+
use cancelToken = new CancellationTokenSource()
165+
match! initScenarios consoleStatus cancelToken.Token sessionArgs with
166+
| Ok initializedScenarios ->
167+
_log.Information "Init finished"
168+
cancelToken.Cancel()
169+
_targetScenarios <- initializedScenarios
170+
_sessionArgs <- sessionArgs
171+
_currentOperation <- OperationType.None
172+
return Ok _targetScenarios
173+
174+
| Error appError ->
175+
_log.Error "Init failed"
176+
cancelToken.Cancel()
177+
_currentOperation <- OperationType.Stop
178+
return AppError.createResult appError
179+
})
204180

205181
member _.StartWarmUp(scenarios: Scenario list, ?getScenarioClusterCount: ScenarioName -> int) = backgroundTask {
206182
_stopped <- false
@@ -220,7 +196,7 @@ type internal TestHost(dep: IGlobalDependency,
220196

221197
_currentSchedulers <- warmUpSchedulers
222198

223-
do! startWarmUp warmUpSchedulers
199+
do! startScenarios warmUpSchedulers None
224200
stopSchedulers warmUpSchedulers
225201

226202
_currentOperation <- OperationType.None
@@ -232,13 +208,13 @@ type internal TestHost(dep: IGlobalDependency,
232208
_currentSchedulers <- schedulers
233209

234210
_log.Information "Starting bombing..."
235-
do! startBombing schedulers reportingManager
211+
do! startScenarios schedulers (Some reportingManager)
236212

237213
do! this.StopScenarios()
238214
_currentOperation <- OperationType.Complete
239215
}
240216

241-
member _.StopScenarios([<Optional;DefaultParameterValue("":string)>]reason: string) = backgroundTask {
217+
member _.StopScenarios([<Optional;DefaultParameterValue("":string)>]reason: string) =
242218
if _currentOperation <> OperationType.Stop && not _stopped then
243219
_currentOperation <- OperationType.Stop
244220

@@ -247,12 +223,17 @@ type internal TestHost(dep: IGlobalDependency,
247223
else
248224
_log.Information "Stopping scenarios..."
249225

250-
stopSchedulers _currentSchedulers
251-
do! cleanScenarios(_sessionArgs, _globalCancelToken.Token, _targetScenarios)
226+
TestHostConsole.displayStatus "Cleaning scenarios..." (fun consoleStatus -> backgroundTask {
227+
use cancelToken = new CancellationTokenSource()
228+
stopSchedulers _currentSchedulers
252229

253-
_stopped <- true
254-
_currentOperation <- OperationType.None
255-
}
230+
do! cleanScenarios(_sessionArgs, consoleStatus, cancelToken.Token, _targetScenarios)
231+
232+
_stopped <- true
233+
_currentOperation <- OperationType.None
234+
})
235+
else
236+
Task.FromResult()
256237

257238
member _.CreateScenarioSchedulers(scenarios: Scenario list,
258239
operation: ScenarioOperation,

0 commit comments

Comments
 (0)