jspurefix 5.9.1 → 5.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/BACKPORT_PLAN.md +20 -7
  2. package/WORKLOG.md +119 -0
  3. package/dist/benchmark/bench-report.d.ts +5 -0
  4. package/dist/benchmark/bench-report.js +91 -0
  5. package/dist/benchmark/bench-report.js.map +1 -0
  6. package/dist/benchmark/bench-runner.d.ts +12 -0
  7. package/dist/benchmark/bench-runner.js +193 -0
  8. package/dist/benchmark/bench-runner.js.map +1 -0
  9. package/dist/benchmark/bench-stats.d.ts +10 -0
  10. package/dist/benchmark/bench-stats.js +56 -0
  11. package/dist/benchmark/bench-stats.js.map +1 -0
  12. package/dist/benchmark/bench-types.d.ts +74 -0
  13. package/dist/benchmark/bench-types.js +3 -0
  14. package/dist/benchmark/bench-types.js.map +1 -0
  15. package/dist/benchmark/gc-probe.d.ts +18 -0
  16. package/dist/benchmark/gc-probe.js +103 -0
  17. package/dist/benchmark/gc-probe.js.map +1 -0
  18. package/dist/benchmark/index.d.ts +6 -0
  19. package/dist/benchmark/index.js +23 -0
  20. package/dist/benchmark/index.js.map +1 -0
  21. package/dist/benchmark/parse-bench.d.ts +28 -0
  22. package/dist/benchmark/parse-bench.js +85 -0
  23. package/dist/benchmark/parse-bench.js.map +1 -0
  24. package/dist/buffer/ascii/ascii-parser.d.ts +1 -0
  25. package/dist/buffer/ascii/ascii-parser.js +4 -1
  26. package/dist/buffer/ascii/ascii-parser.js.map +1 -1
  27. package/dist/buffer/ascii/ascii-segment-parser.js +3 -2
  28. package/dist/buffer/ascii/ascii-segment-parser.js.map +1 -1
  29. package/dist/buffer/ascii/tag-index.js.map +1 -1
  30. package/dist/buffer/msg-view.d.ts +1 -0
  31. package/dist/buffer/msg-view.js +25 -19
  32. package/dist/buffer/msg-view.js.map +1 -1
  33. package/dist/dictionary/parser/quickfix/dictionary-validator.d.ts +1 -1
  34. package/dist/dictionary/parser/quickfix/dictionary-validator.js.map +1 -1
  35. package/dist/dictionary/parser/quickfix/quick-fix-graph-parser.d.ts +1 -1
  36. package/dist/dictionary/parser/quickfix/quick-fix-graph-parser.js +1 -2
  37. package/dist/dictionary/parser/quickfix/quick-fix-graph-parser.js.map +1 -1
  38. package/dist/dictionary/parser/quickfix/validation-error.d.ts +2 -2
  39. package/dist/dictionary/parser/quickfix/validation-error.js.map +1 -1
  40. package/dist/jsfix-bench.d.ts +1 -0
  41. package/dist/jsfix-bench.js +213 -0
  42. package/dist/jsfix-bench.js.map +1 -0
  43. package/dist/store/file-session-store.js +1 -1
  44. package/dist/store/file-session-store.js.map +1 -1
  45. package/dist/store/file-session-stream-provider.d.ts +1 -0
  46. package/dist/store/file-session-stream-provider.js +13 -13
  47. package/dist/store/file-session-stream-provider.js.map +1 -1
  48. package/dist/store/fix-session-store-factory.d.ts +1 -1
  49. package/dist/store/fix-session-store-factory.js.map +1 -1
  50. package/dist/store/fix-session-store.d.ts +11 -11
  51. package/dist/store/fix-session-store.js.map +1 -1
  52. package/dist/store/memory-session-store.js.map +1 -1
  53. package/dist/store/session-stream-provider.d.ts +13 -13
  54. package/dist/store/session-stream-provider.js.map +1 -1
  55. package/dist/transport/session/fix-clock.d.ts +1 -1
  56. package/dist/transport/session/fix-clock.js.map +1 -1
  57. package/dist/transport/session/resend-request-manager.d.ts +2 -2
  58. package/dist/transport/session/resend-request-manager.js.map +1 -1
  59. package/dist/transport/session/session-sequence-coordinator.d.ts +1 -1
  60. package/dist/transport/session/session-sequence-coordinator.js.map +1 -1
  61. package/dist/transport/session/session-sequence-store.d.ts +3 -3
  62. package/dist/transport/session/session-sequence-store.js.map +1 -1
  63. package/dist/transport/tcp/tcp-initiator.js.map +1 -1
  64. package/docs/benchmarking.md +117 -0
  65. package/eslint.config.mjs +15 -1
  66. package/jsfix.test_client.txt +62 -62
  67. package/jsfix.test_server.txt +72 -72
  68. package/package.json +9 -1
  69. package/src/benchmark/bench-report.ts +101 -0
  70. package/src/benchmark/bench-runner.ts +196 -0
  71. package/src/benchmark/bench-stats.ts +86 -0
  72. package/src/benchmark/bench-types.ts +110 -0
  73. package/src/benchmark/gc-probe.ts +145 -0
  74. package/src/benchmark/index.ts +6 -0
  75. package/src/benchmark/parse-bench.ts +144 -0
  76. package/src/buffer/ascii/ascii-parser.ts +16 -1
  77. package/src/buffer/ascii/ascii-segment-parser.ts +9 -6
  78. package/src/buffer/ascii/tag-index.ts +3 -3
  79. package/src/buffer/msg-view.ts +35 -18
  80. package/src/dictionary/parser/quickfix/dictionary-validator.ts +1 -1
  81. package/src/dictionary/parser/quickfix/quick-fix-graph-parser.ts +2 -3
  82. package/src/dictionary/parser/quickfix/validation-error.ts +1 -1
  83. package/src/jsfix-bench.ts +204 -0
  84. package/src/store/file-session-store.ts +2 -2
  85. package/src/store/file-session-stream-provider.ts +17 -11
  86. package/src/store/fix-session-store-factory.ts +1 -1
  87. package/src/store/fix-session-store.ts +11 -11
  88. package/src/store/memory-session-store.ts +1 -1
  89. package/src/store/session-stream-provider.ts +13 -13
  90. package/src/transport/session/fix-clock.ts +1 -1
  91. package/src/transport/session/resend-request-manager.ts +4 -4
  92. package/src/transport/session/session-sequence-coordinator.ts +1 -1
  93. package/src/transport/session/session-sequence-store.ts +3 -3
  94. package/src/transport/tcp/tcp-initiator.ts +1 -1
