fixtureqa 0.4.23__tar.gz → 0.4.25__tar.gz
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.
- {fixtureqa-0.4.23/fixtureqa.egg-info → fixtureqa-0.4.25}/PKG-INFO +1 -1
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/perf.py +64 -14
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/perf_payload.py +7 -2
- fixtureqa-0.4.23/fixture/static/assets/index-B3ccKxdv.js → fixtureqa-0.4.25/fixture/static/assets/index-76KAxdQ5.js +1 -1
- fixtureqa-0.4.23/fixture/static/assets/index-meqdijDf.css → fixtureqa-0.4.25/fixture/static/assets/index-C9bcm_lN.css +1 -1
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/static/index.html +2 -2
- {fixtureqa-0.4.23 → fixtureqa-0.4.25/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixtureqa.egg-info/SOURCES.txt +2 -2
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/pyproject.toml +1 -1
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_perf_api.py +46 -5
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_perf_engine.py +29 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/LICENSE +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/README.md +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/__init__.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/__main__.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/app.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/deps.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/auth.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/events.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/fix_builder.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/latency_csv_writer.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/models.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/perf_charts.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/perf_engine.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/perf_models.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/perf_stats.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/session.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/server.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/setup.cfg +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_auth.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_config_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_health.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_inbound.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_message_store.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_session_auto_response.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_sessions.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_templates.py +0 -0
- {fixtureqa-0.4.23 → fixtureqa-0.4.25}/tests/test_ws.py +0 -0
|
@@ -44,6 +44,32 @@ Admin = Annotated[User, Depends(require_platform_admin)]
|
|
|
44
44
|
|
|
45
45
|
_TERMINAL = ("completed", "stopped", "error")
|
|
46
46
|
|
|
47
|
+
# Per-message persistence — durable_seqnums (fsync per message) and log_messages
|
|
48
|
+
# (a messages.db write per message) — happens inline on the event loop's
|
|
49
|
+
# send/receive path. Measured sustainable rate is ~36 msg/s on a local disk and
|
|
50
|
+
# materially worse on a Docker/WSL2 named volume; past that the loop stalls on
|
|
51
|
+
# fsync and the whole server appears frozen. We hold a conservative ceiling so a
|
|
52
|
+
# logged run at a low rate still works, while a high-rate logged run is refused
|
|
53
|
+
# up front (before the run starts) rather than wedging the server.
|
|
54
|
+
DURABLE_SAFE_MSG_RATE = 30 # messages/second a logged/durable session can sustain
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _est_msgs_per_order(body: RunConfig) -> int:
|
|
58
|
+
"""Rough peak FIX message count generated per order, for the durability rate
|
|
59
|
+
guard: 35=D out + 35=8 ack + fills, plus a request+reply pair for each of the
|
|
60
|
+
amend / cancel flavours when enabled. Deliberately an upper-ish bound."""
|
|
61
|
+
n = 2 + body.payload.fill.fills_per_order # order + ack + fills
|
|
62
|
+
if body.amend.enabled:
|
|
63
|
+
n += 2 # 35=G + 35=8 replace
|
|
64
|
+
if body.cancel.enabled:
|
|
65
|
+
n += 2 # 35=F + 35=8/9 reply
|
|
66
|
+
return max(1, n)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _order_rate_per_s(body: RunConfig) -> float:
|
|
70
|
+
r = body.rate
|
|
71
|
+
return r.orders_per_window * 1000.0 / r.order_window_ms
|
|
72
|
+
|
|
47
73
|
|
|
48
74
|
def _require_started(sm: SessionManager, sid: str, label: str):
|
|
49
75
|
try:
|
|
@@ -68,10 +94,45 @@ async def create_run(body: RunConfig, sm: SM, reg: REG, ts: TS, admin: Admin):
|
|
|
68
94
|
# (a sync def route runs in a threadpool with no running loop).
|
|
69
95
|
need_client = body.mode in ("loopback", "manager", "client")
|
|
70
96
|
need_venue = body.mode in ("loopback", "manager", "venue")
|
|
97
|
+
participating: list[tuple[str, object]] = [] # (label, config) for fixture legs
|
|
71
98
|
if need_client:
|
|
72
|
-
_require_started(sm, body.client_session_id, "client")
|
|
99
|
+
participating.append(("client", _require_started(sm, body.client_session_id, "client").config))
|
|
73
100
|
if need_venue:
|
|
74
|
-
_require_started(sm, body.venue_session_id, "venue")
|
|
101
|
+
participating.append(("venue", _require_started(sm, body.venue_session_id, "venue").config))
|
|
102
|
+
|
|
103
|
+
# Durability rate guard — BEFORE reg.create() (which starts the run). A
|
|
104
|
+
# session with durable_seqnums or log_messages persists every message inline
|
|
105
|
+
# on the event loop; above a low rate this stalls the loop and freezes the
|
|
106
|
+
# whole server. Allow a logged run at a safe low rate (useful to inspect the
|
|
107
|
+
# messages); refuse above it with the safe order rate spelled out.
|
|
108
|
+
logged = [(label, cfg) for label, cfg in participating
|
|
109
|
+
if cfg.durable_seqnums or cfg.log_messages]
|
|
110
|
+
durable_warnings: list[str] = []
|
|
111
|
+
if logged:
|
|
112
|
+
mpo = _est_msgs_per_order(body)
|
|
113
|
+
est_msg_rate = _order_rate_per_s(body) * mpo
|
|
114
|
+
safe_order_rate = DURABLE_SAFE_MSG_RATE / mpo
|
|
115
|
+
|
|
116
|
+
def _flags(cfg) -> str:
|
|
117
|
+
f = [n for n, on in (("durable_seqnums", cfg.durable_seqnums),
|
|
118
|
+
("log_messages", cfg.log_messages)) if on]
|
|
119
|
+
return "+".join(f)
|
|
120
|
+
who = ", ".join(f"{label} ({_flags(cfg)})" for label, cfg in logged)
|
|
121
|
+
|
|
122
|
+
if est_msg_rate > DURABLE_SAFE_MSG_RATE:
|
|
123
|
+
raise HTTPException(status_code=409, detail=(
|
|
124
|
+
f"This run would generate ~{est_msg_rate:.0f} msg/s "
|
|
125
|
+
f"(~{mpo} msgs/order × {_order_rate_per_s(body):.0f} orders/s), but "
|
|
126
|
+
f"{who} persist every message inline on the event loop "
|
|
127
|
+
f"(fsync / messages.db write). Above ~{DURABLE_SAFE_MSG_RATE} msg/s "
|
|
128
|
+
f"this stalls the server. Either lower the order rate to "
|
|
129
|
+
f"≤ {safe_order_rate:.0f}/s to keep message logging, or convert the "
|
|
130
|
+
f"session(s) to a Performance session (durable_seqnums + log_messages "
|
|
131
|
+
f"off) for high-rate runs."))
|
|
132
|
+
durable_warnings.append(
|
|
133
|
+
f"message persistence is on for {who}; safe only up to "
|
|
134
|
+
f"~{safe_order_rate:.0f} orders/s (~{DURABLE_SAFE_MSG_RATE} msg/s) — at "
|
|
135
|
+
f"higher rates the event loop stalls on per-message fsync/DB writes")
|
|
75
136
|
|
|
76
137
|
# CompIDs from the driving (client, else venue) session — for template tokens.
|
|
77
138
|
drive_sid = body.client_session_id if need_client else body.venue_session_id
|
|
@@ -82,18 +143,7 @@ async def create_run(body: RunConfig, sm: SM, reg: REG, ts: TS, admin: Admin):
|
|
|
82
143
|
except PerfConfigError as e:
|
|
83
144
|
raise HTTPException(status_code=422, detail=str(e))
|
|
84
145
|
|
|
85
|
-
|
|
86
|
-
checked = []
|
|
87
|
-
if need_client:
|
|
88
|
-
checked.append((body.client_session_id, "client"))
|
|
89
|
-
if need_venue:
|
|
90
|
-
checked.append((body.venue_session_id, "venue"))
|
|
91
|
-
for sid, label in checked:
|
|
92
|
-
cfg = sm.get_state(sid).config
|
|
93
|
-
if cfg.durable_seqnums:
|
|
94
|
-
run.warnings.append(f"{label} session has durable_seqnums=True — fsync per message caps throughput")
|
|
95
|
-
if cfg.log_messages:
|
|
96
|
-
run.warnings.append(f"{label} session has log_messages=True — persistence churn adds loop latency")
|
|
146
|
+
run.warnings.extend(durable_warnings)
|
|
97
147
|
return {"run_id": run.run_id}
|
|
98
148
|
|
|
99
149
|
|
|
@@ -503,8 +503,13 @@ class PayloadFactory:
|
|
|
503
503
|
order_qty = ctx["qty"]
|
|
504
504
|
msg.set_field(38, str(order_qty))
|
|
505
505
|
self._apply_expiry(msg, fields)
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
# Mirror the values that actually went on the wire (template-rendered when
|
|
507
|
+
# present, else the ctx defaults we just set) so amend/cancel 35=G/35=F
|
|
508
|
+
# echo the order's real Symbol(55)/Side(54), not the ORDER PAYLOAD section.
|
|
509
|
+
return msg, {"corr_id": corr, "clordid": clordid,
|
|
510
|
+
"symbol": rendered.get(55, ctx["symbol"]),
|
|
511
|
+
"side": rendered.get(54, ctx["side"]),
|
|
512
|
+
"qty": ctx["qty"], "price": ctx["price"],
|
|
508
513
|
"order_qty": order_qty, "msg_type": mtype,
|
|
509
514
|
"mirror_tags": self._mirror_tags(msg)}
|
|
510
515
|
|