Coverage Report

Created: 2026-03-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/io/load.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: 0ecc25882597ff9131557359b105fed4b2322191 $
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: 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
0
#define IBETA (4)
38
#define IALPHA (8)
39
40
#define DIFF(_rtt, _t) \
41
0
  (\
42
0
    fr_time_delta_lt(_rtt, _t) ? \
43
0
      fr_time_delta_sub(_t, _rtt) : \
44
0
      fr_time_delta_sub(_rtt, _t)\
45
0
  )
46
47
#define RTTVAR(_rtt, _rttvar, _t) \
48
0
  fr_time_delta_div(\
49
0
    fr_time_delta_add(\
50
0
      fr_time_delta_mul(_rttvar, IBETA - 1), \
51
0
      DIFF(_rtt, _t)\
52
0
    ), \
53
0
    fr_time_delta_wrap(IBETA)\
54
0
  )
55
56
0
#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 {
59
  FR_LOAD_STATE_INIT = 0,
60
  FR_LOAD_STATE_SENDING,
61
  FR_LOAD_STATE_GATED,
62
  FR_LOAD_STATE_DRAINING,
63
} fr_load_state_t;
64
65
struct fr_load_s {
66
  fr_load_state_t   state;
67
  fr_event_list_t   *el;
68
  fr_load_config_t const *config;
69
  fr_load_callback_t  callback;
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
75
  int     step_received;
76
77
  uint32_t    pps;
78
  fr_time_delta_t   delta;      //!< between packets
79
80
  uint32_t    count;
81
  bool      header;     //!< for printing statistics
82
83
  fr_time_t   next;     //!< The next time we're supposed to send a packet
84
  fr_timer_t    *ev;
85
};
86
87
fr_load_t *fr_load_generator_create(TALLOC_CTX *ctx, fr_event_list_t *el, fr_load_config_t *config,
88
            fr_load_callback_t callback, void *uctx)
89
0
{
90
0
  fr_load_t *l;
91
92
0
  l = talloc_zero(ctx, fr_load_t);
93
0
  if (!l) return NULL;
94
95
0
  if (!config->start_pps) config->start_pps = 1;
96
0
  if (!config->milliseconds) config->milliseconds = 1000;
97
0
  if (!config->parallel) config->parallel = 1;
98
99
0
  l->el = el;
100
0
  l->config = config;
101
0
  l->callback = callback;
102
0
  l->uctx = uctx;
103
104
0
  return l;
105
0
}
106
107
/** Send one or more packets.
108
 *
109
 */
110
static void fr_load_generator_send(fr_load_t *l, fr_time_t now, int count)
111
0
{
112
0
  int i;
113
114
  /*
115
   *  Send as many packets as necessary.
116
   */
117
0
  l->stats.sent += count;
118
0
  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
0
  for (i = 0; i < count; i++) {
125
0
    l->callback(fr_time_add(now, fr_time_delta_from_nsec(i)), l->uctx);
126
0
  }
127
0
}
128
129
static void load_timer(fr_timer_list_t *tl, fr_time_t now, void *uctx)
130
0
{
131
0
  fr_load_t *l = uctx;
132
0
  fr_time_delta_t delta;
133
0
  uint32_t count;
134
135
  /*
136
   *  Keep track of the overall maximum backlog for the
137
   *  duration of the entire test run.
138
   */
139
0
  l->stats.backlog = l->stats.sent - l->stats.received;
140
0
  if (l->stats.backlog > l->stats.max_backlog) l->stats.max_backlog = l->stats.backlog;
141
142
  /*
143
   *  If we're done this step, go to the next one.
144
   */
145
0
  if (fr_time_gteq(l->next, l->step_end)) {
146
0
    l->step_start = l->next;
147
0
    l->step_end = fr_time_add(l->next, l->config->duration);
148
0
    l->step_received = l->stats.received;
149
0
    l->pps += l->config->step;
150
0
    l->stats.pps = l->pps;
151
0
    l->stats.skipped = 0;
152
0
    l->delta = fr_time_delta_div(fr_time_delta_from_sec(l->config->parallel), fr_time_delta_wrap(l->pps));
153
154
    /*
155
     *  Stop at max PPS, if it's set.  Otherwise
156
     *  continue without limit.
157
     */
158
0
    if (l->config->max_pps && (l->pps > l->config->max_pps)) {
159
0
      l->state = FR_LOAD_STATE_DRAINING;
160
0
      return;
161
0
    }
162
0
  }
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
0
  if (((size_t) l->stats.backlog * 1000) < ((size_t) l->pps * l->config->milliseconds)) {
176
0
    uint32_t capacity;
177
178
0
    l->state = FR_LOAD_STATE_SENDING;
179
0
    l->stats.blocked = false;
180
0
    count = l->config->parallel;
181
0
    l->stats.skipped = 0;
182
183
0
    capacity = ((l->pps * l->config->milliseconds) / 1000) - l->stats.backlog;
184
185
    /*
186
     *  Limit "count" so that it doesn't overflow.
187
     */
188
0
    if (count > capacity) count = capacity;
189
190
0
  } 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
     */
199
0
    l->state = FR_LOAD_STATE_GATED;
200
0
    l->stats.blocked = true;
201
0
    count = 0;
202
0
    l->stats.skipped += l->count;
203
0
  }