@@ -1,75 +1,75 @@
1
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:15.489|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=104|
2
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:15.494|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=148|
3
- 8=FIX.4.4|9=0000079|35=2|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:51:15.495|7=1|16=1|10=236|
4
- 8=FIX.4.4|9=0000112|35=4|49=accept-comp|56=init-comp|34=1|57=fix|43=Y|52=20260801-14:51:15.500|122=20260801-14:51:15.500|123=Y|36=2|10=077|
5
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260801-14:51:17.498|58=5|10=218|
6
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:17.499|58=5|10=218|
7
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:20.313|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=086|
8
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:20.315|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=136|
9
- 8=FIX.4.4|9=0000079|35=2|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:51:20.315|7=1|16=0|10=222|
10
- 8=FIX.4.4|9=0000112|35=4|49=accept-comp|56=init-comp|34=1|57=fix|43=Y|52=20260801-14:51:20.318|122=20260801-14:51:20.318|123=Y|36=2|10=083|
11
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260801-14:51:22.316|58=5|10=203|
12
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:22.318|58=5|10=204|
13
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:24.804|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=095|
14
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:24.806|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=145|
15
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:51:24.806|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=098|
16
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:25.344|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=095|
17
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:25.345|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=144|
18
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:51:26.346|58=5|10=209|
19
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:26.347|58=5|10=210|
20
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:29.063|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=097|
21
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:29.065|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=147|
22
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:51:30.067|58=5|10=204|
23
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:30.068|58=5|10=205|
24
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:32.552|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=094|
25
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:32.553|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=143|
26
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=0|57=fix|52=20260801-14:51:33.554|58=5|10=206|
27
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:36.035|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=094|
28
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=5|57=fix|52=20260801-14:51:36.036|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=147|
29
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:51:37.038|58=5|10=209|
30
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:37.039|58=5|10=210|
31
- 8=FIX.4.4|9=0000110|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:39.686|98=0|141=Y|553=js-client|554=pwd-client|10=177|
32
- 8=FIX.4.4|9=0000125|35=3|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:39.687|45=1|372=A|373=1|58=msgType A missing required tag 108|10=011|
33
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:44.447|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=100|
34
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:44.449|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=150|
1
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:29.192|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=108|
2
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:29.199|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=163|
3
+ 8=FIX.4.4|9=0000079|35=2|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:47:29.200|7=1|16=1|10=233|
4
+ 8=FIX.4.4|9=0000112|35=4|49=accept-comp|56=init-comp|34=1|57=fix|43=Y|52=20260802-16:47:29.205|122=20260802-16:47:29.204|123=Y|36=2|10=106|
5
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260802-16:47:31.202|58=5|10=205|
6
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:47:31.203|58=5|10=205|
7
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:34.035|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=100|
8
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:34.037|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=150|
9
+ 8=FIX.4.4|9=0000079|35=2|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:47:34.038|7=1|16=0|10=237|
10
+ 8=FIX.4.4|9=0000112|35=4|49=accept-comp|56=init-comp|34=1|57=fix|43=Y|52=20260802-16:47:34.041|122=20260802-16:47:34.041|123=Y|36=2|10=095|
11
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260802-16:47:36.040|58=5|10=210|
12
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:47:36.041|58=5|10=210|
13
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:38.525|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=108|
14
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:38.527|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=158|
15
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:47:38.527|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=111|
16
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:39.070|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=104|
17
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:39.071|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=153|
18
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:47:40.074|58=5|10=211|
19
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:47:40.075|58=5|10=212|
20
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:42.868|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=113|
21
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:42.870|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=154|
22
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:47:43.871|58=5|10=219|
23
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:47:43.872|58=5|10=220|
24
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:46.667|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=114|
25
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:46.668|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=163|
26
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=0|57=fix|52=20260802-16:47:47.670|58=5|10=218|
27
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:50.181|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=100|
28
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=5|57=fix|52=20260802-16:47:50.182|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=153|
29
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:47:51.183|58=5|10=214|
30
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:47:51.184|58=5|10=215|
31
+ 8=FIX.4.4|9=0000110|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:53.673|98=0|141=Y|553=js-client|554=pwd-client|10=177|
32
+ 8=FIX.4.4|9=0000125|35=3|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:53.674|45=1|372=A|373=1|58=msgType A missing required tag 108|10=011|
33
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:47:58.294|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=113|
34
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:47:58.296|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=163|
35
35
  8=FIX4.4|9=0000136|35=ZZ|49=init-comp|56=accept-comp|34=2|57=fix|52=20180902-12:25:28.980|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=177|
