The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
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: 0ecc25882597ff9131557359b105fed4b2322191 $
19 *
20 * @brief Load generation algorithms
21 * @file io/load.c
22 *
23 * @copyright 2019 Network RADIUS SAS (legal@networkradius.com)
24 */
25RCSID("$Id: 0ecc25882597ff9131557359b105fed4b2322191 $")
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, 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
64
65struct 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
129static void load_timer(fr_timer_list_t *tl, fr_time_t now, void *uctx)
130{
131 fr_load_t *l = uctx;
132 fr_time_delta_t delta;
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;
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 (((size_t) l->stats.backlog * 1000) < ((size_t) l->pps * l->config->milliseconds)) {
176 uint32_t capacity;
177
179 l->stats.blocked = false;
180 count = l->config->parallel;
181 l->stats.skipped = 0;
182
183 capacity = ((l->pps * l->config->milliseconds) / 1000) - l->stats.backlog;
184
185 /*
186 * Limit "count" so that it doesn't overflow.
187 */
188 if (count > capacity) count = capacity;
189
190 } else {
191
192 /*
193 * We have too many packets in the backlog, we're
194 * gated. Don't send more packets until we have
195 * a reply.
196 *
197 * Note that we will send *these* packets.
198 */
200 l->stats.blocked = true;
201 count = 0;
202 l->stats.skipped += l->count;
203 }
204
205 /*
206 * Skip timers if we're too busy.
207 */
208 l->next = fr_time_add(l->next, l->delta);
209 if (fr_time_lt(l->next, now)) {
210 while (fr_time_lt(fr_time_add(l->next, l->delta), now)) {
211// l->stats.skipped += l->count;
212 l->next = fr_time_add(l->next, l->delta);
213 }
214 }
215 delta = fr_time_sub(l->next, now);
216
217 /*
218 * Set the timer for the next packet.
219 */
220 if (fr_timer_in(l, tl, &l->ev, delta, false, load_timer, l) < 0) {
222 return;
223 }
224
225 if (count) fr_load_generator_send(l, now, count);
226}
227
228
229/** Start the load generator.
230 *
231 */
233{
234 uint32_t max;
235
236 l->stats.start = fr_time();
237 l->step_start = l->stats.start;
239
240 l->pps = l->config->start_pps;
241
242 /*
243 * Check for numerical overflow. We later multiply pps*milliseconds, and we don't want overflow.
244 */
245 max = UINT32_MAX / l->config->milliseconds;
246
247 if (l->pps > max) l->pps = max;
248
249 l->stats.pps = l->pps;
250 l->count = l->config->parallel;
251
253 l->next = fr_time_add(l->step_start, l->delta);
254
255 load_timer(l->el->tl, l->step_start, l);
256 return 0;
257}
258
259
260/** Stop the load generation through the simple expedient of deleting
261 * the timer associated with it.
262 *
263 */
265{
266 if (!fr_timer_armed(l->ev)) return 0;
267
269 return 0;
270}
271
272
273/** Tell the load generator that we have a reply to a packet we sent.
274 *
275 */
277{
278 fr_time_t now;
280
281 /*
282 * Note that the replies may come out of order with
283 * respect to the request. So we can't use this reply
284 * for any kind of timing.
285 */
286 now = fr_time();
287 t = fr_time_sub(now, request_time);
288
289 l->stats.rttvar = RTTVAR(l->stats.rtt, l->stats.rttvar, t);
290 l->stats.rtt = RTT(l->stats.rtt, t);
291
292 l->stats.received++;
293
294 /*
295 * t is in nanoseconds.
296 */
297 if (fr_time_delta_lt(t, fr_time_delta_wrap(1000))) {
298 l->stats.times[0]++; /* < microseconds */
299 } else if (fr_time_delta_lt(t, fr_time_delta_wrap(10000))) {
300 l->stats.times[1]++; /* microseconds */
301 } else if (fr_time_delta_lt(t, fr_time_delta_wrap(100000))) {
302 l->stats.times[2]++; /* 10s of microseconds */
303 } else if (fr_time_delta_lt(t, fr_time_delta_wrap(1000000))) {
304 l->stats.times[3]++; /* 100s of microseconds */
305 } else if (fr_time_delta_lt(t, fr_time_delta_wrap(10000000))) {
306 l->stats.times[4]++; /* milliseconds */
307 } else if (fr_time_delta_lt(t, fr_time_delta_wrap(100000000))) {
308 l->stats.times[5]++; /* 10s of milliseconds */
309 } else if (fr_time_delta_lt(t, fr_time_delta_wrap(NSEC))) {
310 l->stats.times[6]++; /* 100s of milliseconds */
311 } else {
312 l->stats.times[7]++; /* seconds */
313 }
314
315 /*
316 * Still sending packets. Rely on the timer to send more
317 * packets.
318 */
320
321 /*
322 * The send code has decided that the backlog is too
323 * high. New requests are blocked until replies come in.
324 * Since we have a reply, send another request.
325 */
326 if (l->state == FR_LOAD_STATE_GATED) {
327 if (l->stats.skipped > 0) {
328 l->stats.skipped--;
329 fr_load_generator_send(l, now, 1);
330 }
331 return FR_LOAD_CONTINUE;
332 }
333
334 /*
335 * We're still sending or gated, tell the caller to
336 * continue.
337 */
338 if (l->state != FR_LOAD_STATE_DRAINING) {
339 return FR_LOAD_CONTINUE;
340 }
341 /*
342 * Not yet received all replies. Wait until we have all
343 * replies.
344 */
345 if (l->stats.received < l->stats.sent) return FR_LOAD_CONTINUE;
346
347 l->stats.end = now;
348 return FR_LOAD_DONE;
349}
350
351/** Print load generator statistics in CVS format.
352 *
353 */
354size_t fr_load_generator_stats_sprint(fr_load_t *l, fr_time_t now, char *buffer, size_t buflen)
355{
356 double now_f, last_send_f;
357
358 if (!l->header) {
359 l->header = true;
360 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");
361 }
362
363
364 now_f = fr_time_delta_unwrap(fr_time_sub(now, l->stats.start)) / (double)NSEC;
365
366 last_send_f = fr_time_delta_unwrap(fr_time_sub(l->stats.last_send, l->stats.start)) / (double)NSEC;
367
368 /*
369 * Track packets/s. Since times are in nanoseconds, we
370 * have to scale the counters up by NSEC. And since NSEC
371 * is 1B, the calculations have to be done via 64-bit
372 * numbers, and then converted to a final 32-bit counter.
373 */
374 if (fr_time_gt(now, l->step_start)) {
378 fr_time_sub(now, l->step_start))
379 );
380 }
381
382 return snprintf(buffer, buflen,
383 "%f,%f,"
384 "%" PRIu64 ",%" PRIu64 ","
385 "%d,%d,"
386 "%d,%d,"
387 "%d,%d,"
388 "%d,%d,%d,%d,%d,%d,%d,%d,"
389 "%d\n",
390 now_f, last_send_f,
393 l->stats.sent, l->stats.received,
395 l->stats.times[0], l->stats.times[1], l->stats.times[2], l->stats.times[3],
396 l->stats.times[4], l->stats.times[5], l->stats.times[6], l->stats.times[7],
397 l->stats.blocked);
398}
399
401{
402 return &l->stats;
403}
static int const char char buffer[256]
Definition acutest.h:578
#define RCSID(id)
Definition build.h:487
#define fr_time()
Definition event.c:60
Stores all information relating to an event list.
Definition event.c:377
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
static void load_timer(fr_timer_list_t *tl, fr_time_t now, void *uctx)
Definition load.c:129
fr_load_stats_t const * fr_load_generator_stats(fr_load_t const *l)
Definition load.c:400
#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_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
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_timer_t * 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:232
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:264
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:354
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:276
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
static const conf_parser_t config[]
Definition base.c:169
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
return count
Definition module.c:155
#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:285
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#define fr_time_delta_wrap(_time)
Definition time.h:152
#define NSEC
Definition time.h:379
#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:563
#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
An event timer list.
Definition timer.c:50
A timer event.
Definition timer.c:84
#define FR_TIMER_DELETE_RETURN(_ev_p)
Definition timer.h:110
#define fr_timer_in(...)
Definition timer.h:87
static bool fr_timer_armed(fr_timer_t *ev)
Definition timer.h:120
static fr_event_list_t * el