···
109
+
mutable latencies : (float * float) list; (* (timestamp, latency) pairs *)
let create_latency_stats () = {
···
118
-
let update_latency stats latency =
120
+
let update_latency stats latency timestamp =
stats.count <- stats.count + 1;
stats.total <- stats.total +. latency;
stats.min <- min stats.min latency;
122
-
stats.max <- max stats.max latency
124
+
stats.max <- max stats.max latency;
125
+
stats.latencies <- (timestamp, latency) :: stats.latencies
(** Generate a random message of given size *)
let generate_message size =
···
(** Client test: connect via pool, send message, verify echo *)
168
-
let run_client_test ~clock ~collector pool endpoint endpoint_id message client_id latency_stats errors =
171
+
let run_client_test ~clock ~test_start_time pool endpoint message latency_stats errors =
let msg_len = String.length message in
let start_time = Eio.Time.now clock in
172
-
(* Get or create connection ID for tracking *)
173
-
let conn_id = Trace.next_connection_id collector in
Conpool.with_connection pool endpoint (fun flow ->
177
-
(* Record acquire event *)
178
-
Trace.record collector ~clock ~event_type:Trace.Connection_acquired
179
-
~endpoint_id ~connection_id:conn_id ~client_id ();
Eio.Flow.copy_string message flow;
Eio.Flow.copy_string "\n" flow;
184
-
Trace.record collector ~clock ~event_type:Trace.Message_sent
185
-
~endpoint_id ~connection_id:conn_id ~client_id ();
let response = Eio.Buf_read.of_flow flow ~max_size:(msg_len + 1) in
let echoed = Eio.Buf_read.line response in
190
-
Trace.record collector ~clock ~event_type:Trace.Message_received
191
-
~endpoint_id ~connection_id:conn_id ~client_id ();
let end_time = Eio.Time.now clock in
let latency = (end_time -. start_time) *. 1000.0 in (* Convert to ms *)
187
+
let relative_time = (end_time -. test_start_time) *. 1000.0 in (* ms since test start *)
if String.equal echoed message then begin
197
-
update_latency latency_stats latency;
198
-
Trace.record collector ~clock ~event_type:Trace.Message_verified
199
-
~endpoint_id ~connection_id:conn_id ~client_id ()
190
+
update_latency latency_stats latency relative_time
202
-
Trace.record collector ~clock ~event_type:(Trace.Connection_error "echo_mismatch")
203
-
~endpoint_id ~connection_id:conn_id ~client_id ()
206
-
(* Record release event *)
207
-
Trace.record collector ~clock ~event_type:Trace.Connection_released
208
-
~endpoint_id ~connection_id:conn_id ~client_id ()
212
-
Trace.record collector ~clock ~event_type:(Trace.Connection_error (Printexc.to_string ex))
213
-
~endpoint_id ~connection_id:conn_id ~client_id ()
(** Run a single client that sends multiple messages *)
216
-
let run_client ~clock ~collector pool endpoints config latency_stats errors client_id =
217
-
for _ = 1 to config.messages_per_client do
199
+
let run_client ~clock ~test_start_time pool endpoints (cfg : config) latency_stats errors client_id =
200
+
for _ = 1 to cfg.messages_per_client do
let endpoint_idx = Random.int (Array.length endpoints) in
let endpoint = endpoints.(endpoint_idx) in
220
-
let message = Printf.sprintf "c%d-%s" client_id (generate_message config.message_size) in
221
-
run_client_test ~clock ~collector pool endpoint endpoint_idx message client_id latency_stats errors
203
+
let message = Printf.sprintf "c%d-%s" client_id (generate_message cfg.message_size) in
204
+
run_client_test ~clock ~test_start_time pool endpoint message latency_stats errors
224
-
(** Main stress test runner - returns a test trace *)
225
-
let run_stress_test ~env config : Trace.test_trace =
207
+
(** Pool statistics aggregated from all endpoints *)
208
+
type pool_stats = {
209
+
total_created : int;
210
+
total_reused : int;
211
+
total_closed : int;
217
+
(** Test result type *)
218
+
type test_result = {
219
+
test_name : string;
222
+
messages_per_client : int;
225
+
total_messages : int;
226
+
total_errors : int;
227
+
throughput : float;
228
+
avg_latency : float;
229
+
min_latency : float;
230
+
max_latency : float;
231
+
latency_data : (float * float) list; (* (timestamp, latency) pairs for visualization *)
232
+
pool_stats : pool_stats;
235
+
(** Main stress test runner - returns a test result *)
236
+
let run_stress_test ~env (cfg : config) : test_result =
let net = Eio.Stdenv.net env in
let clock = Eio.Stdenv.clock env in
229
-
let collector = Trace.create_collector () in
let latency_stats = create_latency_stats () in
234
-
let trace_config : Trace.test_config = {
235
-
num_servers = config.num_servers;
236
-
num_clients = config.num_clients;
237
-
messages_per_client = config.messages_per_client;
238
-
max_parallel_clients = config.max_parallel_clients;
239
-
message_size = config.message_size;
240
-
pool_size = config.pool_size;
243
-
let start_unix_time = Unix.gettimeofday () in
245
-
let result = ref None in
244
+
let result : test_result option ref = ref None in
Eio.Switch.run @@ fun sw ->
251
-
ports := Array.init config.num_servers (fun _ ->
250
+
ports := Array.init cfg.num_servers (fun _ ->
start_echo_server ~sw net
···
Conpool.Endpoint.make ~host:"127.0.0.1" ~port
261
-
(* Create connection pool with hooks to track events *)
260
+
(* Create connection pool *)
let pool_config = Conpool.Config.make
263
-
~max_connections_per_endpoint:config.pool_size
262
+
~max_connections_per_endpoint:cfg.pool_size
~max_connection_lifetime:120.0
268
-
~on_connection_created:(fun ep ->
269
-
let port = Conpool.Endpoint.port ep in
270
-
let endpoint_id = Array.to_list !ports
271
-
|> List.mapi (fun i p -> (i, p))
272
-
|> List.find (fun (_, p) -> p = port)
274
-
let conn_id = Trace.next_connection_id collector in
275
-
Trace.record collector ~clock ~event_type:Trace.Connection_created
276
-
~endpoint_id ~connection_id:conn_id ()
278
-
~on_connection_reused:(fun ep ->
279
-
let port = Conpool.Endpoint.port ep in
280
-
let endpoint_id = Array.to_list !ports
281
-
|> List.mapi (fun i p -> (i, p))
282
-
|> List.find (fun (_, p) -> p = port)
284
-
let conn_id = Trace.next_connection_id collector in
285
-
Trace.record collector ~clock ~event_type:Trace.Connection_reused
286
-
~endpoint_id ~connection_id:conn_id ()
288
-
~on_connection_closed:(fun ep ->
289
-
let port = Conpool.Endpoint.port ep in
290
-
let endpoint_id = Array.to_list !ports
291
-
|> List.mapi (fun i p -> (i, p))
292
-
|> List.find (fun (_, p) -> p = port)
294
-
let conn_id = Trace.next_connection_id collector in
295
-
Trace.record collector ~clock ~event_type:Trace.Connection_closed
296
-
~endpoint_id ~connection_id:conn_id ()
···
let start_time = Eio.Time.now clock in
305
-
Trace.set_start_time collector start_time;
(* Run clients in parallel *)
308
-
let total_clients = config.num_servers * config.num_clients in
276
+
let total_clients = cfg.num_servers * cfg.num_clients in
let client_ids = List.init total_clients (fun i -> i) in
310
-
Eio.Fiber.List.iter ~max_fibers:config.max_parallel_clients
278
+
Eio.Fiber.List.iter ~max_fibers:cfg.max_parallel_clients
312
-
run_client ~clock ~collector pool endpoints config latency_stats errors client_id)
280
+
run_client ~clock ~test_start_time:start_time pool endpoints cfg latency_stats errors client_id)
let end_time = Eio.Time.now clock in
let duration = end_time -. start_time in
286
+
(* Collect pool statistics from all endpoints *)
287
+
let all_stats = Conpool.all_stats pool in
288
+
let pool_stats = List.fold_left (fun acc (_, stats) ->
290
+
total_created = acc.total_created + Conpool.Stats.total_created stats;
291
+
total_reused = acc.total_reused + Conpool.Stats.total_reused stats;
292
+
total_closed = acc.total_closed + Conpool.Stats.total_closed stats;
293
+
active = acc.active + Conpool.Stats.active stats;
294
+
idle = acc.idle + Conpool.Stats.idle stats;
295
+
pool_errors = acc.pool_errors + Conpool.Stats.errors stats;
297
+
) { total_created = 0; total_reused = 0; total_closed = 0; active = 0; idle = 0; pool_errors = 0 } all_stats in
319
-
let events = Trace.get_events collector in
320
-
let endpoint_summaries = Trace.compute_endpoint_summaries events config.num_servers !ports in
323
-
Trace.test_name = config.name;
324
-
config = trace_config;
325
-
start_time = start_unix_time;
300
+
let r : test_result = {
301
+
test_name = cfg.name;
302
+
num_servers = cfg.num_servers;
303
+
num_clients = cfg.num_clients;
304
+
messages_per_client = cfg.messages_per_client;
305
+
pool_size = cfg.pool_size;
328
-
endpoint_summaries;
total_messages = latency_stats.count;
throughput = float_of_int latency_stats.count /. duration;
···
min_latency = if latency_stats.count > 0 then latency_stats.min else 0.0;
max_latency = latency_stats.max;
315
+
latency_data = List.rev latency_stats.latencies;
···
| None -> failwith "Test failed to produce result"
347
-
(** Run all preset tests and return traces *)
328
+
(** Convert result to JSON string *)
329
+
let result_to_json result =
334
+
"messages_per_client": %d,
336
+
"total_messages": %d,
337
+
"total_errors": %d,
338
+
"throughput": %.2f,
339
+
"avg_latency": %.2f,
340
+
"min_latency": %.2f,
341
+
"max_latency": %.2f
346
+
result.messages_per_client
348
+
result.total_messages
349
+
result.total_errors
355
+
(** Escape strings for JavaScript *)
357
+
let buf = Buffer.create (String.length s) in
358
+
String.iter (fun c ->
360
+
| '\\' -> Buffer.add_string buf "\\\\"
361
+
| '"' -> Buffer.add_string buf "\\\""
362
+
| '\n' -> Buffer.add_string buf "\\n"
363
+
| '\r' -> Buffer.add_string buf "\\r"
364
+
| '\t' -> Buffer.add_string buf "\\t"
365
+
| _ -> Buffer.add_char buf c
367
+
Buffer.contents buf
369
+
(** Calculate histogram buckets for latency data *)
370
+
let calculate_histogram latencies num_buckets =
371
+
if List.length latencies = 0 then ([], []) else
372
+
let latency_values = List.map snd latencies in
373
+
let min_lat = List.fold_left min Float.infinity latency_values in
374
+
let max_lat = List.fold_left max 0.0 latency_values in
375
+
let bucket_width = (max_lat -. min_lat) /. float_of_int num_buckets in
377
+
let buckets = Array.make num_buckets 0 in
378
+
List.iter (fun lat ->
379
+
let bucket_idx = min (num_buckets - 1) (int_of_float ((lat -. min_lat) /. bucket_width)) in
380
+
buckets.(bucket_idx) <- buckets.(bucket_idx) + 1
383
+
let bucket_labels = List.init num_buckets (fun i ->
384
+
let start = min_lat +. (float_of_int i *. bucket_width) in
385
+
Printf.sprintf "%.2f" start
387
+
let bucket_counts = Array.to_list buckets in
388
+
(bucket_labels, bucket_counts)
390
+
(** Generate HTML report from test results *)
391
+
let generate_html_report results =
392
+
let timestamp = Unix.time () |> Unix.gmtime in
393
+
let date_str = Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d UTC"
394
+
(timestamp.Unix.tm_year + 1900)
395
+
(timestamp.Unix.tm_mon + 1)
396
+
timestamp.Unix.tm_mday
397
+
timestamp.Unix.tm_hour
398
+
timestamp.Unix.tm_min
399
+
timestamp.Unix.tm_sec
402
+
(* Calculate summary statistics *)
403
+
let total_messages = List.fold_left (fun acc r -> acc + r.total_messages) 0 results in
404
+
let total_errors = List.fold_left (fun acc r -> acc + r.total_errors) 0 results in
405
+
let total_duration = List.fold_left (fun acc r -> acc +. r.duration) 0.0 results in
407
+
(* Generate JavaScript arrays for comparison charts *)
408
+
let test_names = String.concat ", " (List.map (fun r -> Printf.sprintf "\"%s\"" (js_escape r.test_name)) results) in
409
+
let throughputs = String.concat ", " (List.map (fun r -> Printf.sprintf "%.2f" r.throughput) results) in
410
+
let avg_latencies = String.concat ", " (List.map (fun r -> Printf.sprintf "%.2f" r.avg_latency) results) in
411
+
let error_rates = String.concat ", " (List.map (fun r ->
412
+
if r.total_messages > 0 then
413
+
Printf.sprintf "%.2f" (float_of_int r.total_errors /. float_of_int r.total_messages *. 100.0)
417
+
(* Generate per-test detailed sections with histograms and timelines *)
418
+
let test_details = String.concat "\n" (List.mapi (fun idx r ->
419
+
let (hist_labels, hist_counts) = calculate_histogram r.latency_data 20 in
420
+
let hist_labels_str = String.concat ", " (List.map (fun s -> Printf.sprintf "\"%s\"" s) hist_labels) in
421
+
let hist_counts_str = String.concat ", " (List.map string_of_int hist_counts) in
423
+
(* Sample data points for timeline (take every Nth point if too many) *)
424
+
let max_points = 500 in
425
+
let sample_rate = max 1 ((List.length r.latency_data) / max_points) in
426
+
let sampled_data = List.filteri (fun i _ -> i mod sample_rate = 0) r.latency_data in
427
+
let timeline_data = String.concat ", " (List.map (fun (t, l) ->
428
+
Printf.sprintf "{x: %.2f, y: %.3f}" t l
432
+
<div class="test-detail">
434
+
<div class="compact-grid">
435
+
<div class="compact-metric"><span class="label">Servers:</span> <span class="value">%d</span></div>
436
+
<div class="compact-metric"><span class="label">Clients:</span> <span class="value">%d</span></div>
437
+
<div class="compact-metric"><span class="label">Msgs/Client:</span> <span class="value">%d</span></div>
438
+
<div class="compact-metric"><span class="label">Pool Size:</span> <span class="value">%d</span></div>
439
+
<div class="compact-metric"><span class="label">Total Msgs:</span> <span class="value">%d</span></div>
440
+
<div class="compact-metric"><span class="label">Duration:</span> <span class="value">%.2fs</span></div>
441
+
<div class="compact-metric highlight"><span class="label">Throughput:</span> <span class="value">%.0f/s</span></div>
442
+
<div class="compact-metric highlight"><span class="label">Avg Lat:</span> <span class="value">%.2fms</span></div>
443
+
<div class="compact-metric"><span class="label">Min Lat:</span> <span class="value">%.2fms</span></div>
444
+
<div class="compact-metric"><span class="label">Max Lat:</span> <span class="value">%.2fms</span></div>
445
+
<div class="compact-metric %s"><span class="label">Errors:</span> <span class="value">%d</span></div>
447
+
<div class="compact-grid" style="margin-top: 0.5rem;">
448
+
<div class="compact-metric"><span class="label">Conns Created:</span> <span class="value">%d</span></div>
449
+
<div class="compact-metric"><span class="label">Conns Reused:</span> <span class="value">%d</span></div>
450
+
<div class="compact-metric"><span class="label">Conns Closed:</span> <span class="value">%d</span></div>
451
+
<div class="compact-metric"><span class="label">Active:</span> <span class="value">%d</span></div>
452
+
<div class="compact-metric"><span class="label">Idle:</span> <span class="value">%d</span></div>
453
+
<div class="compact-metric"><span class="label">Reuse Rate:</span> <span class="value">%.1f%%%%</span></div>
455
+
<div class="chart-row">
456
+
<div class="chart-half">
457
+
<h4>Latency Distribution</h4>
458
+
<canvas id="hist_%d"></canvas>
460
+
<div class="chart-half">
461
+
<h4>Latency Timeline</h4>
462
+
<canvas id="timeline_%d"></canvas>
467
+
new Chart(document.getElementById('hist_%d'), {
474
+
backgroundColor: 'rgba(102, 126, 234, 0.6)',
475
+
borderColor: 'rgba(102, 126, 234, 1)',
481
+
maintainAspectRatio: false,
482
+
plugins: { legend: { display: false } },
484
+
x: { title: { display: true, text: 'Latency (ms)' } },
485
+
y: { beginAtZero: true, title: { display: true, text: 'Count' } }
490
+
new Chart(document.getElementById('timeline_%d'), {
496
+
backgroundColor: 'rgba(118, 75, 162, 0.5)',
497
+
borderColor: 'rgba(118, 75, 162, 0.8)',
503
+
maintainAspectRatio: false,
504
+
plugins: { legend: { display: false } },
506
+
x: { title: { display: true, text: 'Time (ms)' } },
507
+
y: { beginAtZero: true, title: { display: true, text: 'Latency (ms)' } }
512
+
(js_escape r.test_name)
515
+
r.messages_per_client
523
+
(if r.total_errors > 0 then "error" else "")
525
+
r.pool_stats.total_created
526
+
r.pool_stats.total_reused
527
+
r.pool_stats.total_closed
528
+
r.pool_stats.active
530
+
(if r.pool_stats.total_created > 0 then
531
+
(float_of_int r.pool_stats.total_reused /. float_of_int r.pool_stats.total_created *. 100.0)
540
+
Printf.sprintf {|<!DOCTYPE html>
543
+
<meta charset="UTF-8">
544
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
545
+
<title>Connection Pool Stress Test Results</title>
546
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
548
+
* { margin: 0; padding: 0; box-sizing: border-box; }
550
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
551
+
background: #f5f5f5;
556
+
.container { max-width: 1600px; margin: 0 auto; }
559
+
text-align: center;
560
+
margin-bottom: 0.3rem;
564
+
text-align: center;
565
+
margin-bottom: 1rem;
571
+
border-radius: 6px;
573
+
margin-bottom: 1rem;
574
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
578
+
margin-bottom: 0.8rem;
583
+
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
587
+
text-align: center;
589
+
background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%);
590
+
border-radius: 4px;
593
+
.summary-metric-label {
594
+
font-size: 0.75rem;
596
+
margin-bottom: 0.3rem;
598
+
.summary-metric-value {
604
+
border-radius: 6px;
606
+
margin-bottom: 1rem;
607
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
611
+
margin-bottom: 0.8rem;
614
+
.comparison-charts {
616
+
grid-template-columns: repeat(3, 1fr);
619
+
.comparison-chart {
621
+
position: relative;
625
+
border-radius: 6px;
627
+
margin-bottom: 1rem;
628
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
629
+
border-left: 3px solid #667eea;
633
+
margin-bottom: 0.6rem;
638
+
margin-bottom: 0.4rem;
644
+
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
646
+
margin-bottom: 0.8rem;
647
+
font-size: 0.85rem;
650
+
background: #f8f9fa;
651
+
padding: 0.4rem 0.6rem;
652
+
border-radius: 3px;
654
+
justify-content: space-between;
655
+
align-items: center;
657
+
.compact-metric .label {
661
+
.compact-metric .value {
665
+
.compact-metric.highlight {
666
+
background: linear-gradient(135deg, #667eea 0%%, #764ba2 100%%);
669
+
.compact-metric.highlight .label,
670
+
.compact-metric.highlight .value {
673
+
.compact-metric.error {
675
+
border: 1px solid #fcc;
679
+
grid-template-columns: 1fr 1fr;
683
+
position: relative;
686
+
@media (max-width: 1200px) {
687
+
.comparison-charts { grid-template-columns: 1fr; }
688
+
.chart-row { grid-template-columns: 1fr; }
690
+
@media (max-width: 768px) {
691
+
.compact-grid { grid-template-columns: repeat(2, 1fr); }
696
+
<div class="container">
697
+
<h1>Connection Pool Stress Test Results</h1>
698
+
<div class="subtitle">%s</div>
700
+
<div class="summary">
702
+
<div class="summary-grid">
703
+
<div class="summary-metric">
704
+
<div class="summary-metric-label">Tests</div>
705
+
<div class="summary-metric-value">%d</div>
707
+
<div class="summary-metric">
708
+
<div class="summary-metric-label">Messages</div>
709
+
<div class="summary-metric-value">%s</div>
711
+
<div class="summary-metric">
712
+
<div class="summary-metric-label">Errors</div>
713
+
<div class="summary-metric-value">%d</div>
715
+
<div class="summary-metric">
716
+
<div class="summary-metric-label">Duration</div>
717
+
<div class="summary-metric-value">%.1fs</div>
722
+
<div class="comparison">
723
+
<h2>Comparison</h2>
724
+
<div class="comparison-charts">
725
+
<div class="comparison-chart"><canvas id="cmpThroughput"></canvas></div>
726
+
<div class="comparison-chart"><canvas id="cmpLatency"></canvas></div>
727
+
<div class="comparison-chart"><canvas id="cmpErrors"></canvas></div>
735
+
const testNames = [%s];
736
+
const throughputs = [%s];
737
+
const avgLatencies = [%s];
738
+
const errorRates = [%s];
741
+
primary: 'rgba(102, 126, 234, 0.8)',
742
+
secondary: 'rgba(118, 75, 162, 0.8)',
743
+
danger: 'rgba(220, 53, 69, 0.8)',
746
+
new Chart(document.getElementById('cmpThroughput'), {
753
+
backgroundColor: cc.primary,
754
+
borderColor: cc.primary,
760
+
maintainAspectRatio: false,
762
+
legend: { display: false },
763
+
title: { display: true, text: 'Throughput (msg/s)' }
765
+
scales: { y: { beginAtZero: true } }
769
+
new Chart(document.getElementById('cmpLatency'), {
775
+
data: avgLatencies,
776
+
backgroundColor: cc.secondary,
777
+
borderColor: cc.secondary,
783
+
maintainAspectRatio: false,
785
+
legend: { display: false },
786
+
title: { display: true, text: 'Avg Latency (ms)' }
788
+
scales: { y: { beginAtZero: true } }
792
+
new Chart(document.getElementById('cmpErrors'), {
799
+
backgroundColor: cc.danger,
800
+
borderColor: cc.danger,
806
+
maintainAspectRatio: false,
808
+
legend: { display: false },
809
+
title: { display: true, text: 'Error Rate (%%)' }
811
+
scales: { y: { beginAtZero: true } }
818
+
(List.length results)
819
+
(if total_messages >= 1000 then
820
+
Printf.sprintf "%d,%03d" (total_messages / 1000) (total_messages mod 1000)
822
+
string_of_int total_messages)
831
+
(** Run all preset tests and return results *)
let run_all_presets ~env =
Printf.eprintf "Running test: %s\n%!" config.name;
···
let config = if config.name = "default" then custom_config else config in
Eio_main.run @@ fun env ->
424
-
let trace = run_stress_test ~env config in
425
-
let json = Printf.sprintf "[%s]" (Trace.trace_to_json trace) in
908
+
let result = run_stress_test ~env config in
909
+
let results = [result] in
912
+
let json = Printf.sprintf "[%s]" (result_to_json result) in
let oc = open_out output_file in
Printf.printf "Results written to %s\n" output_file;
920
+
if Filename.check_suffix output_file ".json" then
921
+
Filename.chop_suffix output_file ".json" ^ ".html"
923
+
output_file ^ ".html"
925
+
let html = generate_html_report results in
926
+
let oc_html = open_out html_file in
927
+
output_string oc_html html;
929
+
Printf.printf "HTML report written to %s\n" html_file;
Printf.printf "Test: %s - %d messages, %.2f msg/s, %.2fms avg latency, %d errors\n"
431
-
trace.test_name trace.total_messages trace.throughput trace.avg_latency trace.total_errors
932
+
result.test_name result.total_messages result.throughput result.avg_latency result.total_errors
Eio_main.run @@ fun env ->
435
-
let traces = run_all_presets ~env in
436
-
let json = "[" ^ String.concat ",\n" (List.map Trace.trace_to_json traces) ^ "]" in
936
+
let results = run_all_presets ~env in
939
+
let json = "[" ^ String.concat ",\n" (List.map result_to_json results) ^ "]" in
let oc = open_out output_file in
Printf.printf "Results written to %s\n" output_file;
441
-
List.iter (fun t ->
947
+
if Filename.check_suffix output_file ".json" then
948
+
Filename.chop_suffix output_file ".json" ^ ".html"
950
+
output_file ^ ".html"
952
+
let html = generate_html_report results in
953
+
let oc_html = open_out html_file in
954
+
output_string oc_html html;
956
+
Printf.printf "HTML report written to %s\n" html_file;
958
+
List.iter (fun r ->
Printf.printf " %s: %d messages, %.2f msg/s, %.2fms avg latency, %d errors\n"
443
-
t.Trace.test_name t.total_messages t.throughput t.avg_latency t.total_errors
960
+
r.test_name r.total_messages r.throughput r.avg_latency r.total_errors
Printf.printf "Running extended stress test: %d servers, %d clients/server, %d msgs/client\n"
···
Printf.printf "Total messages: %d\n%!"
(extended_preset.num_servers * extended_preset.num_clients * extended_preset.messages_per_client);
Eio_main.run @@ fun env ->
452
-
let trace = run_stress_test ~env extended_preset in
453
-
let json = Printf.sprintf "[%s]" (Trace.trace_to_json trace) in
969
+
let result = run_stress_test ~env extended_preset in
970
+
let results = [result] in
973
+
let json = Printf.sprintf "[%s]" (result_to_json result) in
let oc = open_out output_file in
Printf.printf "Results written to %s\n" output_file;
981
+
if Filename.check_suffix output_file ".json" then
982
+
Filename.chop_suffix output_file ".json" ^ ".html"
984
+
output_file ^ ".html"
986
+
let html = generate_html_report results in
987
+
let oc_html = open_out html_file in
988
+
output_string oc_html html;
990
+
Printf.printf "HTML report written to %s\n" html_file;
Printf.printf "Test: %s - %d messages, %.2f msg/s, %.2fms avg latency, %d errors\n"
459
-
trace.test_name trace.total_messages trace.throughput trace.avg_latency trace.total_errors
993
+
result.test_name result.total_messages result.throughput result.avg_latency result.total_errors