0
0
Fork 0
mirror of https://github.com/netdata/netdata.git synced 2025-04-14 01:29:11 +00:00
netdata_netdata/libnetdata/libnetdata.c
Costa Tsaousis f466b8aef5
DYNCFG: dynamically configured alerts ()
* cleanup alerts

* fix references

* fix references

* fix references

* load alerts once and apply them to each node

* simplify health_create_alarm_entry()

* Compile without warnings with compiler flags:

   -Wall -Wextra -Wformat=2 -Wshadow -Wno-format-nonliteral -Winit-self

* code re-organization and cleanup

* generate patterns when applying prototypes; give unique dyncfg names to all alerts

* eval expressions keep the source and the parsed_as as STRING pointers

* renamed host to node in dyncfg ids

* renamed host to node in dyncfg ids

* add all cloud roles to the list of parsed X-Netdata-Role header and also default to member access level

* working functionality

* code re-organization: moved health event-loop to a new file, moved health globals to health.c

* rrdcalctemplate is removed; alert_cfg is removed; foreach dimension is removed; RRDCALCs are now instanciated only when they are linked to RRDSETs

* dyncfg alert prototypes initialization for alerts

* health dyncfg split to separate file

* cleanup not-needed code

* normalize matches between parsing and json

* also detect !* for disabled alerts

* dyncfg capability disabled

* Store alert config part1

* Add rrdlabels_common_count

* wip health variables lookup without indexes

* Improve rrdlabels_common_count by reusing rrdlabels_find_label_with_key_unsafe with an additional parameter

* working variables with runtime lookup

* working variables with runtime lookup

* delete rrddimvar and rrdfamily index

* remove rrdsetvar; now all variables are in RRDVARs inside hosts and charts

* added /api/v1/variable that resolves a variable the same way alerts do

* remove rrdcalc from eval

* remove debug code

* remove duplicate assignment

* Fix memory leak

* all alert variables are now handled by alert_variable_lookup() and EVAL is now independent of alerts

* hide all internal structures of EVAL

* Enable -Wformat flag

Signed-off-by: Tasos Katsoulas <tasos@netdata.cloud>

* Adjust binding for calculation, warning, critical

* Remove unused macro

* Update config hash id

* use the right info and summary in alerts log

* use synchronous queries for alerts

* Handle cases when config_hash_id is missing from health_log

* remove deadlock from health worker

* parsing to json payload for health alert prototypes

* cleaner parsing and avoiding memory leaks in case of duplicate members in json

* fix left-over rename of function

* Keep original lookup field to send to the cloud
Cleanup / rename function to store config
Remove unused DEFINEs, functions

* Use ac->lookup

* link jobs to the host when the template is registered; do not accept running a function without a host

* full dyncfg support for health alerts, except action TEST

* working dyncfg additions, updates, removals

* fixed missing source, wrong status updates

* add alerts by type, component, classification, recipient and module at the /api/v2/alerts endpoint

* fix dyncfg unittest

* rename functions

* generalize the json-c parser macros and move them to libnetdata

* report progress when enabling and disabling dyncfg templates

* moved rrdcalc and rrdvar to health

* update alarms

* added schema for alerts; separated alert_action_options from rrdr_options; restructured the json payload for alerts

* enable parsed json alerts; allow sending back accepted but disabled

* added format_version for alerts payload; enables/disables status now is also inheritted by the status of the rules; fixed variable names in json output

* remove the RRDHOST pointer from DYNCFG

* Fix command field submitted to the cloud

* do not send updates to creation requests, for DYNCFG jobs

---------

Signed-off-by: Tasos Katsoulas <tasos@netdata.cloud>
Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com>
Co-authored-by: Tasos Katsoulas <tasos@netdata.cloud>
Co-authored-by: ilyam8 <ilya@netdata.cloud>
2024-01-23 20:20:41 +02:00

