/src/freeradius-server/src/lib/io/channel.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 693d7f9ff179420537b600091c13173c70ef1eda $ |
19 | | * |
20 | | * @brief Two-way thread-safe channels. |
21 | | * @file io/channel.c |
22 | | * |
23 | | * @copyright 2016 Alan DeKok (aland@freeradius.org) |
24 | | */ |
25 | | RCSID("$Id: 693d7f9ff179420537b600091c13173c70ef1eda $") |
26 | | |
27 | | #include <freeradius-devel/io/channel.h> |
28 | | #include <freeradius-devel/util/debug.h> |
29 | | |
30 | | #ifdef HAVE_STDATOMIC_H |
31 | | # include <stdatomic.h> |
32 | | #else |
33 | | # include <freeradius-devel/util/stdatomic.h> |
34 | | #endif |
35 | | |
36 | | /* |
37 | | * Debugging, mainly for channel_test |
38 | | */ |
39 | | #ifdef DEBUG_CHANNEL |
40 | | #define MPRINT(...) fprintf(stdout, __VA_ARGS__) |
41 | | #else |
42 | | #define MPRINT(...) |
43 | | #endif |
44 | | |
45 | | typedef enum : uint8_t { |
46 | | TO_RESPONDER = 0, |
47 | | TO_REQUESTOR = 1 |
48 | | } fr_channel_direction_t; |
49 | | |
50 | | #ifdef DEBUG_CHANNEL |
51 | | static fr_table_num_sorted_t const channel_direction[] = { |
52 | | { L("to responder"), TO_RESPONDER }, |
53 | | { L("to requestor"), TO_REQUESTOR }, |
54 | | }; |
55 | | size_t channel_direction_len = NUM_ELEMENTS(channel_direction); |
56 | | #endif |
57 | | |
58 | | #if 0 |
59 | | #define SIGNAL_INTERVAL (1000000) //!< The minimum interval between responder signals. |
60 | | #endif |
61 | | |
62 | | /** Size of the atomic queues |
63 | | * |
64 | | * The queue reader MUST service the queue occasionally, |
65 | | * otherwise the writer will not be able to write. If it's too |
66 | | * low, the writer will fail. If it's too high, it will |
67 | | * unnecessarily use memory. So we're better off putting it on |
68 | | * the high side. |
69 | | * |
70 | | * The reader SHOULD service the queues at inter-packet latency. |
71 | | * i.e. at 1M pps, the queue will get serviced every microsecond. |
72 | | */ |
73 | 0 | #define ATOMIC_QUEUE_SIZE (1024) |
74 | | |
75 | | /** The payload of every channel control message |
76 | | * |
77 | | * Which event it is comes from the control message, not from the payload. |
78 | | */ |
79 | | typedef struct { |
80 | | uint64_t ack; //!< the sender's ack sequence, for data done. |
81 | | fr_channel_t *ch; //!< the channel |
82 | | fr_message_set_t *reply_ms; //!< the reply message set, for open only. |
83 | | } fr_channel_control_t; |
84 | | |
85 | | /** The callbacks one control plane's owner registered for channel events |
86 | | */ |
87 | | typedef struct { |
88 | | fr_control_t *control; //!< the control plane these were registered on |
89 | | fr_channel_callbacks_t cb; //!< what the owner does with each event |
90 | | void *uctx; //!< passed to each callback |
91 | | } fr_channel_hooks_t; |
92 | | |
93 | | /** One end of a channel |
94 | | * |
95 | | * Consists of a kqueue descriptor, and an atomic queue. |
96 | | * The atomic queue is there to get bulk data through, because it's more efficient |
97 | | * than pushing 1M+ events per second through a kqueue. |
98 | | */ |
99 | | typedef struct { |
100 | | fr_channel_direction_t direction; //!< Use for debug messages. |
101 | | |
102 | | fr_control_t *control; //!< The control plane we signal this end through. |
103 | | |
104 | | void *uctx; //!< Worker context. |
105 | | |
106 | | fr_channel_recv_callback_t recv; //!< callback for receiving messages |
107 | | void *recv_uctx; //!< context for receiving messages |
108 | | |
109 | | uint64_t sequence; //!< Sequence number for this channel. |
110 | | uint64_t ack; //!< Sequence number of the other end. |
111 | | uint64_t their_view_of_my_sequence; //!< Should be clear. |
112 | | |
113 | | uint64_t sequence_at_last_signal; //!< When we last signaled. |
114 | | |
115 | | fr_atomic_queue_t *aq; //!< The queue of messages - visible only to this channel. |
116 | | |
117 | | atomic_bool active; //!< Whether the channel is active. |
118 | | |
119 | | fr_channel_stats_t stats; //!< channel statistics |
120 | | } fr_channel_end_t; |
121 | | |
122 | | typedef struct fr_channel_s fr_channel_t; |
123 | | |
124 | | /** A full channel, which consists of two ends |
125 | | * |
126 | | * A channel consists of an I/O identifier that can be placed in kequeue |
127 | | * and an atomic queue in each direction to allow for bidirectional communication. |
128 | | */ |
129 | | struct fr_channel_s { |
130 | | fr_time_delta_t cpu_time; //!< Total time used by the responder for this channel. |
131 | | fr_time_delta_t processing_time; //!< Time spent by the responder processing requests. |
132 | | |
133 | | bool same_thread; //!< are both ends in the same thread? |
134 | | |
135 | | fr_channel_end_t end[2]; //!< Two ends of the channel. |
136 | | }; |
137 | | |
138 | | fr_table_num_sorted_t const channel_signals[] = { |
139 | | { L("close"), FR_CHANNEL_CONTROL_CLOSE }, |
140 | | { L("close-ack"), FR_CHANNEL_CONTROL_CLOSE_ACK }, |
141 | | { L("data-done-responder"), FR_CHANNEL_CONTROL_DATA_DONE_RESPONDER }, |
142 | | { L("data-to-requestor"), FR_CHANNEL_CONTROL_DATA_TO_REQUESTOR }, |
143 | | { L("data-to-responder"), FR_CHANNEL_CONTROL_DATA_TO_RESPONDER }, |
144 | | { L("open"), FR_CHANNEL_CONTROL_OPEN }, |
145 | | }; |
146 | | size_t channel_signals_len = NUM_ELEMENTS(channel_signals); |
147 | | |
148 | | fr_table_num_sorted_t const channel_packet_priority[] = { |
149 | | { L("high"), PRIORITY_HIGH }, |
150 | | { L("low"), PRIORITY_LOW }, |
151 | | { L("normal"), PRIORITY_NORMAL }, |
152 | | { L("now"), PRIORITY_NOW } |
153 | | }; |
154 | | size_t channel_packet_priority_len = NUM_ELEMENTS(channel_packet_priority); |
155 | | |
156 | | |
157 | | /** Create a new channel |
158 | | * |
159 | | * @param[in] ctx The talloc_ctx to allocate channel data in. |
160 | | * @param[in] requestor control plane. |
161 | | * @param[in] responder control plane. |
162 | | * @param[in] same whether or not the channel is for the same thread |
163 | | * @return |
164 | | * - NULL on error |
165 | | * - channel on success |
166 | | */ |
167 | | fr_channel_t *fr_channel_create(TALLOC_CTX *ctx, fr_control_t *requestor, fr_control_t *responder, bool same) |
168 | 0 | { |
169 | 0 | fr_time_t now; |
170 | 0 | fr_channel_t *ch; |
171 | |
|
172 | 0 | ch = talloc_zero(ctx, fr_channel_t); |
173 | 0 | if (!ch) { |
174 | 0 | nomem: |
175 | 0 | fr_strerror_const("Failed allocating memory"); |
176 | 0 | return NULL; |
177 | 0 | } |
178 | | |
179 | 0 | ch->same_thread = same; |
180 | |
|
181 | 0 | ch->end[TO_RESPONDER].direction = TO_RESPONDER; |
182 | 0 | ch->end[TO_REQUESTOR].direction = TO_REQUESTOR; |
183 | |
|
184 | 0 | ch->end[TO_RESPONDER].aq = fr_atomic_queue_talloc(ch, ATOMIC_QUEUE_SIZE, sizeof(uintptr_t)); |
185 | 0 | if (!ch->end[TO_RESPONDER].aq) { |
186 | 0 | talloc_free(ch); |
187 | 0 | goto nomem; |
188 | 0 | } |
189 | | |
190 | 0 | ch->end[TO_REQUESTOR].aq = fr_atomic_queue_talloc(ch, ATOMIC_QUEUE_SIZE, sizeof(uintptr_t)); |
191 | 0 | if (!ch->end[TO_REQUESTOR].aq) { |
192 | 0 | talloc_free(ch); |
193 | 0 | goto nomem; |
194 | 0 | } |
195 | | |
196 | 0 | ch->end[TO_RESPONDER].control = responder; |
197 | 0 | ch->end[TO_REQUESTOR].control = requestor; |
198 | | |
199 | | /* |
200 | | * Initialize all of the timers to now. |
201 | | */ |
202 | 0 | now = fr_time(); |
203 | |
|
204 | 0 | ch->end[TO_RESPONDER].stats.last_write = now; |
205 | 0 | ch->end[TO_RESPONDER].stats.last_read_other = now; |
206 | 0 | ch->end[TO_RESPONDER].stats.last_sent_signal = now; |
207 | 0 | atomic_store(&ch->end[TO_RESPONDER].active, true); |
208 | |
|
209 | 0 | ch->end[TO_REQUESTOR].stats.last_write = now; |
210 | 0 | ch->end[TO_REQUESTOR].stats.last_read_other = now; |
211 | 0 | ch->end[TO_REQUESTOR].stats.last_sent_signal = now; |
212 | 0 | atomic_store(&ch->end[TO_REQUESTOR].active, true); |
213 | |
|
214 | 0 | return ch; |
215 | 0 | } |
216 | | |
217 | | |
218 | | /** Send a message via a kq user signal |
219 | | * |
220 | | * Note that the caller doesn't care about data in the event, that is |
221 | | * sent via the atomic queue. The kevent code takes care of |
222 | | * delivering the signal once, even if it's sent by multiple requestor |
223 | | * threads. |
224 | | * |
225 | | * The thread watching the KQ knows which end it is. So when it gets |
226 | | * the signal (and the channel pointer) it knows to look at end[0] or |
227 | | * end[1]. We also send which end in 'which' (0, 1) to further help |
228 | | * the recipient. |
229 | | * |
230 | | * @param[in] ch the channel. |
231 | | * @param[in] when the data was ready. Typically taken from the message. |
232 | | * @param[in] end of the channel that the message was written to. |
233 | | * @param[in] ev the event to send. |
234 | | * @return |
235 | | * - <0 on error |
236 | | * - 0 on success |
237 | | */ |
238 | | static int fr_channel_data_ready(fr_channel_t *ch, fr_time_t when, fr_channel_end_t *end, fr_channel_control_event_t ev) |
239 | 0 | { |
240 | 0 | fr_channel_control_t cc; |
241 | |
|
242 | 0 | end->stats.last_sent_signal = when; |
243 | 0 | end->stats.signals++; |
244 | |
|
245 | 0 | cc.ack = end->ack; |
246 | 0 | cc.ch = ch; |
247 | 0 | cc.reply_ms = NULL; |
248 | |
|
249 | 0 | MPRINT("Signalling %s, with %s\n", |
250 | 0 | fr_table_str_by_value(channel_direction, end->direction, "<INVALID>"), |
251 | 0 | fr_table_str_by_value(channel_signals, ev, "<INVALID>")); |
252 | |
|
253 | 0 | return fr_control_message_send(end->control, FR_CONTROL_EVENTSET_CHANNEL, ev, &cc, sizeof(cc)); |
254 | 0 | } |
255 | | |
256 | | #define IALPHA (8) |
257 | 0 | #define RTT(_old, _new) fr_time_delta_wrap((fr_time_delta_unwrap(_new) + (fr_time_delta_unwrap(_old) * (IALPHA - 1))) / IALPHA) |
258 | | |
259 | | /** Send a request message into the channel |
260 | | * |
261 | | * The message should be initialized, other than "sequence" and "ack". |
262 | | * |
263 | | * This function automatically calls the recv_reply callback if there is a reply. |
264 | | * |
265 | | * @param[in] ch the channel to send the request on. |
266 | | * @param[in] cd the message to send. |
267 | | * @return |
268 | | * - <0 on error |
269 | | * - 0 on success |
270 | | */ |
271 | | int fr_channel_send_request(fr_channel_t *ch, fr_channel_data_t *cd) |
272 | 0 | { |
273 | 0 | uint64_t sequence; |
274 | 0 | fr_time_t when; |
275 | 0 | fr_time_delta_t message_interval; |
276 | 0 | fr_channel_end_t *requestor; |
277 | |
|
278 | 0 | if (!fr_cond_assert_msg(atomic_load(&ch->end[TO_RESPONDER].active), "Channel not active")) return -1; |
279 | | |
280 | | /* |
281 | | * Same thread? Just call the "recv" function directly. |
282 | | */ |
283 | 0 | if (ch->same_thread) { |
284 | 0 | ch->end[TO_REQUESTOR].recv(ch, cd, ch->end[TO_REQUESTOR].recv_uctx); |
285 | 0 | return 0; |
286 | 0 | } |
287 | | |
288 | 0 | requestor = &(ch->end[TO_RESPONDER]); |
289 | 0 | when = cd->m.when; |
290 | |
|
291 | 0 | sequence = requestor->sequence + 1; |
292 | 0 | cd->live.sequence = sequence; |
293 | 0 | cd->live.ack = requestor->ack; |
294 | | |
295 | | /* |
296 | | * Push the message onto the queue for the other end. If |
297 | | * the push fails, the caller should try another queue. |
298 | | */ |
299 | 0 | if (!fr_atomic_queue_spsc_push(requestor->aq, &cd)) { |
300 | 0 | fr_strerror_printf("Failed pushing to atomic queue - full. Queue contains %zu items", |
301 | 0 | fr_atomic_queue_size(requestor->aq)); |
302 | 0 | while (fr_channel_recv_reply(ch)); |
303 | 0 | return -1; |
304 | 0 | } |
305 | | |
306 | 0 | requestor->sequence = sequence; |
307 | 0 | message_interval = fr_time_sub(when, requestor->stats.last_write); |
308 | |
|
309 | 0 | if (!fr_time_delta_ispos(requestor->stats.message_interval)) { |
310 | 0 | requestor->stats.message_interval = message_interval; |
311 | 0 | } else { |
312 | 0 | requestor->stats.message_interval = RTT(requestor->stats.message_interval, message_interval); |
313 | 0 | } |
314 | |
|
315 | 0 | fr_assert_msg(fr_time_lteq(requestor->stats.last_write, when), |
316 | 0 | "Channel data timestamp (%" PRId64") older than last channel data sent (%" PRId64 ")", |
317 | 0 | fr_time_unwrap(when), fr_time_unwrap(requestor->stats.last_write)); |
318 | 0 | requestor->stats.last_write = when; |
319 | |
|
320 | 0 | requestor->stats.outstanding++; |
321 | 0 | requestor->stats.packets++; |
322 | |
|
323 | 0 | MPRINT("REQUESTOR requests %"PRIu64", num_outstanding %"PRIu64"\n", requestor->stats.packets, requestor->stats.outstanding); |
324 | | |
325 | | /* |
326 | | * The message is in the queue. If the responder is |
327 | | * awake it will find it before it next bsleeps, so |
328 | | * the signal is only needed if it is already asleep. |
329 | | */ |
330 | 0 | if (!fr_control_is_sleeping(requestor->control)) { |
331 | 0 | MPRINT("REQUESTOR SKIPS signal\n"); |
332 | 0 | return 0; |
333 | 0 | } |
334 | | |
335 | | /* |
336 | | * Tell the other end that there is new data ready. |
337 | | * |
338 | | * Ignore errors on signalling. The responder already has |
339 | | * the packet in its inbound queue, so at some point, it |
340 | | * will pick up the message. |
341 | | */ |
342 | 0 | MPRINT("REQUESTOR SIGNALS\n"); |
343 | 0 | (void) fr_channel_data_ready(ch, when, requestor, FR_CHANNEL_CONTROL_DATA_TO_RESPONDER); |
344 | 0 | return 0; |
345 | 0 | } |
346 | | |
347 | | /** Receive a reply message from the channel |
348 | | * |
349 | | * @param[in] ch the channel to read data from. |
350 | | * @return |
351 | | * - true if there was a message received |
352 | | * - false if there are no more messages |
353 | | */ |
354 | | bool fr_channel_recv_reply(fr_channel_t *ch) |
355 | 0 | { |
356 | 0 | fr_channel_data_t *cd; |
357 | 0 | fr_channel_end_t *requestor; |
358 | 0 | fr_atomic_queue_t *aq; |
359 | |
|
360 | 0 | fr_assert(ch->end[TO_RESPONDER].recv != NULL); |
361 | |
|
362 | 0 | aq = ch->end[TO_REQUESTOR].aq; |
363 | 0 | requestor = &(ch->end[TO_RESPONDER]); |
364 | | |
365 | | /* |
366 | | * It's OK for the queue to be empty. |
367 | | */ |
368 | 0 | if (!fr_atomic_queue_spsc_pop(aq, &cd)) return false; |
369 | | |
370 | | /* |
371 | | * We want an exponential moving average for round trip |
372 | | * time, where "alpha" is a number between [0,1) |
373 | | * |
374 | | * RTT_new = alpha * RTT_old + (1 - alpha) * RTT_sample |
375 | | * |
376 | | * BUT we use fixed-point arithmetic, so we need to use inverse alpha, |
377 | | * which works out to the following equation: |
378 | | * |
379 | | * RTT_new = (RTT_sample + (ialpha - 1) * RTT_old) / ialpha |
380 | | * |
381 | | * NAKs have zero processing time, so we ignore them for |
382 | | * the purpose of RTT. |
383 | | */ |
384 | 0 | if (fr_time_delta_ispos(cd->reply.processing_time)) { |
385 | 0 | ch->processing_time = RTT(ch->processing_time, cd->reply.processing_time); |
386 | 0 | } |
387 | 0 | ch->cpu_time = cd->reply.cpu_time; |
388 | | |
389 | | /* |
390 | | * Update the outbound channel with the knowledge that |
391 | | * we've received one more reply, and with the responders |
392 | | * ACK. |
393 | | */ |
394 | 0 | fr_assert(requestor->stats.outstanding > 0); |
395 | 0 | fr_assert(cd->live.sequence > requestor->ack); |
396 | 0 | fr_assert(cd->live.sequence <= requestor->sequence); /* must have fewer replies than requests */ |
397 | |
|
398 | 0 | requestor->stats.outstanding--; |
399 | 0 | requestor->ack = cd->live.sequence; |
400 | 0 | requestor->their_view_of_my_sequence = cd->live.ack; |
401 | |
|
402 | 0 | fr_assert(fr_time_lteq(requestor->stats.last_read_other, cd->m.when)); |
403 | 0 | requestor->stats.last_read_other = cd->m.when; |
404 | |
|
405 | 0 | ch->end[TO_RESPONDER].recv(ch, cd, ch->end[TO_RESPONDER].recv_uctx); |
406 | |
|
407 | 0 | return true; |
408 | 0 | } |
409 | | |
410 | | |
411 | | /** Receive a request message from the channel |
412 | | * |
413 | | * @param[in] ch the channel |
414 | | * @return |
415 | | * - true if there was a message received |
416 | | * - false if there are no more messages |
417 | | */ |
418 | | bool fr_channel_recv_request(fr_channel_t *ch) |
419 | 0 | { |
420 | 0 | fr_channel_data_t *cd; |
421 | 0 | fr_channel_end_t *responder; |
422 | 0 | fr_atomic_queue_t *aq; |
423 | |
|
424 | 0 | aq = ch->end[TO_RESPONDER].aq; |
425 | 0 | responder = &(ch->end[TO_REQUESTOR]); |
426 | | |
427 | | /* |
428 | | * It's OK for the queue to be empty. |
429 | | */ |
430 | 0 | if (!fr_atomic_queue_spsc_pop(aq, &cd)) return false; |
431 | | |
432 | 0 | fr_assert(cd->live.sequence > responder->ack); |
433 | 0 | fr_assert(cd->live.sequence >= responder->sequence); /* must have more requests than replies */ |
434 | |
|
435 | 0 | responder->stats.outstanding++; |
436 | 0 | responder->ack = cd->live.sequence; |
437 | 0 | responder->their_view_of_my_sequence = cd->live.ack; |
438 | |
|
439 | 0 | fr_assert(fr_time_lteq(responder->stats.last_read_other, cd->m.when)); |
440 | 0 | responder->stats.last_read_other = cd->m.when; |
441 | |
|
442 | 0 | ch->end[TO_REQUESTOR].recv(ch, cd, ch->end[TO_REQUESTOR].recv_uctx); |
443 | |
|
444 | 0 | return true; |
445 | 0 | } |
446 | | |
447 | | /** Send a reply message into the channel |
448 | | * |
449 | | * The message should be initialized, other than "sequence" and "ack". |
450 | | * |
451 | | * @param[in] ch the channel to send the reply on. |
452 | | * @param[in] cd the message to send |
453 | | * @return |
454 | | * - <0 on error |
455 | | * - 0 on success |
456 | | */ |
457 | | int fr_channel_send_reply(fr_channel_t *ch, fr_channel_data_t *cd) |
458 | 0 | { |
459 | 0 | uint64_t sequence; |
460 | 0 | fr_time_t when; |
461 | 0 | fr_time_delta_t message_interval; |
462 | 0 | fr_channel_end_t *responder; |
463 | |
|
464 | 0 | if (!fr_cond_assert_msg(atomic_load(&ch->end[TO_REQUESTOR].active), "Channel not active")) return -1; |
465 | | |
466 | | /* |
467 | | * Same thread? Just call the "recv" function directly. |
468 | | */ |
469 | 0 | if (ch->same_thread) { |
470 | 0 | ch->end[TO_RESPONDER].recv(ch, cd, ch->end[TO_RESPONDER].recv_uctx); |
471 | 0 | return 0; |
472 | 0 | } |
473 | | |
474 | 0 | responder = &(ch->end[TO_REQUESTOR]); |
475 | |
|
476 | 0 | when = cd->m.when; |
477 | |
|
478 | 0 | sequence = responder->sequence + 1; |
479 | 0 | cd->live.sequence = sequence; |
480 | 0 | cd->live.ack = responder->ack; |
481 | |
|
482 | 0 | if (!fr_atomic_queue_spsc_push(responder->aq, &cd)) { |
483 | 0 | fr_strerror_printf("Failed pushing to atomic queue - full. Queue contains %zu items", |
484 | 0 | fr_atomic_queue_size(responder->aq)); |
485 | 0 | while (fr_channel_recv_request(ch)); |
486 | 0 | return -1; |
487 | 0 | } |
488 | | |
489 | 0 | fr_assert(responder->stats.outstanding > 0); |
490 | 0 | responder->stats.outstanding--; |
491 | 0 | responder->stats.packets++; |
492 | |
|
493 | 0 | MPRINT("\tRESPONDER replies %"PRIu64", num_outstanding %"PRIu64"\n", responder->stats.packets, responder->stats.outstanding); |
494 | |
|
495 | 0 | responder->sequence = sequence; |
496 | 0 | message_interval = fr_time_sub(when, responder->stats.last_write); |
497 | 0 | if (!fr_time_delta_ispos(responder->stats.message_interval)) { |
498 | 0 | responder->stats.message_interval = message_interval; |
499 | 0 | } else { |
500 | 0 | responder->stats.message_interval = RTT(responder->stats.message_interval, message_interval); |
501 | 0 | } |
502 | |
|
503 | 0 | fr_assert_msg(fr_time_lteq(responder->stats.last_write, when), |
504 | 0 | "Channel data timestamp (%" PRId64") older than last channel data sent (%" PRId64 ")", |
505 | 0 | fr_time_unwrap(when), fr_time_unwrap(responder->stats.last_write)); |
506 | 0 | responder->stats.last_write = when; |
507 | | |
508 | | /* |
509 | | * Even if we think we have no more packets to process, |
510 | | * the caller may have sent us one. Go check the input |
511 | | * channel. |
512 | | */ |
513 | 0 | while (fr_channel_recv_request(ch)); |
514 | | |
515 | | /* |
516 | | * The reply is in the queue. If the requestor is awake |
517 | | * it will find it before it next blocks, so the signal is |
518 | | * only needed if it is already asleep. The mirror of the |
519 | | * check in fr_channel_send_request(). |
520 | | */ |
521 | 0 | if (!fr_control_is_sleeping(responder->control)) { |
522 | 0 | MPRINT("\tRESPONDER SKIPS signal\n"); |
523 | 0 | return 0; |
524 | 0 | } |
525 | | |
526 | | /* |
527 | | * No packets outstanding. Tell the requestor how far we |
528 | | * got, so it can re-signal us if it has sent more. |
529 | | */ |
530 | 0 | if (responder->stats.outstanding == 0) { |
531 | 0 | (void) fr_channel_data_ready(ch, when, responder, FR_CHANNEL_CONTROL_DATA_DONE_RESPONDER); |
532 | 0 | return 0; |
533 | 0 | } |
534 | | |
535 | 0 | MPRINT("\twhen - last_read_other = %"PRIu64" - %"PRIu64" = %"PRIu64"\n", when, responder->stats.last_read_other, when - responder->stats.last_read_other); |
536 | 0 | MPRINT("\twhen - last signal = %"PRIu64" - %"PRIu64" = %"PRIu64"\n", when, responder->stats.last_sent_signal, when - responder->stats.last_sent_signal); |
537 | 0 | MPRINT("\tsequence - ack = %"PRIu64" - %"PRIu64" = %"PRIu64"\n", responder->sequence, responder->their_view_of_my_sequence, responder->sequence - responder->their_view_of_my_sequence); |
538 | |
|
539 | | #ifdef __APPLE__ |
540 | | /* |
541 | | * If we've sent them a signal since the last ACK, they |
542 | | * will receive it, and process the packets. So we don't |
543 | | * need to signal them again. |
544 | | * |
545 | | * But... this doesn't appear to work on the Linux |
546 | | * libkqueue implementation. |
547 | | */ |
548 | | if (responder->sequence_at_last_signal > responder->their_view_of_my_sequence) return 0; |
549 | | #endif |
550 | | |
551 | | /* |
552 | | * If we've received a new packet in the last while, OR |
553 | | * we've sent a signal in the last while, then we don't |
554 | | * need to send a new signal. But we DO send a signal if |
555 | | * we haven't seen an ACK for a few packets. |
556 | | * |
557 | | * FIXME: make these limits configurable, or include |
558 | | * predictions about packet processing time? |
559 | | */ |
560 | 0 | fr_assert(responder->their_view_of_my_sequence <= responder->sequence); |
561 | | #if 0 |
562 | | if (((responder->sequence - their_view_of_my_sequence) <= 1000) && |
563 | | ((when - responder->stats.last_read_other < SIGNAL_INTERVAL) || |
564 | | ((when - responder->stats.last_sent_signal) < SIGNAL_INTERVAL))) { |
565 | | MPRINT("\tRESPONDER SKIPS signal\n"); |
566 | | return 0; |
567 | | } |
568 | | #endif |
569 | |
|
570 | 0 | MPRINT("\tRESPONDER SIGNALS num_outstanding %"PRIu64"\n", responder->stats.outstanding); |
571 | 0 | (void) fr_channel_data_ready(ch, when, responder, FR_CHANNEL_CONTROL_DATA_TO_REQUESTOR); |
572 | 0 | return 0; |
573 | 0 | } |
574 | | |
575 | | |
576 | | /** Don't send a reply message into the channel |
577 | | * |
578 | | * The message should be the one we received from the network. |
579 | | * |
580 | | * @param[in] ch the channel on which we're dropping a packet |
581 | | * @return |
582 | | * - <0 on error |
583 | | * - 0 on success |
584 | | */ |
585 | | int fr_channel_null_reply(fr_channel_t *ch) |
586 | 0 | { |
587 | 0 | fr_channel_end_t *responder; |
588 | |
|
589 | 0 | responder = &(ch->end[TO_REQUESTOR]); |
590 | |
|
591 | 0 | responder->sequence++; |
592 | 0 | return 0; |
593 | 0 | } |
594 | | |
595 | | |
596 | | |
597 | | /** Unpack the payload of a channel control message |
598 | | */ |
599 | | static inline void channel_message_unpack(fr_channel_control_t *cc, void const *data, NDEBUG_UNUSED size_t data_size) |
600 | 0 | { |
601 | 0 | fr_assert(data_size == sizeof(*cc)); |
602 | 0 | memcpy(cc, data, sizeof(*cc)); |
603 | 0 | } |
604 | | |
605 | | /** Drain the requests queued for the responder |
606 | | * |
607 | | * Runs on the responder's thread, so a signal which finds nothing is |
608 | | * counted in the responder's own stats. Each end's stats are only |
609 | | * ever written by that end's thread. |
610 | | */ |
611 | | static inline void channel_drain_requests(fr_channel_t *ch) |
612 | 0 | { |
613 | 0 | if (!fr_channel_recv_request(ch)) { |
614 | 0 | ch->end[TO_REQUESTOR].stats.spurious++; |
615 | 0 | return; |
616 | 0 | } |
617 | 0 | while (fr_channel_recv_request(ch)); |
618 | 0 | } |
619 | | |
620 | | /** Drain the replies queued for the requestor |
621 | | * |
622 | | * Runs on the requestor's thread, see channel_drain_requests(). |
623 | | */ |
624 | | static inline void channel_drain_replies(fr_channel_t *ch) |
625 | 0 | { |
626 | 0 | if (!fr_channel_recv_reply(ch)) { |
627 | 0 | ch->end[TO_RESPONDER].stats.spurious++; |
628 | 0 | return; |
629 | 0 | } |
630 | 0 | while (fr_channel_recv_reply(ch)); |
631 | 0 | } |
632 | | |
633 | | /** The requestor has queued requests for the responder |
634 | | * |
635 | | * Must arrive on the responder's control plane. |
636 | | */ |
637 | | static void channel_data_to_responder(void const *data, size_t data_size, UNUSED fr_time_t now, void *uctx) |
638 | | { |
639 | | fr_channel_hooks_t *hooks = talloc_get_type_abort(uctx, fr_channel_hooks_t); |
640 | | fr_channel_control_t cc; |
641 | | |
642 | | channel_message_unpack(&cc, data, data_size); |
643 | | MPRINT("channel got data_to_responder\n"); |
644 | | |
645 | | if (!fr_cond_assert(hooks->control == cc.ch->end[TO_RESPONDER].control)) return; |
646 | | channel_drain_requests(cc.ch); |
647 | | } |
648 | | |
649 | | /** The responder has queued replies for the requestor |
650 | | * |
651 | | * Must arrive on the requestor's control plane. |
652 | | */ |
653 | | static void channel_data_to_requestor(void const *data, size_t data_size, UNUSED fr_time_t now, void *uctx) |
654 | | { |
655 | | fr_channel_hooks_t *hooks = talloc_get_type_abort(uctx, fr_channel_hooks_t); |
656 | | fr_channel_control_t cc; |
657 | | |
658 | | channel_message_unpack(&cc, data, data_size); |
659 | | MPRINT("channel got data_to_requestor\n"); |
660 | | |
661 | | if (!fr_cond_assert(hooks->control == cc.ch->end[TO_REQUESTOR].control)) return; |
662 | | channel_drain_replies(cc.ch); |
663 | | } |
664 | | |
665 | | /** The responder has serviced its requests, and says how many it has seen |
666 | | * |
667 | | * Only sent by the responder. Replies may be waiting, so drain them. |
668 | | * First compare their ACK to the last sequence we sent, and if they |
669 | | * have not seen our latest, signal the responder to wake up. |
670 | | */ |
671 | | static void channel_data_done_responder(void const *data, size_t data_size, fr_time_t now, void *uctx) |
672 | 0 | { |
673 | 0 | fr_channel_hooks_t *hooks = talloc_get_type_abort(uctx, fr_channel_hooks_t); |
674 | 0 | fr_channel_control_t cc; |
675 | 0 | fr_channel_t *ch; |
676 | 0 | fr_channel_end_t *requestor; |
677 | |
|
678 | 0 | channel_message_unpack(&cc, data, data_size); |
679 | 0 | ch = cc.ch; |
680 | 0 | MPRINT("channel got data_done_responder\n"); |
681 | |
|
682 | 0 | if (!fr_cond_assert(hooks->control == ch->end[TO_REQUESTOR].control)) return; |
683 | | |
684 | 0 | requestor = &ch->end[TO_RESPONDER]; |
685 | | |
686 | | /* |
687 | | * We have told this end to close, so there is nothing left to |
688 | | * wake it for: fr_channel_send_request() refuses on an inactive |
689 | | * end, so no more data can be queued. Signalling it anyway |
690 | | * writes into the control plane which the responder may have |
691 | | * already freed (this has been observed and caused a crash-on-exit). |
692 | | * |
693 | | * Servicing the message is still correct: it was in flight |
694 | | * before the close, and we must keep servicing to receive the |
695 | | * ack at all. The load is relaxed because only this thread |
696 | | * clears "active" for this end. |
697 | | */ |
698 | 0 | if (unlikely(!atomic_load_explicit(&requestor->active, memory_order_relaxed))) goto drain; |
699 | | |
700 | | /* |
701 | | * Their ack covers everything we have sent. Anything |
702 | | * sent later signals for itself. |
703 | | */ |
704 | 0 | fr_assert(cc.ack <= requestor->sequence); |
705 | 0 | if (cc.ack == requestor->sequence) { |
706 | 0 | MPRINT("REQUESTOR SKIPS signal AFTER data_done_responder num_outstanding %"PRIu64"\n", |
707 | 0 | requestor->stats.outstanding); |
708 | 0 | goto drain; |
709 | 0 | } |
710 | | |
711 | | /* |
712 | | * They have not seen our latest. Each of those sends |
713 | | * already signalled, or was pushed while the responder was |
714 | | * awake and so will be found by its final drain. Signal |
715 | | * again only if it has since blocked: cheap insurance |
716 | | * against a dropped wakeup, free when it is running. |
717 | | */ |
718 | 0 | if (!fr_control_is_sleeping(requestor->control)) goto drain; |
719 | | |
720 | 0 | requestor->stats.resignals++; |
721 | |
|
722 | 0 | MPRINT("REQUESTOR SIGNALS AFTER data_done_responder\n"); |
723 | 0 | if (fr_channel_data_ready(ch, now, requestor, FR_CHANNEL_CONTROL_DATA_TO_RESPONDER) < 0) return; |
724 | | |
725 | 0 | drain: |
726 | 0 | channel_drain_replies(ch); |
727 | 0 | } |
728 | | |
729 | | /** A requestor has opened a channel to us |
730 | | */ |
731 | | static void channel_open(void const *data, size_t data_size, fr_time_t now, void *uctx) |
732 | | { |
733 | | fr_channel_hooks_t *hooks = talloc_get_type_abort(uctx, fr_channel_hooks_t); |
734 | | fr_channel_control_t cc; |
735 | | |
736 | | channel_message_unpack(&cc, data, data_size); |
737 | | MPRINT("channel got open\n"); |
738 | | |
739 | | if (!fr_cond_assert(hooks->cb.open)) return; |
740 | | hooks->cb.open(cc.ch, cc.reply_ms, now, hooks->uctx); |
741 | | } |
742 | | |
743 | | /** The requestor is closing a channel to us |
744 | | */ |
745 | | static void channel_close(void const *data, size_t data_size, fr_time_t now, void *uctx) |
746 | | { |
747 | | fr_channel_hooks_t *hooks = talloc_get_type_abort(uctx, fr_channel_hooks_t); |
748 | | fr_channel_control_t cc; |
749 | | |
750 | | channel_message_unpack(&cc, data, data_size); |
751 | | MPRINT("channel got close\n"); |
752 | | |
753 | | if (!fr_cond_assert(hooks->cb.close)) return; |
754 | | hooks->cb.close(cc.ch, now, hooks->uctx); |
755 | | } |
756 | | |
757 | | /** The responder has finished closing a channel we asked it to close |
758 | | */ |
759 | | static void channel_close_ack(void const *data, size_t data_size, fr_time_t now, void *uctx) |
760 | | { |
761 | | fr_channel_hooks_t *hooks = talloc_get_type_abort(uctx, fr_channel_hooks_t); |
762 | | fr_channel_control_t cc; |
763 | | |
764 | | channel_message_unpack(&cc, data, data_size); |
765 | | MPRINT("channel got close_ack\n"); |
766 | | |
767 | | if (!fr_cond_assert(hooks->cb.closed)) return; |
768 | | hooks->cb.closed(cc.ch, now, hooks->uctx); |
769 | | } |
770 | | |
771 | | /** Register the owner's channel callbacks on its control plane |
772 | | * |
773 | | * Call before fr_control_open(). Every channel event gets its |
774 | | * own entry in the FR_CONTROL_EVENTSET_CHANNEL event set, so the control |
775 | | * plane dispatches straight to the handler for that event. |
776 | | * |
777 | | * @param[in] c the control plane channels will signal. |
778 | | * @param[in] callbacks what the owner does with each event. Copied. |
779 | | * @param[in] uctx passed to each callback. |
780 | | * @return |
781 | | * - <0 on error |
782 | | * - 0 on success |
783 | | */ |
784 | | int fr_channel_callbacks_add(fr_control_t *c, fr_channel_callbacks_t const *callbacks, void *uctx) |
785 | 0 | { |
786 | 0 | fr_channel_hooks_t *hooks; |
787 | |
|
788 | 0 | MEM(hooks = talloc_zero(c, fr_channel_hooks_t)); |
789 | 0 | hooks->control = c; |
790 | 0 | hooks->cb = *callbacks; |
791 | 0 | hooks->uctx = uctx; |
792 | |
|
793 | 0 | if ((fr_control_callback_add(c, FR_CONTROL_EVENTSET_CHANNEL, FR_CHANNEL_CONTROL_DATA_TO_RESPONDER, |
794 | 0 | channel_data_to_responder, hooks) < 0) || |
795 | 0 | (fr_control_callback_add(c, FR_CONTROL_EVENTSET_CHANNEL, FR_CHANNEL_CONTROL_DATA_TO_REQUESTOR, |
796 | 0 | channel_data_to_requestor, hooks) < 0) || |
797 | 0 | (fr_control_callback_add(c, FR_CONTROL_EVENTSET_CHANNEL, FR_CHANNEL_CONTROL_DATA_DONE_RESPONDER, |
798 | 0 | channel_data_done_responder, hooks) < 0) || |
799 | 0 | (fr_control_callback_add(c, FR_CONTROL_EVENTSET_CHANNEL, FR_CHANNEL_CONTROL_OPEN, |
800 | 0 | channel_open, hooks) < 0) || |
801 | 0 | (fr_control_callback_add(c, FR_CONTROL_EVENTSET_CHANNEL, FR_CHANNEL_CONTROL_CLOSE, |
802 | 0 | channel_close, hooks) < 0) || |
803 | 0 | (fr_control_callback_add(c, FR_CONTROL_EVENTSET_CHANNEL, FR_CHANNEL_CONTROL_CLOSE_ACK, |
804 | 0 | channel_close_ack, hooks) < 0)) { |
805 | 0 | talloc_free(hooks); |
806 | 0 | return -1; |
807 | 0 | } |
808 | | |
809 | 0 | return 0; |
810 | 0 | } |
811 | | |
812 | | |
813 | | /** Service a control-plane event. |
814 | | * |
815 | | * The channels use control planes for internal signaling. Note that |
816 | | * the caller does NOT pass the channel into this function. Instead, |
817 | | * the channel is taken from the kevent. |
818 | | * |
819 | | * @param[in] ch The channel to service. |
820 | | * @param[in] c The control plane on which we received the kev. |
821 | | * @param[in] kev The kevent data, should get passed to the control plane. |
822 | | * @return |
823 | | * - <0 on error |
824 | | * - 0 on success |
825 | | */ |
826 | | int fr_channel_service_kevent(fr_channel_t *ch, fr_control_t *c, UNUSED struct kevent const *kev) |
827 | 0 | { |
828 | 0 | (void) talloc_get_type_abort(ch, fr_channel_t); |
829 | |
|
830 | 0 | if (c == ch->end[TO_RESPONDER].control) { |
831 | 0 | ch->end[TO_RESPONDER].stats.kevents++; |
832 | 0 | } else { |
833 | 0 | ch->end[TO_REQUESTOR].stats.kevents++; |
834 | 0 | } |
835 | |
|
836 | 0 | return 0; |
837 | 0 | } |
838 | | |
839 | | |
840 | | /** Check if a channel is active. |
841 | | * |
842 | | * A channel may be closed by either end. If so, it stays alive (but |
843 | | * inactive) until both ends acknowledge the close. |
844 | | * |
845 | | * @param[in] ch the channel |
846 | | * @return |
847 | | * - false the channel is closing. |
848 | | * - true the channel is active |
849 | | */ |
850 | | bool fr_channel_active(fr_channel_t *ch) |
851 | 0 | { |
852 | 0 | return atomic_load(&ch->end[TO_REQUESTOR].active) && atomic_load(&ch->end[TO_RESPONDER].active); |
853 | 0 | } |
854 | | |
855 | | /** Signal a responder that the channel is closing |
856 | | * |
857 | | * @param[in] ch The channel. |
858 | | * @return |
859 | | * - <0 on error |
860 | | * - 0 on success |
861 | | */ |
862 | | int fr_channel_signal_responder_close(fr_channel_t *ch) |
863 | 0 | { |
864 | 0 | int ret; |
865 | 0 | bool active; |
866 | 0 | fr_channel_control_t cc; |
867 | |
|
868 | 0 | active = atomic_load(&ch->end[TO_RESPONDER].active); |
869 | 0 | if (!active) return 0; /* Already signalled to close */ |
870 | | |
871 | 0 | atomic_store(&ch->end[TO_RESPONDER].active, false); /* Prevent further requests */ |
872 | |
|
873 | 0 | (void) talloc_get_type_abort(ch, fr_channel_t); |
874 | |
|
875 | 0 | cc.ack = 0; |
876 | 0 | cc.ch = ch; |
877 | 0 | cc.reply_ms = NULL; |
878 | |
|
879 | 0 | ret = fr_control_message_send(ch->end[TO_RESPONDER].control, FR_CONTROL_EVENTSET_CHANNEL, |
880 | 0 | FR_CHANNEL_CONTROL_CLOSE, &cc, sizeof(cc)); |
881 | |
|
882 | 0 | return ret; |
883 | 0 | } |
884 | | |
885 | | /** Discard any requests the requestor queued but we never received |
886 | | * |
887 | | * The messages belong to the requestor's message set, and it cannot reclaim them |
888 | | * until they are marked done. A responder that closes with requests still in |
889 | | * the queue would strand them there, and with them the memory they were |
890 | | * allocated from. |
891 | | * |
892 | | * @param[in] ch to discard the queued requests of. |
893 | | * @return the number of requests discarded. |
894 | | */ |
895 | | unsigned int fr_channel_responder_discard(fr_channel_t *ch) |
896 | 0 | { |
897 | 0 | fr_channel_data_t *cd; |
898 | 0 | fr_atomic_queue_t *aq = ch->end[TO_RESPONDER].aq; |
899 | 0 | unsigned int num = 0; |
900 | |
|
901 | 0 | while (fr_atomic_queue_spsc_pop(aq, &cd)) { |
902 | 0 | fr_message_done(&cd->m); |
903 | 0 | num++; |
904 | 0 | } |
905 | |
|
906 | 0 | return num; |
907 | 0 | } |
908 | | |
909 | | /** Acknowledge that the channel is closing |
910 | | * |
911 | | * @param[in] ch The channel. |
912 | | * @return |
913 | | * - <0 on error |
914 | | * - 0 on success |
915 | | */ |
916 | | int fr_channel_responder_ack_close(fr_channel_t *ch) |
917 | 0 | { |
918 | 0 | int ret; |
919 | 0 | bool active; |
920 | |
|
921 | 0 | fr_channel_control_t cc; |
922 | |
|
923 | 0 | active = atomic_load(&ch->end[TO_REQUESTOR].active); |
924 | 0 | if (!active) return 0; /* Already signalled to close */ |
925 | | |
926 | 0 | atomic_store(&ch->end[TO_REQUESTOR].active, false); /* Prevent further responses */ |
927 | |
|
928 | 0 | (void) talloc_get_type_abort(ch, fr_channel_t); |
929 | |
|
930 | 0 | cc.ack = 0; |
931 | 0 | cc.ch = ch; |
932 | 0 | cc.reply_ms = NULL; |
933 | |
|
934 | 0 | ret = fr_control_message_send(ch->end[TO_REQUESTOR].control, FR_CONTROL_EVENTSET_CHANNEL, |
935 | 0 | FR_CHANNEL_CONTROL_CLOSE_ACK, &cc, sizeof(cc)); |
936 | |
|
937 | 0 | return ret; |
938 | 0 | } |
939 | | |
940 | | /** Add responder-specific data to a channel |
941 | | * |
942 | | * @param[in] ch The channel. |
943 | | * @param[in] uctx The context to add. |
944 | | */ |
945 | | void fr_channel_responder_uctx_add(fr_channel_t *ch, void *uctx) |
946 | 0 | { |
947 | 0 | (void) talloc_get_type_abort(ch, fr_channel_t); |
948 | |
|
949 | 0 | ch->end[TO_REQUESTOR].uctx = uctx; |
950 | 0 | } |
951 | | |
952 | | |
953 | | /** Get responder-specific data from a channel |
954 | | * |
955 | | * @param[in] ch The channel. |
956 | | */ |
957 | | void *fr_channel_responder_uctx_get(fr_channel_t *ch) |
958 | 0 | { |
959 | 0 | (void) talloc_get_type_abort(ch, fr_channel_t); |
960 | |
|
961 | 0 | return ch->end[TO_REQUESTOR].uctx; |
962 | 0 | } |
963 | | |
964 | | |
965 | | /** Add network-specific data to a channel |
966 | | * |
967 | | * @param[in] ch The channel. |
968 | | * @param[in] uctx The context to add. |
969 | | */ |
970 | | void fr_channel_requestor_uctx_add(fr_channel_t *ch, void *uctx) |
971 | 0 | { |
972 | 0 | (void) talloc_get_type_abort(ch, fr_channel_t); |
973 | |
|
974 | 0 | ch->end[TO_RESPONDER].uctx = uctx; |
975 | 0 | } |
976 | | |
977 | | |
978 | | /** Get network-specific data from a channel |
979 | | * |
980 | | * @param[in] ch The channel. |
981 | | */ |
982 | | void *fr_channel_requestor_uctx_get(fr_channel_t *ch) |
983 | 0 | { |
984 | 0 | (void) talloc_get_type_abort(ch, fr_channel_t); |
985 | |
|
986 | 0 | return ch->end[TO_RESPONDER].uctx; |
987 | 0 | } |
988 | | |
989 | | |
990 | | int fr_channel_set_recv_reply(fr_channel_t *ch, fr_channel_recv_callback_t recv_reply, void *uctx) |
991 | 0 | { |
992 | 0 | ch->end[TO_RESPONDER].recv = recv_reply; |
993 | 0 | ch->end[TO_RESPONDER].recv_uctx = uctx; |
994 | |
|
995 | 0 | return 0; |
996 | 0 | } |
997 | | |
998 | | int fr_channel_set_recv_request(fr_channel_t *ch, fr_channel_recv_callback_t recv_request, void *uctx) |
999 | 0 | { |
1000 | 0 | ch->end[TO_REQUESTOR].recv = recv_request; |
1001 | 0 | ch->end[TO_REQUESTOR].recv_uctx = uctx; |
1002 | 0 | return 0; |
1003 | 0 | } |
1004 | | |
1005 | | /** Send a channel to a responder |
1006 | | * |
1007 | | * @param[in] ch The channel. |
1008 | | * @param[in] reply_ms The message set the responder allocates replies from. |
1009 | | * @return |
1010 | | * - <0 on error |
1011 | | * - 0 on success |
1012 | | */ |
1013 | | int fr_channel_signal_open(fr_channel_t *ch, fr_message_set_t *reply_ms) |
1014 | 0 | { |
1015 | 0 | fr_channel_control_t cc; |
1016 | |
|
1017 | 0 | cc.ack = 0; |
1018 | 0 | cc.ch = ch; |
1019 | 0 | cc.reply_ms = reply_ms; |
1020 | |
|
1021 | 0 | return fr_control_message_send(ch->end[TO_RESPONDER].control, FR_CONTROL_EVENTSET_CHANNEL, |
1022 | 0 | FR_CHANNEL_CONTROL_OPEN, &cc, sizeof(cc)); |
1023 | 0 | } |
1024 | | |
1025 | | void fr_channel_stats_log(fr_channel_t const *ch, fr_log_t const *log, char const *file, int line) |
1026 | 0 | { |
1027 | 0 | fr_log(log, L_INFO, file, line, "requestor\n"); |
1028 | 0 | fr_log(log, L_INFO, file, line, "\tsignals sent = %" PRIu64 "\n", ch->end[TO_RESPONDER].stats.signals); |
1029 | 0 | fr_log(log, L_INFO, file, line, "\tsignals re-sent = %" PRIu64 "\n", ch->end[TO_RESPONDER].stats.resignals); |
1030 | 0 | fr_log(log, L_INFO, file, line, "\tsignals received empty = %" PRIu64 "\n", |
1031 | 0 | ch->end[TO_RESPONDER].stats.spurious); |
1032 | 0 | fr_log(log, L_INFO, file, line, "\tkevents checked = %" PRIu64 "\n", ch->end[TO_RESPONDER].stats.kevents); |
1033 | 0 | fr_log(log, L_INFO, file, line, "\toutstanding = %" PRIu64 "\n", ch->end[TO_RESPONDER].stats.outstanding); |
1034 | 0 | fr_log(log, L_INFO, file, line, "\tpackets processed = %" PRIu64 "\n", ch->end[TO_RESPONDER].stats.packets); |
1035 | 0 | fr_log(log, L_INFO, file, line, "\tmessage interval (RTT) = %" PRId64 "\n", fr_time_delta_unwrap(ch->end[TO_RESPONDER].stats.message_interval)); |
1036 | 0 | fr_log(log, L_INFO, file, line, "\tlast write = %" PRId64 "\n", fr_time_unwrap(ch->end[TO_RESPONDER].stats.last_write)); |
1037 | 0 | fr_log(log, L_INFO, file, line, "\tlast read other end = %" PRId64 "\n", fr_time_unwrap(ch->end[TO_RESPONDER].stats.last_read_other)); |
1038 | 0 | fr_log(log, L_INFO, file, line, "\tlast signal other = %" PRId64 "\n", fr_time_unwrap(ch->end[TO_RESPONDER].stats.last_sent_signal)); |
1039 | |
|
1040 | 0 | fr_log(log, L_INFO, file, line, "responder\n"); |
1041 | 0 | fr_log(log, L_INFO, file, line, "\tsignals sent = %" PRIu64"\n", ch->end[TO_REQUESTOR].stats.signals); |
1042 | 0 | fr_log(log, L_INFO, file, line, "\tsignals received empty = %" PRIu64 "\n", |
1043 | 0 | ch->end[TO_REQUESTOR].stats.spurious); |
1044 | 0 | fr_log(log, L_INFO, file, line, "\tkevents checked = %" PRIu64 "\n", ch->end[TO_REQUESTOR].stats.kevents); |
1045 | 0 | fr_log(log, L_INFO, file, line, "\tpackets processed = %" PRIu64 "\n", ch->end[TO_REQUESTOR].stats.packets); |
1046 | 0 | fr_log(log, L_INFO, file, line, "\tmessage interval (RTT) = %" PRId64 "\n", fr_time_delta_unwrap(ch->end[TO_REQUESTOR].stats.message_interval)); |
1047 | 0 | fr_log(log, L_INFO, file, line, "\tlast write = %" PRId64 "\n", fr_time_unwrap(ch->end[TO_REQUESTOR].stats.last_write)); |
1048 | 0 | fr_log(log, L_INFO, file, line, "\tlast read other end = %" PRId64 "\n", fr_time_unwrap(ch->end[TO_REQUESTOR].stats.last_read_other)); |
1049 | 0 | fr_log(log, L_INFO, file, line, "\tlast signal other = %" PRId64 "\n", fr_time_unwrap(ch->end[TO_REQUESTOR].stats.last_sent_signal)); |
1050 | 0 | } |
1051 | | |
1052 | | /** Statistics for the requestor's end of a channel |
1053 | | * |
1054 | | * Signals the requestor sent, and signals it received which found nothing. |
1055 | | */ |
1056 | | fr_channel_stats_t const *fr_channel_requestor_stats(fr_channel_t const *ch) |
1057 | 0 | { |
1058 | 0 | return &ch->end[TO_RESPONDER].stats; |
1059 | 0 | } |
1060 | | |
1061 | | /** Statistics for the responder's end of a channel |
1062 | | */ |
1063 | | fr_channel_stats_t const *fr_channel_responder_stats(fr_channel_t const *ch) |
1064 | 0 | { |
1065 | 0 | return &ch->end[TO_REQUESTOR].stats; |
1066 | 0 | } |