mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-09 16:47:18 +02:00
Some code called by the debug handlers in the context of a signal handler accesses to some freq_ctr and occasionally ends up on a locked one from the same thread that is dumping it. Let's introduce a non-blocking version that at least allows to return even if the value is in the process of being updated, it's less problematic than hanging.
257 lines
7.8 KiB
C
257 lines
7.8 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. This function does not
|
|
* access the freq_ctr itself, it's supposed to be called with the stabilized
|
|
* values. See freq_ctr_total() and freq_ctr_total_estimate() instead.
|
|
*
|
|
* 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_from_values(uint period, int pend,
|
|
uint tick, ullong past, ullong curr)
|
|
{
|
|
int remain;
|
|
|
|
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 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;
|
|
|
|
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();
|
|
};
|
|
return _freq_ctr_total_from_values(period, pend, tick, past, curr);
|
|
}
|
|
|
|
/* Like the function above but doesn't block if the entry is locked. In this
|
|
* case it will only return the most accurate estimate it can bring. Based on
|
|
* the update order in update_freq_ctr_period_slow() above, it may return a
|
|
* low value caused by the replacement of the curr value before the past one
|
|
* and/or the tick was updated. Otherwise the value will be correct most of
|
|
* the time. This is only meant to be used from debug handlers.
|
|
*/
|
|
ullong freq_ctr_total_estimate(const struct freq_ctr *ctr, uint period, int pend)
|
|
{
|
|
ullong curr, past;
|
|
uint tick;
|
|
|
|
tick = HA_ATOMIC_LOAD(&ctr->curr_tick);
|
|
curr = HA_ATOMIC_LOAD(&ctr->curr_ctr);
|
|
past = HA_ATOMIC_LOAD(&ctr->prev_ctr);
|
|
|
|
tick &= ~1;
|
|
return _freq_ctr_total_from_values(period, pend, tick, past, curr);
|
|
}
|
|
|
|
/* 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:
|
|
*/
|