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: fad5700eff479d44ac12b188428b8c75157f2ebb $
19 *
20 * @brief Load generation algorithms
21 * @file io/load.c
22 *
23 * @copyright 2019 Network RADIUS SAS (legal@networkradius.com)
24 */
25RCSID("$Id: fad5700eff479d44ac12b188428b8c75157f2ebb $")
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_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;
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;
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 */
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 */
341size_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)) {
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,
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:576
#define RCSID(id)
Definition build.h:483
#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:1611
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_stats_t const * fr_load_generator_stats(fr_load_t const *l)
Definition load.c:387
#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_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
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:183
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
return count
Definition module.c:163
#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: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
static fr_event_list_t * el