FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
wire_latency.hpp
Go to the documentation of this file.
1#pragma once
2// Network-thread order latency: what happens to an outbound order after the engine pushed it
3// into the venue's outbound ring. Per order message the venue takes three rdtscp stamps
4// (before encoding, after encoding, after the WebSocket/REST send call returned) and records
5//
6// encode JSON encoding and signing
7// send the send call (TLS + WebSocket write, or queueing the REST request)
8// tick_to_trade after-send minus the header's t0_cycles, the network thread's receive stamp
9// of the inbound event that triggered the order (only when t0_cycles != 0)
10//
11// Histograms are in TSC cycles and owned by the network thread; summarize() converts to ns
12// with a calibration when the venue publishes its status, for the whole session and for the last
13// minute and the last hour (core RecentHistogram), so a recent tail shows under a long session's
14// totals. Recording never allocates.
15//
16// Coalesced sends (drain_outbound_coalesced): between begin_batch() and end_batch() record()
17// keeps the encode stamps of up to kMaxBatch orders and ignores its after-send stamp, since the
18// send call only queued the bytes; end_batch() records every kept order with the stamp taken
19// after the one write of the batch returned.
20#include "fastmm/core/config_macros.hpp"
21#include "fastmm/core/latency.hpp"
23#include "fastmm/core/msg_ring.hpp"
24#include "fastmm/core/time.hpp"
26
27#include <array>
28#include <cstddef>
29#include <cstdint>
30#include <span>
31
32namespace fastmm::venues {
33
34struct WireLatencyStats {
35 std::uint64_t count = 0;
36 std::uint64_t p50_ns = 0;
37 std::uint64_t p99_ns = 0;
38 std::uint64_t p999_ns = 0;
39 std::uint64_t max_ns = 0;
40};
42// The three over one window (WireLatencyRecorder::summarize).
43struct WireLatencyWindow {
50 public:
51 static constexpr std::size_t kMaxBatch = 64;
52
53 FASTMM_FORCE_INLINE void record(Cycles t0,
54 Cycles before_encode,
55 Cycles after_encode,
56 Cycles after_send) noexcept {
57 if (batching_ && batch_len_ < kMaxBatch) {
58 batch_[batch_len_++] = Staged{t0, before_encode, after_encode};
59 return;
60 }
61 record_now(t0, before_encode, after_encode, after_send);
62 }
63
64 void begin_batch() noexcept {
65 batching_ = true;
66 batch_len_ = 0;
67 }
68 [[nodiscard]] bool batch_full() const noexcept { return batch_len_ == kMaxBatch; }
69 // `sent`: the batch's write succeeded (or was queued); false drops the kept stamps.
70 void end_batch(Cycles after_send, bool sent) noexcept {
71 batching_ = false;
72 if (sent) {
73 for (std::size_t i = 0; i < batch_len_; ++i) {
74 const Staged& s = batch_[i];
75 record_now(s.t0, s.before_encode, s.after_encode, after_send);
76 }
77 }
78 batch_len_ = 0;
79 }
80
81 // The session's samples.
82 [[nodiscard]] LogLinearHistogram encode() const noexcept { return encode_.total(); }
83 [[nodiscard]] LogLinearHistogram send() const noexcept { return send_.total(); }
84 [[nodiscard]] LogLinearHistogram tick_to_trade() const noexcept { return tick_to_trade_.total(); }
86 // Percentiles in ns under `c`'s TSC rate (also set when only constant_tsc is present);
87 // without a rate only the counts are filled in. The session's samples.
88 void summarize(const TscCalibration& c,
91 WireLatencyStats& send) const noexcept {
92 fill(tick_to_trade_.total(), c, tick_to_trade);
93 fill(encode_.total(), c, encode);
94 fill(send_.total(), c, send);
95 }
96 // The same, after closing the slices `now_ns` (the venue's clock) has passed, and for the last
97 // minute and the last hour too.
98 void summarize(const TscCalibration& c,
99 std::int64_t now_ns,
103 WireLatencyWindow& last_minute,
104 WireLatencyWindow& last_hour) noexcept {
105 tick_to_trade_.roll(now_ns);
106 encode_.roll(now_ns);
107 send_.roll(now_ns);
109 fill(tick_to_trade_.last_minute(), c, last_minute.tick_to_trade);
110 fill(encode_.last_minute(), c, last_minute.encode);
111 fill(send_.last_minute(), c, last_minute.send);
112 fill(tick_to_trade_.last_hour(), c, last_hour.tick_to_trade);
113 fill(encode_.last_hour(), c, last_hour.encode);
114 fill(send_.last_hour(), c, last_hour.send);
115 }
116
117 void reset() noexcept {
118 encode_.reset();
119 send_.reset();
120 tick_to_trade_.reset();
121 }
122
123 private:
124 struct Staged {
125 Cycles t0;
126 Cycles before_encode;
127 Cycles after_encode;
128 };
129
130 FASTMM_FORCE_INLINE void record_now(Cycles t0,
131 Cycles before_encode,
132 Cycles after_encode,
133 Cycles after_send) noexcept {
134 encode_.record(delta(after_encode, before_encode));
135 send_.record(delta(after_send, after_encode));
136 if (t0.v != 0) tick_to_trade_.record(delta(after_send, t0));
137 }
138 [[nodiscard]] static std::uint64_t delta(Cycles later, Cycles earlier) noexcept {
139 return later.v > earlier.v ? later.v - earlier.v : 0;
140 }
141 [[nodiscard]] static std::uint64_t to_ns(std::uint64_t cycles, const TscCalibration& c) noexcept {
142 return tsc_interval_ns(c, cycles); // 0 without a TSC rate
143 }
144 static void fill(const LogLinearHistogram& h,
145 const TscCalibration& c,
146 WireLatencyStats& out) noexcept {
147 out.count = h.count();
148 out.p50_ns = to_ns(h.percentile(0.50), c);
149 out.p99_ns = to_ns(h.percentile(0.99), c);
150 out.p999_ns = to_ns(h.percentile(0.999), c);
151 out.max_ns = to_ns(h.max(), c);
152 }
153
154 RecentHistogram encode_;
155 RecentHistogram send_;
156 RecentHistogram tick_to_trade_;
157 Staged batch_[kMaxBatch] = {};
158 std::size_t batch_len_ = 0;
159 bool batching_ = false;
160};
161
162// The orders a venue encoded into a corked connection. Between cork() and uncork() a frame is
163// only queued, so a failed uncork() means none of them reached the venue and the venue must
164// reject them instead of leaving the engine with orders it believes are live. Only the fields a
165// reject needs are kept, because the ring slots are released before uncork() runs.
166//
167// An order whose frame filled the send buffer mid-batch did leave (send_frame() flushes then),
168// and gets rejected here too. That is the safe error: uncork() only fails when the connection
169// failed, and the venue answers that by cancelling everything over the control path.
170class BatchedOrders {
171 public:
172 struct Entry {
176 ClientOrderId orig_cl_ord_id{}; // Replace only
177 };
179 void clear() noexcept { len_ = 0; }
180 void note(const OrderCommand& c) noexcept {
181 if (len_ < entries_.size())
182 entries_[len_++] = Entry{c.kind, c.instrument, c.cl_ord_id, c.orig_cl_ord_id};
183 }
184 [[nodiscard]] std::span<const Entry> entries() const noexcept { return {entries_.data(), len_}; }
185
186 private:
187 std::array<Entry, WireLatencyRecorder::kMaxBatch> entries_{};
188 std::size_t len_ = 0;
189};
190
191// The engine's batch handed over directly (run-to-completion, Venue::send_now) behind the
192// consumer side of MsgRing, so drain_outbound_coalesced takes either.
193class OutboundBatch {
194 public:
195 explicit OutboundBatch(std::span<const EventHeader* const> batch) noexcept : batch_(batch) {}
196 [[nodiscard]] const std::byte* try_peek() const noexcept {
197 return next_ < batch_.size() ? reinterpret_cast<const std::byte*>(batch_[next_]) : nullptr;
199 void release() noexcept { ++next_; }
200
201 private:
202 std::span<const EventHeader* const> batch_;
203 std::size_t next_ = 0;
204};
205
206// Drains the engine's outbound ring (or an OutboundBatch) in batches of at most kMaxBatch
207// messages: cork() holds the connection's writes back, on_message(header) encodes and "sends" each
208// message into the connection's buffer, uncork() writes the batch with one system call and returns
209// false when the connection failed (the venue rejects the batch's orders there, see
210// BatchedOrders). Every order of a batch gets the stamp taken after uncork() returned.
211template <class Ring, class Cork, class OnMessage, class Uncork>
213 Ring& ring, WireLatencyRecorder& wire, Cork&& cork, OnMessage&& on_message, Uncork&& uncork) {
214 while (ring.try_peek() != nullptr) {
215 cork();
216 wire.begin_batch();
217 for (std::size_t n = 0; n < WireLatencyRecorder::kMaxBatch; ++n) {
218 const std::byte* p = ring.try_peek();
219 if (p == nullptr) break;
220 on_message(*reinterpret_cast<const EventHeader*>(p));
221 ring.release();
222 }
223 const bool sent = uncork();
224 wire.end_batch(rdtscp(), sent);
225 }
226}
227
228} // namespace fastmm::venues
void note(const OrderCommand &c) noexcept
Definition wire_latency.hpp:182
std::span< const Entry > entries() const noexcept
Definition wire_latency.hpp:186
void clear() noexcept
Definition wire_latency.hpp:181
The engine's batch handed over directly (run-to-completion, Venue::send_now) behind the consumer side...
Definition wire_latency.hpp:195
const std::byte * try_peek() const noexcept
Definition wire_latency.hpp:198
void release() noexcept
Definition wire_latency.hpp:201
Definition wire_latency.hpp:51
void reset() noexcept
Definition wire_latency.hpp:119
void begin_batch() noexcept
Definition wire_latency.hpp:66
static constexpr std::size_t kMaxBatch
Definition wire_latency.hpp:53
LogLinearHistogram send() const noexcept
Definition wire_latency.hpp:85
LogLinearHistogram encode() const noexcept
The session's samples.
Definition wire_latency.hpp:84
void summarize(const TscCalibration &c, WireLatencyStats &tick_to_trade, WireLatencyStats &encode, WireLatencyStats &send) const noexcept
Percentiles in ns under c's TSC rate (also set when only constant_tsc is present); without a rate onl...
Definition wire_latency.hpp:90
FASTMM_FORCE_INLINE void record(Cycles t0, Cycles before_encode, Cycles after_encode, Cycles after_send) noexcept
Definition wire_latency.hpp:55
void end_batch(Cycles after_send, bool sent) noexcept
sent: the batch's write succeeded (or was queued); false drops the kept stamps.
Definition wire_latency.hpp:72
LogLinearHistogram tick_to_trade() const noexcept
Definition wire_latency.hpp:86
bool batch_full() const noexcept
Definition wire_latency.hpp:70
Event bus message structs (5.5).
Definition venue.hpp:38
void drain_outbound_coalesced(Ring &ring, WireLatencyRecorder &wire, Cork &&cork, OnMessage &&on_message, Uncork &&uncork)
Drains the engine's outbound ring (or an OutboundBatch) in batches of at most kMaxBatch messages: cor...
Definition wire_latency.hpp:214
OrderCommandKind
Definition order_commands.hpp:15
FASTMM_FORCE_INLINE std::uint64_t tsc_interval_ns(const TscCalibration &c, std::uint64_t cycles) noexcept
A TSC cycle interval in ns; 0 without a rate.
Definition time.hpp:101
FASTMM_FORCE_INLINE Cycles rdtscp() noexcept
Serialising read (rdtscp) is what latency hops want: it waits for prior loads to retire.
Definition time.hpp:66
OrderCommand: a uniform, non-owning view over the three outbound engine messages (OutNewOrderMsg / Ou...
Definition time.hpp:59
Definition messages.hpp:37
Definition time.hpp:89
Definition wire_latency.hpp:174
InstrumentId instrument
Definition wire_latency.hpp:176
OrderCommandKind kind
Definition wire_latency.hpp:175
ClientOrderId cl_ord_id
Definition wire_latency.hpp:177
ClientOrderId orig_cl_ord_id
Replace only.
Definition wire_latency.hpp:178
Definition order_commands.hpp:28
Definition wire_latency.hpp:36
std::uint64_t p99_ns
Definition wire_latency.hpp:39
std::uint64_t max_ns
Definition wire_latency.hpp:41
std::uint64_t p50_ns
Definition wire_latency.hpp:38
std::uint64_t count
Definition wire_latency.hpp:37
std::uint64_t p999_ns
Definition wire_latency.hpp:40
The three over one window (WireLatencyRecorder::summarize).
Definition wire_latency.hpp:45
WireLatencyStats tick_to_trade
Definition wire_latency.hpp:46
WireLatencyStats send
Definition wire_latency.hpp:48
WireLatencyStats encode
Definition wire_latency.hpp:47
Time primitives.