204
205
  /*
206
   *  Skip timers if we're too busy.
207
   */
208
0
  l->next = fr_time_add(l->next, l->delta);
209
0
  if (fr_time_lt(l->next, now)) {
210
0
    while (fr_time_lt(fr_time_add(l->next, l->delta), now)) {
211
//      l->stats.skipped += l->count;
212
0
      l->next = fr_time_add(l->next, l->delta);
213
0
    }
214
0
  }
215
0
  delta = fr_time_sub(l->next, now);
216
217
  /*
218
   *  Set the timer for the next packet.
219
   */
220
0
  if (fr_timer_in(l, tl, &l->ev, delta, false, load_timer, l) < 0) {
221
0
    l->state = FR_LOAD_STATE_DRAINING;
222
0
    return;
223
0
  }
224
225
0
  if (count) fr_load_generator_send(l, now, count);
226
0
}
227
228
229
/** Start the load generator.
230
 *
231
 */
232
int fr_load_generator_start(fr_load_t *l)
233
0
{
234
0
  uint32_t max;
235
236
0
  l->stats.start = fr_time();
237
0
  l->step_start = l->stats.start;
238
0
  l->step_end = fr_time_add(l->step_start, l->config->duration);
239
240
0
  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
0
  max = UINT32_MAX / l->config->milliseconds;
246
247
0
  if (l->pps > max) l->pps = max;
248
249
0
  l->stats.pps = l->pps;
250
0
  l->count = l->config->parallel;
251
252
0
  l->delta = fr_time_delta_div(fr_time_delta_from_sec(l->config->parallel), fr_time_delta_wrap(l->pps));
253
0
  l->next = fr_time_add(l->step_start, l->delta);
254
255
0
  load_timer(l->el->tl, l->step_start, l);
256
0
  return 0;
257
0
}
258
259
260
/** Stop the load generation through the simple expedient of deleting
261
 * the timer associated with it.
262
 *
263
 */
264
int fr_load_generator_stop(fr_load_t *l)
265
{
266
  if (!fr_timer_armed(l->ev)) return 0;
267
268
  FR_TIMER_DELETE_RETURN(&l->ev);
269
  return 0;
270
}
271
272
273
/** Tell the load generator that we have a reply to a packet we sent.
274
 *
275
 */