36
- 8=FIX.4.4|9=0000111|35=3|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:44.451|45=2|372=ZZ|373=11|58=msgType ZZ unknown|10=144|
37
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260801-14:51:46.450|58=5|10=208|
38
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260801-14:51:46.451|58=5|10=209|
39
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:48.955|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=108|
40
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:48.956|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=157|
36
+ 8=FIX.4.4|9=0000111|35=3|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:47:58.298|45=2|372=ZZ|373=11|58=msgType ZZ unknown|10=166|
37
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260802-16:48:00.297|58=5|10=216|
38
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260802-16:48:00.298|58=5|10=217|
39
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:48:03.059|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=103|
40
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:48:03.061|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=144|
41
41
  8=FIX4.4|9=0000123|35=0|49=init-comp|56=accept-comp|34=2|57=fix|52=20180902-12:25:59.161|999=Sun, 02 Sep 2018 12:25:59 GMT|10=95|
42
- 8=FIX.4.4|9=0000146|35=3|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:48.958|45=2|372=0|373=5|58=msgType 0 checksum failed. received = 95 computed = 143|10=081|
43
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260801-14:51:50.958|58=5|10=216|
44
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260801-14:51:50.959|58=5|10=217|
45
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:51:57.567|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=107|
46
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:51:57.568|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=156|
42
+ 8=FIX.4.4|9=0000146|35=3|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:48:03.063|45=2|372=0|373=5|58=msgType 0 checksum failed. received = 95 computed = 143|10=068|
43
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260802-16:48:05.062|58=5|10=211|
44
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260802-16:48:05.063|58=5|10=212|
45
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:48:11.555|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=103|
46
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:48:11.556|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=152|
47
47
  8=FIX4.4|9=0000123|35=0|49=init-not!|56=accept-comp|34=2|57=fix|52=20180902-12:25:59.161|112=Sun, 02 Sep 2018 12:25:59 GMT|10=95|
