Coverage Report

Created: 2026-06-09 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/freq_ctr.c
Line
Count
Source
1
/*
2
 * Event rate calculation functions.
3
 *
4
 * Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version
9
 * 2 of the License, or (at your option) any later version.
10
 *
11
 */
12
13
#include <haproxy/api.h>
14
#include <haproxy/freq_ctr.h>
15
#include <haproxy/tools.h>
16
17
/* Update a frequency counter by <inc> incremental units. It is automatically
18
 * rotated if the period is over. It is important that it correctly initializes
19
 * a null area. This one works on frequency counters which have a period
20
 * different from one second. It relies on the process-wide clock that is
21
 * guaranteed to be monotonic. It's important to avoid forced rotates between
22
 * threads. A faster wrapper (update_freq_ctr_period) should be used instead,
23
 * which uses the thread's local time whenever possible and falls back to this
24
 * one when needed (less than 0.003% of the time).
25
 */
26
uint update_freq_ctr_period_slow(struct freq_ctr *ctr, uint period, uint inc)
27
0
{
28
0
  uint curr_tick;
29
0
  uint32_t now_ms_tmp;
30
31
  /* atomically update the counter if still within the period, even if
32
   * a rotation is in progress (no big deal).
33
   */
34
0
  for (;; __ha_cpu_relax()) {
35
0
    curr_tick  = HA_ATOMIC_LOAD(&ctr->curr_tick);
36
0
    now_ms_tmp = HA_ATOMIC_LOAD(global_now_ms);
37
38
0
    if (now_ms_tmp - curr_tick < period)
39
0
      return HA_ATOMIC_ADD_FETCH(&ctr->curr_ctr, inc);
40
41
    /* a rotation is needed. While extremely rare, contention may
42
     * happen because it will be triggered on time, and all threads
43
     * see the time change simultaneously.
44
     */
45
0
    if (!(curr_tick & 1) &&
46
0
        HA_ATOMIC_CAS(&ctr->curr_tick, &curr_tick, curr_tick | 0x1))
47
0
      break;
48
0
  }
49
50
  /* atomically switch the new period into the old one without losing any
51
   * potential concurrent update. We're the only one performing the rotate
52
   * (locked above), others are only adding positive values to curr_ctr.
53
   */
54
0
  HA_ATOMIC_STORE(&ctr->prev_ctr, HA_ATOMIC_XCHG(&ctr->curr_ctr, inc));
55
0
  curr_tick += period;
56
0
  if (likely(now_ms_tmp - curr_tick >= period)) {
57
    /* we missed at least two periods */
58
0
    HA_ATOMIC_STORE(&ctr->prev_ctr, 0);
59
0
    curr_tick = now_ms_tmp;
60
0
  }
61
62
  /* release the lock and update the time in case of rotate. */
63
0
  HA_ATOMIC_STORE(&ctr->curr_tick, curr_tick & ~1);
64
0
  return inc;
65
0
}
66
67
/* Returns the total number of events over the current + last period, including
68
 * a number of already pending events <pend>. The average frequency will be
69
 * obtained by dividing the output by <period>. This is essentially made to
70
 * ease implementation of higher-level read functions. This function does not
71
 * access the freq_ctr itself, it's supposed to be called with the stabilized
72
 * values. See freq_ctr_total() and freq_ctr_total_estimate() instead.
73
 *
74
 * As a special case, if pend < 0, it's assumed there are no pending
75
 * events and a flapping correction must be applied at the end. This is used by
76
 * read_freq_ctr_period() to avoid reporting ups and downs on low-frequency
77
 * events when the past value is <= 1.
78
 */
79
ullong _freq_ctr_total_from_values(uint period, int pend,
80
           uint tick, ullong past, ullong curr)
81
0
{
82
0
  int remain;
83
84
0
  remain = tick + period - HA_ATOMIC_LOAD(global_now_ms);
85
0
  if (unlikely(remain < 0)) {
86
    /* We're past the first period, check if we can still report a
87
     * part of last period or if we're too far away.
88
     */
89
0
    remain += period;
90
0
    past = (remain >= 0) ? curr : 0;
91
0
    curr = 0;
92
0
  }
93
94
0
  if (pend < 0) {
95
    /* enable flapping correction at very low rates */
96
0
    pend = 0;
97
0
    if (!curr && past <= 1)
98
0
      return past * period;
99
0
  }
100
101
  /* compute the total number of confirmed events over the period */
102
0
  return past * remain + (curr + pend) * period;
103
0
}
104
105
/* Returns the total number of events over the current + last period, including
106
 * a number of already pending events <pend>. The average frequency will be
107
 * obtained by dividing the output by <period>. This is essentially made to
108
 * ease implementation of higher-level read functions.
109
 *
110
 * As a special case, if pend < 0, it's assumed there are no pending
111
 * events and a flapping correction must be applied at the end. This is used by
112
 * read_freq_ctr_period() to avoid reporting ups and downs on low-frequency
113
 * events when the past value is <= 1.
114
 */
