From 4aba1a3c07797e80d58f3a1e4ddf5451c77e8b0d Mon Sep 17 00:00:00 2001 From: Costa Tsaousis <costa@netdata.cloud> Date: Mon, 4 Nov 2024 13:55:02 +0200 Subject: [PATCH] better randomness for heartbeat (#18930) * add 10ms buffering to streaming senders * queue for 1ms * added jitter to heartbeat and sender * remove jitter and add os_random in libnetdata * cleanup * cleanup * fix random for all operating systems * simpler random number generator * make microsleep wait at least 1 nsec * detect random number generators * use libnetdata os_random() * use the buffer versions of the random generators; fallback to using random() * provide random functions per size * getrandom() can fail; handle this case too * fix for wrong params --- CMakeLists.txt | 20 ++- packaging/cmake/config.cmake.h.in | 4 + src/aclk/aclk_util.c | 6 +- src/libnetdata/clocks/clocks.c | 17 ++- src/libnetdata/clocks/clocks.h | 2 + src/libnetdata/os/os.h | 3 +- src/libnetdata/os/random.c | 214 ++++++++++++++++++++++++++++++ src/libnetdata/os/random.h | 16 +++ src/libnetdata/os/sleep.c | 36 +++++ src/libnetdata/os/sleep.h | 9 ++ src/libnetdata/os/tinysleep.c | 21 --- src/libnetdata/os/tinysleep.h | 8 -- src/streaming/compression.c | 24 ++-- 13 files changed, 326 insertions(+), 54 deletions(-) create mode 100644 src/libnetdata/os/random.c create mode 100644 src/libnetdata/os/random.h create mode 100644 src/libnetdata/os/sleep.c create mode 100644 src/libnetdata/os/sleep.h delete mode 100644 src/libnetdata/os/tinysleep.c delete mode 100644 src/libnetdata/os/tinysleep.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cf2e5e17f..2653aeab62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,6 +388,9 @@ check_function_exists(sched_get_priority_max HAVE_SCHED_GET_PRIORITY_MAX) check_function_exists(close_range HAVE_CLOSE_RANGE) check_function_exists(backtrace HAVE_BACKTRACE) +check_function_exists(arc4random_buf HAVE_ARC4RANDOM_BUF) +check_function_exists(getrandom HAVE_GETRANDOM) + # # check source compilation # @@ -539,6 +542,17 @@ int my_function() { } " HAVE_FUNC_ATTRIBUTE_WARN_UNUSED_RESULT) +# Windows MSVCRT random number generator +# used only when compiling natively (not MSYS/CYGWIN) +check_c_source_compiles(" + #define _CRT_RAND_S + #include <stdlib.h> + int main() { + unsigned int x; + return rand_s(&x); + } +" HAVE_RAND_S) + if(OS_FREEBSD OR OS_MACOS) set(HAVE_BUILTIN_ATOMICS True) endif() @@ -759,8 +773,8 @@ set(LIBNETDATA_FILES src/libnetdata/os/os-windows-wrappers.h src/libnetdata/os/get_system_cpus.c src/libnetdata/os/get_system_cpus.h - src/libnetdata/os/tinysleep.c - src/libnetdata/os/tinysleep.h + src/libnetdata/os/sleep.c + src/libnetdata/os/sleep.h src/libnetdata/os/uuid_generate.c src/libnetdata/os/uuid_generate.h src/libnetdata/os/setenv.c @@ -862,6 +876,8 @@ set(LIBNETDATA_FILES src/libnetdata/log/nd_log-to-windows-common.h src/libnetdata/common.h src/libnetdata/xxHash/xxhash.h + src/libnetdata/os/random.c + src/libnetdata/os/random.h ) if(ENABLE_PLUGIN_EBPF) diff --git a/packaging/cmake/config.cmake.h.in b/packaging/cmake/config.cmake.h.in index d322826300..b183bb509e 100644 --- a/packaging/cmake/config.cmake.h.in +++ b/packaging/cmake/config.cmake.h.in @@ -69,6 +69,10 @@ #cmakedefine HAVE_DLSYM #cmakedefine HAVE_LIBCURL +#cmakedefine HAVE_ARC4RANDOM_BUF +#cmakedefine HAVE_RAND_S +#cmakedefine HAVE_GETRANDOM + #cmakedefine HAVE_BACKTRACE #cmakedefine HAVE_CLOSE_RANGE #cmakedefine HAVE_SCHED_GETSCHEDULER diff --git a/src/aclk/aclk_util.c b/src/aclk/aclk_util.c index 16f57fe9ae..d01fa8f2c7 100644 --- a/src/aclk/aclk_util.c +++ b/src/aclk/aclk_util.c @@ -342,15 +342,13 @@ unsigned long int aclk_tbeb_delay(int reset, int base, unsigned long int min, un attempt++; - if (attempt == 0) { - srandom(time(NULL)); + if (attempt == 0) return 0; - } unsigned long int delay = pow(base, attempt - 1); delay *= MSEC_PER_SEC; - delay += (random() % (MAX(1000, delay/2))); + delay += (os_random32() % (MAX(1000, delay/2))); if (delay <= min * MSEC_PER_SEC) return min; diff --git a/src/libnetdata/clocks/clocks.c b/src/libnetdata/clocks/clocks.c index 362e87bb2f..c658861984 100644 --- a/src/libnetdata/clocks/clocks.c +++ b/src/libnetdata/clocks/clocks.c @@ -260,6 +260,9 @@ void sleep_to_absolute_time(usec_t usec) { } #endif +#define HEARTBEAT_MIN_OFFSET_UT (150 * USEC_PER_MS) +#define HEARTBEAT_RANDOM_OFFSET_UT (350 * USEC_PER_MS) + #define HEARTBEAT_ALIGNMENT_STATISTICS_SIZE 20 static SPINLOCK heartbeat_alignment_spinlock = NETDATA_SPINLOCK_INITIALIZER; static size_t heartbeat_alignment_id = 0; @@ -306,22 +309,27 @@ void heartbeat_statistics(usec_t *min_ptr, usec_t *max_ptr, usec_t *average_ptr, memcpy(old, current, sizeof(struct heartbeat_thread_statistics) * HEARTBEAT_ALIGNMENT_STATISTICS_SIZE); } -static usec_t heartbeat_randomness(usec_t step __maybe_unused, size_t statistics_id) { +static XXH64_hash_t heartbeat_hash(usec_t step, size_t statistics_id) { struct { + usec_t step; pid_t pid; pid_t tid; usec_t now_ut; size_t statistics_id; char tag[ND_THREAD_TAG_MAX + 1]; } key = { + .step = step, .pid = getpid(), .tid = os_gettid(), .now_ut = now_realtime_usec(), .statistics_id = statistics_id, }; strncpyz(key.tag, nd_thread_tag(), sizeof(key.tag) - 1); - XXH64_hash_t hash = XXH3_64bits(&key, sizeof(key)); - usec_t offset_ut = (100 * USEC_PER_MS) + (hash % (400 * USEC_PER_MS)); + return XXH3_64bits(&key, sizeof(key)); +} + +static usec_t heartbeat_randomness(XXH64_hash_t hash) { + usec_t offset_ut = HEARTBEAT_MIN_OFFSET_UT + (hash % HEARTBEAT_RANDOM_OFFSET_UT); // Calculate the scheduler tick interval in microseconds usec_t scheduler_step_ut = USEC_PER_SEC / (usec_t)system_hz; @@ -345,7 +353,8 @@ inline void heartbeat_init(heartbeat_t *hb, usec_t step) { hb->step = step; hb->realtime = 0ULL; - hb->randomness = heartbeat_randomness(hb->step, hb->statistics_id); + hb->hash = heartbeat_hash(hb->step, hb->statistics_id); + hb->randomness = heartbeat_randomness(hb->hash); if(hb->statistics_id < HEARTBEAT_ALIGNMENT_STATISTICS_SIZE) { heartbeat_alignment_values[hb->statistics_id].dt = 0; diff --git a/src/libnetdata/clocks/clocks.h b/src/libnetdata/clocks/clocks.h index 41458527b0..03860d66db 100644 --- a/src/libnetdata/clocks/clocks.h +++ b/src/libnetdata/clocks/clocks.h @@ -4,6 +4,7 @@ #define NETDATA_CLOCKS_H 1 #include "../libnetdata.h" +#include "libnetdata/os/random.h" #ifndef HAVE_CLOCK_GETTIME struct timespec { @@ -30,6 +31,7 @@ typedef struct heartbeat { usec_t realtime; usec_t randomness; size_t statistics_id; + XXH64_hash_t hash; } heartbeat_t; /* Linux value is as good as any other */ diff --git a/src/libnetdata/os/os.h b/src/libnetdata/os/os.h index 77d530b01b..1846afb6d8 100644 --- a/src/libnetdata/os/os.h +++ b/src/libnetdata/os/os.h @@ -7,6 +7,7 @@ #include <sys/syscall.h> #endif +#include "random.h" #include "timestamps.h" #include "setproctitle.h" #include "close_range.h" @@ -17,7 +18,7 @@ #include "gettid.h" #include "get_pid_max.h" #include "get_system_cpus.h" -#include "tinysleep.h" +#include "sleep.h" #include "uuid_generate.h" #include "setenv.h" #include "os-freebsd-wrappers.h" diff --git a/src/libnetdata/os/random.c b/src/libnetdata/os/random.c new file mode 100644 index 0000000000..90a876a9b9 --- /dev/null +++ b/src/libnetdata/os/random.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +#if !defined(HAVE_ARC4RANDOM_BUF) && !defined(HAVE_RAND_S) +static SPINLOCK random_lock = NETDATA_SPINLOCK_INITIALIZER; +static __attribute__((constructor)) void seed_random() { + // Use current time and process ID to create a high-entropy seed + struct timeval tv; + gettimeofday(&tv, NULL); + + uint32_t seed = (uint32_t)(tv.tv_sec ^ tv.tv_usec ^ getpid()); + + // Seed the random number generator + srandom(seed); +} + +#if defined(HAVE_GETRANDOM) +#include <sys/random.h> +void getrandom_helper(void *buf, size_t buflen) { + ssize_t result; + while (buflen > 0) { + result = getrandom(buf, buflen, 0); + if (result == -1) { + if (errno == EINTR) { + // Interrupted, retry + continue; + } else if (errno == EAGAIN) { + // Insufficient entropy; wait and retry + tinysleep(); + continue; + } else { + // Fallback to using random() with a spinlock + spinlock_lock(&random_lock); + while (buflen > 0) { + if (buflen >= sizeof(uint32_t)) { + // Generate 4 bytes at a time + uint32_t temp = random(); + memcpy(buf, &temp, sizeof(uint32_t)); + buf = (uint8_t *)buf + sizeof(uint32_t); + buflen -= sizeof(uint32_t); + } else if (buflen >= sizeof(uint16_t)) { + // Generate 2 bytes at a time + uint16_t temp = random(); + memcpy(buf, &temp, sizeof(uint16_t)); + buf = (uint8_t *)buf + sizeof(uint16_t); + buflen -= sizeof(uint16_t); + } else { + // Generate remaining bytes + uint32_t temp = random(); + for (size_t i = 0; i < buflen; i++) { + ((uint8_t *)buf)[i] = temp & 0xFF; + temp >>= 8; + } + buflen = 0; + } + } + spinlock_unlock(&random_lock); + return; + } + } + buf = (uint8_t *)buf + result; + buflen -= result; + } +} +#endif // HAVE_GETRANDOM +#endif // !HAVE_ARC4RANDOM_BUF && !HAVE_RAND_S + +// return a random number 0 to max - 1 +uint64_t os_random(uint64_t max) { + if (max <= 1) return 0; + + uint64_t value; + +#if defined(HAVE_ARC4RANDOM_BUF) + if (max <= UINT8_MAX) { + uint8_t v; + arc4random_buf(&v, sizeof(v)); + value = v; + } else if(max <= UINT16_MAX) { + uint16_t v; + arc4random_buf(&v, sizeof(v)); + value = v; + } else if (max <= UINT32_MAX) { + uint32_t v; + arc4random_buf(&v, sizeof(v)); + value = v; + } else + arc4random_buf(&value, sizeof(value)); + +#elif defined(HAVE_RAND_S) + if (max <= UINT_MAX) { + unsigned int temp; + rand_s(&temp); + value = temp; + } else { + unsigned int temp_lo, temp_hi; + rand_s(&temp_lo); + rand_s(&temp_hi); + value = ((uint64_t)temp_hi << 32) + (uint64_t)temp_lo; + } + +#elif defined(HAVE_GETRANDOM) + if (max <= UINT8_MAX) { + uint8_t v; + getrandom_helper(&v, sizeof(v)); + value = v; + } else if(max <= UINT16_MAX) { + uint16_t v; + getrandom_helper(&v, sizeof(v)); + value = v; + } else if (max <= UINT32_MAX) { + uint32_t v; + getrandom_helper(&v, sizeof(v)); + value = v; + } else + getrandom_helper(&value, sizeof(value)); + +#else + spinlock_lock(&random_lock); + if(max <= INT32_MAX) + value = random(); + else + value = ((uint64_t) random() << 33) | ((uint64_t) random() << 2) | (random() & 0x3); + spinlock_unlock(&random_lock); +#endif + + return value % max; +} + +// Generate an 8-bit random number +uint8_t os_random8(void) { + uint8_t value; + +#if defined(HAVE_ARC4RANDOM_BUF) + arc4random_buf(&value, sizeof(value)); +#elif defined(HAVE_GETRANDOM) + getrandom_helper(&value, sizeof(value)); +#elif defined(HAVE_RAND_S) + unsigned int temp; + rand_s(&temp); + value = (uint8_t)temp; +#else + spinlock_lock(&random_lock); + value = (uint8_t)random(); + spinlock_unlock(&random_lock); +#endif + + return value; +} + +// Generate a 16-bit random number +uint16_t os_random16(void) { + uint16_t value; + +#if defined(HAVE_ARC4RANDOM_BUF) + arc4random_buf(&value, sizeof(value)); +#elif defined(HAVE_GETRANDOM) + getrandom_helper(&value, sizeof(value)); +#elif defined(HAVE_RAND_S) + unsigned int temp; + rand_s(&temp); + value = (uint16_t)temp; +#else + spinlock_lock(&random_lock); + value = (uint16_t)random(); + spinlock_unlock(&random_lock); +#endif + + return value; +} + +// Generate a 32-bit random number +uint32_t os_random32(void) { + uint32_t value; + +#if defined(HAVE_ARC4RANDOM_BUF) + arc4random_buf(&value, sizeof(value)); +#elif defined(HAVE_GETRANDOM) + getrandom_helper(&value, sizeof(value)); +#elif defined(HAVE_RAND_S) + unsigned int temp; + rand_s(&temp); + value = temp; +#else + spinlock_lock(&random_lock); + value = random(); + spinlock_unlock(&random_lock); +#endif + + return value; +} + +// Generate a 64-bit random number +uint64_t os_random64(void) { + uint64_t value; + +#if defined(HAVE_ARC4RANDOM_BUF) + arc4random_buf(&value, sizeof(value)); +#elif defined(HAVE_GETRANDOM) + getrandom_helper(&value, sizeof(value)); +#elif defined(HAVE_RAND_S) + unsigned int temp_lo, temp_hi; + rand_s(&temp_lo); + rand_s(&temp_hi); + value = ((uint64_t)temp_hi << 32) | (uint64_t)temp_lo; +#else + spinlock_lock(&random_lock); + value = ((uint64_t)random() << 33) | ((uint64_t)random() << 2) | (random() & 0x3); + spinlock_unlock(&random_lock); +#endif + + return value; +} diff --git a/src/libnetdata/os/random.h b/src/libnetdata/os/random.h new file mode 100644 index 0000000000..a40699e8e7 --- /dev/null +++ b/src/libnetdata/os/random.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_RANDOM_H +#define NETDATA_RANDOM_H + +#include "libnetdata/common.h" + +// return a random number 0 to max - 1 +uint64_t os_random(uint64_t max); + +uint8_t os_random8(void); +uint16_t os_random16(void); +uint32_t os_random32(void); +uint64_t os_random64(void); + +#endif //NETDATA_RANDOM_H diff --git a/src/libnetdata/os/sleep.c b/src/libnetdata/os/sleep.c new file mode 100644 index 0000000000..131b47c44d --- /dev/null +++ b/src/libnetdata/os/sleep.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +#ifdef OS_WINDOWS +void tinysleep(void) { + Sleep(1); +} +#else +void tinysleep(void) { + static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 }; + nanosleep(&ns, NULL); +} +#endif + +#ifdef OS_WINDOWS +void microsleep(usec_t ut) { + size_t ms = ut / USEC_PER_MS + ((ut == 0 || (ut % USEC_PER_MS)) ? 1 : 0); + Sleep(ms); +} +#else +void microsleep(usec_t ut) { + time_t secs = (time_t)(ut / USEC_PER_SEC); + nsec_t nsec = (ut % USEC_PER_SEC) * NSEC_PER_USEC + ((ut == 0) ? 1 : 0); + + struct timespec remaining = { + .tv_sec = secs, + .tv_nsec = nsec, + }; + + errno_clear(); + while (nanosleep(&remaining, &remaining) == -1 && errno == EINTR && (remaining.tv_sec || remaining.tv_nsec)) { + // Loop continues if interrupted by a signal + } +} +#endif diff --git a/src/libnetdata/os/sleep.h b/src/libnetdata/os/sleep.h new file mode 100644 index 0000000000..3582387623 --- /dev/null +++ b/src/libnetdata/os/sleep.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_SLEEP_H +#define NETDATA_SLEEP_H + +void tinysleep(void); +void microsleep(usec_t ut); + +#endif //NETDATA_SLEEP_H diff --git a/src/libnetdata/os/tinysleep.c b/src/libnetdata/os/tinysleep.c deleted file mode 100644 index efacf55b88..0000000000 --- a/src/libnetdata/os/tinysleep.c +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "../libnetdata.h" - -#ifdef OS_WINDOWS -void tinysleep(void) { - // Improve the system timer resolution to 1 ms - timeBeginPeriod(1); - - // Sleep for the desired duration - Sleep(1); - - // Reset the system timer resolution - timeEndPeriod(1); -} -#else -void tinysleep(void) { - static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 }; - nanosleep(&ns, NULL); -} -#endif diff --git a/src/libnetdata/os/tinysleep.h b/src/libnetdata/os/tinysleep.h deleted file mode 100644 index 480575a3a7..0000000000 --- a/src/libnetdata/os/tinysleep.h +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_TINYSLEEP_H -#define NETDATA_TINYSLEEP_H - -void tinysleep(void); - -#endif //NETDATA_TINYSLEEP_H diff --git a/src/streaming/compression.c b/src/streaming/compression.c index 8e41c8c337..448f423533 100644 --- a/src/streaming/compression.c +++ b/src/streaming/compression.c @@ -395,21 +395,17 @@ size_t rrdpush_decompress(struct decompressor_state *state, const char *compress // ---------------------------------------------------------------------------- // unit test -static inline long int my_random (void) { - return random(); -} - void unittest_generate_random_name(char *dst, size_t size) { if(size < 7) size = 7; - size_t len = 5 + my_random() % (size - 6); + size_t len = 5 + os_random32() % (size - 6); for(size_t i = 0; i < len ; i++) { - if(my_random() % 2 == 0) - dst[i] = 'A' + my_random() % 26; + if(os_random8() % 2 == 0) + dst[i] = 'A' + os_random8() % 26; else - dst[i] = 'a' + my_random() % 26; + dst[i] = 'a' + os_random8() % 26; } dst[len] = '\0'; @@ -423,9 +419,9 @@ void unittest_generate_message(BUFFER *wb, time_t now_s, size_t counter) { time_t point_end_time_s = now_s; time_t wall_clock_time_s = now_s; size_t chart_slot = counter + 1; - size_t dimensions = 2 + my_random() % 5; + size_t dimensions = 2 + os_random8() % 5; char chart[RRD_ID_LENGTH_MAX + 1] = "name"; - unittest_generate_random_name(chart, 5 + my_random() % 30); + unittest_generate_random_name(chart, 5 + os_random8() % 30); buffer_fast_strcat(wb, PLUGINSD_KEYWORD_BEGIN_V2, sizeof(PLUGINSD_KEYWORD_BEGIN_V2) - 1); @@ -451,10 +447,10 @@ void unittest_generate_message(BUFFER *wb, time_t now_s, size_t counter) { for(size_t d = 0; d < dimensions ;d++) { size_t dim_slot = d + 1; char dim_id[RRD_ID_LENGTH_MAX + 1] = "dimension"; - unittest_generate_random_name(dim_id, 10 + my_random() % 20); - int64_t last_collected_value = (my_random() % 2 == 0) ? (int64_t)(counter + d) : (int64_t)my_random(); - NETDATA_DOUBLE value = (my_random() % 2 == 0) ? (NETDATA_DOUBLE)my_random() / ((NETDATA_DOUBLE)my_random() + 1) : (NETDATA_DOUBLE)last_collected_value; - SN_FLAGS flags = (my_random() % 1000 == 0) ? SN_FLAG_NONE : SN_FLAG_NOT_ANOMALOUS; + unittest_generate_random_name(dim_id, 10 + os_random8() % 20); + int64_t last_collected_value = (os_random8() % 2 == 0) ? (int64_t)(counter + d) : (int64_t)os_random32(); + NETDATA_DOUBLE value = (os_random8() % 2 == 0) ? (NETDATA_DOUBLE)os_random64() / ((NETDATA_DOUBLE)os_random64() + 1) : (NETDATA_DOUBLE)last_collected_value; + SN_FLAGS flags = (os_random16() % 1000 == 0) ? SN_FLAG_NONE : SN_FLAG_NOT_ANOMALOUS; buffer_fast_strcat(wb, PLUGINSD_KEYWORD_SET_V2, sizeof(PLUGINSD_KEYWORD_SET_V2) - 1);