mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-01-06 20:29:48 +00:00
5705a6e3b7
The function returning the excess of events over the current period for a target frequency (the overshoot) has a flaw if the inactivity period is too long. In this case, the result may overflow. Instead to be negative, a very high positive value is returned. This function is used by the bandwidth limitation filter. It means after a long inactivity period, a huge burst may be detected while it should not. In fact, the problem arise from the moment we're past the current period. In this case, we should not report any overshoot and just get the number of remaining events as usual. This patch should be backported as far as 2.7.
219 lines
6.1 KiB
C
219 lines
6.1 KiB
C
/*
|
|
* Event rate calculation functions.
|
|
*
|
|
* Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/freq_ctr.h>
|
|
#include <haproxy/tools.h>
|
|
|
|
/* Update a frequency counter by <inc> incremental units. It is automatically
|
|
* rotated if the period is over. It is important that it correctly initializes
|
|
* a null area. This one works on frequency counters which have a period
|
|
* different from one second. It relies on the process-wide clock that is
|
|
* guaranteed to be monotonic. It's important to avoid forced rotates between
|
|
* threads. A faster wrapper (update_freq_ctr_period) should be used instead,
|
|
* which uses the thread's local time whenever possible and falls back to this
|
|
* one when needed (less than 0.003% of the time).
|
|
*/
|
|
uint update_freq_ctr_period_slow(struct freq_ctr *ctr, uint period, uint inc)
|
|
{
|
|
uint curr_tick;
|
|
uint32_t now_ms_tmp;
|
|
|
|
/* atomically update the counter if still within the period, even if
|
|
* a rotation is in progress (no big deal).
|
|
*/
|
|
for (;; __ha_cpu_relax()) {
|
|
curr_tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
now_ms_tmp = HA_ATOMIC_LOAD(&global_now_ms);
|
|
|
|
if (now_ms_tmp - curr_tick < period)
|
|
return HA_ATOMIC_ADD_FETCH(&ctr->curr_ctr, inc);
|
|
|
|
/* a rotation is needed. While extremely rare, contention may
|
|
* happen because it will be triggered on time, and all threads
|
|
* see the time change simultaneously.
|
|
*/
|
|
if (!(curr_tick & 1) &&
|
|
HA_ATOMIC_CAS(&ctr->curr_tick, &curr_tick, curr_tick | 0x1))
|
|
break;
|
|
}
|
|
|
|
/* atomically switch the new period into the old one without losing any
|
|
* potential concurrent update. We're the only one performing the rotate
|
|
* (locked above), others are only adding positive values to curr_ctr.
|
|
*/
|
|
HA_ATOMIC_STORE(&ctr->prev_ctr, HA_ATOMIC_XCHG(&ctr->curr_ctr, inc));
|
|
curr_tick += period;
|
|
if (likely(now_ms_tmp - curr_tick >= period)) {
|
|
/* we missed at least two periods */
|
|
HA_ATOMIC_STORE(&ctr->prev_ctr, 0);
|
|
curr_tick = now_ms_tmp;
|
|
}
|
|
|
|
/* release the lock and update the time in case of rotate. */
|
|
HA_ATOMIC_STORE(&ctr->curr_tick, curr_tick & ~1);
|
|
return inc;
|
|
}
|
|
|
|
/* Returns the total number of events over the current + last period, including
|
|
* a number of already pending events <pend>. The average frequency will be
|
|
* obtained by dividing the output by <period>. This is essentially made to
|
|
* ease implementation of higher-level read functions.
|
|
*
|
|
* As a special case, if pend < 0, it's assumed there are no pending
|
|
* events and a flapping correction must be applied at the end. This is used by
|
|
* read_freq_ctr_period() to avoid reporting ups and downs on low-frequency
|
|
* events when the past value is <= 1.
|
|
*/
|
|
ullong freq_ctr_total(const struct freq_ctr *ctr, uint period, int pend)
|
|
{
|
|
ullong curr, past, old_curr, old_past;
|
|
uint tick, old_tick;
|
|
int remain;
|
|
|
|
tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
|
|
past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
|
|
|
|
while (1) {
|
|
if (tick & 0x1) // change in progress
|
|
goto redo0;
|
|
|
|
old_tick = tick;
|
|
old_curr = curr;
|
|
old_past = past;
|
|
|
|
/* now let's load the values a second time and make sure they
|
|
* did not change, which will indicate it was a stable reading.
|
|
*/
|
|
|
|
tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
if (tick & 0x1) // change in progress
|
|
goto redo0;
|
|
|
|
if (tick != old_tick)
|
|
goto redo1;
|
|
|
|
curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
|
|
if (curr != old_curr)
|
|
goto redo2;
|
|
|
|
past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
|
|
if (past != old_past)
|
|
goto redo3;
|
|
|
|
/* all values match between two loads, they're stable, let's
|
|
* quit now.
|
|
*/
|
|
break;
|
|
redo0:
|
|
tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
redo1:
|
|
curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
|
|
redo2:
|
|
past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
|
|
redo3:
|
|
__ha_cpu_relax();
|
|
};
|
|
|
|
remain = tick + period - HA_ATOMIC_LOAD(&global_now_ms);
|
|
if (unlikely(remain < 0)) {
|
|
/* We're past the first period, check if we can still report a
|
|
* part of last period or if we're too far away.
|
|
*/
|
|
remain += period;
|
|
past = (remain >= 0) ? curr : 0;
|
|
curr = 0;
|
|
}
|
|
|
|
if (pend < 0) {
|
|
/* enable flapping correction at very low rates */
|
|
pend = 0;
|
|
if (!curr && past <= 1)
|
|
return past * period;
|
|
}
|
|
|
|
/* compute the total number of confirmed events over the period */
|
|
return past * remain + (curr + pend) * period;
|
|
}
|
|
|
|
/* Returns the excess of events (may be negative) over the current period for
|
|
* target frequency <freq>. It returns 0 if the counter is in the future or if
|
|
* the counter is empty. The result considers the position of the current time
|
|
* within the current period.
|
|
*
|
|
* The caller may safely add new events if result is negative or null.
|
|
*/
|
|
int freq_ctr_overshoot_period(const struct freq_ctr *ctr, uint period, uint freq)
|
|
{
|
|
ullong curr, old_curr;
|
|
uint tick, old_tick;
|
|
int elapsed;
|
|
|
|
tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
|
|
|
|
while (1) {
|
|
if (tick & 0x1) // change in progress
|
|
goto redo0;
|
|
|
|
old_tick = tick;
|
|
old_curr = curr;
|
|
|
|
/* now let's load the values a second time and make sure they
|
|
* did not change, which will indicate it was a stable reading.
|
|
*/
|
|
|
|
tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
if (tick & 0x1) // change in progress
|
|
goto redo0;
|
|
|
|
if (tick != old_tick)
|
|
goto redo1;
|
|
|
|
curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
|
|
if (curr != old_curr)
|
|
goto redo2;
|
|
|
|
/* all values match between two loads, they're stable, let's
|
|
* quit now.
|
|
*/
|
|
break;
|
|
redo0:
|
|
tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
redo1:
|
|
curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
|
|
redo2:
|
|
__ha_cpu_relax();
|
|
};
|
|
|
|
if (!curr && !tick) {
|
|
/* The counter is empty, there is no overshoot */
|
|
return 0;
|
|
}
|
|
|
|
elapsed = HA_ATOMIC_LOAD(&global_now_ms) - tick;
|
|
if (unlikely(elapsed < 0 || elapsed > period)) {
|
|
/* The counter is in the future or the elapsed time is higher than the period, there is no overshoot */
|
|
return 0;
|
|
}
|
|
|
|
return curr - div64_32((uint64_t)elapsed * freq, period);
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|