48
- 8=FIX.4.4|9=0000145|35=3|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:51:57.569|45=2|372=0|373=5|58=msgType 0 checksum failed. received = 95 computed = 59|10=036|
49
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260801-14:51:59.570|58=5|10=215|
50
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260801-14:51:59.570|58=5|10=215|
51
- 8=FIX.4.4|9=0000116|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:52:02.309|98=0|108=2|141=Y|553=js-client|554=pwd-client|10=042|
52
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:52:02.310|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=132|
53
- 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:52:04.315|112=Sat, 01 Aug 2026 14:52:04 GMT|10=120|
54
- 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=3|57=fix|52=20260801-14:52:06.317|112=Sat, 01 Aug 2026 14:52:06 GMT|10=127|
55
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=4|57=fix|52=20260801-14:52:08.312|58=5|10=205|
56
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:52:08.313|58=5|10=204|
57
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:52:10.828|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=097|
58
- 8=FIX.4.4|9=0000116|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:52:10.829|98=0|108=2|141=Y|553=js-server|554=pwd-server|10=096|
59
- 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:52:12.833|112=Sat, 01 Aug 2026 14:52:12 GMT|10=123|
60
- 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=3|57=fix|52=20260801-14:52:14.838|112=Sat, 01 Aug 2026 14:52:14 GMT|10=133|
61
- 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=4|57=fix|52=20260801-14:52:16.841|112=Sat, 01 Aug 2026 14:52:16 GMT|10=132|
62
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:52:18.830|58=5|10=209|
63
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=5|57=fix|52=20260801-14:52:18.831|58=5|10=213|
64
- 8=FIX.4.4|9=0000116|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:52:21.327|98=0|108=2|141=Y|553=js-client|554=pwd-client|10=043|
65
- 8=FIX.4.4|9=0000116|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:52:21.328|98=0|108=5|141=Y|553=js-server|554=pwd-server|10=095|
66
- 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:52:23.334|112=Sat, 01 Aug 2026 14:52:23 GMT|10=123|
67
- 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=3|57=fix|52=20260801-14:52:25.336|112=Sat, 01 Aug 2026 14:52:25 GMT|10=130|
68
- 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:52:26.339|112=Sat, 01 Aug 2026 14:52:26 GMT|10=134|
69
- 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=4|57=fix|52=20260801-14:52:27.338|112=Sat, 01 Aug 2026 14:52:27 GMT|10=137|
70
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=5|57=fix|52=20260801-14:52:29.329|58=5|10=217|
71
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260801-14:52:29.330|58=5|10=207|
72
- 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260801-14:54:22.785|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=104|
73
- 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260801-14:54:22.794|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=152|
74
- 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260801-14:54:23.797|58=5|10=219|
75
- 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260801-14:54:23.799|58=5|10=221|
48
+ 8=FIX.4.4|9=0000145|35=3|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:48:11.558|45=2|372=0|373=5|58=msgType 0 checksum failed. received = 95 computed = 59|10=033|
49
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=3|57=fix|52=20260802-16:48:13.558|58=5|10=220|
50
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260802-16:48:13.559|58=5|10=221|
51
+ 8=FIX.4.4|9=0000116|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:48:16.035|98=0|108=2|141=Y|553=js-client|554=pwd-client|10=051|
52
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:48:16.036|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=150|
53
+ 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:48:18.042|112=Sun, 02 Aug 2026 16:48:18 GMT|10=157|
54
+ 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=3|57=fix|52=20260802-16:48:20.242|112=Sun, 02 Aug 2026 16:48:20 GMT|10=146|
55
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=4|57=fix|52=20260802-16:48:22.038|58=5|10=214|
56
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:48:22.039|58=5|10=213|
57
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:48:24.515|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=103|
58
+ 8=FIX.4.4|9=0000116|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:48:24.516|98=0|108=2|141=Y|553=js-server|554=pwd-server|10=102|
59
+ 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:48:26.523|112=Sun, 02 Aug 2026 16:48:26 GMT|10=159|
60
+ 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=3|57=fix|52=20260802-16:48:28.525|112=Sun, 02 Aug 2026 16:48:28 GMT|10=166|
61
+ 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=4|57=fix|52=20260802-16:48:30.531|112=Sun, 02 Aug 2026 16:48:30 GMT|10=150|
62
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:48:32.517|58=5|10=215|
63
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=5|57=fix|52=20260802-16:48:32.518|58=5|10=219|
64
+ 8=FIX.4.4|9=0000116|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:48:35.238|98=0|108=2|141=Y|553=js-client|554=pwd-client|10=057|
65
+ 8=FIX.4.4|9=0000116|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:48:35.239|98=0|108=5|141=Y|553=js-server|554=pwd-server|10=109|
66
+ 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:48:37.244|112=Sun, 02 Aug 2026 16:48:37 GMT|10=163|
67
+ 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=3|57=fix|52=20260802-16:48:39.248|112=Sun, 02 Aug 2026 16:48:39 GMT|10=172|
68
+ 8=FIX.4.4|9=0000104|35=0|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:48:40.250|112=Sun, 02 Aug 2026 16:48:40 GMT|10=148|
69
+ 8=FIX.4.4|9=0000104|35=0|49=init-comp|56=accept-comp|34=4|57=fix|52=20260802-16:48:41.251|112=Sun, 02 Aug 2026 16:48:41 GMT|10=153|
70
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=5|57=fix|52=20260802-16:48:43.241|58=5|10=214|
71
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=3|57=fix|52=20260802-16:48:43.242|58=5|10=213|
72
+ 8=FIX.4.4|9=0000117|35=A|49=init-comp|56=accept-comp|34=1|57=fix|52=20260802-16:50:41.579|98=0|108=30|141=Y|553=js-client|554=pwd-client|10=105|
73
+ 8=FIX.4.4|9=0000117|35=A|49=accept-comp|56=init-comp|34=1|57=fix|52=20260802-16:50:41.589|98=0|108=30|141=Y|553=js-server|554=pwd-server|10=154|
74
+ 8=FIX.4.4|9=0000075|35=5|49=init-comp|56=accept-comp|34=2|57=fix|52=20260802-16:50:42.592|58=5|10=212|
75
+ 8=FIX.4.4|9=0000075|35=5|49=accept-comp|56=init-comp|34=2|57=fix|52=20260802-16:50:42.593|58=5|10=213|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jspurefix",
3
- "version": "5.9.1",
3
+ "version": "5.9.2",
4
4
  "description": "pure node js fix engine",
