E2SAR 0.4.0
Loading...
Searching...
No Matches
e2sarDPSegmenter.hpp
1#ifndef E2SARDSEGMENTERPHPP
2#define E2SARDSEGMENTERPHPP
3
4#include <sys/types.h>
5#include <sys/socket.h>
6
7#ifdef LIBURING_AVAILABLE
8#include <liburing.h>
9#endif
10
11#include <boost/asio.hpp>
12#include <boost/lockfree/queue.hpp>
13#include <boost/pool/pool.hpp>
14#include <boost/pool/object_pool.hpp>
15#include <boost/thread.hpp>
16#include <boost/tuple/tuple.hpp>
17#include <boost/tuple/tuple_io.hpp>
18#include <boost/circular_buffer.hpp>
19#include <boost/any.hpp>
20#include <boost/asio/ip/udp.hpp>
21#include <boost/variant.hpp>
22#include <boost/random.hpp>
23#include <boost/chrono.hpp>
24
25#include <atomic>
26
27#include "e2sar.hpp"
28#include "e2sarUtil.hpp"
29#include "e2sarHeaders.hpp"
30#include "e2sarNetUtil.hpp"
31#include "portable_endian.h"
32
33/***
34 * Dataplane definitions for E2SAR Segmenter
35*/
36
37namespace e2sar
38{
39 /*
40 The Segmenter class knows how to break up the provided
41 events into segments consumable by the hardware loadbalancer.
42 It relies on header structures to segment into UDP packets and
43 follows other LB rules while doing it.
44
45 It runs on or next to the source of events.
46 */
48 {
49 friend class Reassembler;
50 private:
51 EjfatURI dpuri;
52 // unique identifier of the originating segmentation
53 // point (e.g. a DAQ), carried in RE header (could be persistent
54 // as set here, or specified per event buffer)
55 const u_int16_t dataId;
56 // unique identifier of an individual LB packet transmitting
57 // host/daq, 32-bit to accommodate IP addresses more easily
58 // carried in Sync header
59 const u_int32_t eventSrcId;
60
61 // number of send sockets we will be using (to help randomize LAG ports on FPGAs)
62 const size_t numSendSockets;
63
64 // send socket buffer size for setsockop
65 const int sndSocketBufSize;
66
67 // send rate (ignore if negative)
68 const float rateGbps;
69 // used to avoid floating point comparisons, set to false if rateGbps <= 0
70 const bool rateLimit;
71 // use smoothing rate shaping, i.e. only in sendmsg wait after every call
72 // WARNING: Incompatible with optimizations that send entire batches of
73 // frames to the kernel, i.e. sendMmsg and io_uring
74 const bool smooth;
75 // which LB header version are we using
76 const u_int8_t lbHdrVersion;
77 // sync address family: false=IPv4 (default), true=IPv6
78 const bool syncV6;
79
80 // size of CQE batch we peek
81 static constexpr unsigned cqeBatchSize{100};
82
83 // how long data send thread spends sleeping
84 static constexpr boost::chrono::milliseconds sleepTime{1};
85
86 // Structure to hold each send-queue item
87 struct EventQueueItem {
88 uint32_t bytes;
89 EventNum_t eventNum;
90 u_int16_t dataId;
91 u_int8_t *event;
92 u_int16_t entropy; // optional per event entropy
93 void (*callback)(boost::any);
94 boost::any cbArg;
95 };
96
97 // Fast, lock-free, wait-free queue (supports multiple producers/consumers)
98 boost::lockfree::queue<EventQueueItem*, boost::lockfree::fixed_sized<true>> eventQueue;
99
100#ifdef LIBURING_AVAILABLE
101 std::vector<struct io_uring> rings;
102 std::vector<boost::mutex> ringMtxs;
103
104 // each ring has to have a predefined size - we want to
105 // put at least 2*eventSize/bufferSize entries onto it
106 const size_t uringSize = 1000;
107
108 // we need to be able to call the callback from the CQE thread
109 // instead of send thread when liburing optimization is turned on
110 struct SQEUserData
111 {
112 struct msghdr* msghdr;
113 void (*callback)(boost::any);
114 boost::any cbArg;
115 };
116#endif
117
118 // structure that maintains send stats
119 struct SendStats {
120 // Last time a sync message sent to CP in nanosec since epoch.
121 UnixTimeNano_t lastSyncTimeNanos;
122 // Number of events sent since last sync message sent to CP.
123 EventNum_t eventsSinceLastSync;
124 };
125 // event metadata fifo to keep track of stats
126 boost::circular_buffer<SendStats> eventStatsBuffer;
127 // keep these atomic, as they are accessed by Sync, Send (and maybe main) thread
128 boost::atomic<UnixTimeNano_t> currentSyncStartNano{0};
129 boost::atomic<EventNum_t> eventsInCurrentSync{0};
130
131 // currently user-assigned or sequential event number at enqueuing and reported in RE header
132 boost::atomic<EventNum_t> userEventNum{0};
133
134 // fast random number generator
135 boost::random::ranlux24_base ranlux;
136 // to get better entropy in usec clock samples (if needed)
137 boost::random::uniform_int_distribution<> lsbDist{0, 255};
138
139 // we RR through these FDs for send thread _send
140 size_t roundRobinIndex{0};
141
146 struct AtomicStats {
147 // sync messages sent
148 std::atomic<u_int64_t> msgCnt{0};
149 // sync errors seen on send
150 std::atomic<u_int64_t> errCnt{0};
151 // last error code
152 std::atomic<int> lastErrno{0};
153 // last e2sar error
154 std::atomic<E2SARErrorc> lastE2SARError{E2SARErrorc::NoError};
155 };
156 // independent stats for each thread
157 AtomicStats syncStats;
158 AtomicStats sendStats;
159
163 struct SyncThreadState {
164 // owner object
165 Segmenter &seg;
166 boost::thread threadObj;
167 // period in ms
168 const u_int16_t period_ms{100};
169 // connect socket flag (usually true)
170 const bool connectSocket{true};
171 // sockaddr_in[6] union (use boost::get<sockaddr_in> or
172 // boost::get<sockaddr_in6> to get to the appropriate structure)
173#define GET_V4_SYNC_STRUCT(sas) boost::get<sockaddr_in>(sas)
174#define GET_V6_SYNC_STRUCT(sas) boost::get<sockaddr_in6>(sas)
175 boost::variant<sockaddr_in, sockaddr_in6> syncAddrStruct;
176 // flag that tells us we are v4 or v6
177 bool isV6{false};
178 // UDP sockets
179 int socketFd{0};
180
181 inline SyncThreadState(Segmenter &s, u_int16_t time_period_ms, bool cnct=true):
182 seg{s},
183 period_ms{time_period_ms},
184 connectSocket{cnct}
185 {}
186
187 result<int> _open();
188 result<int> _close();
189 result<int> _send(SyncHdr *hdr);
190 void _threadBody();
191
192
193 };
194 friend struct SyncThreadState;
195
196 SyncThreadState syncThreadState;
197
202 struct SendThreadState {
203 // owner object
204 Segmenter &seg;
205 boost::thread threadObj;
206 // thread index (to help pick core)
207 int threadIndex;
208 // connect socket flag (usually true)
209 const bool connectSocket{true};
210
211 // flags
212 const bool useV6;
213 const bool ticksAsREEventNum;
214
215 // transmit parameters
216 size_t mtu{0}; // must accommodate typical IP, UDP, LB+RE headers and payload; not a const because we may change it
217 std::string iface{""}; // outgoing interface - we may set it if possible
218 size_t maxPldLen; // not a const because mtu is not a const
219
220 // UDP sockets and matching sockaddr structures (local and remote)
221 // <socket fd, local address, remote address>
222#define GET_FD(sas, i) boost::get<0>(sas[i])
223#define GET_LOCAL_SEND_STRUCT(sas,i) boost::get<1>(sas[i])
224#define GET_REMOTE_SEND_STRUCT(sas, i) boost::get<2>(sas[i])
225 std::vector<boost::tuple<int, sockaddr_in, sockaddr_in>> socketFd4;
226 std::vector<boost::tuple<int, sockaddr_in6, sockaddr_in6>> socketFd6;
227
228 // fast random number generator to create entropy values for events
229 // this entropy value is held the same for all packets of a given
230 // event guaranteeing the same destination UDP port for all of them
231 boost::random::ranlux24_base ranlux;
232 boost::random::uniform_int_distribution<> randDist{0, std::numeric_limits<u_int16_t>::max()};
233 // to get random port numbers we skip low numbered privileged ports
234 boost::random::uniform_int_distribution<> portDist{10000, std::numeric_limits<u_int16_t>::max()};
235
236 inline SendThreadState(Segmenter &s, int idx, bool v6, u_int16_t mtu, bool tasreenum, bool cnct=true):
237 seg{s}, threadIndex{idx}, connectSocket{cnct}, useV6{v6}, ticksAsREEventNum{tasreenum}, mtu{mtu},
238 maxPldLen{mtu - getTotalHeaderLength(v6)}, socketFd4(s.numSendSockets),
239 socketFd6(s.numSendSockets),
240 ranlux{static_cast<u_int32_t>(std::time(0))}
241 {
242 // this way every segmenter send thread has a unique PRNG sequence
243 auto nowT = boost::chrono::system_clock::now();
244 ranlux.seed(boost::chrono::duration_cast<boost::chrono::nanoseconds>(nowT.time_since_epoch()).count());
245 }
246
247 // open v4/v6 sockets
248 result<int> _open();
249 // close sockets
250 result<int> _close();
251 // close a given socket, wait that it has sent all the data (in Linux)
252 result<int> _waitAndCloseFd(int fd);
253 // fragment and send the event
254 result<int> _send(u_int8_t *event, size_t bytes, EventNum_t altEventNum, u_int16_t dataId,
255 u_int16_t entropy, size_t roundRobinIndex, int64_t interFrameSleepUsec = 0,
256 void (*callback)(boost::any) = nullptr, boost::any cbArg = nullptr);
257 // thread loop
258 void _threadBody();
259#ifdef LIBURING_AVAILABLE
260 // reap CQEs if LIBURING is used
261 void _reap(size_t roundRobinIndex);
262#endif
263 };
264 friend struct SendThreadState;
265
266 SendThreadState sendThreadState;
267 const size_t numSendThreads{1};
268 // list of cores we can use to run threads
269 // can be longer than the number of threads
270 // thread at index i uses core cpuCoreList[i]
271 // we don't check cores are unique
272 const std::vector<int> cpuCoreList;
273
274#ifdef LIBURING_AVAILABLE
275 // this is the sleep time for kernel thread in poll mode
276 // it is in milliseconds
277 static constexpr unsigned pollWaitTime{2000};
278 // atomic counter of outstanging sends
279 boost::atomic<u_int32_t> outstandingSends{0};
280#endif
281
282 // lock with send thread
283 boost::mutex sendThreadMtx;
284 // condition variable for send thread
285 //boost::condition_variable sendThreadCond;
286 // warm up period in MS between sync thread starting and data being allowed to be sent
287 u_int16_t warmUpMs;
288 // use control plane (can be disabled for debugging)
289 bool useCP;
290#define MIN_CLOCK_ENTROPY 6
291 bool addEntropy;
292
296 inline void sanityChecks()
297 {
298 if (numSendSockets > 128)
299 throw E2SARException("Too many sending sockets threads requested, limit 128");
300
301 if (syncThreadState.period_ms > 10000)
302 throw E2SARException("Sync period too long, limit 10s");
303
304 if (sendThreadState.mtu > 9000)
305 throw E2SARException("MTU set too long, limit 9000");
306
307 if (useCP and not dpuri.has_syncAddr())
308 throw E2SARException("Sync address not present in the URI");
309
310 if (not dpuri.has_dataAddr())
311 throw E2SARException("Data address is not present in the URI");
312
313 if (sendThreadState.mtu <= getTotalHeaderLength(sendThreadState.useV6))
314 throw E2SARErrorInfo{E2SARErrorc::SocketError, "Insufficient MTU length to accommodate headers"};
315 }
316
318 bool threadsStop{false};
320 bool syncThreadStop{false};
321 public:
331 u_int64_t msgCnt;
332 u_int64_t errCnt;
333 int lastErrno;
334 E2SARErrorc lastE2SARError;
335
336 ReportedStats() = delete;
337 ReportedStats(const AtomicStats &as): msgCnt{as.msgCnt}, errCnt{as.errCnt},
338 lastErrno{as.lastErrno}, lastE2SARError{as.lastE2SARError}
339 {}
340 };
341
369 {
370 bool dpV6;
371 bool connectedSocket;
372 bool useCP;
373 u_int16_t warmUpMs;
374 u_int16_t syncPeriodMs;
375 u_int16_t syncPeriods;
376 u_int16_t mtu;
377 size_t eventQueueSize;
378 size_t numSendSockets;
379 int sndSocketBufSize;
380 float rateGbps;
381 bool smooth;
382 bool ticksAsREEventNum;
383 u_int8_t lbHdrVersion;
384 bool syncV6;
385
386 SegmenterFlags(): dpV6{false}, connectedSocket{true},
387 useCP{true}, warmUpMs{1000}, syncPeriodMs{1000}, syncPeriods{2}, mtu{1500},
388 eventQueueSize{2047}, numSendSockets{4},sndSocketBufSize{1024*1024*3}, rateGbps{-1.0}, smooth{false},
389 ticksAsREEventNum{false}, lbHdrVersion{lbhdrVersion2}, syncV6{false} {}
394 static result<SegmenterFlags> getFromINI(const std::string &iniFile) noexcept;
395 };
406 Segmenter(const EjfatURI &uri, u_int16_t dataId, u_int32_t eventSrcId,
407 std::vector<int> cpuCoreList,
408 const SegmenterFlags &sflags=SegmenterFlags());
409
419 Segmenter(const EjfatURI &uri, u_int16_t dataId, u_int32_t eventSrcId,
420 const SegmenterFlags &sflags=SegmenterFlags());
421
425 Segmenter(const Segmenter &s) = delete;
429 Segmenter & operator=(const Segmenter &o) = delete;
430
435 {
436 stopThreads();
437
438#ifdef LIBURING_AVAILABLE
439 if (Optimizations::isSelected(Optimizations::Code::liburing_send))
440 {
441 for (size_t i = 0; i < rings.size(); ++i)
442 {
443 io_uring_unregister_files(&rings[i]);
444 // deallocate the ring
445 io_uring_queue_exit(&rings[i]);
446 }
447 }
448#endif
449 // pool memory is implicitly freed when pool goes out of scope
450 }
451
457 result<int> openAndStart() noexcept;
458
468 result<int> sendEvent(u_int8_t *event, size_t bytes, EventNum_t _eventNumber=0LL,
469 u_int16_t _dataId=0, u_int16_t _entropy=0) noexcept;
470
482 result<int> addToSendQueue(u_int8_t *event, size_t bytes,
483 EventNum_t _eventNum=0LL, u_int16_t _dataId = 0, u_int16_t entropy=0,
484 void (*callback)(boost::any) = nullptr,
485 boost::any cbArg = nullptr) noexcept;
486
490 inline const ReportedStats getSyncStats() const noexcept
491 {
492 return ReportedStats(syncStats);
493 }
494
498 inline const ReportedStats getSendStats() const noexcept
499 {
500 return ReportedStats(sendStats);
501 }
502
506 inline const std::string getIntf() const noexcept
507 {
508 return sendThreadState.iface;
509 }
510
514 inline u_int16_t getMTU() const noexcept
515 {
516 return sendThreadState.mtu;
517 }
518
522 inline size_t getMaxPldLen() const noexcept
523 {
524 return sendThreadState.maxPldLen;
525 }
526
530 inline bool isUsingIPv6() const noexcept
531 {
532 return sendThreadState.useV6;
533 }
534 /*
535 * Tell threads to stop
536 */
537 inline void stopThreads()
538 {
539 if (not threadsStop)
540 {
541 // wait until queue empties
542 while (not eventQueue.empty()) {}
543
544 // tell sending threads to stop and
545 // wait till they are done
546 threadsStop = true;
547 sendThreadState.threadObj.join();
548 // now we can stop the sync thread
549 syncThreadStop = true;
550 syncThreadState.threadObj.join();
551 }
552 }
553 private:
554 // Calculate the average event rate from circular buffer
555 // note that locking lives outside this function, as needed.
556 // NOTE: this is only useful to sync messages if sequential
557 // event IDs are used. When usec timestamp is used for LB event numbers
558 // the event is constant 1 MHz
559 inline EventRate_t eventRate(UnixTimeNano_t currentTimeNanos)
560 {
561 // no rate to report
562 if (eventStatsBuffer.size() == 0)
563 return 1;
564 EventNum_t eventTotal{0LL};
565 // walk the circular buffer
566 for(auto el: eventStatsBuffer)
567 {
568 // add up the events
569 eventTotal += el.eventsSinceLastSync;
570 }
571 auto timeDiff = currentTimeNanos -
572 eventStatsBuffer.begin()->lastSyncTimeNanos;
573
574 // convert to Hz and return
575 //return (eventTotal*1000000000UL)/timeDiff;
576 // this uses floating point but is more accurate at low rates
577 return std::round(static_cast<float>(eventTotal*1000000000UL)/timeDiff);
578 }
579
580 // doesn't require locking as it looks at only srcId in segmenter, which
581 // never changes past initialization
582 inline void fillSyncHdr(SyncHdr *hdr, UnixTimeNano_t tnano)
583 {
584 EventRate_t reportedRate{1000000};
585 // figure out what event number would be at this moment, don't worry about its entropy
586 auto nowT = boost::chrono::system_clock::now();
587 // Convert the time point to microseconds since the epoch
588 EventNum_t reportedEventNum = boost::chrono::duration_cast<boost::chrono::microseconds>(nowT.time_since_epoch()).count();
589 hdr->set(eventSrcId, reportedEventNum, reportedRate, tnano);
590 }
591
599 inline int_least64_t addClockEntropy(int_least64_t clockSample)
600 {
601 return (clockSample & ~0xFF) | lsbDist(ranlux);
602 }
603 };
604}
605#endif
Definition e2sarError.hpp:62
Definition e2sarUtil.hpp:59
const bool has_syncAddr() const
Definition e2sarUtil.hpp:325
const bool has_dataAddr() const
Definition e2sarUtil.hpp:319
static const bool isSelected(Code o) noexcept
Definition e2sarUtil.cpp:153
Definition e2sarDPReassembler.hpp:49
Definition e2sarDPSegmenter.hpp:48
result< int > addToSendQueue(u_int8_t *event, size_t bytes, EventNum_t _eventNum=0LL, u_int16_t _dataId=0, u_int16_t entropy=0, void(*callback)(boost::any)=nullptr, boost::any cbArg=nullptr) noexcept
Definition e2sarDPSegmenter.cpp:965
~Segmenter()
Definition e2sarDPSegmenter.hpp:434
bool isUsingIPv6() const noexcept
Definition e2sarDPSegmenter.hpp:530
const ReportedStats getSyncStats() const noexcept
Definition e2sarDPSegmenter.hpp:490
result< int > openAndStart() noexcept
Definition e2sarDPSegmenter.cpp:162
const std::string getIntf() const noexcept
Definition e2sarDPSegmenter.hpp:506
Segmenter(const EjfatURI &uri, u_int16_t dataId, u_int32_t eventSrcId, std::vector< int > cpuCoreList, const SegmenterFlags &sflags=SegmenterFlags())
Definition e2sarDPSegmenter.cpp:27
result< int > sendEvent(u_int8_t *event, size_t bytes, EventNum_t _eventNumber=0LL, u_int16_t _dataId=0, u_int16_t _entropy=0) noexcept
Definition e2sarDPSegmenter.cpp:946
size_t getMaxPldLen() const noexcept
Definition e2sarDPSegmenter.hpp:522
Segmenter(const Segmenter &s)=delete
u_int16_t getMTU() const noexcept
Definition e2sarDPSegmenter.hpp:514
Segmenter & operator=(const Segmenter &o)=delete
const ReportedStats getSendStats() const noexcept
Definition e2sarDPSegmenter.hpp:498
Definition e2sar.hpp:11
E2SARErrorc
Definition e2sarError.hpp:24
Definition e2sarError.hpp:42
Definition e2sarDPSegmenter.hpp:330
Definition e2sarDPSegmenter.hpp:369
static result< SegmenterFlags > getFromINI(const std::string &iniFile) noexcept
Definition e2sarDPSegmenter.cpp:995
Definition e2sarHeaders.hpp:324