276
fr_load_reply_t fr_load_generator_have_reply(fr_load_t *l, fr_time_t request_time)
277
0
{
278
0
  fr_time_t now;
279
0
  fr_time_delta_t t;
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
0
  now = fr_time();
287
0
  t = fr_time_sub(now, request_time);
288
289
0
  l->stats.rttvar = RTTVAR(l->stats.rtt, l->stats.rttvar, t);
290
0
  l->stats.rtt = RTT(l->stats.rtt, t);
291
292
0
  l->stats.received++;
293
294
  /*
295
   *  t is in nanoseconds.
296
   */
297
0
  if (fr_time_delta_lt(t, fr_time_delta_wrap(1000))) {
298
0
         l->stats.times[0]++; /* < microseconds */
299
0
  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(10000))) {
300
0
         l->stats.times[1]++; /* microseconds */
301
0
  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(100000))) {
302
0
         l->stats.times[2]++; /* 10s of microseconds */
303
0
  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(1000000))) {
304
0
         l->stats.times[3]++; /* 100s of microseconds */
305
0
  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(10000000))) {
306
0
         l->stats.times[4]++; /* milliseconds */
307
0
  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(100000000))) {
308
0
         l->stats.times[5]++; /* 10s of milliseconds */
309
0
  } else if (fr_time_delta_lt(t, fr_time_delta_wrap(NSEC))) {
310
0
         l->stats.times[6]++; /* 100s of milliseconds */
311
0
  } else {
312
0
         l->stats.times[7]++; /* seconds */
313
0
  }
314
315
  /*
316
   *  Still sending packets.  Rely on the timer to send more
317
   *  packets.
318
   */
319
0
  if (l->state == FR_LOAD_STATE_SENDING) return FR_LOAD_CONTINUE;
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
0
  if (l->state == FR_LOAD_STATE_GATED) {
327
0
    if (l->stats.skipped > 0) {
328
0
      l->stats.skipped--;
329
0
      fr_load_generator_send(l, now, 1);
330
0
    }
331
0
    return FR_LOAD_CONTINUE;
332
0
  }
333
334
  /*
335
   *  We're still sending or gated, tell the caller to
336
   *  continue.
337
   */
338
0
  if (l->state != FR_LOAD_STATE_DRAINING) {
339
0
    return FR_LOAD_CONTINUE;
340
0
  }
341
  /*
342
   *  Not yet received all replies.  Wait until we have all
343
   *  replies.
344
   */
345
0
  if (l->stats.received < l->stats.sent) return FR_LOAD_CONTINUE;
346
347
0
  l->stats.end = now;
348
0
  return FR_LOAD_DONE;
349
0
}
350
351
/** Print load generator statistics in CVS format.
352
 *
353
 */
354
size_t fr_load_generator_stats_sprint(fr_load_t *l, fr_time_t now, char *buffer, size_t buflen)
355
0
{
356
0
  double now_f, last_send_f;
357
358
0
  if (!l->header) {
359
0
    l->header = true;
360
0
    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
0
  }
362
363
364
0
  now_f = fr_time_delta_unwrap(fr_time_sub(now, l->stats.start)) / (double)NSEC;
365
366
0
  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
0
  if (fr_time_gt(now, l->step_start)) {
375
0
    l->stats.pps_accepted =
376
0
      fr_time_delta_unwrap(
377
0
        fr_time_delta_div(fr_time_delta_from_sec(l->stats.received - l->step_received),
378
0
                fr_time_sub(now, l->step_start))
379
0
      );
380
0
  }
381
382
0
  return snprintf(buffer, buflen,
383
0
      "%f,%f,"
384
0
      "%" PRIu64 ",%" PRIu64 ","
385
0
      "%d,%d,"
386
0
      "%d,%d,"
387
0
      "%d,%d,"
388
0
      "%d,%d,%d,%d,%d,%d,%d,%d,"
389
0
      "%d\n",
390
0
      now_f, last_send_f,
391
0
      fr_time_delta_unwrap(l->stats.rtt), fr_time_delta_unwrap(l->stats.rttvar),
392
0
      l->stats.pps, l->stats.pps_accepted,
393
0
      l->stats.sent, l->stats.received,
394
0
      l->stats.backlog, l->stats.max_backlog,
395
0
      l->stats.times[0], l->stats.times[1], l->stats.times[2], l->stats.times[3],
396
0
      l->stats.times[4], l->stats.times[5], l->stats.times[6], l->stats.times[7],
397
0
      l->stats.blocked);
398
0
}
399
400
fr_load_stats_t const * fr_load_generator_stats(fr_load_t const *l)
401
0
{
402
0
  return &l->stats;
403
0
}