As
built:
2026-09-17,
commit
04582813b
The order gateway records six latency histograms at its own boundaries. They measure:
OrderCanceled
event;
The gateway times a WebSocket request by storing a timestamp on the open-orders entry of the order the request names. The drop-copy execution that answers the request names the same order, so the gateway finds the timestamp there. There is no separate table of in-flight requests.
The histograms are Prometheus histograms. They export over OTLP to the observability ClickHouse with every other gateway metric. Nothing is written to the exchange database.
The design follows three rules, in priority order:
No series is the client's round trip. TLS terminates at nginx. A completed gateway socket write means the gateway handed the bytes to its connection toward nginx. It does not mean the client received them. Only the client can measure its own round trip.
| series | labels | start | end |
|---|---|---|---|
og_ws_order_to_ep3_ms |
req:
place,
cancel,
replace,
cancel_all |
session loop takes the request frame off the socket, before parsing | handler is about to issue its EP3 RPC |
og_ws_order_to_ep3_ack_ms |
req:
place,
replace;
result:
acked,
rejected,
replaced |
same frame read | drop-copy processor matches the answering execution to the order, before any event is written |
og_ws_order_to_ack_ms |
req:
place,
replace;
result:
acked,
rejected,
replaced |
same frame read | requesting session's write of the answering event returns |
og_ws_cancel_to_out_ms |
none | cancel request frame read | requesting
session's
write
of
the
drop-copy
OrderCanceled
event
returns |
og_dropcopy_to_ws_ms |
event:
heartbeat,
cancel_rejected,
acked,
canceled,
replaced,
rejected,
expired,
done_for_day,
partial_fill,
fill |
drop-copy batch arrives at the gateway | session loop's write of the derived event returns; one sample per session that writes it |
og_rest_request_to_response_ms |
route:
matched
route
pattern;
status:
HTTP
status |
REST order request enters the timing middleware | authorization and handler return a response, before the body is written |
In
these
series,
"ack"
means
the
event
that
answers
a
place
or
replace:
OrderAcked,
OrderRejected,
or
OrderReplacedOrAmended.
The three order series start at the same frame read, and each ends later than the one before:
og_ws_order_to_ep3_ms
covers
the
gateway's
work
before
it
calls
EP3.
og_ws_order_to_ep3_ack_ms
adds
EP3's
processing
and
the
drop-copy
transit.
og_ws_order_to_ack_ms
adds
the
gateway's
fan-out
and
socket
write.
og_ws_order_to_ack_ms
minus
og_ws_order_to_ep3_ack_ms
is
the
gateway's
delivery
time
for
that
order.
og_dropcopy_to_ws_ms
measures
the
same
interval
for
every
drop-copy
event.
The start of a WebSocket series is the instant the session loop takes the frame off the socket. It is not the network arrival time. A frame can wait in kernel buffers while the loop is busy, for example while the loop is blocked on a write. That wait is outside the measured span.
No series has an account, client, order ID, or request ID label.
A request stamp holds three values: the request kind, the instant the session loop read the frame, and a process-local ID for the session that read it. The session loop creates a stamp for every place, cancel, replace, and cancel-all request and passes it to the spawned handler. REST requests have no stamp.
The stamp follows the request through these steps:
begin_replace
creates
it.
og_ws_order_to_ep3_ms.
This
is
the
only
series
a
cancel-all
records.
New
or
Rejected
execution
takes
the
order's
stamp
and
records
og_ws_order_to_ep3_ack_ms.
The
stamp
goes
on
the
OrderAcked
or
OrderRejected
event.
Replace
execution
takes
the
replacement
order's
stamp
and
records
og_ws_order_to_ep3_ack_ms.
The
stamp
goes
on
the
OrderReplacedOrAmended
event.
Canceled
execution
takes
the
cancel
stamp.
The
stamp
goes
on
the
OrderCanceled
event.
og_ws_order_to_ack_ms
or
og_ws_cancel_to_out_ms
after
its
write
returns.
Other
sessions
record
nothing
for
the
stamp.
The
handler
sometimes
rejects
a
request
itself,
for
example
when
the
client
order
ID
is
a
duplicate
or
the
EP3
RPC
returns
a
definitive
rejection.
The
OrderRejected
event
it
broadcasts
carries
the
handler's
stamp.
The
requesting
session
records
og_ws_order_to_ack_ms
with
result="rejected"
when
it
writes
that
event.
No
og_ws_order_to_ep3_ack_ms
sample
is
recorded,
because
no
drop-copy
execution
answered
the
request.
Each stamp produces at most one sample per series, because the drop-copy processor removes the stamp when it uses it.
A stamp that no execution takes stays on its open-orders entry and is deleted with the entry. Stamps exist only in memory, so a gateway restart discards them.
CancelRejected,
the
cancel
stamp
is
unused
and
is
deleted
with
the
entry.
CancelRejected
event
with
no
stamp.
OrderRejected
event
has
no
stamp.
og_ws_order_to_ep3_ack_ms
is
recorded.
og_ws_order_to_ack_ms
and
og_ws_cancel_to_out_ms
are
not.
The
drop-copy
reader
records
a
monotonic
instant
when
a
batch
arrives.
The
drop-copy
processor
sets
that
instant
on
every
RoutedEvent
it
broadcasts
for
the
batch.
This
includes
the
CancelRejected
events
and
the
rejections
of
orphaned
replacement
orders
that
the
processor
generates
while
it
handles
the
batch.
After
a
session
loop's
write
of
the
event
returns,
the
loop
records
og_dropcopy_to_ws_ms
under
the
event's
label.
Events
that
the
gateway
generates
outside
the
drop-copy
path,
such
as
a
handler's
own
reject,
have
no
arrival
instant
and
produce
no
delivery
sample.
The
label
set
includes
heartbeat
because
the
label
function
covers
every
event
type.
The
session
heartbeat
is
generated
in
the
session
loop,
so
that
label
has
no
samples.
The
gateway
closes
a
session
that
falls
too
far
behind
the
event
broadcast,
and
counts
the
closure
in
og_ws_slow_consumer_evictions_total.
The
events
that
session
had
not
yet
read
produce
no
delivery
samples.
The
counter
gives
the
number
of
closed
sessions,
not
the
number
of
missing
samples.
The public event wire shape does not include the arrival instant or the stamp.
A timing middleware wraps the REST order write routes. It is the outermost layer, so a sample covers authorization, the full-access check, and the handler. The sample ends when the handler returns a response, before the body is written.
The
route
label
is
the
matched
pattern
from
a
fixed
set:
/place-order,
/cancel-order,
/replace-order,
/cancel-all-orders,
/place_order,
/cancel_order,
/cancel_all_orders.
Any
other
matched
route
is
labelled
other.
A
request
with
no
matched
route
is
labelled
unmatched.
A
unit
test
asserts
that
every
write
route
has
its
own
label.
The
status
label
is
the
exact
code
for
200,
400,
401,
403,
404,
409,
422,
429,
500,
502,
and
503.
Any
other
code
is
labelled
with
its
class:
2xx,
3xx,
4xx,
or
5xx.
REST requests have no request stamp, so they record none of the WebSocket series.
All six histograms share one set of explicit bucket boundaries, in milliseconds. The set is log-spaced at eight buckets per octave (ratio 2^⅛ ≈ 1.09), from 0.05 ms until a bound covers 30 s: 155 bounds, each rounded to four significant figures. A linearly interpolated quantile is off by at most half a bucket, about ±4.5% of its value. One shared set also lets any two series be compared bucket for bucket. Explicit buckets make the Prometheus exporter emit a histogram, which can be aggregated across time, labels, and gateway replicas.
A
histogram
carries
no
minimum
or
maximum.
Each
series
is
therefore
recorded
a
second
time
under
<name>_window,
with
the
same
labels
and
no
configured
buckets.
The
exporter
renders
the
_window
series
as
a
rolling
summary
over
a
60
s
window.
Only
its
quantile
0
(min)
and
quantile
1
(max)
are
meant
to
be
read:
a
min
of
mins
and
a
max
of
maxes
are
exact
under
any
re-aggregation,
which
the
summary's
other
quantiles
are
not.
Quantiles
such
as
p50
and
p99
come
from
the
histogram.
The
rolling
window
means
a
min
or
max
can
appear
for
up
to
a
minute
after
the
sample
that
set
it.
Each
series
is
one
LatencySeries
constant
that
holds
its
name,
its
_window
name,
and
its
description.
LatencySeries::record
writes
a
sample
to
both
the
histogram
and
the
_window
summary.
The
Prometheus
builder
and
the
metric
descriptions
both
read
from
the
list
of
these
constants.
The
second
record
costs
about
the
same
as
the
first.
benches/latency_probe.rs
measures
one
probe
with
and
without
the
_window
record.
The gateway stores no individual samples. A gateway crash loses the observations made since the last scrape.
On every heartbeat tick, the session loop sends a WebSocket ping with an empty payload, then the application heartbeat event. The gateway does not measure the ping round trip.
The WebSocket library answers client pings. The session loop sends no pong of its own, so a client receives exactly one pong for each ping it sends.
og_rest_request_to_response_ms
does
not
include
writing
the
response
body.
CancelRejected
answer
takes,
how
long
a
cancel-all
takes
to
complete,
or
how
long
an
order
takes
to
fill.
The public SDK protocol has no latency fields. A client can measure its own round trip by timing each response against its request ID.
Unit tests cover:
_window
name
is
the
series
name
plus
_window;
Integration tests over the real WebSocket stack cover:
_window
series
renders
as
a
summary
with
quantiles
0
and
1;
| file | contents |
|---|---|
rs/sdk-internal/latency-series/src/lib.rs |
LatencySeries,
the
bucket
set,
Prometheus
builder;
shared
with
the
market
data
publisher |
rs/order-gateway/src/in_situ_latency_measurement.rs |
request
stamp,
session
ID,
series
recording,
the
LatencySeries
constants |
rs/order-gateway/benches/latency_probe.rs |
cost
of
one
probe,
with
and
without
the
_window
record |
rs/order-gateway/src/open_orders.rs |
request and cancel stamps on the open-orders entry |
rs/order-gateway/src/ws_service.rs |
session loop; creates request stamps and records the written series |
rs/order-gateway/src/rest_service.rs |
REST
timing
middleware
and
route
labels;
shared
order
paths
that
store
stamps
and
record
og_ws_order_to_ep3_ms |
rs/order-gateway/src/lib.rs |
drop-copy arrival instant; moves stamps from orders onto routed events |
rs/order-gateway/src/exchange_reject.rs |
attaches the handler's stamp to a reject the handler broadcasts |
rs/order-gateway/src/state.rs |
RoutedEvent
arrival
instant
and
request
stamp |