5
5
  "keywords": [
6
6
  "typescript",
@@ -35,6 +35,14 @@
35
35
  "repo44-script": "node dist/jsfix-cmd --dict=repo44 --generate --density=0.8 --script --delimiter=\"|\" --repeats=50 --session=\"data/session/test-initiator.json\"",
36
36
  "repo44-replay": "node dist/jsfix-cmd --dict=repo44 --fix=data/examples/FIX.4.4/fix.txt --delimiter=\"|\" --stats--delimiter=\"|\" --objects",
37
37
  "repo44-repscr": "node dist/jsfix-cmd --dict=repo44 --fix=data/examples/FIX.4.4/fix.txt --delimiter=\"|\" --stats--delimiter=\"|\" --stats",
38
+ "bench": "node --expose-gc dist/jsfix-bench",
39
+ "bench:er": "node --expose-gc dist/jsfix-bench --dict=qf44 --fix=data/examples/FIX.4.4/quickfix/execution-report/fix.txt",
40
+ "bench:er2": "node --expose-gc dist/jsfix-bench --dict=qf44 --fix=data/examples/FIX.4.4/quickfix/execution-report2/fix.txt",
41
+ "bench:nos": "node --expose-gc dist/jsfix-bench --dict=qf43 --fix=data/examples/FIX.4.3/quickfix/new-order-single/fix.txt",
42
+ "bench:md": "node --expose-gc dist/jsfix-bench --dict=qf50sp2 --fix=data/examples/FIX.5.0SP2/quickfix/md-data-snapshot/fix.txt",
43
+ "bench:tc": "node --expose-gc dist/jsfix-bench --dict=repo44 --fix=data/examples/FIX.4.4/repo/trade-capture/fix.txt",
44
+ "bench:save": "node --expose-gc dist/jsfix-bench --save=benchmarks/baseline.json",
45
+ "bench:compare": "node --expose-gc dist/jsfix-bench --baseline=benchmarks/baseline.json",
38
46
  "repo44-bench": "node dist/jsfix-cmd --dict=repo44 --benchmark --delimiter=\"|\"",
39
47
  "qf43-bench-nos": "node dist/jsfix-cmd --dict=qf43 --fix=data/examples/FIX.4.3/quickfix/new-order-single/fix.txt --benchmark --delimiter=\"|\" --repeats=80000",
40
48
  "qf44-bench-er": "node dist/jsfix-cmd --dict=qf44 --fix=data/examples/FIX.4.4/quickfix/execution-report/fix.txt --benchmark --delimiter=\"|\" --repeats=80000",
@@ -0,0 +1,101 @@
1
+ import { compare } from './bench-stats'
2
+ import { IBenchResult, IBenchSuiteResult } from './bench-types'
3
+
4
+ const bytesPerKb = 1024
5
+ const bytesPerMb = bytesPerKb * 1024
6
+ const nanosPerMicro = 1e3
7
+
8
+ function micros (nanos: number): string {
9
+ return (nanos / nanosPerMicro).toFixed(3)
10
+ }
11
+
12
+ function mb (bytes: number): string {
13
+ return (bytes / bytesPerMb).toFixed(2)
14
+ }
15
+
16
+ function thousands (n: number): string {
17
+ return Math.round(n).toLocaleString('en-US')
18
+ }
19
+
20
+ function signed (n: number, digits: number = 2): string {
21
+ return `${n >= 0 ? '+' : ''}${n.toFixed(digits)}`
22
+ }
23
+
24
+ export function formatEnvironment (suite: IBenchSuiteResult): string {
25
+ const env = suite.environment
26
+ const lines = [
27
+ `${suite.label} ${suite.capturedAt}`,
28
+ `node ${env.node} v8 ${env.v8} ${env.platform} ${env.arch}`,
29
+ `${env.cpu} x${env.cpuCount}`
30
+ ]
31
+ if (!env.forcedGcAvailable) {
32
+ lines.push('WARNING: no --expose-gc, rounds are not isolated and retained heap is indicative only')
33
+ }
34
+ return lines.join('\n')
35
+ }
36
+
37
+ export function formatResult (result: IBenchResult): string {
38
+ const t = result.timing
39
+ const m = result.memory
40
+ const noise = t.coefficientOfVariation * 100
41
+ const lines = [
42
+ `${result.name}`,
43
+ result.description ? ` ${result.description}` : '',
44
+ ` rounds ${t.samples} x ${result.options.operationsPerRound} msgs` +
45
+ ` (warmup ${result.options.warmupOperations})`,
46
+ ` per msg median ${micros(t.median)}us min ${micros(t.min)}us` +
47
+ ` p95 ${micros(t.p95)}us max ${micros(t.max)}us`,
48
+ ` throughput ${thousands(result.operationsPerSecond)} msg/sec`,
49
+ ` noise ${noise.toFixed(2)}% of median (a change smaller than this means nothing)`,
50
+ ` gc pass ${m.operations} msgs uninterrupted: ${m.gc.minor} minor ${m.gc.major} major ` +
51
+ `${m.gc.totalPauseMs.toFixed(1)}ms paused longest ${m.gc.longestPauseMs.toFixed(2)}ms`,
52
+ ` gc rate ${m.minorGcPerMillionOperations.toFixed(1)} minor/M msgs ` +
53
+ `${m.majorGcPerMillionOperations.toFixed(1)} major/M msgs ` +
54
+ `${m.gcPauseMsPerMillionOperations.toFixed(0)}ms paused/M msgs`,
55
+ ` retained ${mb(m.retainedHeapBytes)}MB ` +
56
+ `${m.retainedBytesPerOperation.toFixed(1)} bytes/msg heap at end of pass ${mb(m.heapAtEndOfPassBytes)}MB`
57
+ ]
58
+ return lines.filter(l => l.length > 0).join('\n')
59
+ }
60
+
61
+ export function formatSuite (suite: IBenchSuiteResult): string {
62
+ const blocks = suite.results.map(formatResult)
63
+ return [formatEnvironment(suite), '', ...blocks].join('\n\n')
64
+ }
65
+
66
+ /**
67
+ * Compare a suite against a stored baseline, matching results by name.
68
+ *
69
+ * A difference is only called out as real when it exceeds the run to run
70
+ * spread both suites displayed. Everything else is reported as noise, because
71
+ * that is what it is.
72
+ */
73
+ export function formatComparison (baseline: IBenchSuiteResult, candidate: IBenchSuiteResult): string {
74
+ const lines: string[] = [`baseline ${baseline.label} (${baseline.capturedAt})`, '']
75
+ if (baseline.environment.cpu !== candidate.environment.cpu ||
76
+ baseline.environment.node !== candidate.environment.node) {
77
+ lines.push('WARNING: baseline was taken on a different machine or node build, timings are not comparable')
78
+ lines.push('')
79
+ }
80
+ for (const result of candidate.results) {
81
+ const before = baseline.results.find(r => r.name === result.name)
82
+ if (!before) {
83
+ lines.push(`${result.name} no baseline`)
84
+ continue
85
+ }
86
+ const timing = compare(before.timing, result.timing)
87
+ const verdict = timing.significant
88
+ ? (timing.deltaPercent < 0 ? 'FASTER' : 'SLOWER')
89
+ : 'noise'
90
+ lines.push(`${result.name}`)
91
+ lines.push(` per msg ${micros(before.timing.median)}us -> ${micros(result.timing.median)}us` +
92
+ ` ${signed(timing.deltaPercent)}% ${verdict}` +
93
+ ` (noise floor +/-${timing.noiseThresholdPercent.toFixed(2)}%)`)
94
+ const gcBefore = before.memory.minorGcPerMillionOperations
95
+ const gcAfter = result.memory.minorGcPerMillionOperations
96
+ const gcDelta = gcBefore === 0 ? 0 : ((gcAfter - gcBefore) / gcBefore) * 100
97
+ lines.push(` minor gc ${gcBefore.toFixed(1)} -> ${gcAfter.toFixed(1)} per M msgs ${signed(gcDelta)}%`)
98
+ lines.push('')
99
+ }
100
+ return lines.join('\n')
101
+ }
@@ -0,0 +1,196 @@
1
+ import * as os from 'os'
2
+ import { GcProbe } from './gc-probe'
3
+ import { summarise } from './bench-stats'
4
+ import {
5
+ IBenchCase,
6
+ IBenchOptions,
7
+ IBenchResult,
8
+ IBenchSuiteResult,
9
+ IEnvironment,
10
+ IMemoryResult,
11
+ IRoundResult
12
+ } from './bench-types'
13
+
14
+ const nanosPerSecond = 1e9
15
+ const operationsPerMillion = 1e6
16
+
17
+ export const defaultBenchOptions: IBenchOptions = {
18
+ // v8 needs to see a function run a few thousand times before it stops
19
+ // interpreting it, so an unwarmed first round measures the wrong code
20
+ warmupOperations: 20000,
21
+ rounds: 10,
22
+ operationsPerRound: 20000,
23
+ gcBetweenRounds: true
24
+ }
25
+
26
+ export function environment (): IEnvironment {
27
+ const cpus = os.cpus()
28
+ return {
29
+ node: process.version,
30
+ v8: process.versions.v8,
31
+ platform: `${os.type()} ${os.release()}`,
32
+ arch: process.arch,
33
+ cpu: cpus.length > 0 ? cpus[0].model.trim() : 'unknown',
34
+ cpuCount: cpus.length,
35
+ forcedGcAvailable: GcProbe.forcedGcAvailable()
36
+ }
37
+ }
38
+
39
+ /** hand the event loop one turn, so queued observer callbacks can run */
40
+ async function tick (): Promise<void> {
41
+ await new Promise<void>(resolve => { setImmediate(resolve) })
42
+ }
43
+
44
+ /**
45
+ * Yield until the probe stops seeing new entries. Node dispatches a gc entry on
46
+ * the turn after the collection, and a burst takes several turns to come
47
+ * through, so a single yield loses most of them. Bounded so a pathological case
48
+ * cannot spin here.
49
+ */
50
+ async function drain (probe: GcProbe, maxTurns: number = 50): Promise<void> {
51
+ let quietTurns = 0
52
+ for (let turn = 0; turn < maxTurns && quietTurns < 3; ++turn) {
53
+ const before = probe.seen()
54
+ await tick()
55
+ quietTurns = probe.seen() === before ? quietTurns + 1 : 0
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Drives a case through a warmup, a series of timed rounds and a separate
61
+ * diagnostics pass.
62
+ *
63
+ * One round is one sample. A single long run cannot tell you whether a number
64
+ * is repeatable, and a difference smaller than the spread between rounds is not
65
+ * a difference at all.
66
+ *
67
+ * Timing and memory are deliberately not measured together. Isolating rounds
68
+ * means forcing a collection between them, and a forced collection is itself a
69
+ * gc event - counting those would report the harness rather than the work. So
70
+ * the timed rounds collect between them and report no gc figures, and a final
71
+ * pass runs uninterrupted with the probe attached and reports no timings.
72
+ */
73
+ export class BenchRunner {
74
+ constructor (private readonly options: IBenchOptions = defaultBenchOptions) {
75
+ }
76
+
77
+ public async run (benchCase: IBenchCase): Promise<IBenchResult> {
78
+ const options = this.options
79
+ benchCase.setup?.()
80
+ try {
81
+ this.warmup(benchCase)
82
+ const rounds: IRoundResult[] = []
83
+ for (let round = 0; round < options.rounds; ++round) {
84
+ if (options.gcBetweenRounds) {
85
+ // a collection provoked by the previous round must not be charged
86
+ // to the next one
87
+ GcProbe.forceGc()
88
+ }
89
+ rounds.push(this.measureRound(benchCase, round))
90
+ }
91
+ const timing = summarise(rounds.map(r => r.nanosPerOperation))
92
+ return {
93
+ name: benchCase.name,
94
+ description: benchCase.description,
95
+ options,
96
+ rounds,
97
+ timing,
98
+ operationsPerSecond: timing.median === 0 ? 0 : nanosPerSecond / timing.median,
99
+ memory: await this.measureMemory(benchCase)
100
+ }
101
+ } finally {
102
+ benchCase.teardown?.()
103
+ }
104
+ }
105
+
106
+ public async runAll (label: string, cases: IBenchCase[]): Promise<IBenchSuiteResult> {
107
+ const results: IBenchResult[] = []
108
+ for (const benchCase of cases) {
109
+ results.push(await this.run(benchCase))
110
+ }
111
+ return {
112
+ label,
113
+ // stamped from the clock rather than passed in, a suite is a record of
114
+ // when it was taken
115
+ capturedAt: new Date().toISOString(),
116
+ environment: environment(),
117
+ results
118
+ }
119
+ }
120
+
121
+ /**
122
+ * One uninterrupted pass with the gc probe attached.
123
+ *
124
+ * Nothing is forced while it runs, so every collection counted is one the
125
+ * work itself provoked. The heap readings either side are taken after a
126
+ * forced collection, making them what the pass retained rather than what it
127
+ * churned - churn is what the collection counts are for.
128
+ */
129
+ private async measureMemory (benchCase: IBenchCase): Promise<IMemoryResult> {
130
+ // settle, then let any delivery from that settling drain before the probe
131
+ // exists - entries are dispatched a turn late and would otherwise land in
132
+ // the middle of the measured pass
133
+ GcProbe.forceGc()
134
+ await tick()
135
+
136
+ const heapBefore = process.memoryUsage().heapUsed
137
+ const probe = new GcProbe()
138
+ probe.start()
139
+ const operations = benchCase.run(this.options.operationsPerRound)
140
+ const heapAtEndOfPassBytes = process.memoryUsage().heapUsed
141
+ await drain(probe)
142
+ const gc = probe.stop()
143
+
144
+ GcProbe.forceGc()
145
+ const heapAfter = process.memoryUsage().heapUsed
146
+
147
+ const retainedHeapBytes = heapAfter - heapBefore
148
+ const perMillion = operations === 0 ? 0 : operationsPerMillion / operations
149
+ return {
150
+ operations,
151
+ retainedHeapBytes,
152
+ retainedBytesPerOperation: operations === 0 ? 0 : retainedHeapBytes / operations,
153
+ heapAtEndOfPassBytes,
154
+ gc,
155
+ minorGcPerMillionOperations: gc.minor * perMillion,
156
+ majorGcPerMillionOperations: gc.major * perMillion,
157
+ gcPauseMsPerMillionOperations: gc.totalPauseMs * perMillion,
158
+ forcedGcAvailable: GcProbe.forcedGcAvailable()
159
+ }
160
+ }
161
+
162
+ private warmup (benchCase: IBenchCase): void {
163
+ const target = this.options.warmupOperations
164
+ if (target <= 0) return
165
+ let done = 0
166
+ // in chunks the size of a real round, so warmup exercises the same shape of
167
+ // work rather than one long unbroken call
168
+ const chunk = Math.max(1, Math.min(target, this.options.operationsPerRound))
169
+ while (done < target) {
170
+ const requested = Math.min(chunk, target - done)
171
+ const completed = benchCase.run(requested)
172
+ // a case that reports less than it was asked for would never let this
173
+ // loop reach its target, so refuse it here rather than spin
174
+ if (completed !== requested) {
175
+ throw new Error(`${benchCase.name}: asked for ${requested} operations during warmup but completed ${completed}`)
176
+ }
177
+ done += completed
178
+ }
179
+ }
180
+
181
+ private measureRound (benchCase: IBenchCase, round: number): IRoundResult {
182
+ const requested = this.options.operationsPerRound
183
+ const startedAt = process.hrtime.bigint()
184
+ const operations = benchCase.run(requested)
185
+ const elapsedNanos = Number(process.hrtime.bigint() - startedAt)
186
+ if (operations !== requested) {
187
+ throw new Error(`${benchCase.name}: asked for ${requested} operations in round ${round} but completed ${operations}`)
188
+ }
189
+ return {
190
+ round,
191
+ operations,
192
+ elapsedNanos,
193
+ nanosPerOperation: operations === 0 ? 0 : elapsedNanos / operations
194
+ }
195
+ }
196
+ }
@@ -0,0 +1,86 @@
1
+ import { ISampleStats } from './bench-types'
2
+
3
+ /**
4
+ * Descriptive statistics over a set of measured rounds.
5
+ *
6
+ * min matters as much as median here. A round can only ever be made slower by
7
+ * interference - a collection landing mid round, the scheduler taking the core
8
+ * away - so the fastest round is the closest reading to the true cost of the
9
+ * work, while the median tells you what to expect in practice.
10
+ */
11
+ export function summarise (values: number[]): ISampleStats {
12
+ if (values.length === 0) {
13
+ throw new Error('cannot summarise an empty sample set')
14
+ }
15
+ const sorted = [...values].sort((a, b) => a - b)
16
+ const n = sorted.length
17
+ const mean = sorted.reduce((a, v) => a + v, 0) / n
18
+ const variance = sorted.reduce((a, v) => a + (v - mean) * (v - mean), 0) / n
19
+ const stdDev = Math.sqrt(variance)
20
+ return {
21
+ samples: n,
22
+ min: sorted[0],
23
+ max: sorted[n - 1],
24
+ mean,
25
+ median: median(sorted),
26
+ p95: percentile(sorted, 95),
27
+ p99: percentile(sorted, 99),
28
+ stdDev,
29
+ coefficientOfVariation: mean === 0 ? 0 : stdDev / mean
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @param sorted values in ascending order
35
+ */
36
+ export function median (sorted: number[]): number {
37
+ const n = sorted.length
38
+ if (n === 0) return 0
39
+ const mid = n >> 1
40
+ return n % 2 === 1
41
+ ? sorted[mid]
42
+ : (sorted[mid - 1] + sorted[mid]) / 2
43
+ }
44
+
45
+ /**
46
+ * Nearest rank percentile - no interpolation, so every value reported is a
47
+ * reading that actually occurred.
48
+ *
49
+ * @param sorted values in ascending order
50
+ * @param p percentile in the range 0 to 100
51
+ */
52
+ export function percentile (sorted: number[], p: number): number {
53
+ const n = sorted.length
54
+ if (n === 0) return 0
55
+ const rank = Math.ceil((p / 100) * n)
56
+ const index = Math.min(Math.max(rank - 1, 0), n - 1)
57
+ return sorted[index]
58
+ }
59
+
60
+ export interface IComparison {
61
+ /** positive means the candidate took longer than the baseline */
62
+ readonly deltaPercent: number
63
+ /**
64
+ * true when the change is larger than the noise both runs displayed. Below
65
+ * this threshold a difference is not evidence of anything.
66
+ */
67
+ readonly significant: boolean
68
+ readonly noiseThresholdPercent: number
69
+ }
70
+
71
+ /**
72
+ * Compare a candidate timing against a baseline, using the run to run spread
73
+ * each of them showed to decide whether the difference is real.
74
+ */
75
+ export function compare (baseline: ISampleStats, candidate: ISampleStats): IComparison {
76
+ const deltaPercent = baseline.median === 0
77
+ ? 0
78
+ : ((candidate.median - baseline.median) / baseline.median) * 100
79
+ // both runs contribute their own noise to any difference between them
80
+ const noiseThresholdPercent = (baseline.coefficientOfVariation + candidate.coefficientOfVariation) * 100
81
+ return {
82
+ deltaPercent,
83
+ significant: Math.abs(deltaPercent) > noiseThresholdPercent,
84
+ noiseThresholdPercent
85
+ }
86
+ }