115
ullong freq_ctr_total(const struct freq_ctr *ctr, uint period, int pend)
116
0
{
117
0
  ullong curr, past, old_curr, old_past;
118
0
  uint tick, old_tick;
119
120
0
  tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
121
0
  curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
122
0
  past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
123
124
0
  while (1) {
125
0
    if (tick & 0x1) // change in progress
126
0
      goto redo0;
127
128
0
    old_tick = tick;
129
0
    old_curr = curr;
130
0
    old_past = past;
131
132
    /* now let's load the values a second time and make sure they
133
     * did not change, which will indicate it was a stable reading.
134
     */
135
136
0
    tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
137
0
    if (tick & 0x1) // change in progress
138
0
      goto redo0;
139
140
0
    if (tick != old_tick)
141
0
      goto redo1;
142
143
0
    curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
144
0
    if (curr != old_curr)
145
0
      goto redo2;
146
147
0
    past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
148
0
    if (past != old_past)
149
0
      goto redo3;
150
151
    /* all values match between two loads, they're stable, let's
152
     * quit now.
153
     */
154
0
    break;
155
0
  redo0:
156
0
    tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
157
0
  redo1:
158
0
    curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
159
0
  redo2:
160
0
    past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
161
0
  redo3:
162
0
    __ha_cpu_relax();
163
0
  };
164
0
  return _freq_ctr_total_from_values(period, pend, tick, past, curr);
165
0
}
166
167
/* Like the function above but doesn't block if the entry is locked. In this
168
 * case it will only return the most accurate estimate it can bring. Based on
169
 * the update order in update_freq_ctr_period_slow() above, it may return a
170
 * low value caused by the replacement of the curr value before the past one
171
 * and/or the tick was updated. Otherwise the value will be correct most of
172
 * the time. This is only meant to be used from debug handlers.
173
 */
174
ullong freq_ctr_total_estimate(const struct freq_ctr *ctr, uint period, int pend)
175
0
{
176
0
  ullong curr, past;
177
0
  uint tick;
178
179
0
  tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
180
0
  curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
181
0
  past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
182
183
0
  tick &= ~1;
184
0
  return _freq_ctr_total_from_values(period, pend, tick, past, curr);
185
0
}
186
187
/* Returns the excess of events over the current period for target frequency
188
 * <freq>. It returns 0 if the counter is in the future or if the counter is
189
 * empty. The result considers the position of the current time within the
190
 * current period.
191
 *
192
 * The caller may safely add new events if result is zero.
193
 */
194
uint freq_ctr_overshoot_period(const struct freq_ctr *ctr, uint period, uint freq)
195
0
{
196
0
  ullong curr, old_curr;
197
0
  uint tick, old_tick, linear_usage;
198
0
  int elapsed;
199
200
0
  tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
201
0
  curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
202
203
0
  while (1) {
204
0
    if (tick & 0x1) // change in progress
205
0
      goto redo0;
206
207
0
    old_tick = tick;
208
0
    old_curr = curr;
209
210
    /* now let's load the values a second time and make sure they
211
     * did not change, which will indicate it was a stable reading.
212
     */
213
214
0
    tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
215
0
    if (tick & 0x1) // change in progress
216
0
      goto redo0;
217
218
0
    if (tick != old_tick)
219
0
      goto redo1;
220
221
0
    curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
222
0
    if (curr != old_curr)
223
0
      goto redo2;
224
225
    /* all values match between two loads, they're stable, let's
226
     * quit now.
227
     */
228
0
    break;
229
0
  redo0:
230
0
    tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
231
0
  redo1:
232
0
    curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
233
0
  redo2:
234
0
    __ha_cpu_relax();
235
0
  };
236
237
0
  if (!curr && !tick) {
238
    /* The counter is empty, there is no overshoot */
239
0
    return 0;
240
0
  }
241
242
0
  elapsed = HA_ATOMIC_LOAD(global_now_ms) - tick;
243
0
  if (unlikely(elapsed < 0 || elapsed > period)) {
244
    /* The counter is in the future or the elapsed time is higher than the period, there is no overshoot */
245
0
    return 0;
246
0
  }
247
248
0
  linear_usage = div64_32((uint64_t)elapsed * freq, period);
249
0
  if (curr < linear_usage) {
250
    /* The counter is below a uniform linear increase across the period, no overshoot */
251
0
    return 0;
252
0
  }
253
0
  return curr - linear_usage;
254
0
}
255
256
/*
257
 * Local variables:
258
 *  c-indent-level: 8
259
 *  c-basic-offset: 8
260
 * End:
261
 */