2083 lines
61 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
#include "libnetdata.h"
#ifdef __APPLE__
#define INHERIT_NONE 0
#endif /* __APPLE__ */
#if defined(__FreeBSD__) || defined(__APPLE__)
# define O_NOATIME 0
# define MADV_DONTFORK INHERIT_NONE
#endif /* __FreeBSD__ || __APPLE__*/
struct rlimit rlimit_nofile = { .rlim_cur = 1024, .rlim_max = 1024 };
#ifdef MADV_MERGEABLE
int enable_ksm = 1;
#else
int enable_ksm = 0;
#endif
volatile sig_atomic_t netdata_exit = 0;
const char *program_version = VERSION;
#define MAX_JUDY_SIZE_TO_ARAL 24
static bool judy_sizes_config[MAX_JUDY_SIZE_TO_ARAL + 1] = {
[3] = true,
[4] = true,
[5] = true,
[6] = true,
[7] = true,
[8] = true,
[10] = true,
[11] = true,
[15] = true,
[23] = true,
};
static ARAL *judy_sizes_aral[MAX_JUDY_SIZE_TO_ARAL + 1] = {};
struct aral_statistics judy_sizes_aral_statistics = {};
void aral_judy_init(void) {
for(size_t Words = 0; Words <= MAX_JUDY_SIZE_TO_ARAL; Words++)
if(judy_sizes_config[Words]) {
char buf[30+1];
snprintfz(buf, sizeof(buf) - 1, "judy-%zu", Words * sizeof(Word_t));
judy_sizes_aral[Words] = aral_create(
buf,
Words * sizeof(Word_t),
0,
65536,
&judy_sizes_aral_statistics,
NULL, NULL, false, false);
}
}
size_t judy_aral_overhead(void) {
return aral_overhead_from_stats(&judy_sizes_aral_statistics);
}
size_t judy_aral_structures(void) {
return aral_structures_from_stats(&judy_sizes_aral_statistics);
}
static ARAL *judy_size_aral(Word_t Words) {
if(Words <= MAX_JUDY_SIZE_TO_ARAL && judy_sizes_aral[Words])
return judy_sizes_aral[Words];
return NULL;
}
inline Word_t JudyMalloc(Word_t Words) {
Word_t Addr;
ARAL *ar = judy_size_aral(Words);
if(ar)
Addr = (Word_t) aral_mallocz(ar);
else
Addr = (Word_t) mallocz(Words * sizeof(Word_t));
return(Addr);
}
inline void JudyFree(void * PWord, Word_t Words) {
ARAL *ar = judy_size_aral(Words);
if(ar)
aral_freez(ar, PWord);
else
freez(PWord);
}
Word_t JudyMallocVirtual(Word_t Words) {
return JudyMalloc(Words);
}
void JudyFreeVirtual(void * PWord, Word_t Words) {
JudyFree(PWord, Words);
}
// ----------------------------------------------------------------------------
// memory allocation functions that handle failures
// although netdata does not use memory allocations too often (netdata tries to
// maintain its memory footprint stable during runtime, i.e. all buffers are
// allocated during initialization and are adapted to current use throughout
// its lifetime), these can be used to override the default system allocation
// routines.
#ifdef NETDATA_TRACE_ALLOCATIONS
#warning NETDATA_TRACE_ALLOCATIONS ENABLED
#include "Judy.h"
#if defined(HAVE_DLSYM) && defined(ENABLE_DLSYM)
#include <dlfcn.h>
typedef void (*libc_function_t)(void);
static void *malloc_first_run(size_t size);
static void *(*libc_malloc)(size_t) = malloc_first_run;
static void *calloc_first_run(size_t n, size_t size);
static void *(*libc_calloc)(size_t, size_t) = calloc_first_run;
static void *realloc_first_run(void *ptr, size_t size);
static void *(*libc_realloc)(void *, size_t) = realloc_first_run;
static void free_first_run(void *ptr);
static void (*libc_free)(void *) = free_first_run;
static char *strdup_first_run(const char *s);
static char *(*libc_strdup)(const char *) = strdup_first_run;
static char *strndup_first_run(const char *s, size_t len);
static char *(*libc_strndup)(const char *, size_t) = strndup_first_run;
static size_t malloc_usable_size_first_run(void *ptr);
#ifdef HAVE_MALLOC_USABLE_SIZE
static size_t (*libc_malloc_usable_size)(void *) = malloc_usable_size_first_run;
#else
static size_t (*libc_malloc_usable_size)(void *) = NULL;
#endif
static void link_system_library_function(libc_function_t *func_pptr, const char *name, bool required) {
*func_pptr = dlsym(RTLD_NEXT, name);
if(!*func_pptr && required) {
fprintf(stderr, "FATAL: Cannot find system's %s() function.\n", name);
abort();
}
}
static void *malloc_first_run(size_t size) {
link_system_library_function((libc_function_t *) &libc_malloc, "malloc", true);
return libc_malloc(size);
}
static void *calloc_first_run(size_t n, size_t size) {
link_system_library_function((libc_function_t *) &libc_calloc, "calloc", true);
return libc_calloc(n, size);
}
static void *realloc_first_run(void *ptr, size_t size) {
link_system_library_function((libc_function_t *) &libc_realloc, "realloc", true);
return libc_realloc(ptr, size);
}
static void free_first_run(void *ptr) {
link_system_library_function((libc_function_t *) &libc_free, "free", true);
libc_free(ptr);
}
static char *strdup_first_run(const char *s) {
link_system_library_function((libc_function_t *) &libc_strdup, "strdup", true);
return libc_strdup(s);
}
static char *strndup_first_run(const char *s, size_t len) {
link_system_library_function((libc_function_t *) &libc_strndup, "strndup", true);
return libc_strndup(s, len);
}
static size_t malloc_usable_size_first_run(void *ptr) {
link_system_library_function((libc_function_t *) &libc_malloc_usable_size, "malloc_usable_size", false);
if(libc_malloc_usable_size)
return libc_malloc_usable_size(ptr);
else
return 0;
}
void *malloc(size_t size) {
return mallocz(size);
}
void *calloc(size_t n, size_t size) {
return callocz(n, size);
}
void *realloc(void *ptr, size_t size) {
return reallocz(ptr, size);
}
void *reallocarray(void *ptr, size_t n, size_t size) {
return reallocz(ptr, n * size);
}
void free(void *ptr) {
freez(ptr);
}
char *strdup(const char *s) {
return strdupz(s);
}
char *strndup(const char *s, size_t len) {
return strndupz(s, len);
}
size_t malloc_usable_size(void *ptr) {
return mallocz_usable_size(ptr);
}
#else // !HAVE_DLSYM
static void *(*libc_malloc)(size_t) = malloc;
static void *(*libc_calloc)(size_t, size_t) = calloc;
static void *(*libc_realloc)(void *, size_t) = realloc;
static void (*libc_free)(void *) = free;
#ifdef HAVE_MALLOC_USABLE_SIZE
static size_t (*libc_malloc_usable_size)(void *) = malloc_usable_size;
#else
static size_t (*libc_malloc_usable_size)(void *) = NULL;
#endif
#endif // HAVE_DLSYM
void posix_memfree(void *ptr) {
libc_free(ptr);
}
struct malloc_header_signature {
uint32_t magic;
uint32_t size;
struct malloc_trace *trace;
};
struct malloc_header {
struct malloc_header_signature signature;
uint8_t padding[(sizeof(struct malloc_header_signature) % MALLOC_ALIGNMENT) ? MALLOC_ALIGNMENT - (sizeof(struct malloc_header_signature) % MALLOC_ALIGNMENT) : 0];
uint8_t data[];
};
static size_t malloc_header_size = sizeof(struct malloc_header);
int malloc_trace_compare(void *A, void *B) {
struct malloc_trace *a = A;
struct malloc_trace *b = B;
return strcmp(a->function, b->function);
}
static avl_tree_lock malloc_trace_index = {
.avl_tree = {
.root = NULL,
.compar = malloc_trace_compare},
.rwlock = AVL_LOCK_INITIALIZER
};
int malloc_trace_walkthrough(int (*callback)(void *item, void *data), void *data) {
return avl_traverse_lock(&malloc_trace_index, callback, data);
}
NEVERNULL WARNUNUSED
static struct malloc_trace *malloc_trace_find_or_create(const char *file, const char *function, size_t line) {
struct malloc_trace tmp = {
.line = line,
.function = function,
.file = file,
};
struct malloc_trace *t = (struct malloc_trace *)avl_search_lock(&malloc_trace_index, (avl_t *)&tmp);
if(!t) {
t = libc_calloc(1, sizeof(struct malloc_trace));
if(!t) fatal("No memory");
t->line = line;
t->function = function;
t->file = file;
struct malloc_trace *t2 = (struct malloc_trace *)avl_insert_lock(&malloc_trace_index, (avl_t *)t);
if(t2 != t)
free(t);
t = t2;
}
if(!t)
fatal("Cannot insert to AVL");
return t;
}
void malloc_trace_mmap(size_t size) {
struct malloc_trace *p = malloc_trace_find_or_create("unknown", "netdata_mmap", 1);
size_t_atomic_count(add, p->mmap_calls, 1);
size_t_atomic_count(add, p->allocations, 1);
size_t_atomic_bytes(add, p->bytes, size);
}
void malloc_trace_munmap(size_t size) {
struct malloc_trace *p = malloc_trace_find_or_create("unknown", "netdata_mmap", 1);
size_t_atomic_count(add, p->munmap_calls, 1);
size_t_atomic_count(sub, p->allocations, 1);
size_t_atomic_bytes(sub, p->bytes, size);
}
void *mallocz_int(size_t size, const char *file, const char *function, size_t line) {
struct malloc_trace *p = malloc_trace_find_or_create(file, function, line);
size_t_atomic_count(add, p->malloc_calls, 1);
size_t_atomic_count(add, p->allocations, 1);
size_t_atomic_bytes(add, p->bytes, size);
struct malloc_header *t = (struct malloc_header *)libc_malloc(malloc_header_size + size);
if (unlikely(!t)) fatal("mallocz() cannot allocate %zu bytes of memory (%zu with header).", size, malloc_header_size + size);
t->signature.magic = 0x0BADCAFE;
t->signature.trace = p;
t->signature.size = size;
#ifdef NETDATA_INTERNAL_CHECKS
for(ssize_t i = 0; i < (ssize_t)sizeof(t->padding) ;i++) // signed to avoid compiler warning when zero-padded
t->padding[i] = 0xFF;
#endif
return (void *)&t->data;
}
void *callocz_int(size_t nmemb, size_t size, const char *file, const char *function, size_t line) {
struct malloc_trace *p = malloc_trace_find_or_create(file, function, line);
size = nmemb * size;
size_t_atomic_count(add, p->calloc_calls, 1);
size_t_atomic_count(add, p->allocations, 1);
size_t_atomic_bytes(add, p->bytes, size);
struct malloc_header *t = (struct malloc_header *)libc_calloc(1, malloc_header_size + size);
if (unlikely(!t)) fatal("mallocz() cannot allocate %zu bytes of memory (%zu with header).", size, malloc_header_size + size);
t->signature.magic = 0x0BADCAFE;
t->signature.trace = p;
t->signature.size = size;
#ifdef NETDATA_INTERNAL_CHECKS
for(ssize_t i = 0; i < (ssize_t)sizeof(t->padding) ;i++) // signed to avoid compiler warning when zero-padded
t->padding[i] = 0xFF;
#endif
return &t->data;
}
char *strdupz_int(const char *s, const char *file, const char *function, size_t line) {
struct malloc_trace *p = malloc_trace_find_or_create(file, function, line);
size_t size = strlen(s) + 1;
size_t_atomic_count(add, p->strdup_calls, 1);
size_t_atomic_count(add, p->allocations, 1);
size_t_atomic_bytes(add, p->bytes, size);
struct malloc_header *t = (struct malloc_header *)libc_malloc(malloc_header_size + size);
if (unlikely(!t)) fatal("strdupz() cannot allocate %zu bytes of memory (%zu with header).", size, malloc_header_size + size);
t->signature.magic = 0x0BADCAFE;
t->signature.trace = p;
t->signature.size = size;
#ifdef NETDATA_INTERNAL_CHECKS
for(ssize_t i = 0; i < (ssize_t)sizeof(t->padding) ;i++) // signed to avoid compiler warning when zero-padded
t->padding[i] = 0xFF;
#endif
memcpy(&t->data, s, size);
return (char *)&t->data;
}
char *strndupz_int(const char *s, size_t len, const char *file, const char *function, size_t line) {
struct malloc_trace *p = malloc_trace_find_or_create(file, function, line);
size_t size = len + 1;
size_t_atomic_count(add, p->strdup_calls, 1);
size_t_atomic_count(add, p->allocations, 1);
size_t_atomic_bytes(add, p->bytes, size);
struct malloc_header *t = (struct malloc_header *)libc_malloc(malloc_header_size + size);
if (unlikely(!t)) fatal("strndupz() cannot allocate %zu bytes of memory (%zu with header).", size, malloc_header_size + size);
t->signature.magic = 0x0BADCAFE;
t->signature.trace = p;
t->signature.size = size;
#ifdef NETDATA_INTERNAL_CHECKS
for(ssize_t i = 0; i < (ssize_t)sizeof(t->padding) ;i++) // signed to avoid compiler warning when zero-padded
t->padding[i] = 0xFF;
#endif
memcpy(&t->data, s, size);
t->data[len] = '\0';
return (char *)&t->data;
}
static struct malloc_header *malloc_get_header(void *ptr, const char *caller, const char *file, const char *function, size_t line) {
uint8_t *ret = (uint8_t *)ptr - malloc_header_size;
struct malloc_header *t = (struct malloc_header *)ret;
if(t->signature.magic != 0x0BADCAFE) {
netdata_log_error("pointer %p is not our pointer (called %s() from %zu@%s, %s()).", ptr, caller, line, file, function);
return NULL;
}
return t;
}
void *reallocz_int(void *ptr, size_t size, const char *file, const char *function, size_t line) {
if(!ptr) return mallocz_int(size, file, function, line);
struct malloc_header *t = malloc_get_header(ptr, __FUNCTION__, file, function, line);
if(!t)
return libc_realloc(ptr, size);
if(t->signature.size == size) return ptr;
size_t_atomic_count(add, t->signature.trace->free_calls, 1);
size_t_atomic_count(sub, t->signature.trace->allocations, 1);
size_t_atomic_bytes(sub, t->signature.trace->bytes, t->signature.size);
struct malloc_trace *p = malloc_trace_find_or_create(file, function, line);
size_t_atomic_count(add, p->realloc_calls, 1);
size_t_atomic_count(add, p->allocations, 1);
size_t_atomic_bytes(add, p->bytes, size);
t = (struct malloc_header *)libc_realloc(t, malloc_header_size + size);
if (unlikely(!t)) fatal("reallocz() cannot allocate %zu bytes of memory (%zu with header).", size, malloc_header_size + size);
t->signature.magic = 0x0BADCAFE;
t->signature.trace = p;
t->signature.size = size;
#ifdef NETDATA_INTERNAL_CHECKS
for(ssize_t i = 0; i < (ssize_t)sizeof(t->padding) ;i++) // signed to avoid compiler warning when zero-padded
t->padding[i] = 0xFF;
#endif
return (void *)&t->data;
}
size_t mallocz_usable_size_int(void *ptr, const char *file, const char *function, size_t line) {
if(unlikely(!ptr)) return 0;
struct malloc_header *t = malloc_get_header(ptr, __FUNCTION__, file, function, line);
if(!t) {
if(libc_malloc_usable_size)
return libc_malloc_usable_size(ptr);
else
return 0;
}
return t->signature.size;
}
void freez_int(void *ptr, const char *file, const char *function, size_t line) {
if(unlikely(!ptr)) return;
struct malloc_header *t = malloc_get_header(ptr, __FUNCTION__, file, function, line);
if(!t) {
libc_free(ptr);
return;
}
size_t_atomic_count(add, t->signature.trace->free_calls, 1);
size_t_atomic_count(sub, t->signature.trace->allocations, 1);
size_t_atomic_bytes(sub, t->signature.trace->bytes, t->signature.size);
#ifdef NETDATA_INTERNAL_CHECKS
// it should crash if it is used after freeing it
memset(t, 0, malloc_header_size + t->signature.size);
#endif
libc_free(t);
}
#else
char *strdupz(const char *s) {
char *t = strdup(s);
if (unlikely(!t)) fatal("Cannot strdup() string '%s'", s);
return t;
}
char *strndupz(const char *s, size_t len) {
char *t = strndup(s, len);
if (unlikely(!t)) fatal("Cannot strndup() string '%s' of len %zu", s, len);
return t;
}
// If ptr is NULL, no operation is performed.
void freez(void *ptr) {
free(ptr);
}
void *mallocz(size_t size) {
void *p = malloc(size);
if (unlikely(!p)) fatal("Cannot allocate %zu bytes of memory.", size);
return p;
}
void *callocz(size_t nmemb, size_t size) {
void *p = calloc(nmemb, size);
if (unlikely(!p)) fatal("Cannot allocate %zu bytes of memory.", nmemb * size);
return p;
}
void *reallocz(void *ptr, size_t size) {
void *p = realloc(ptr, size);
if (unlikely(!p)) fatal("Cannot re-allocate memory to %zu bytes.", size);
return p;
}
void posix_memfree(void *ptr) {
free(ptr);
}
#endif
// --------------------------------------------------------------------------------------------------------------------
void json_escape_string(char *dst, const char *src, size_t size) {
const char *t;
char *d = dst, *e = &dst[size - 1];
for(t = src; *t && d < e ;t++) {
if(unlikely(*t == '\\' || *t == '"')) {
if(unlikely(d + 1 >= e)) break;
*d++ = '\\';
}
*d++ = *t;
}
*d = '\0';
}
void json_fix_string(char *s) {
unsigned char c;
while((c = (unsigned char)*s)) {
if(unlikely(c == '\\'))
*s++ = '/';
else if(unlikely(c == '"'))
*s++ = '\'';
else if(unlikely(isspace(c) || iscntrl(c)))
*s++ = ' ';
else if(unlikely(!isprint(c) || c > 127))
*s++ = '_';
else
s++;
}
}
unsigned char netdata_map_chart_names[256] = {
[0] = '\0', //
[1] = '_', //
[2] = '_', //
[3] = '_', //
[4] = '_', //
[5] = '_', //
[6] = '_', //
[7] = '_', //
[8] = '_', //
[9] = '_', //
[10] = '_', //
[11] = '_', //
[12] = '_', //
[13] = '_', //
[14] = '_', //
[15] = '_', //
[16] = '_', //
[17] = '_', //
[18] = '_', //
[19] = '_', //
[20] = '_', //
[21] = '_', //
[22] = '_', //
[23] = '_', //
[24] = '_', //
[25] = '_', //
[26] = '_', //
[27] = '_', //
[28] = '_', //
[29] = '_', //
[30] = '_', //
[31] = '_', //
[32] = '_', //
[33] = '_', // !
[34] = '_', // "
[35] = '_', // #
[36] = '_', // $
[37] = '_', // %
[38] = '_', // &
[39] = '_', // '
[40] = '_', // (
[41] = '_', // )
[42] = '_', // *
[43] = '_', // +
[44] = '.', // ,
[45] = '-', // -
[46] = '.', // .
[47] = '/', // /
[48] = '0', // 0
[49] = '1', // 1
[50] = '2', // 2
[51] = '3', // 3
[52] = '4', // 4
[53] = '5', // 5
[54] = '6', // 6
[55] = '7', // 7
[56] = '8', // 8
[57] = '9', // 9
[58] = '_', // :
[59] = '_', // ;
[60] = '_', // <
[61] = '_', // =
[62] = '_', // >
[63] = '_', // ?
[64] = '_', // @
[65] = 'a', // A
[66] = 'b', // B
[67] = 'c', // C
[68] = 'd', // D
[69] = 'e', // E
[70] = 'f', // F
[71] = 'g', // G
[72] = 'h', // H
[73] = 'i', // I
[74] = 'j', // J
[75] = 'k', // K
[76] = 'l', // L
[77] = 'm', // M
[78] = 'n', // N
[79] = 'o', // O
[80] = 'p', // P
[81] = 'q', // Q
[82] = 'r', // R
[83] = 's', // S
[84] = 't', // T
[85] = 'u', // U
[86] = 'v', // V
[87] = 'w', // W
[88] = 'x', // X
[89] = 'y', // Y
[90] = 'z', // Z
[91] = '_', // [
[92] = '/', // backslash
[93] = '_', // ]
[94] = '_', // ^
[95] = '_', // _
[96] = '_', // `
[97] = 'a', // a
[98] = 'b', // b
[99] = 'c', // c
[100] = 'd', // d
[101] = 'e', // e
[102] = 'f', // f
[103] = 'g', // g
[104] = 'h', // h
[105] = 'i', // i
[106] = 'j', // j
[107] = 'k', // k
[108] = 'l', // l
[109] = 'm', // m
[110] = 'n', // n
[111] = 'o', // o
[112] = 'p', // p
[113] = 'q', // q
[114] = 'r', // r
[115] = 's', // s
[116] = 't', // t
[117] = 'u', // u
[118] = 'v', // v
[119] = 'w', // w
[120] = 'x', // x
[121] = 'y', // y
[122] = 'z', // z
[123] = '_', // {
[124] = '_', // |
[125] = '_', // }
[126] = '_', // ~
[127] = '_', //
[128] = '_', //
[129] = '_', //
[130] = '_', //
[131] = '_', //
[132] = '_', //
[133] = '_', //
[134] = '_', //
[135] = '_', //
[136] = '_', //
[137] = '_', //
[138] = '_', //
[139] = '_', //
[140] = '_', //
[141] = '_', //
[142] = '_', //
[143] = '_', //
[144] = '_', //
[145] = '_', //
[146] = '_', //
[147] = '_', //
[148] = '_', //
[149] = '_', //
[150] = '_', //
[151] = '_', //
[152] = '_', //
[153] = '_', //
[154] = '_', //
[155] = '_', //
[156] = '_', //
[157] = '_', //
[158] = '_', //
[159] = '_', //
[160] = '_', //
[161] = '_', //
[162] = '_', //
[163] = '_', //
[164] = '_', //
[165] = '_', //
[166] = '_', //
[167] = '_', //
[168] = '_', //
[169] = '_', //
[170] = '_', //
[171] = '_', //
[172] = '_', //
[173] = '_', //
[174] = '_', //
[175] = '_', //
[176] = '_', //
[177] = '_', //
[178] = '_', //
[179] = '_', //
[180] = '_', //
[181] = '_', //
[182] = '_', //
[183] = '_', //
[184] = '_', //
[185] = '_', //
[186] = '_', //
[187] = '_', //
[188] = '_', //
[189] = '_', //
[190] = '_', //
[191] = '_', //
[192] = '_', //
[193] = '_', //
[194] = '_', //
[195] = '_', //
[196] = '_', //
[197] = '_', //
[198] = '_', //
[199] = '_', //
[200] = '_', //
[201] = '_', //
[202] = '_', //
[203] = '_', //
[204] = '_', //
[205] = '_', //
[206] = '_', //
[207] = '_', //
[208] = '_', //
[209] = '_', //
[210] = '_', //
[211] = '_', //
[212] = '_', //
[213] = '_', //
[214] = '_', //
[215] = '_', //
[216] = '_', //
[217] = '_', //
[218] = '_', //
[219] = '_', //
[220] = '_', //
[221] = '_', //
[222] = '_', //
[223] = '_', //
[224] = '_', //
[225] = '_', //
[226] = '_', //
[227] = '_', //
[228] = '_', //
[229] = '_', //
[230] = '_', //
[231] = '_', //
[232] = '_', //
[233] = '_', //
[234] = '_', //
[235] = '_', //
[236] = '_', //
[237] = '_', //
[238] = '_', //
[239] = '_', //
[240] = '_', //
[241] = '_', //
[242] = '_', //
[243] = '_', //
[244] = '_', //
[245] = '_', //
[246] = '_', //
[247] = '_', //
[248] = '_', //
[249] = '_', //
[250] = '_', //
[251] = '_', //
[252] = '_', //
[253] = '_', //
[254] = '_', //
[255] = '_' //
};
// make sure the supplied string
// is good for a netdata chart/dimension ID/NAME
void netdata_fix_chart_name(char *s) {
while ((*s = netdata_map_chart_names[(unsigned char) *s])) s++;
}
unsigned char netdata_map_chart_ids[256] = {
[0] = '\0', //
[1] = '_', //
[2] = '_', //
[3] = '_', //
[4] = '_', //
[5] = '_', //
[6] = '_', //
[7] = '_', //
[8] = '_', //
[9] = '_', //
[10] = '_', //
[11] = '_', //
[12] = '_', //
[13] = '_', //
[14] = '_', //
[15] = '_', //
[16] = '_', //
[17] = '_', //
[18] = '_', //
[19] = '_', //
[20] = '_', //
[21] = '_', //
[22] = '_', //
[23] = '_', //
[24] = '_', //
[25] = '_', //
[26] = '_', //
[27] = '_', //
[28] = '_', //
[29] = '_', //
[30] = '_', //
[31] = '_', //
[32] = '_', //
[33] = '_', // !
[34] = '_', // "
[35] = '_', // #
[36] = '_', // $
[37] = '_', // %
[38] = '_', // &
[39] = '_', // '
[40] = '_', // (
[41] = '_', // )
[42] = '_', // *
[43] = '_', // +
[44] = '.', // ,
[45] = '-', // -
[46] = '.', // .
[47] = '_', // /
[48] = '0', // 0
[49] = '1', // 1
[50] = '2', // 2
[51] = '3', // 3
[52] = '4', // 4
[53] = '5', // 5
[54] = '6', // 6
[55] = '7', // 7
[56] = '8', // 8
[57] = '9', // 9
[58] = '_', // :
[59] = '_', // ;
[60] = '_', // <
[61] = '_', // =
[62] = '_', // >
[63] = '_', // ?
[64] = '_', // @
[65] = 'a', // A
[66] = 'b', // B
[67] = 'c', // C
[68] = 'd', // D
[69] = 'e', // E
[70] = 'f', // F
[71] = 'g', // G
[72] = 'h', // H
[73] = 'i', // I
[74] = 'j', // J
[75] = 'k', // K
[76] = 'l', // L
[77] = 'm', // M
[78] = 'n', // N
[79] = 'o', // O
[80] = 'p', // P
[81] = 'q', // Q
[82] = 'r', // R
[83] = 's', // S
[84] = 't', // T
[85] = 'u', // U
[86] = 'v', // V
[87] = 'w', // W
[88] = 'x', // X
[89] = 'y', // Y
[90] = 'z', // Z
[91] = '_', // [
[92] = '_', // backslash
[93] = '_', // ]
[94] = '_', // ^
[95] = '_', // _
[96] = '_', // `
[97] = 'a', // a
[98] = 'b', // b
[99] = 'c', // c
[100] = 'd', // d
[101] = 'e', // e
[102] = 'f', // f
[103] = 'g', // g
[104] = 'h', // h
[105] = 'i', // i
[106] = 'j', // j
[107] = 'k', // k
[108] = 'l', // l
[109] = 'm', // m
[110] = 'n', // n
[111] = 'o', // o
[112] = 'p', // p
[113] = 'q', // q
[114] = 'r', // r
[115] = 's', // s
[116] = 't', // t
[117] = 'u', // u
[118] = 'v', // v
[119] = 'w', // w
[120] = 'x', // x
[121] = 'y', // y
[122] = 'z', // z
[123] = '_', // {
[124] = '_', // |
[125] = '_', // }
[126] = '_', // ~
[127] = '_', //
[128] = '_', //
[129] = '_', //
[130] = '_', //
[131] = '_', //
[132] = '_', //
[133] = '_', //
[134] = '_', //
[135] = '_', //
[136] = '_', //
[137] = '_', //
[138] = '_', //
[139] = '_', //
[140] = '_', //
[141] = '_', //
[142] = '_', //
[143] = '_', //
[144] = '_', //
[145] = '_', //
[146] = '_', //
[147] = '_', //
[148] = '_', //
[149] = '_', //
[150] = '_', //
[151] = '_', //
[152] = '_', //
[153] = '_', //
[154] = '_', //
[155] = '_', //
[156] = '_', //
[157] = '_', //
[158] = '_', //
[159] = '_', //
[160] = '_', //
[161] = '_', //
[162] = '_', //
[163] = '_', //
[164] = '_', //
[165] = '_', //
[166] = '_', //
[167] = '_', //
[168] = '_', //
[169] = '_', //
[170] = '_', //
[171] = '_', //
[172] = '_', //
[173] = '_', //
[174] = '_', //
[175] = '_', //
[176] = '_', //
[177] = '_', //
[178] = '_', //
[179] = '_', //
[180] = '_', //
[181] = '_', //
[182] = '_', //
[183] = '_', //
[184] = '_', //
[185] = '_', //
[186] = '_', //
[187] = '_', //
[188] = '_', //
[189] = '_', //
[190] = '_', //
[191] = '_', //
[192] = '_', //
[193] = '_', //
[194] = '_', //
[195] = '_', //
[196] = '_', //
[197] = '_', //
[198] = '_', //
[199] = '_', //
[200] = '_', //
[201] = '_', //
[202] = '_', //
[203] = '_', //
[204] = '_', //
[205] = '_', //
[206] = '_', //
[207] = '_', //
[208] = '_', //
[209] = '_', //
[210] = '_', //
[211] = '_', //
[212] = '_', //
[213] = '_', //
[214] = '_', //
[215] = '_', //
[216] = '_', //
[217] = '_', //
[218] = '_', //
[219] = '_', //
[220] = '_', //
[221] = '_', //
[222] = '_', //
[223] = '_', //
[224] = '_', //
[225] = '_', //
[226] = '_', //
[227] = '_', //
[228] = '_', //
[229] = '_', //
[230] = '_', //
[231] = '_', //
[232] = '_', //
[233] = '_', //
[234] = '_', //
[235] = '_', //
[236] = '_', //
[237] = '_', //
[238] = '_', //
[239] = '_', //
[240] = '_', //
[241] = '_', //
[242] = '_', //
[243] = '_', //
[244] = '_', //
[245] = '_', //
[246] = '_', //
[247] = '_', //
[248] = '_', //
[249] = '_', //
[250] = '_', //
[251] = '_', //
[252] = '_', //
[253] = '_', //
[254] = '_', //
[255] = '_' //
};
// make sure the supplied string
// is good for a netdata chart/dimension ID/NAME
void netdata_fix_chart_id(char *s) {
while ((*s = netdata_map_chart_ids[(unsigned char) *s])) s++;
}
static int memory_file_open(const char *filename, size_t size) {
// netdata_log_info("memory_file_open('%s', %zu", filename, size);
int fd = open(filename, O_RDWR | O_CREAT | O_NOATIME, 0664);
if (fd != -1) {
if (lseek(fd, size, SEEK_SET) == (off_t) size) {
if (write(fd, "", 1) == 1) {
if (ftruncate(fd, size))
netdata_log_error("Cannot truncate file '%s' to size %zu. Will use the larger file.", filename, size);
}
else
netdata_log_error("Cannot write to file '%s' at position %zu.", filename, size);
}
else
netdata_log_error("Cannot seek file '%s' to size %zu.", filename, size);
}
else
netdata_log_error("Cannot create/open file '%s'.", filename);
return fd;
}
inline int madvise_sequential(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_SEQUENTIAL);
if (ret != 0 && logger-- > 0)
netdata_log_error("madvise(MADV_SEQUENTIAL) failed.");
return ret;
}
inline int madvise_random(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_RANDOM);
if (ret != 0 && logger-- > 0)
netdata_log_error("madvise(MADV_RANDOM) failed.");
return ret;
}
inline int madvise_dontfork(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_DONTFORK);
if (ret != 0 && logger-- > 0)
netdata_log_error("madvise(MADV_DONTFORK) failed.");
return ret;
}
inline int madvise_willneed(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_WILLNEED);
if (ret != 0 && logger-- > 0)
netdata_log_error("madvise(MADV_WILLNEED) failed.");
return ret;
}
inline int madvise_dontneed(void *mem, size_t len) {
static int logger = 1;
int ret = madvise(mem, len, MADV_DONTNEED);
if (ret != 0 && logger-- > 0)
netdata_log_error("madvise(MADV_DONTNEED) failed.");
return ret;
}
inline int madvise_dontdump(void *mem __maybe_unused, size_t len __maybe_unused) {
#if __linux__
static int logger = 1;
int ret = madvise(mem, len, MADV_DONTDUMP);
if (ret != 0 && logger-- > 0)
netdata_log_error("madvise(MADV_DONTDUMP) failed.");
return ret;
#else
return 0;
#endif
}
inline int madvise_mergeable(void *mem __maybe_unused, size_t len __maybe_unused) {
#ifdef MADV_MERGEABLE
static int logger = 1;
int ret = madvise(mem, len, MADV_MERGEABLE);
if (ret != 0 && logger-- > 0)
netdata_log_error("madvise(MADV_MERGEABLE) failed.");
return ret;
#else
return 0;
#endif
}
void *netdata_mmap(const char *filename, size_t size, int flags, int ksm, bool read_only, int *open_fd)
{
// netdata_log_info("netdata_mmap('%s', %zu", filename, size);
// MAP_SHARED is used in memory mode map
// MAP_PRIVATE is used in memory mode ram and save
if(unlikely(!(flags & MAP_SHARED) && !(flags & MAP_PRIVATE)))
fatal("Neither MAP_SHARED or MAP_PRIVATE were given to netdata_mmap()");
if(unlikely((flags & MAP_SHARED) && (flags & MAP_PRIVATE)))
fatal("Both MAP_SHARED and MAP_PRIVATE were given to netdata_mmap()");
if(unlikely((flags & MAP_SHARED) && (!filename || !*filename)))
fatal("MAP_SHARED requested, without a filename to netdata_mmap()");
// don't enable ksm is the global setting is disabled
if(unlikely(!enable_ksm)) ksm = 0;
// KSM only merges anonymous (private) pages, never pagecache (file) pages
// but MAP_PRIVATE without MAP_ANONYMOUS it fails too, so we need it always
if((flags & MAP_PRIVATE)) flags |= MAP_ANONYMOUS;
int fd = -1;
void *mem = MAP_FAILED;
if(filename && *filename) {
// open/create the file to be used
fd = memory_file_open(filename, size);
if(fd == -1) goto cleanup;
}
int fd_for_mmap = fd;
if(fd != -1 && (flags & MAP_PRIVATE)) {
// this is MAP_PRIVATE allocation
// no need for mmap() to use our fd
// we will copy the file into the memory allocated
fd_for_mmap = -1;
}
mem = mmap(NULL, size, read_only ? PROT_READ : PROT_READ | PROT_WRITE, flags, fd_for_mmap, 0);
if (mem != MAP_FAILED) {
#ifdef NETDATA_TRACE_ALLOCATIONS
malloc_trace_mmap(size);
#endif
// if we have a file open, but we didn't give it to mmap(),
// we have to read the file into the memory block we allocated
if(fd != -1 && fd_for_mmap == -1) {
if (lseek(fd, 0, SEEK_SET) == 0) {
if (read(fd, mem, size) != (ssize_t) size)
netdata_log_info("Cannot read from file '%s'", filename);
}
else netdata_log_info("Cannot seek to beginning of file '%s'.", filename);
}
// madvise_sequential(mem, size);
madvise_dontfork(mem, size);
madvise_dontdump(mem, size);
// if(flags & MAP_SHARED) madvise_willneed(mem, size);
if(ksm) madvise_mergeable(mem, size);
}
cleanup:
if(fd != -1) {
if (open_fd)
*open_fd = fd;
else
close(fd);
}
if(mem == MAP_FAILED) return NULL;
errno = 0;
return mem;
}
int netdata_munmap(void *ptr, size_t size) {
#ifdef NETDATA_TRACE_ALLOCATIONS
malloc_trace_munmap(size);
#endif
return munmap(ptr, size);
}
char *fgets_trim_len(char *buf, size_t buf_size, FILE *fp, size_t *len) {
char *s = fgets(buf, (int)buf_size, fp);
if (!s) return NULL;
char *t = s;
if (*t != '\0') {
// find the string end
while (*++t != '\0');
// trim trailing spaces/newlines/tabs
while (--t > s && *t == '\n')
*t = '\0';
}
if (len)
*len = t - s + 1;
return s;
}
// vsnprintfz() returns the number of bytes actually written - after possible truncation
int vsnprintfz(char *dst, size_t n, const char *fmt, va_list args) {
if(unlikely(!n)) return 0;
int size = vsnprintf(dst, n, fmt, args);
dst[n - 1] = '\0';
if (unlikely((size_t) size >= n)) size = (int)(n - 1);
return size;
}
// snprintfz() returns the number of bytes actually written - after possible truncation
int snprintfz(char *dst, size_t n, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int ret = vsnprintfz(dst, n, fmt, args);
va_end(args);
return ret;
}
static int is_procfs(const char *path, char **reason) {
#if defined(__APPLE__) || defined(__FreeBSD__)
(void)path;
(void)reason;
#else
struct statfs stat;
if (statfs(path, &stat) == -1) {
if (reason)
*reason = "failed to statfs()";
return -1;
}
#if defined PROC_SUPER_MAGIC
if (stat.f_type != PROC_SUPER_MAGIC) {
if (reason)
*reason = "type is not procfs";
return -1;
}
#endif
#endif
return 0;
}
static int is_sysfs(const char *path, char **reason) {
#if defined(__APPLE__) || defined(__FreeBSD__)
(void)path;
(void)reason;
#else
struct statfs stat;
if (statfs(path, &stat) == -1) {
if (reason)
*reason = "failed to statfs()";
return -1;
}
#if defined SYSFS_MAGIC
if (stat.f_type != SYSFS_MAGIC) {
if (reason)
*reason = "type is not sysfs";
return -1;
}
#endif
#endif
return 0;
}
int verify_netdata_host_prefix(bool log_msg) {
if(!netdata_configured_host_prefix)
netdata_configured_host_prefix = "";
if(!*netdata_configured_host_prefix)
return 0;
char buffer[FILENAME_MAX + 1];
char *path = netdata_configured_host_prefix;
char *reason = "unknown reason";
errno = 0;
struct stat sb;
if (stat(path, &sb) == -1) {
reason = "failed to stat()";
goto failed;
}
if((sb.st_mode & S_IFMT) != S_IFDIR) {
errno = EINVAL;
reason = "is not a directory";
goto failed;
}
path = buffer;
snprintfz(path, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
if(is_procfs(path, &reason) == -1)
goto failed;
snprintfz(path, FILENAME_MAX, "%s/sys", netdata_configured_host_prefix);
if(is_sysfs(path, &reason) == -1)
goto failed;
if (netdata_configured_host_prefix && *netdata_configured_host_prefix) {
if (log_msg)
netdata_log_info("Using host prefix directory '%s'", netdata_configured_host_prefix);
}
return 0;
failed:
if (log_msg)
netdata_log_error("Ignoring host prefix '%s': path '%s' %s", netdata_configured_host_prefix, path, reason);
netdata_configured_host_prefix = "";
return -1;
}
char *strdupz_path_subpath(const char *path, const char *subpath) {
if(unlikely(!path || !*path)) path = ".";
if(unlikely(!subpath)) subpath = "";
// skip trailing slashes in path
size_t len = strlen(path);
while(len > 0 && path[len - 1] == '/') len--;
// skip leading slashes in subpath
while(subpath[0] == '/') subpath++;
// if the last character in path is / and (there is a subpath or path is now empty)
// keep the trailing slash in path and remove the additional slash
char *slash = "/";
if(path[len] == '/' && (*subpath || len == 0)) {
slash = "";
len++;
}
else if(!*subpath) {
// there is no subpath
// no need for trailing slash
slash = "";
}
char buffer[FILENAME_MAX + 1];
snprintfz(buffer, FILENAME_MAX, "%.*s%s%s", (int)len, path, slash, subpath);
return strdupz(buffer);
}
int path_is_dir(const char *path, const char *subpath) {
char *s = strdupz_path_subpath(path, subpath);
size_t max_links = 100;
int is_dir = 0;
struct stat statbuf;
while(max_links-- && stat(s, &statbuf) == 0) {
if((statbuf.st_mode & S_IFMT) == S_IFDIR) {
is_dir = 1;
break;
}
else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
char buffer[FILENAME_MAX + 1];
ssize_t l = readlink(s, buffer, FILENAME_MAX);
if(l > 0) {
buffer[l] = '\0';
freez(s);
s = strdupz(buffer);
continue;
}
else {
is_dir = 0;
break;
}
}
else {
is_dir = 0;
break;
}
}
freez(s);
return is_dir;
}
int path_is_file(const char *path, const char *subpath) {
char *s = strdupz_path_subpath(path, subpath);
size_t max_links = 100;
int is_file = 0;
struct stat statbuf;
while(max_links-- && stat(s, &statbuf) == 0) {
if((statbuf.st_mode & S_IFMT) == S_IFREG) {
is_file = 1;
break;
}
else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
char buffer[FILENAME_MAX + 1];
ssize_t l = readlink(s, buffer, FILENAME_MAX);
if(l > 0) {
buffer[l] = '\0';
freez(s);
s = strdupz(buffer);
continue;
}
else {
is_file = 0;
break;
}
}
else {
is_file = 0;
break;
}
}
freez(s);
return is_file;
}
void recursive_config_double_dir_load(const char *user_path, const char *stock_path, const char *subpath, int (*callback)(const char *filename, void *data, bool stock_config), void *data, size_t depth) {
if(depth > 3) {
netdata_log_error("CONFIG: Max directory depth reached while reading user path '%s', stock path '%s', subpath '%s'", user_path, stock_path, subpath);
return;
}
if(!stock_path)
stock_path = user_path;
char *udir = strdupz_path_subpath(user_path, subpath);
char *sdir = strdupz_path_subpath(stock_path, subpath);
netdata_log_debug(D_HEALTH, "CONFIG traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
DIR *dir = opendir(udir);
if (!dir) {
netdata_log_error("CONFIG cannot open user-config directory '%s'.", udir);
}
else {
struct dirent *de = NULL;
while((de = readdir(dir))) {
if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
if( !de->d_name[0] ||
(de->d_name[0] == '.' && de->d_name[1] == '\0') ||
(de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
) {
netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config directory '%s/%s'", udir, de->d_name);
continue;
}
if(path_is_dir(udir, de->d_name)) {
recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
continue;
}
}
if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
size_t len = strlen(de->d_name);
if(path_is_file(udir, de->d_name) &&
len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
char *filename = strdupz_path_subpath(udir, de->d_name);
netdata_log_debug(D_HEALTH, "CONFIG calling callback for user file '%s'", filename);
callback(filename, data, false);
freez(filename);
continue;
}
}
netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
}
closedir(dir);
}
netdata_log_debug(D_HEALTH, "CONFIG traversing stock config directory '%s', user config directory '%s'", sdir, udir);
dir = opendir(sdir);
if (!dir) {
netdata_log_error("CONFIG cannot open stock config directory '%s'.", sdir);
}
else {
if (strcmp(udir, sdir)) {
struct dirent *de = NULL;
while((de = readdir(dir))) {
if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
if( !de->d_name[0] ||
(de->d_name[0] == '.' && de->d_name[1] == '\0') ||
(de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
) {
netdata_log_debug(D_HEALTH, "CONFIG ignoring stock config directory '%s/%s'", sdir, de->d_name);
continue;
}
if(path_is_dir(sdir, de->d_name)) {
// we recurse in stock subdirectory, only when there is no corresponding
// user subdirectory - to avoid reading the files twice
if(!path_is_dir(udir, de->d_name))
recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
continue;
}
}
if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
size_t len = strlen(de->d_name);
if(path_is_file(sdir, de->d_name) && !path_is_file(udir, de->d_name) &&
len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
char *filename = strdupz_path_subpath(sdir, de->d_name);
netdata_log_debug(D_HEALTH, "CONFIG calling callback for stock file '%s'", filename);
callback(filename, data, true);
freez(filename);
continue;
}
}
netdata_log_debug(D_HEALTH, "CONFIG ignoring stock-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
}
}
closedir(dir);
}
netdata_log_debug(D_HEALTH, "CONFIG done traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
freez(udir);
freez(sdir);
}
// Returns the number of bytes read from the file if file_size is not NULL.
// The actual buffer has an extra byte set to zero (not included in the count).
char *read_by_filename(const char *filename, long *file_size)
{
FILE *f = fopen(filename, "r");
if (!f)
return NULL;
if (fseek(f, 0, SEEK_END) < 0) {
fclose(f);
return NULL;
}
long size = ftell(f);
if (size <= 0 || fseek(f, 0, SEEK_END) < 0) {
fclose(f);
return NULL;
}
char *contents = callocz(size + 1, 1);
if (!contents) {
fclose(f);
return NULL;
}
if (fseek(f, 0, SEEK_SET) < 0) {
fclose(f);
freez(contents);
return NULL;
}
size_t res = fread(contents, 1, size, f);
if ( res != (size_t)size) {
freez(contents);
fclose(f);
return NULL;
}
fclose(f);
if (file_size)
*file_size = size;
return contents;
}
char *find_and_replace(const char *src, const char *find, const char *replace, const char *where)
{
size_t size = strlen(src) + 1;
size_t find_len = strlen(find);
size_t repl_len = strlen(replace);
char *value, *dst;
if (likely(where))
size += (repl_len - find_len);
value = mallocz(size);
dst = value;
if (likely(where)) {
size_t count = where - src;
memmove(dst, src, count);
src += count;
dst += count;
memmove(dst, replace, repl_len);
src += find_len;
dst += repl_len;
}
strcpy(dst, src);
return value;
}
BUFFER *run_command_and_get_output_to_buffer(const char *command, int max_line_length) {
BUFFER *wb = buffer_create(0, NULL);
pid_t pid;
FILE *fp = netdata_popen(command, &pid, NULL);
if(fp) {
char buffer[max_line_length + 1];
while (fgets(buffer, max_line_length, fp)) {
buffer[max_line_length] = '\0';
buffer_strcat(wb, buffer);
}
}
else {
buffer_free(wb);
netdata_log_error("Failed to execute command '%s'.", command);
return NULL;
}
netdata_pclose(NULL, fp, pid);
return wb;
}
bool run_command_and_copy_output_to_stdout(const char *command, int max_line_length) {
pid_t pid;
FILE *fp = netdata_popen(command, &pid, NULL);
if(fp) {
char buffer[max_line_length + 1];
while (fgets(buffer, max_line_length, fp))
fprintf(stdout, "%s", buffer);
}
else {
netdata_log_error("Failed to execute command '%s'.", command);
return false;
}
netdata_pclose(NULL, fp, pid);
return true;
}
static int fd_is_valid(int fd) {
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
void for_each_open_fd(OPEN_FD_ACTION action, OPEN_FD_EXCLUDE excluded_fds){
int fd;
switch(action){
case OPEN_FD_ACTION_CLOSE:
if(!(excluded_fds & OPEN_FD_EXCLUDE_STDIN)) (void)close(STDIN_FILENO);
if(!(excluded_fds & OPEN_FD_EXCLUDE_STDOUT)) (void)close(STDOUT_FILENO);
if(!(excluded_fds & OPEN_FD_EXCLUDE_STDERR)) (void)close(STDERR_FILENO);
#if defined(HAVE_CLOSE_RANGE)
if(close_range(STDERR_FILENO + 1, ~0U, 0) == 0) return;
netdata_log_error("close_range() failed, will try to close fds one by one");
#endif
break;
case OPEN_FD_ACTION_FD_CLOEXEC:
if(!(excluded_fds & OPEN_FD_EXCLUDE_STDIN)) (void)fcntl(STDIN_FILENO, F_SETFD, FD_CLOEXEC);
if(!(excluded_fds & OPEN_FD_EXCLUDE_STDOUT)) (void)fcntl(STDOUT_FILENO, F_SETFD, FD_CLOEXEC);
if(!(excluded_fds & OPEN_FD_EXCLUDE_STDERR)) (void)fcntl(STDERR_FILENO, F_SETFD, FD_CLOEXEC);
#if defined(HAVE_CLOSE_RANGE) && defined(CLOSE_RANGE_CLOEXEC) // Linux >= 5.11, FreeBSD >= 13.1
if(close_range(STDERR_FILENO + 1, ~0U, CLOSE_RANGE_CLOEXEC) == 0) return;
netdata_log_error("close_range() failed, will try to mark fds for closing one by one");
#endif
break;
default:
break; // do nothing
}
DIR *dir = opendir("/proc/self/fd");
if (dir == NULL) {
struct rlimit rl;
int open_max = -1;
if(getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY) open_max = rl.rlim_max;
#ifdef _SC_OPEN_MAX
else open_max = sysconf(_SC_OPEN_MAX);
#endif
if (open_max == -1) open_max = 65535; // 65535 arbitrary default if everything else fails
for (fd = STDERR_FILENO + 1; fd < open_max; fd++) {
switch(action){
case OPEN_FD_ACTION_CLOSE:
if(fd_is_valid(fd)) (void)close(fd);
break;
case OPEN_FD_ACTION_FD_CLOEXEC:
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
break;
default:
break; // do nothing
}
}
} else {
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
fd = str2i(entry->d_name);
if(unlikely((fd == STDIN_FILENO ) || (fd == STDOUT_FILENO) || (fd == STDERR_FILENO) )) continue;
switch(action){
case OPEN_FD_ACTION_CLOSE:
if(fd_is_valid(fd)) (void)close(fd);
break;
case OPEN_FD_ACTION_FD_CLOEXEC:
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
break;
default:
break; // do nothing
}
}
closedir(dir);
}
}
struct timing_steps {
const char *name;
usec_t time;
size_t count;
} timing_steps[TIMING_STEP_MAX + 1] = {
[TIMING_STEP_INTERNAL] = { .name = "internal", .time = 0, },
[TIMING_STEP_BEGIN2_PREPARE] = { .name = "BEGIN2 prepare", .time = 0, },
[TIMING_STEP_BEGIN2_FIND_CHART] = { .name = "BEGIN2 find chart", .time = 0, },
[TIMING_STEP_BEGIN2_PARSE] = { .name = "BEGIN2 parse", .time = 0, },
[TIMING_STEP_BEGIN2_ML] = { .name = "BEGIN2 ml", .time = 0, },
[TIMING_STEP_BEGIN2_PROPAGATE] = { .name = "BEGIN2 propagate", .time = 0, },
[TIMING_STEP_BEGIN2_STORE] = { .name = "BEGIN2 store", .time = 0, },
[TIMING_STEP_SET2_PREPARE] = { .name = "SET2 prepare", .time = 0, },
[TIMING_STEP_SET2_LOOKUP_DIMENSION] = { .name = "SET2 find dimension", .time = 0, },
[TIMING_STEP_SET2_PARSE] = { .name = "SET2 parse", .time = 0, },
[TIMING_STEP_SET2_ML] = { .name = "SET2 ml", .time = 0, },
[TIMING_STEP_SET2_PROPAGATE] = { .name = "SET2 propagate", .time = 0, },
[TIMING_STEP_RRDSET_STORE_METRIC] = { .name = "SET2 rrdset store", .time = 0, },
[TIMING_STEP_DBENGINE_FIRST_CHECK] = { .name = "db 1st check", .time = 0, },
[TIMING_STEP_DBENGINE_CHECK_DATA] = { .name = "db check data", .time = 0, },
[TIMING_STEP_DBENGINE_PACK] = { .name = "db pack", .time = 0, },
[TIMING_STEP_DBENGINE_PAGE_FIN] = { .name = "db page fin", .time = 0, },
[TIMING_STEP_DBENGINE_MRG_UPDATE] = { .name = "db mrg update", .time = 0, },
[TIMING_STEP_DBENGINE_PAGE_ALLOC] = { .name = "db page alloc", .time = 0, },
[TIMING_STEP_DBENGINE_CREATE_NEW_PAGE] = { .name = "db new page", .time = 0, },
[TIMING_STEP_DBENGINE_FLUSH_PAGE] = { .name = "db page flush", .time = 0, },
[TIMING_STEP_SET2_STORE] = { .name = "SET2 store", .time = 0, },
[TIMING_STEP_END2_PREPARE] = { .name = "END2 prepare", .time = 0, },
[TIMING_STEP_END2_PUSH_V1] = { .name = "END2 push v1", .time = 0, },
[TIMING_STEP_END2_ML] = { .name = "END2 ml", .time = 0, },
[TIMING_STEP_END2_RRDSET] = { .name = "END2 rrdset", .time = 0, },
[TIMING_STEP_END2_PROPAGATE] = { .name = "END2 propagate", .time = 0, },
[TIMING_STEP_END2_STORE] = { .name = "END2 store", .time = 0, },
// terminator
[TIMING_STEP_MAX] = { .name = NULL, .time = 0, },
};
void timing_action(TIMING_ACTION action, TIMING_STEP step) {
static __thread usec_t last_action_time = 0;
static struct timing_steps timings2[TIMING_STEP_MAX + 1] = {};
switch(action) {
case TIMING_ACTION_INIT:
last_action_time = now_monotonic_usec();
break;
case TIMING_ACTION_STEP: {
if(!last_action_time)
return;
usec_t now = now_monotonic_usec();
__atomic_add_fetch(&timing_steps[step].time, now - last_action_time, __ATOMIC_RELAXED);
__atomic_add_fetch(&timing_steps[step].count, 1, __ATOMIC_RELAXED);
last_action_time = now;
break;
}
case TIMING_ACTION_FINISH: {
if(!last_action_time)
return;
usec_t expected = __atomic_load_n(&timing_steps[TIMING_STEP_INTERNAL].time, __ATOMIC_RELAXED);
if(last_action_time - expected < 10 * USEC_PER_SEC) {
last_action_time = 0;
return;
}
if(!__atomic_compare_exchange_n(&timing_steps[TIMING_STEP_INTERNAL].time, &expected, last_action_time, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
last_action_time = 0;
return;
}
struct timing_steps timings3[TIMING_STEP_MAX + 1];
memcpy(timings3, timing_steps, sizeof(timings3));
size_t total_reqs = 0;
usec_t total_usec = 0;
for(size_t t = 1; t < TIMING_STEP_MAX ; t++) {
total_usec += timings3[t].time - timings2[t].time;
total_reqs += timings3[t].count - timings2[t].count;
}
BUFFER *wb = buffer_create(1024, NULL);
for(size_t t = 1; t < TIMING_STEP_MAX ; t++) {
size_t requests = timings3[t].count - timings2[t].count;
if(!requests) continue;
buffer_sprintf(wb, "TIMINGS REPORT: [%3zu. %-20s]: # %10zu, t %11.2f ms (%6.2f %%), avg %6.2f usec/run\n",
t,
timing_steps[t].name ? timing_steps[t].name : "x",
requests,
(double) (timings3[t].time - timings2[t].time) / (double)USEC_PER_MS,
(double) (timings3[t].time - timings2[t].time) * 100.0 / (double) total_usec,
(double) (timings3[t].time - timings2[t].time) / (double)requests
);
}
netdata_log_info("TIMINGS REPORT:\n%sTIMINGS REPORT: total # %10zu, t %11.2f ms",
buffer_tostring(wb), total_reqs, (double)total_usec / USEC_PER_MS);
memcpy(timings2, timings3, sizeof(timings2));
last_action_time = 0;
buffer_free(wb);
}
}
}
#ifdef ENABLE_HTTPS
int hash256_string(const unsigned char *string, size_t size, char *hash) {
EVP_MD_CTX *ctx;
ctx = EVP_MD_CTX_create();
if (!ctx)
return 0;
if (!EVP_DigestInit(ctx, EVP_sha256())) {
EVP_MD_CTX_destroy(ctx);
return 0;
}
if (!EVP_DigestUpdate(ctx, string, size)) {
EVP_MD_CTX_destroy(ctx);
return 0;
}
if (!EVP_DigestFinal(ctx, (unsigned char *)hash, NULL)) {
EVP_MD_CTX_destroy(ctx);
return 0;
}
EVP_MD_CTX_destroy(ctx);
return 1;
}
#endif
bool rrdr_relative_window_to_absolute(time_t *after, time_t *before, time_t now) {
if(!now) now = now_realtime_sec();
int absolute_period_requested = -1;
time_t before_requested = *before;
time_t after_requested = *after;
// allow relative for before (smaller than API_RELATIVE_TIME_MAX)
if(ABS(before_requested) <= API_RELATIVE_TIME_MAX) {
// if the user asked for a positive relative time,
// flip it to a negative
if(before_requested > 0)
before_requested = -before_requested;
before_requested = now + before_requested;
absolute_period_requested = 0;
}
// allow relative for after (smaller than API_RELATIVE_TIME_MAX)
if(ABS(after_requested) <= API_RELATIVE_TIME_MAX) {
if(after_requested > 0)
after_requested = -after_requested;
// if the user didn't give an after, use the number of points
// to give a sane default
if(after_requested == 0)
after_requested = -600;
// since the query engine now returns inclusive timestamps
// it is awkward to return 6 points when after=-5 is given
// so for relative queries we add 1 second, to give
// more predictable results to users.
after_requested = before_requested + after_requested + 1;
absolute_period_requested = 0;
}
if(absolute_period_requested == -1)
absolute_period_requested = 1;
// check if the parameters are flipped
if(after_requested > before_requested) {
long long t = before_requested;
before_requested = after_requested;
after_requested = t;
}
// if the query requests future data
// shift the query back to be in the present time
// (this may also happen because of the rules above)
if(before_requested > now) {
time_t delta = before_requested - now;
before_requested -= delta;
after_requested -= delta;
}
*before = before_requested;
*after = after_requested;
return (absolute_period_requested != 1);
}
// Returns 1 if an absolute period was requested or 0 if it was a relative period
bool rrdr_relative_window_to_absolute_query(time_t *after, time_t *before, time_t *now_ptr, bool unittest) {
time_t now = now_realtime_sec() - 1;
if(now_ptr)
*now_ptr = now;
time_t before_requested = *before;
time_t after_requested = *after;
int absolute_period_requested = rrdr_relative_window_to_absolute(&after_requested, &before_requested, now);
time_t absolute_minimum_time = now - (10 * 365 * 86400);
time_t absolute_maximum_time = now + (1 * 365 * 86400);
if (after_requested < absolute_minimum_time && !unittest)
after_requested = absolute_minimum_time;
if (after_requested > absolute_maximum_time && !unittest)
after_requested = absolute_maximum_time;
if (before_requested < absolute_minimum_time && !unittest)
before_requested = absolute_minimum_time;
if (before_requested > absolute_maximum_time && !unittest)
before_requested = absolute_maximum_time;
*before = before_requested;
*after = after_requested;
return (absolute_period_requested != 1);
}
int netdata_base64_decode(const char *encoded, char *decoded, size_t decoded_size) {
static const unsigned char base64_table[256] = {
['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7,
['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15,
['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23,
['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31,
['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39,
['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47,
['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55,
['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63,
[0 ... '+' - 1] = 255,
['+' + 1 ... '/' - 1] = 255,
['9' + 1 ... 'A' - 1] = 255,
['Z' + 1 ... 'a' - 1] = 255,
['z' + 1 ... 255] = 255
};
size_t count = 0;
unsigned int tmp = 0;
int i, bit;
if (decoded_size < 1)
return 0; // Buffer size must be at least 1 for null termination
for (i = 0, bit = 0; encoded[i]; i++) {
unsigned char value = base64_table[(unsigned char)encoded[i]];
if (value > 63)
return -1; // Invalid character in input
tmp = tmp << 6 | value;
if (++bit == 4) {
if (count + 3 >= decoded_size) break; // Stop decoding if buffer is full
decoded[count++] = (tmp >> 16) & 0xFF;
decoded[count++] = (tmp >> 8) & 0xFF;
decoded[count++] = tmp & 0xFF;
tmp = 0;
bit = 0;
}
}
if (bit > 0 && count + 1 < decoded_size) {
tmp <<= 6 * (4 - bit);
if (bit > 2 && count + 1 < decoded_size) decoded[count++] = (tmp >> 16) & 0xFF;
if (bit > 3 && count + 1 < decoded_size) decoded[count++] = (tmp >> 8) & 0xFF;
}
decoded[count] = '\0'; // Null terminate the output string
return count;
}