The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
load.c
Go to the documentation of this file.
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: 54eac1de9329e784e91377e84c8aa1e5e36dd81a $
19  *
20  * @brief Load generation algorithms
21  * @file io/load.c
22  *
23  * @copyright 2019 Network RADIUS SAS (legal@networkradius.com)
24  */
25 RCSID("$Id: 54eac1de9329e784e91377e84c8aa1e5e36dd81a $")
26 
27 #include <freeradius-devel/io/load.h>
28 
29 /*
30  * We use *inverse* numbers to avoid numerical calculation issues.
31  *
32  * i.e. The bad way is to take two small numbers divide them by
33  * alpha / beta and then add them. That process can drop the
34  * lower digits. Instead, we take two small numbers, add them,
35  * and then divide the result by alpha / beta.
36  */
37 #define IBETA (4)
38 #define IALPHA (8)
39 
40 #define DIFF(_rtt, _t) \
41  (\
42  fr_time_delta_lt(_rtt, _t) ? \
43  fr_time_delta_sub(_t, _rtt) : \
44  fr_time_delta_sub(_rtt, _t)\
45  )
46 
47 #define RTTVAR(_rtt, _rttvar, _t) \
48  fr_time_delta_div(\
49  fr_time_delta_add(\
50  fr_time_delta_mul(_rttvar, fr_time_delta_wrap(IBETA - 1)), \
51  DIFF(_rtt, _t)\
52  ), \
53  fr_time_delta_wrap(IBETA)\
54  )
55 
56 #define RTT(_old, _new) fr_time_delta_wrap((fr_time_delta_unwrap(_new) + (fr_time_delta_unwrap(_old) * (IALPHA - 1))) / IALPHA)
57 
58 typedef enum {
64 
65 struct fr_load_s {
70  void *uctx;
71 
72  fr_load_stats_t stats; //!< sending statistics
73  fr_time_t step_start; //!< when the current step started
74  fr_time_t step_end; //!< when the current step will end
76 
78  fr_time_delta_t delta; //!< between packets
79 
81  bool header; //!< for printing statistics
82 
83  fr_time_t next; //!< The next time we're supposed to send a packet
85 };
86 
88  fr_load_callback_t callback, void *uctx)
89 {
90  fr_load_t *l;
91 
92  l = talloc_zero(ctx, fr_load_t);
93  if (!l) return NULL;
94 
95  if (!config->start_pps) config->start_pps = 1;
96  if (!config->milliseconds) config->milliseconds = 1000;
97  if (!config->parallel) config->parallel = 1;
98 
99  l->el = el;
100  l->config = config;
101  l->callback = callback;
102  l->uctx = uctx;
103 
104  return l;
105 }
106 
107 /** Send one or more packets.
108  *
109  */
111 {
112  int i;
113 
114  /*
115  * Send as many packets as necessary.
116  */
117  l->stats.sent += count;
118  l->stats.last_send = now;
119 
120  /*
121  * Run the callback AFTER we set the timer. Which makes
122  * it more likely that the next timer fires on time.
123  */
124  for (i = 0; i < count; i++) {
126  }
127 }
128 
129 static void load_timer(fr_event_list_t *el, fr_time_t now, void *uctx)
130 {
131  fr_load_t *l = uctx;
132  fr_time_delta_t delta;
133  int count;
134 
135  /*
136  * Keep track of the overall maximum backlog for the
137  * duration of the entire test run.
138  */
139  l->stats.backlog = l->stats.sent - l->stats.received;
141 
142  /*
143  * If we're done this step, go to the next one.
144  */
145  if (fr_time_gteq(l->next, l->step_end)) {
146  l->step_start = l->next;
147  l->step_end = fr_time_add(l->next, l->config->duration);
148  l->step_received = l->stats.received;
149  l->pps += l->config->step;
150  l->stats.pps = l->pps;
151  l->stats.skipped = 0;
153 
154  /*
155  * Stop at max PPS, if it's set. Otherwise
156  * continue without limit.
157  */
158  if (l->config->max_pps && (l->pps > l->config->max_pps)) {
160  return;
161  }
162  }
163 
164  /*
165  * We don't have "pps" packets in the backlog, go send
166  * some more. We scale the backlog by 1000 milliseconds
167  * per second. Then, multiple the PPS by the number of
168  * milliseconds of backlog we want to keep.
169  *
170  * If the backlog is smaller than packets/s *
171  * milliseconds of backlog, then keep sending.
172  * Otherwise, switch to a gated mode where we only send
173  * new packets once a reply comes in.
174  */
175  if (((uint32_t) l->stats.backlog * 1000) < (l->pps * l->config->milliseconds)) {
177  l->stats.blocked = false;
178  count = l->config->parallel;
179  l->stats.skipped = 0;
180 
181  /*
182  * Limit "count" so that it doesn't over-run backlog.
183  */
184  if (((uint32_t) ((count + l->stats.backlog) * 1000)) > (l->pps * l->config->milliseconds)) {
185  count = (count + l->stats.backlog) - ((l->pps * l->config->milliseconds) / 1000);
186  }
187 
188  } else {
189 
190  /*
191  * We have too many packets in the backlog, we're
192  * gated. Don't send more packets until we have
193  * a reply.
194  *
195  * Note that we will send *these* packets.
196  */
198  l->stats.blocked = true;
199  count = 0;
200  l->stats.skipped += l->count;
201  }
202 
203  /*
204  * Skip timers if we're too busy.
205  */
206  l->next = fr_time_add(l->next, l->delta);
207  if (fr_time_lt(l->next, now)) {
208  while (fr_time_lt(fr_time_add(l->next, l->delta), now)) {
209 // l->stats.skipped += l->count;
210  l->next = fr_time_add(l->next, l->delta);
211  }
212  }
213  delta = fr_time_sub(l->next, now);
214 
215  /*
216  * Set the timer for the next packet.
217  */
218  if (fr_event_timer_in(l, el, &l->ev, delta, load_timer, l) < 0) {
220  return;
221  }
222 
223  if (count) fr_load_generator_send(l, now, count);
224 }
225 
226 
227 /** Start the load generator.
228  *
229  */
231 {
232  l->stats.start = fr_time();
233  l->step_start = l->stats.start;
235 
236  l->pps = l->config->start_pps;
237  l->stats.pps = l->pps;
238  l->count = l->config->parallel;
239 
241  l->next = fr_time_add(l->step_start, l->delta);
242 
243  load_timer(l->el, l->step_start, l);
244  return 0;
245 }
246 
247 
248 /** Stop the load generation through the simple expedient of deleting
249  * the timer associated with it.
250  *
251  */
253 {
254  if (!l->ev) return 0;
255 
256  return fr_event_timer_delete(&l->ev);
257 }
258 
259 
260 /** Tell the load generator that we have a reply to a packet we sent.
261  *
262  */
264 {
265  fr_time_t now;
266  fr_time_delta_t t;
267 
268  /*
269  * Note that the replies may come out of order with
270  * respect to the request. So we can't use this reply
271  * for any kind of timing.
272  */
273  now = fr_time();
274  t = fr_time_sub(now, request_time);
275 
276  l->stats.rttvar = RTTVAR(l->stats.rtt, l->stats.rttvar, t);
277  l->stats.rtt = RTT(l->stats.rtt, t);
278 
279  l->stats.received++;
280 
281  /*
282  * t is in nanoseconds.
283  */
284  if (fr_time_delta_lt(t, fr_time_delta_wrap(1000))) {
285  l->stats.times[0]++; /* < microseconds */
286  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(10000))) {
287  l->stats.times[1]++; /* microseconds */
288  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(100000))) {
289  l->stats.times[2]++; /* 10s of microseconds */
290  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(1000000))) {
291  l->stats.times[3]++; /* 100s of microseconds */
292  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(10000000))) {
293  l->stats.times[4]++; /* milliseconds */
294  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(100000000))) {
295  l->stats.times[5]++; /* 10s of milliseconds */
296  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(NSEC))) {
297  l->stats.times[6]++; /* 100s of milliseconds */
298  } else {
299  l->stats.times[7]++; /* seconds */
300  }
301 
302  /*
303  * Still sending packets. Rely on the timer to send more
304  * packets.
305  */
306  if (l->state == FR_LOAD_STATE_SENDING) return FR_LOAD_CONTINUE;
307 
308  /*
309  * The send code has decided that the backlog is too
310  * high. New requests are blocked until replies come in.
311  * Since we have a reply, send another request.
312  */
313  if (l->state == FR_LOAD_STATE_GATED) {
314  if (l->stats.skipped > 0) {
315  l->stats.skipped--;
316  fr_load_generator_send(l, now, 1);
317  }
318  return FR_LOAD_CONTINUE;
319  }
320 
321  /*
322  * We're still sending or gated, tell the caller to
323  * continue.
324  */
325  if (l->state != FR_LOAD_STATE_DRAINING) {
326  return FR_LOAD_CONTINUE;
327  }
328  /*
329  * Not yet received all replies. Wait until we have all
330  * replies.
331  */
332  if (l->stats.received < l->stats.sent) return FR_LOAD_CONTINUE;
333 
334  l->stats.end = now;
335  return FR_LOAD_DONE;
336 }
337 
338 /** Print load generator statistics in CVS format.
339  *
340  */
341 size_t fr_load_generator_stats_sprint(fr_load_t *l, fr_time_t now, char *buffer, size_t buflen)
342 {
343  double now_f, last_send_f;
344 
345  if (!l->header) {
346  l->header = true;
347  return snprintf(buffer, buflen, "\"time\",\"last_packet\",\"rtt\",\"rttvar\",\"pps\",\"pps_accepted\",\"sent\",\"received\",\"backlog\",\"max_backlog\",\"<usec\",\"us\",\"10us\",\"100us\",\"ms\",\"10ms\",\"100ms\",\"s\",\"blocked\"\n");
348  }
349 
350 
351  now_f = fr_time_delta_unwrap(fr_time_sub(now, l->stats.start)) / (double)NSEC;
352 
353  last_send_f = fr_time_delta_unwrap(fr_time_sub(l->stats.last_send, l->stats.start)) / (double)NSEC;
354 
355  /*
356  * Track packets/s. Since times are in nanoseconds, we
357  * have to scale the counters up by NSEC. And since NSEC
358  * is 1B, the calculations have to be done via 64-bit
359  * numbers, and then converted to a final 32-bit counter.
360  */
361  if (fr_time_gt(now, l->step_start)) {
362  l->stats.pps_accepted =
365  fr_time_sub(now, l->step_start))
366  );
367  }
368 
369  return snprintf(buffer, buflen,
370  "%f,%f,"
371  "%" PRIu64 ",%" PRIu64 ","
372  "%d,%d,"
373  "%d,%d,"
374  "%d,%d,"
375  "%d,%d,%d,%d,%d,%d,%d,%d,"
376  "%d\n",
377  now_f, last_send_f,
379  l->stats.pps, l->stats.pps_accepted,
380  l->stats.sent, l->stats.received,
382  l->stats.times[0], l->stats.times[1], l->stats.times[2], l->stats.times[3],
383  l->stats.times[4], l->stats.times[5], l->stats.times[6], l->stats.times[7],
384  l->stats.blocked);
385 }
386 
388 {
389  return &l->stats;
390 }
static int const char char buffer[256]
Definition: acutest.h:574
#define RCSID(id)
Definition: build.h:444
#define fr_event_timer_in(...)
Definition: event.h:255
int fr_event_timer_delete(fr_event_timer_t const **ev_p)
Delete a timer event from the event list.
Definition: event.c:1604
Stores all information relating to an event list.
Definition: event.c:411
A timer event.
Definition: event.c:102
static void load_timer(fr_event_list_t *el, fr_time_t now, void *uctx)
Definition: load.c:129
int step_received
Definition: load.c:75
static void fr_load_generator_send(fr_load_t *l, fr_time_t now, int count)
Send one or more packets.
Definition: load.c:110
void * uctx
Definition: load.c:70
fr_load_t * fr_load_generator_create(TALLOC_CTX *ctx, fr_event_list_t *el, fr_load_config_t *config, fr_load_callback_t callback, void *uctx)
Definition: load.c:87
#define RTTVAR(_rtt, _rttvar, _t)
Definition: load.c:47
#define RTT(_old, _new)
Definition: load.c:56
fr_time_t step_start
when the current step started
Definition: load.c:73
fr_time_t step_end
when the current step will end
Definition: load.c:74
fr_load_callback_t callback
Definition: load.c:69
fr_time_delta_t delta
between packets
Definition: load.c:78
fr_load_state_t state
Definition: load.c:66
fr_event_timer_t const * ev
Definition: load.c:84
fr_time_t next
The next time we're supposed to send a packet.
Definition: load.c:83
int fr_load_generator_start(fr_load_t *l)
Start the load generator.
Definition: load.c:230
int fr_load_generator_stop(fr_load_t *l)
Stop the load generation through the simple expedient of deleting the timer associated with it.
Definition: load.c:252
fr_event_list_t * el
Definition: load.c:67
fr_load_state_t
Definition: load.c:58
@ FR_LOAD_STATE_SENDING
Definition: load.c:60
@ FR_LOAD_STATE_GATED
Definition: load.c:61
@ FR_LOAD_STATE_INIT
Definition: load.c:59
@ FR_LOAD_STATE_DRAINING
Definition: load.c:62
fr_load_stats_t stats
sending statistics
Definition: load.c:72
fr_load_config_t const * config
Definition: load.c:68
bool header
for printing statistics
Definition: load.c:81
uint32_t pps
Definition: load.c:77
uint32_t count
Definition: load.c:80
size_t fr_load_generator_stats_sprint(fr_load_t *l, fr_time_t now, char *buffer, size_t buflen)
Print load generator statistics in CVS format.
Definition: load.c:341
fr_load_reply_t fr_load_generator_have_reply(fr_load_t *l, fr_time_t request_time)
Tell the load generator that we have a reply to a packet we sent.
Definition: load.c:263
fr_load_stats_t const * fr_load_generator_stats(fr_load_t const *l)
Definition: load.c:387
Definition: load.c:65
int sent
total packets sent
Definition: load.h:89
fr_time_t last_send
last packet we sent
Definition: load.h:84
fr_time_delta_t rtt
smoothed round trip time
Definition: load.h:85
int times[8]
response time in microseconds to tens of seconds
Definition: load.h:95
int received
total packets received (should be == sent)
Definition: load.h:90
int(* fr_load_callback_t)(fr_time_t now, void *uctx)
Definition: load.h:109
int skipped
we skipped sending this number of packets
Definition: load.h:91
bool blocked
whether or not we're blocked
Definition: load.h:94
fr_time_t end
when the test started
Definition: load.h:83
int pps_accepted
Accepted PPS for the last second.
Definition: load.h:88
uint32_t start_pps
start PPS
Definition: load.h:73
int pps
current offered packets/s
Definition: load.h:87
fr_load_reply_t
Whether or not the application should continue.
Definition: load.h:103
@ FR_LOAD_DONE
the load generator is done
Definition: load.h:105
@ FR_LOAD_CONTINUE
continue sending packets.
Definition: load.h:104
fr_time_t start
Definition: load.h:82
uint32_t parallel
how many packets in parallel to send
Definition: load.h:77
fr_time_delta_t duration
duration of each step
Definition: load.h:75
int backlog
current backlog
Definition: load.h:92
uint32_t step
how much to increase each load test by
Definition: load.h:76
int max_backlog
maximum backlog we saw during the test
Definition: load.h:93
fr_time_delta_t rttvar
RTT variation.
Definition: load.h:86
uint32_t max_pps
max PPS, 0 for "no limit".
Definition: load.h:74
uint32_t milliseconds
how many milliseconds of backlog to top out at
Definition: load.h:78
Load generation configuration.
Definition: load.h:72
unsigned int uint32_t
Definition: merged_model.c:33
static const conf_parser_t config[]
Definition: base.c:188
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:689
return count
Definition: module.c:175
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition: state_test.c:8
#define fr_time_gteq(_a, _b)
Definition: time.h:238
static int64_t fr_time_delta_unwrap(fr_time_delta_t time)
Definition: time.h:154
#define fr_time_delta_lt(_a, _b)
Definition: time.h:283
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition: time.h:588
#define fr_time_delta_wrap(_time)
Definition: time.h:152
#define NSEC
Definition: time.h:377
#define fr_time_add(_a, _b)
Add a time/time delta together.
Definition: time.h:196
#define fr_time_gt(_a, _b)
Definition: time.h:237
static fr_time_delta_t fr_time_delta_from_nsec(int64_t nsec)
Definition: time.h:561
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition: time.h:229
static fr_time_delta_t fr_time_delta_div(fr_time_delta_t a, fr_time_delta_t b)
Definition: time.h:267
#define fr_time_lt(_a, _b)
Definition: time.h:239
A time delta, a difference in time measured in nanoseconds.
Definition: time.h:80
"server local" time.
Definition: time.h:69
static fr_event_list_t * el