- clang
before_install:
- sudo apt-get update -qq
- - sudo apt-get install -qq automake make zlib1g-dev
+ - sudo apt-get install -qq automake make zlib1g-dev uuid-dev
script:
# default build
- ./autogen.sh && ./configure && make -j4
include_directories(AFTER .)
-add_definitions(-DHAVE_CONFIG_H -DNETDATA_INTERNAL_CHECKS=1 -DCACHE_DIR="/tmp" -DCONFIG_DIR="/tmp" -DLOG_DIR="/tmp" -DPLUGINS_DIR="/tmp" -DWEB_DIR="/tmp")
+add_definitions(-DHAVE_CONFIG_H -DNETDATA_INTERNAL_CHECKS=1 -DCACHE_DIR="/tmp" -DCONFIG_DIR="/tmp" -DLOG_DIR="/tmp" -DPLUGINS_DIR="/tmp" -DWEB_DIR="/tmp" -DVARLIB_DIR="/tmp")
add_executable(netdata ${NETDATA_SOURCE_FILES})
add_executable(apps.plugin ${APPS_PLUGIN_SOURCE_FILES})
## Documentation
Check the **[netdata wiki](https://github.com/firehol/netdata/wiki)**.
+
+
+[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/firehol/netdata/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
+
)
fi
+AC_CHECK_LIB([uuid], [uuid_generate_time_safe], [], \
+ [AC_MSG_ERROR([Function uuid_generate_time_safe was not found in libuuid.
+Is uuid-dev installed? Try running 'sudo apt-get install uuid-dev'.])])
+
if test "${enable_plugin_nfacct}" = "yes"; then
PKG_CHECK_MODULES(
[NFACCT],
AC_DEFINE_UNQUOTED([NETDATA_USER], ["${with_user}"], [use this user to drop privileged])
+AC_SUBST([varlibdir], ["\$(localstatedir)/lib/netdata"])
AC_SUBST([cachedir], ["\$(localstatedir)/cache/netdata"])
AC_SUBST([chartsdir], ["\$(libexecdir)/netdata/charts.d"])
AC_SUBST([nodedir], ["\$(libexecdir)/netdata/node.d"])
fi
LC_ALL=C
+umask 022
# you can set CFLAGS before running installer
CFLAGS="${CFLAGS--O3}"
For the installer to complete successfully, you will need
these packages installed:
- gcc make autoconf automake pkg-config zlib1g-dev
+ gcc make autoconf automake pkg-config zlib1g-dev uuid-dev
For the plugins, you will at least need:
- web files at ${NETDATA_PREFIX}/usr/share/netdata
- plugins at ${NETDATA_PREFIX}/usr/libexec/netdata
- cache files at ${NETDATA_PREFIX}/var/cache/netdata
+ - db files at ${NETDATA_PREFIX}/var/lib/netdata
- log files at ${NETDATA_PREFIX}/var/log/netdata
- pid file at ${NETDATA_PREFIX}/var/run
1. The package zlib1g-dev has to be installed.
-2. You need basic build tools installed, like:
+ If your system cannot find ZLIB, although it is installed
+ run me with the option: --zlib-is-really-here
- gcc make autoconf automake pkg-config
+2. The package uuid-dev has to be installed.
- Autoconf version 2.60 or higher is required
+3. You need basic build tools installed, like:
-3. If your system cannot find ZLIB, although it is installed
- run me with the option: --zlib-is-really-here
+ gcc make autoconf automake pkg-config
+ Autoconf version 2.60 or higher is required.
If you still cannot get it to build, ask for help at github:
NETDATA_PORT="$( config_option "port" ${defport} )"
# directories
+NETDATA_LIB_DIR="$( config_option "lib directory" "${NETDATA_PREFIX}/var/lib/netdata" )"
NETDATA_CACHE_DIR="$( config_option "cache directory" "${NETDATA_PREFIX}/var/cache/netdata" )"
NETDATA_WEB_DIR="$( config_option "web files directory" "${NETDATA_PREFIX}/usr/share/netdata/web" )"
NETDATA_LOG_DIR="$( config_option "log directory" "${NETDATA_PREFIX}/var/log/netdata" )"
fi
echo >&2 "Fixing directories (user: ${NETDATA_USER})..."
-for x in "${NETDATA_WEB_DIR}" "${NETDATA_CONF_DIR}" "${NETDATA_CACHE_DIR}" "${NETDATA_LOG_DIR}"
+for x in "${NETDATA_WEB_DIR}" "${NETDATA_CONF_DIR}" "${NETDATA_CACHE_DIR}" "${NETDATA_LOG_DIR}" "${NETDATA_LIB_DIR}"
do
if [ ! -d "${x}" ]
then
deletedir "${NETDATA_PREFIX}/etc/netdata"
deletedir "${NETDATA_PREFIX}/usr/share/netdata"
deletedir "${NETDATA_PREFIX}/usr/libexec/netdata"
+ deletedir "${NETDATA_PREFIX}/var/lib/netdata"
deletedir "${NETDATA_PREFIX}/var/cache/netdata"
deletedir "${NETDATA_PREFIX}/var/log/netdata"
fi
int main(int argc, char **argv) {
if(argc || argv) {;}
- DICTIONARY *dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED);
+ DICTIONARY *dict = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED|DICTIONARY_FLAG_NAME_CORRUPTION_CHECK);
if(!dict) fatal("Cannot create dictionary.");
struct rusage start, end;
unsigned long long dt;
char buf[100 + 1];
struct myvalue value, *v;
- int i, max = 1000000, max2;
+ int i, max = 100000, max2;
// ------------------------------------------------------------------------
MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
AM_CPPFLAGS = \
+ -DVARLIB_DIR="\"$(varlibdir)\"" \
-DCACHE_DIR="\"$(cachedir)\"" \
-DCONFIG_DIR="\"$(configdir)\"" \
-DLOG_DIR="\"$(logdir)\"" \
sys_kernel_mm_ksm.c \
sys_fs_cgroup.c \
procfile.c procfile.h \
+ registry.c registry.h \
rrd.c rrd.h \
rrd2json.c rrd2json.h \
storage_number.c storage_number.h \
+
+/*
+ * TODO
+ *
+ * 1. Re-write this using DICTIONARY
+ *
+ * FIXME
+ * The way it is now, concurrency locking is incomplete!
+ * It makes sure that only one writes to the structures
+ * but at the same time there may be unlimited readers.
+ * This can cause crashes.
+ *
+ * Of course, rewriting this to use DICTIONARY instead of
+ * directly accessing AVL structures, will solve the problem.
+ *
+ */
+
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
char *name;
struct config_value *values;
- avl_tree values_index;
+ avl_tree_lock values_index;
struct config *next;
// ----------------------------------------------------------------------------
-// config value
+// locking
+
+static inline void config_global_read_lock(void) {
+ pthread_rwlock_rdlock(&config_rwlock);
+}
+
+static inline void config_global_write_lock(void) {
+ pthread_rwlock_wrlock(&config_rwlock);
+}
+
+static inline void config_global_unlock(void) {
+ pthread_rwlock_unlock(&config_rwlock);
+}
+
+static inline void config_section_read_lock(struct config *co) {
+ pthread_rwlock_rdlock(&co->rwlock);
+}
+
+static inline void config_section_write_lock(struct config *co) {
+ pthread_rwlock_wrlock(&co->rwlock);
+}
+
+static inline void config_section_unlock(struct config *co) {
+ pthread_rwlock_unlock(&co->rwlock);
+}
+
+
+// ----------------------------------------------------------------------------
+// config name-value index
static int config_value_iterator(avl *a) { if(a) {}; return 0; }
else return strcmp(((struct config_value *)a)->name, ((struct config_value *)b)->name);
}
-#define config_value_index_add(co, cv) avl_insert(&((co)->values_index), (avl *)(cv))
-#define config_value_index_del(co, cv) avl_remove(&((co)->values_index), (avl *)(cv))
+#define config_value_index_add(co, cv) avl_insert_lock(&((co)->values_index), (avl *)(cv))
+#define config_value_index_del(co, cv) avl_remove_lock(&((co)->values_index), (avl *)(cv))
static struct config_value *config_value_index_find(struct config *co, const char *name, uint32_t hash) {
struct config_value *result = NULL, tmp;
tmp.hash = (hash)?hash:simple_hash(name);
tmp.name = (char *)name;
- avl_search(&(co->values_index), (avl *)&tmp, config_value_iterator, (avl **)&result);
+ avl_search_lock(&(co->values_index), (avl *) &tmp, config_value_iterator, (avl **) &result);
return result;
}
+
// ----------------------------------------------------------------------------
-// config
+// config sections index
static int config_iterator(avl *a) { if(a) {}; return 0; }
else return strcmp(((struct config *)a)->name, ((struct config *)b)->name);
}
-avl_tree config_root_index = {
- NULL,
- config_compare,
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#endif
-#endif
+avl_tree_lock config_root_index = {
+ { NULL, config_compare },
+ AVL_LOCK_INITIALIZER
};
-#define config_index_add(cfg) avl_insert(&config_root_index, (avl *)(cfg))
-#define config_index_del(cfg) avl_remove(&config_root_index, (avl *)(cfg))
+#define config_index_add(cfg) avl_insert_lock(&config_root_index, (avl *)(cfg))
+#define config_index_del(cfg) avl_remove_lock(&config_root_index, (avl *)(cfg))
static struct config *config_index_find(const char *name, uint32_t hash) {
struct config *result = NULL, tmp;
tmp.hash = (hash)?hash:simple_hash(name);
tmp.name = (char *)name;
- avl_search(&config_root_index, (avl *)&tmp, config_iterator, (avl **)&result);
+ avl_search_lock(&config_root_index, (avl *) &tmp, config_iterator, (avl **) &result);
return result;
}
-struct config_value *config_value_create(struct config *co, const char *name, const char *value)
-{
- debug(D_CONFIG, "Creating config entry for name '%s', value '%s', in section '%s'.", name, value, co->name);
- struct config_value *cv = calloc(1, sizeof(struct config_value));
- if(!cv) fatal("Cannot allocate config_value");
-
- cv->name = strdup(name);
- if(!cv->name) fatal("Cannot allocate config.name");
- cv->hash = simple_hash(cv->name);
-
- cv->value = strdup(value);
- if(!cv->value) fatal("Cannot allocate config.value");
-
- config_value_index_add(co, cv);
-
- // no need for string termination, due to calloc()
-
- pthread_rwlock_wrlock(&co->rwlock);
-
- struct config_value *cv2 = co->values;
- if(cv2) {
- while (cv2->next) cv2 = cv2->next;
- cv2->next = cv;
- }
- else co->values = cv;
-
- pthread_rwlock_unlock(&co->rwlock);
+// ----------------------------------------------------------------------------
+// config section methods
- return cv;
+static inline struct config *config_section_find(const char *section) {
+ return config_index_find(section, 0);
}
-struct config *config_create(const char *section)
+static inline struct config *config_section_create(const char *section)
{
debug(D_CONFIG, "Creating section '%s'.", section);
if(!co->name) fatal("Cannot allocate config.name");
co->hash = simple_hash(co->name);
- pthread_rwlock_init(&co->rwlock, NULL);
- avl_init(&co->values_index, config_value_compare);
+ avl_init_lock(&co->values_index, config_value_compare);
- config_index_add(co);
-
- // no need for string termination, due to calloc()
+ config_global_write_lock();
- pthread_rwlock_wrlock(&config_rwlock);
+ config_index_add(co);
struct config *co2 = config_root;
if(co2) {
}
else config_root = co;
- pthread_rwlock_unlock(&config_rwlock);
+ config_global_unlock();
return co;
}
-struct config *config_find_section(const char *section)
-{
- return config_index_find(section, 0);
-}
-
-int load_config(char *filename, int overwrite_used)
-{
- int line = 0;
- struct config *co = NULL;
-
- char buffer[CONFIG_FILE_LINE_MAX + 1], *s;
-
- if(!filename) filename = CONFIG_DIR "/" CONFIG_FILENAME;
- FILE *fp = fopen(filename, "r");
- if(!fp) {
- error("Cannot open file '%s'", filename);
- return 0;
- }
-
- while(fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
- buffer[CONFIG_FILE_LINE_MAX] = '\0';
- line++;
- s = trim(buffer);
- if(!s) {
- debug(D_CONFIG, "Ignoring line %d, it is empty.", line);
- continue;
- }
-
- int len = (int) strlen(s);
- if(*s == '[' && s[len - 1] == ']') {
- // new section
- s[len - 1] = '\0';
- s++;
+// ----------------------------------------------------------------------------
+// config name-value methods
- co = config_find_section(s);
- if(!co) co = config_create(s);
+static inline struct config_value *config_value_create(struct config *co, const char *name, const char *value)
+{
+ debug(D_CONFIG, "Creating config entry for name '%s', value '%s', in section '%s'.", name, value, co->name);
- continue;
- }
+ struct config_value *cv = calloc(1, sizeof(struct config_value));
+ if(!cv) fatal("Cannot allocate config_value");
- if(!co) {
- // line outside a section
- error("Ignoring line %d ('%s'), it is outside all sections.", line, s);
- continue;
- }
+ cv->name = strdup(name);
+ if(!cv->name) fatal("Cannot allocate config.name");
+ cv->hash = simple_hash(cv->name);
- char *name = s;
- char *value = strchr(s, '=');
- if(!value) {
- error("Ignoring line %d ('%s'), there is no = in it.", line, s);
- continue;
- }
- *value = '\0';
- value++;
+ cv->value = strdup(value);
+ if(!cv->value) fatal("Cannot allocate config.value");
- name = trim(name);
- value = trim(value);
+ config_section_write_lock(co);
- if(!name) {
- error("Ignoring line %d, name is empty.", line);
- continue;
- }
- if(!value) {
- debug(D_CONFIG, "Ignoring line %d, value is empty.", line);
- continue;
- }
-
- struct config_value *cv = config_value_index_find(co, name, 0);
+ config_value_index_add(co, cv);
- if(!cv) cv = config_value_create(co, name, value);
- else {
- if(((cv->flags & CONFIG_VALUE_USED) && overwrite_used) || !(cv->flags & CONFIG_VALUE_USED)) {
- debug(D_CONFIG, "Overwriting '%s/%s'.", line, co->name, cv->name);
- free(cv->value);
- cv->value = strdup(value);
- if(!cv->value) fatal("Cannot allocate config.value");
- }
- else
- debug(D_CONFIG, "Ignoring line %d, '%s/%s' is already present and used.", line, co->name, cv->name);
- }
- cv->flags |= CONFIG_VALUE_LOADED;
+ struct config_value *cv2 = co->values;
+ if(cv2) {
+ while (cv2->next) cv2 = cv2->next;
+ cv2->next = cv;
}
+ else co->values = cv;
- fclose(fp);
+ config_section_unlock(co);
- return 1;
+ return cv;
}
char *config_get(const char *section, const char *name, const char *default_value)
debug(D_CONFIG, "request to get config in section '%s', name '%s', default_value '%s'", section, name, default_value);
- struct config *co = config_find_section(section);
- if(!co) co = config_create(section);
+ struct config *co = config_section_find(section);
+ if(!co) co = config_section_create(section);
cv = config_value_index_find(co, name, 0);
if(!cv) {
debug(D_CONFIG, "request to set config in section '%s', name '%s', value '%s'", section, name, value);
- struct config *co = config_find_section(section);
+ struct config *co = config_section_find(section);
if(!co) return config_set(section, name, value);
cv = config_value_index_find(co, name, 0);
debug(D_CONFIG, "request to set config in section '%s', name '%s', value '%s'", section, name, value);
- struct config *co = config_find_section(section);
- if(!co) co = config_create(section);
+ struct config *co = config_section_find(section);
+ if(!co) co = config_section_create(section);
cv = config_value_index_find(co, name, 0);
if(!cv) cv = config_value_create(co, name, value);
return value;
}
+
+// ----------------------------------------------------------------------------
+// config load/save
+
+int load_config(char *filename, int overwrite_used)
+{
+ int line = 0;
+ struct config *co = NULL;
+
+ char buffer[CONFIG_FILE_LINE_MAX + 1], *s;
+
+ if(!filename) filename = CONFIG_DIR "/" CONFIG_FILENAME;
+ FILE *fp = fopen(filename, "r");
+ if(!fp) {
+ error("Cannot open file '%s'", filename);
+ return 0;
+ }
+
+ while(fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
+ buffer[CONFIG_FILE_LINE_MAX] = '\0';
+ line++;
+
+ s = trim(buffer);
+ if(!s) {
+ debug(D_CONFIG, "Ignoring line %d, it is empty.", line);
+ continue;
+ }
+
+ int len = (int) strlen(s);
+ if(*s == '[' && s[len - 1] == ']') {
+ // new section
+ s[len - 1] = '\0';
+ s++;
+
+ co = config_section_find(s);
+ if(!co) co = config_section_create(s);
+
+ continue;
+ }
+
+ if(!co) {
+ // line outside a section
+ error("Ignoring line %d ('%s'), it is outside all sections.", line, s);
+ continue;
+ }
+
+ char *name = s;
+ char *value = strchr(s, '=');
+ if(!value) {
+ error("Ignoring line %d ('%s'), there is no = in it.", line, s);
+ continue;
+ }
+ *value = '\0';
+ value++;
+
+ name = trim(name);
+ value = trim(value);
+
+ if(!name) {
+ error("Ignoring line %d, name is empty.", line);
+ continue;
+ }
+ if(!value) {
+ debug(D_CONFIG, "Ignoring line %d, value is empty.", line);
+ continue;
+ }
+
+ struct config_value *cv = config_value_index_find(co, name, 0);
+
+ if(!cv) cv = config_value_create(co, name, value);
+ else {
+ if(((cv->flags & CONFIG_VALUE_USED) && overwrite_used) || !(cv->flags & CONFIG_VALUE_USED)) {
+ debug(D_CONFIG, "Overwriting '%s/%s'.", line, co->name, cv->name);
+ free(cv->value);
+ cv->value = strdup(value);
+ if(!cv->value) fatal("Cannot allocate config.value");
+ }
+ else
+ debug(D_CONFIG, "Ignoring line %d, '%s/%s' is already present and used.", line, co->name, cv->name);
+ }
+ cv->flags |= CONFIG_VALUE_LOADED;
+ }
+
+ fclose(fp);
+
+ return 1;
+}
+
void generate_config(BUFFER *wb, int only_changed)
{
int i, pri;
break;
}
- pthread_rwlock_wrlock(&config_rwlock);
+ config_global_write_lock();
for(co = config_root; co ; co = co->next) {
- if(strcmp(co->name, "global") == 0 || strcmp(co->name, "plugins") == 0) pri = 0;
+ if(strcmp(co->name, "global") == 0 || strcmp(co->name, "plugins") == 0 || strcmp(co->name, "registry") == 0) pri = 0;
else if(strncmp(co->name, "plugin:", 7) == 0) pri = 1;
else pri = 2;
int changed = 0;
int count = 0;
- pthread_rwlock_wrlock(&co->rwlock);
-
+ config_section_write_lock(co);
for(cv = co->values; cv ; cv = cv->next) {
- used += (cv->flags && CONFIG_VALUE_USED)?1:0;
+ used += (cv->flags & CONFIG_VALUE_USED)?1:0;
changed += (cv->flags & CONFIG_VALUE_CHANGED)?1:0;
count++;
}
-
- pthread_rwlock_unlock(&co->rwlock);
+ config_section_unlock(co);
if(!count) continue;
if(only_changed && !changed) continue;
buffer_sprintf(wb, "\n[%s]\n", co->name);
- pthread_rwlock_wrlock(&co->rwlock);
+ config_section_write_lock(co);
for(cv = co->values; cv ; cv = cv->next) {
if(used && !(cv->flags & CONFIG_VALUE_USED)) {
}
buffer_sprintf(wb, "\t%s%s = %s\n", ((!(cv->flags & CONFIG_VALUE_CHANGED)) && (cv->flags & CONFIG_VALUE_USED))?"# ":"", cv->name, cv->value);
}
- pthread_rwlock_unlock(&co->rwlock);
+ config_section_unlock(co);
}
}
- pthread_rwlock_unlock(&config_rwlock);
+ config_global_unlock();
}
}
-
#include "procfile.h"
#include "../config.h"
+#ifdef NETDATA_INTERNAL_CHECKS
+#include <sys/prctl.h>
+#endif
+
#define MAX_COMPARE_NAME 100
#define MAX_NAME 100
#define MAX_CMDLINE 1024
avl_tree all_files_index = {
NULL,
- file_descriptor_compare,
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#endif
-#endif /* AVL_WITHOUT_PTHREADS */
+ file_descriptor_compare
};
static struct file_descriptor *file_descriptor_find(const char *name, uint32_t hash) {
tmp.magic = 0x0BADCAFE;
#endif /* NETDATA_INTERNAL_CHECKS */
- avl_search(&all_files_index, (avl *)&tmp, file_descriptor_iterator, (avl **)&result);
+ avl_search(&all_files_index, (avl *) &tmp, file_descriptor_iterator, (avl **) &result);
return result;
}
}
else info("Found NETDATA_CONFIG_DIR='%s'", config_dir);
+#ifdef NETDATA_INTERNAL_CHECKS
+ if(debug_flags != 0) {
+ struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
+ if(setrlimit(RLIMIT_CORE, &rl) != 0)
+ info("Cannot request unlimited core dumps for debugging... Proceeding anyway...");
+ prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+ }
+#endif /* NETDATA_INTERNAL_CHECKS */
+
info("starting...");
procfile_adaptive_initial_allocation = 1;
#include "avl.h"
#include "log.h"
-/* Private methods */
-int _avl_removeroot(avl_tree* t);
-
/* Swing to the left
* Warning: no balance maintainance
*/
* returns 1 if the depth of the tree has grown
* Warning: do not insert elements already present
*/
-int _avl_insert(avl_tree* t, avl* a) {
+int avl_insert(avl_tree* t, avl* a) {
/* initialize */
a->left = 0;
a->right = 0;
avl_tree left_subtree;
left_subtree.root = t->root->left;
left_subtree.compar = t->compar;
- if (_avl_insert(&left_subtree, a)) {
+ if (avl_insert(&left_subtree, a)) {
switch (t->root->balance--) {
case 1:
return 0;
avl_tree right_subtree;
right_subtree.root = t->root->right;
right_subtree.compar = t->compar;
- if (_avl_insert(&right_subtree, a)) {
+ if (avl_insert(&right_subtree, a)) {
switch (t->root->balance++) {
case -1:
return 0;
}
}
}
-int avl_insert(avl_tree* t, avl* a) {
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- pthread_mutex_lock(&t->mutex);
-#else
- pthread_rwlock_wrlock(&t->rwlock);
-#endif
-#endif /* AVL_WITHOUT_PTHREADS */
-
- int ret = _avl_insert(t, a);
-
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- pthread_mutex_unlock(&t->mutex);
-#else
- pthread_rwlock_unlock(&t->rwlock);
-#endif
-#endif /* AVL_WITHOUT_PTHREADS */
- return ret;
-}
/* Remove an element a from the AVL tree t
* returns -1 if the depth of the tree has shrunk
* Warning: if the element is not present in the tree,
* returns 0 as if it had been removed succesfully.
*/
-int _avl_remove(avl_tree* t, avl* a) {
+int avl_remove(avl_tree* t, avl* a) {
int b;
if (t->root == a)
- return _avl_removeroot(t);
+ return avl_removeroot(t);
b = t->compar(t->root, a);
if (b >= 0) {
/* remove from the left subtree */
avl_tree left_subtree;
if ((left_subtree.root = t->root->left)) {
left_subtree.compar = t->compar;
- ch = _avl_remove(&left_subtree, a);
+ ch = avl_remove(&left_subtree, a);
t->root->left = left_subtree.root;
if (ch) {
switch (t->root->balance++) {
avl_tree right_subtree;
if ((right_subtree.root = t->root->right)) {
right_subtree.compar = t->compar;
- ch = _avl_remove(&right_subtree, a);
+ ch = avl_remove(&right_subtree, a);
t->root->right = right_subtree.root;
if (ch) {
switch (t->root->balance--) {
return 0;
}
-int avl_remove(avl_tree* t, avl* a) {
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- pthread_mutex_lock(&t->mutex);
-#else
- pthread_rwlock_wrlock(&t->rwlock);
-#endif
-#endif /* AVL_WITHOUT_PTHREADS */
-
- int ret = _avl_remove(t, a);
-
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- pthread_mutex_unlock(&t->mutex);
-#else
- pthread_rwlock_unlock(&t->rwlock);
-#endif
-#endif /* AVL_WITHOUT_PTHREADS */
- return ret;
-}
-
/* Remove the root of the AVL tree t
* Warning: dumps core if t is empty
*/
-int _avl_removeroot(avl_tree* t) {
+int avl_removeroot(avl_tree* t) {
int ch;
avl* a;
if (!t->root->left) {
while (a->left)
a = a->left;
}
- ch = _avl_remove(t, a);
+ ch = avl_remove(t, a);
a->left = t->root->left;
a->right = t->root->right;
a->balance = t->root->balance;
return 0;
}
-int avl_removeroot(avl_tree* t) {
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- pthread_mutex_lock(&t->mutex);
-#else
- pthread_rwlock_wrlock(&t->rwlock);
-#endif
-#endif /* AVL_WITHOUT_PTHREADS */
-
- int ret = _avl_removeroot(t);
-
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- pthread_mutex_unlock(&t->mutex);
-#else
- pthread_rwlock_unlock(&t->rwlock);
-#endif
-#endif /* AVL_WITHOUT_PTHREADS */
- return ret;
-}
-
/* Iterate through elements in t from a range between a and b (inclusive)
* for each element calls iter(a) until it returns 0
* returns the last value returned by iterator or 0 if there were no calls
* Warning: a<=b must hold
*/
-int _avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret) {
+int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret) {
int x, c = 0;
if (!t->root)
return 0;
avl_tree left_subtree;
if ((left_subtree.root = t->root->left)) {
left_subtree.compar = t->compar;
- if (!(c = _avl_range(&left_subtree, a, b, iter, ret)))
+ if (!(c = avl_range(&left_subtree, a, b, iter, ret)))
if (x > 0)
return 0;
}
avl_tree right_subtree;
if ((right_subtree.root = t->root->right)) {
right_subtree.compar = t->compar;
- if (!(c = _avl_range(&right_subtree, a, b, iter, ret)))
+ if (!(c = avl_range(&right_subtree, a, b, iter, ret)))
if (x < 0)
return 0;
}
return c;
}
-int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret) {
+void avl_init(avl_tree *t, int (*compar)(void *a, void *b)) {
+ t->root = NULL;
+ t->compar = compar;
+}
+
+/* ------------------------------------------------------------------------- */
+
+void avl_read_lock(avl_tree_lock *t) {
#ifndef AVL_WITHOUT_PTHREADS
#ifdef AVL_LOCK_WITH_MUTEX
pthread_mutex_lock(&t->mutex);
#else
- pthread_rwlock_wrlock(&t->rwlock);
+ pthread_rwlock_rdlock(&t->rwlock);
#endif
#endif /* AVL_WITHOUT_PTHREADS */
+}
- int ret2 = _avl_range(t, a, b, iter, ret);
+void avl_write_lock(avl_tree_lock *t) {
+#ifndef AVL_WITHOUT_PTHREADS
+#ifdef AVL_LOCK_WITH_MUTEX
+ pthread_mutex_lock(&t->mutex);
+#else
+ pthread_rwlock_wrlock(&t->rwlock);
+#endif
+#endif /* AVL_WITHOUT_PTHREADS */
+}
+void avl_unlock(avl_tree_lock *t) {
#ifndef AVL_WITHOUT_PTHREADS
#ifdef AVL_LOCK_WITH_MUTEX
pthread_mutex_unlock(&t->mutex);
pthread_rwlock_unlock(&t->rwlock);
#endif
#endif /* AVL_WITHOUT_PTHREADS */
-
- return ret2;
}
-/* Iterate through elements in t equal to a
- * for each element calls iter(a) until it returns 0
- * returns the last value returned by iterator or 0 if there were no calls
- */
-int avl_search(avl_tree* t, avl* a, int (*iter)(avl* a), avl** ret) {
- return avl_range(t, a, a, iter, ret);
-}
+/* ------------------------------------------------------------------------- */
-void avl_init(avl_tree* t, int (*compar)(void* a, void* b)) {
- t->root = NULL;
- t->compar = compar;
+void avl_init_lock(avl_tree_lock *t, int (*compar)(void *a, void *b)) {
+ avl_init(&t->avl_tree, compar);
#ifndef AVL_WITHOUT_PTHREADS
int lock;
fatal("Failed to initialize AVL mutex/rwlock, error: %d", lock);
#endif /* AVL_WITHOUT_PTHREADS */
+}
+
+int avl_range_lock(avl_tree_lock *t, avl *a, avl *b, int (*iter)(avl *), avl **ret) {
+ avl_read_lock(t);
+ int ret2 = avl_range(&t->avl_tree, a, b, iter, ret);
+ avl_unlock(t);
+ return ret2;
+}
+
+int avl_removeroot_lock(avl_tree_lock *t) {
+ avl_write_lock(t);
+ int ret = avl_removeroot(&t->avl_tree);
+ avl_unlock(t);
+ return ret;
+}
+
+int avl_remove_lock(avl_tree_lock *t, avl *a) {
+ avl_write_lock(t);
+ int ret = avl_remove(&t->avl_tree, a);
+ avl_unlock(t);
+ return ret;
+}
+int avl_insert_lock(avl_tree_lock *t, avl *a) {
+ avl_write_lock(t);
+ int ret = avl_insert(&t->avl_tree, a);
+ avl_unlock(t);
+ return ret;
}
#ifndef AVL_WITHOUT_PTHREADS
#include <pthread.h>
-#endif /* AVL_WITHOUT_PTHREADS */
// #define AVL_LOCK_WITH_MUTEX 1
+#ifdef AVL_LOCK_WITH_MUTEX
+#define AVL_LOCK_INITIALIZER PTHREAD_MUTEX_INITIALIZER
+#else /* AVL_LOCK_WITH_MUTEX */
+#define AVL_LOCK_INITIALIZER PTHREAD_RWLOCK_INITIALIZER
+#endif /* AVL_LOCK_WITH_MUTEX */
+
+#else /* AVL_WITHOUT_PTHREADS */
+#define AVL_LOCK_INITIALIZER
+#endif /* AVL_WITHOUT_PTHREADS */
+
/* Data structures */
/* One element of the AVL tree */
/* An AVL tree */
typedef struct avl_tree {
- avl* root;
- int (*compar)(void* a, void* b);
+ avl *root;
+
+ int (*compar)(void *a, void *b);
+} avl_tree;
+
+typedef struct avl_tree_lock {
+ avl_tree avl_tree;
#ifndef AVL_WITHOUT_PTHREADS
#ifdef AVL_LOCK_WITH_MUTEX
pthread_rwlock_t rwlock;
#endif /* AVL_LOCK_WITH_MUTEX */
#endif /* AVL_WITHOUT_PTHREADS */
-} avl_tree;
+} avl_tree_lock;
/* Public methods */
* returns 1 if the depth of the tree has grown
* Warning: do not insert elements already present
*/
-int avl_insert(avl_tree* t, avl* a);
+int avl_insert_lock(avl_tree_lock *t, avl *a);
+int avl_insert(avl_tree *t, avl *a);
/* Remove an element a from the AVL tree t
* returns -1 if the depth of the tree has shrunk
* Warning: if the element is not present in the tree,
* returns 0 as if it had been removed succesfully.
*/
-int avl_remove(avl_tree* t, avl* a);
+int avl_remove_lock(avl_tree_lock *t, avl *a);
+int avl_remove(avl_tree *t, avl *a);
/* Remove the root of the AVL tree t
* Warning: dumps core if t is empty
*/
-int avl_removeroot(avl_tree* t);
+int avl_removeroot_lock(avl_tree_lock *t);
+int avl_removeroot(avl_tree *t);
/* Iterate through elements in t from a range between a and b (inclusive)
* for each element calls iter(a) until it returns 0
* returns the last value returned by iterator or 0 if there were no calls
* Warning: a<=b must hold
*/
-int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret);
+int avl_range_lock(avl_tree_lock *t, avl *a, avl *b, int (*iter)(avl *), avl **ret);
+int avl_range(avl_tree *t, avl *a, avl *b, int (*iter)(avl *), avl **ret);
/* Iterate through elements in t equal to a
* for each element calls iter(a) until it returns 0
* returns the last value returned by iterator or 0 if there were no calls
*/
-int avl_search(avl_tree* t, avl* a, int (*iter)(avl*), avl** ret);
+#define avl_search_lock(t, a, iter, ret) avl_range_lock(t, a, a, iter, ret)
+#define avl_search(t, a, iter, ret) avl_range(t, a, a, iter, ret)
-/* Initialize the avl_tree
+/* Initialize the avl_tree_lock
*/
-void avl_init(avl_tree* t, int (*compar)(void* a, void* b));
+void avl_init_lock(avl_tree_lock *t, int (*compar)(void *a, void *b));
+void avl_init(avl_tree *t, int (*compar)(void *a, void *b));
#endif /* avl.h */
return syscall(SYS_gettid);
}
+char *fgets_trim_len(char *buf, size_t buf_size, FILE *fp, size_t *len) {
+ char *s = fgets(buf, 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;
+}
+
char *strncpyz(char *dest, const char *src, size_t n)
{
char *p = dest;
#include <stdarg.h>
#include <sys/time.h>
#include <sys/resource.h>
+#include <stdio.h>
#ifndef NETDATA_COMMON_H
#define NETDATA_COMMON_H 1
extern unsigned long long timems(void);
+extern char *fgets_trim_len(char *buf, size_t buf_size, FILE *fp, size_t *len);
+
/* fix for alpine linux */
#ifndef RUSAGE_THREAD
#ifdef RUSAGE_CHILDREN
void sig_handler(int signo)
{
- switch(signo) {
- case SIGILL:
- case SIGABRT:
- case SIGFPE:
- case SIGSEGV:
- case SIGBUS:
- case SIGSYS:
- case SIGTRAP:
- case SIGXCPU:
- case SIGXFSZ:
- infoerr("Death signaled exit (signal %d).", signo);
- signal(signo, SIG_DFL);
- break;
-
- case SIGKILL:
- case SIGTERM:
- case SIGQUIT:
- case SIGINT:
- case SIGHUP:
- case SIGUSR1:
- case SIGUSR2:
- infoerr("Signaled exit (signal %d).", signo);
- signal(SIGPIPE, SIG_IGN);
- signal(SIGTERM, SIG_IGN);
- signal(SIGQUIT, SIG_IGN);
- signal(SIGHUP, SIG_IGN);
- signal(SIGINT, SIG_IGN);
- signal(SIGCHLD, SIG_IGN);
- netdata_cleanup_and_exit(1);
- break;
-
- case SIGPIPE:
- infoerr("Signaled PIPE (signal %d).", signo);
- // this is received when web clients send a reset
- // no need to log it.
- // infoerr("Ignoring signal %d.", signo);
- break;
-
- default:
- info("Signal %d received. Falling back to default action for it.", signo);
- signal(signo, SIG_DFL);
- break;
- }
+ if(signo)
+ netdata_exit = 1;
}
int become_user(const char *username)
*access_fd = -1;
return -1;
}
+ if(setvbuf(*access_fp, NULL, _IOLBF, 0) != 0)
+ error("Cannot set line buffering on access.log");
}
}
}
}
- signal(SIGCHLD, SIG_IGN);
- signal(SIGHUP, SIG_IGN);
- signal(SIGWINCH, SIG_IGN);
-
// fork() again
if(!dont_fork) {
int i = fork();
dup2(output_fd, STDOUT_FILENO);
close(output_fd);
}
+
+ if(setvbuf(stdout, NULL, _IOLBF, 0) != 0)
+ error("Cannot set line buffering on debug.log");
+
output_fd = -1;
}
else dup2(dev_null, STDOUT_FILENO);
dup2(error_fd, STDERR_FILENO);
close(error_fd);
}
+
+ if(setvbuf(stderr, NULL, _IOLBF, 0) != 0)
+ error("Cannot set line buffering on error.log");
+
error_fd = -1;
}
else dup2(dev_null, STDERR_FILENO);
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
+
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include "dictionary.h"
// ----------------------------------------------------------------------------
-// name_value index
+// dictionary locks
+
+static inline void dictionary_read_lock(DICTIONARY *dict) {
+ if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
+ // debug(D_DICTIONARY, "Dictionary READ lock");
+ pthread_rwlock_rdlock(&dict->rwlock);
+ }
+}
+
+static inline void dictionary_write_lock(DICTIONARY *dict) {
+ if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
+ // debug(D_DICTIONARY, "Dictionary WRITE lock");
+ pthread_rwlock_wrlock(&dict->rwlock);
+ }
+}
+
+static inline void dictionary_unlock(DICTIONARY *dict) {
+ if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) {
+ // debug(D_DICTIONARY, "Dictionary UNLOCK lock");
+ pthread_rwlock_unlock(&dict->rwlock);
+ }
+}
+
+
+// ----------------------------------------------------------------------------
+// avl index
static int name_value_iterator(avl *a) { if(a) {}; return 0; }
tmp.name = (char *)name;
dict->searches++;
- avl_search(&(dict->values_index), (avl *)&tmp, name_value_iterator, (avl **)&result);
- return result;
-}
+ avl_search(&(dict->values_index), (avl *) &tmp, name_value_iterator, (avl **) &result);
-static void dictionary_read_lock(DICTIONARY *dict) {
- if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED)))
- pthread_rwlock_rdlock(&dict->rwlock);
-}
-
-static void dictionary_write_lock(DICTIONARY *dict) {
- if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED)))
- pthread_rwlock_wrlock(&dict->rwlock);
-}
-
-static void dictionary_unlock(DICTIONARY *dict) {
- if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED)))
- pthread_rwlock_unlock(&dict->rwlock);
+ return result;
}
// ----------------------------------------------------------------------------
+// internal methods
static NAME_VALUE *dictionary_name_value_create_nolock(DICTIONARY *dict, const char *name, void *value, size_t value_len, uint32_t hash) {
- debug(D_DICTIONARY, "Creating name value entry for name '%s', value '%s'.", name, value);
+ debug(D_DICTIONARY, "Creating name value entry for name '%s'.", name);
NAME_VALUE *nv = calloc(1, sizeof(NAME_VALUE));
if(unlikely(!nv)) fatal("Cannot allocate name_value of size %z", sizeof(NAME_VALUE));
- nv->name = strdup(name);
- if(unlikely(!nv->name))
- fatal("Cannot allocate name_value.name of size %z", strlen(name));
+ if(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE)
+ nv->name = (char *)name;
+ else {
+ nv->name = strdup(name);
+ if (unlikely(!nv->name))
+ fatal("Cannot allocate name_value.name of size %z", strlen(name));
+ }
nv->hash = (hash)?hash:simple_hash(nv->name);
memcpy(nv->value, value, value_len);
}
- dictionary_write_lock(dict);
-
// index it
dictionary_name_value_index_add_nolock(dict, nv);
-
dict->entries++;
- dictionary_unlock(dict);
-
return nv;
}
dict->entries--;
- free(nv->name);
-
- if(!(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE))
+ if(!(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE)) {
+ debug(D_REGISTRY, "Dictionary freeing value of '%s'", nv->name);
free(nv->value);
+ }
+
+ if(!(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE)) {
+ debug(D_REGISTRY, "Dictionary freeing name '%s'", nv->name);
+ free(nv->name);
+ }
free(nv);
}
// ----------------------------------------------------------------------------
+// API - basic methods
DICTIONARY *dictionary_create(uint32_t flags) {
debug(D_DICTIONARY, "Creating dictionary.");
uint32_t hash = simple_hash(name);
- dictionary_read_lock(dict);
- NAME_VALUE *nv = dictionary_name_value_index_find_nolock(dict, name, hash);
- dictionary_unlock(dict);
+ dictionary_write_lock(dict);
+ NAME_VALUE *nv = dictionary_name_value_index_find_nolock(dict, name, hash);
if(unlikely(!nv)) {
debug(D_DICTIONARY, "Dictionary entry with name '%s' not found. Creating a new one.", name);
- pthread_rwlock_wrlock(&dict->rwlock);
nv = dictionary_name_value_create_nolock(dict, name, value, value_len, hash);
-
if(unlikely(!nv))
fatal("Cannot create name_value.");
-
- dictionary_unlock(dict);
}
else {
debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", name);
- if(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE)
+ if(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE) {
+ debug(D_REGISTRY, "Dictionary: linking value to '%s'", name);
nv->value = value;
+ }
else {
+ debug(D_REGISTRY, "Dictionary: cloning value to '%s'", name);
+
void *value = malloc(value_len),
*old = nv->value;
memcpy(value, value, value_len);
nv->value = value;
+ debug(D_REGISTRY, "Dictionary: freeing old value of '%s'", name);
free(old);
}
}
+ dictionary_unlock(dict);
+
return nv->value;
}
return nv->value;
}
-void dictionary_del(DICTIONARY *dict, const char *name) {
+int dictionary_del(DICTIONARY *dict, const char *name) {
+ int ret;
+
debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name);
- dictionary_read_lock(dict);
- NAME_VALUE *nv = dictionary_name_value_index_find_nolock(dict, name, 0);
- dictionary_unlock(dict);
+ dictionary_write_lock(dict);
+ NAME_VALUE *nv = dictionary_name_value_index_find_nolock(dict, name, 0);
if(unlikely(!nv)) {
debug(D_DICTIONARY, "Not found dictionary entry with name '%s'.", name);
- return;
+ ret = -1;
+ }
+ else {
+ debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
+ dictionary_name_value_destroy_nolock(dict, nv);
+ ret = 0;
}
- debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
- pthread_rwlock_wrlock(&dict->rwlock);
- dictionary_name_value_destroy_nolock(dict, nv);
dictionary_unlock(dict);
+
+ return ret;
+}
+
+
+// ----------------------------------------------------------------------------
+// API - walk through the dictionary
+// the dictionary is locked for reading while this happens
+// do not user other dictionary calls while walking the dictionary - deadlock!
+
+static int dictionary_walker(avl *a, int (*callback)(void *entry, void *data), void *data) {
+ int total = 0, ret = 0;
+
+ if(a->right) {
+ ret = dictionary_walker(a->right, callback, data);
+ if(ret < 0) return ret;
+ total += ret;
+ }
+
+ ret = callback(((NAME_VALUE *)a)->value, data);
+ if(ret < 0) return ret;
+ total += ret;
+
+ if(a->left) {
+ dictionary_walker(a->left, callback, data);
+ if (ret < 0) return ret;
+ total += ret;
+ }
+
+ return total;
+}
+
+int dictionary_get_all(DICTIONARY *dict, int (*callback)(void *entry, void *data), void *data) {
+ int ret = 0;
+
+ dictionary_read_lock(dict);
+
+ if(likely(dict->values_index.root))
+ ret = dictionary_walker(dict->values_index.root, callback, data);
+
+ dictionary_unlock(dict);
+
+ return ret;
}
uint32_t hash; // a simple hash to speed up searching
// we first compare hashes, and only if the hashes are equal we do string comparisons
+
char *name;
void *value;
} NAME_VALUE;
typedef struct dictionary {
- uint32_t flags;
+ avl_tree values_index;
+
+ uint8_t flags;
unsigned long long inserts;
unsigned long long deletes;
unsigned long long searches;
unsigned long long entries;
- avl_tree values_index;
pthread_rwlock_t rwlock;
} DICTIONARY;
#define DICTIONARY_FLAG_DEFAULT 0x00000000
#define DICTIONARY_FLAG_SINGLE_THREADED 0x00000001
#define DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE 0x00000002
+#define DICTIONARY_FLAG_NAME_LINK_DONT_CLONE 0x00000004
extern DICTIONARY *dictionary_create(uint32_t flags);
extern void dictionary_destroy(DICTIONARY *dict);
extern void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len);
extern void *dictionary_get(DICTIONARY *dict, const char *name);
-extern void dictionary_del(DICTIONARY *dict, const char *name);
+extern int dictionary_del(DICTIONARY *dict, const char *name);
+
+extern int dictionary_get_all(DICTIONARY *dict, int (*callback)(void *entry, void *data), void *data);
#endif /* NETDATA_DICTIONARY_H */
vfprintf( stdout, fmt, args );
va_end( args );
fprintf(stdout, "\n");
- fflush( stdout );
+ // fflush( stdout );
if(output_log_syslog) {
va_start( args, fmt );
vfprintf( stdaccess, fmt, args );
va_end( args );
fprintf( stdaccess, "\n");
- fflush( stdaccess );
+ // fflush( stdaccess );
}
if(access_log_syslog) {
#define D_DICTIONARY 0x00040000
#define D_MEMORY 0x00080000
#define D_CGROUP 0x00100000
+#define D_REGISTRY 0x00200000
//#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS)
//#define DEBUG 0xffffffff
#include "plugin_checks.h"
#include "plugin_proc.h"
#include "plugin_nfacct.h"
+#include "registry.h"
#include "main.h"
extern void *cgroups_main(void *ptr);
-int netdata_exit = 0;
+volatile sig_atomic_t netdata_exit = 0;
void netdata_cleanup_and_exit(int ret)
{
}
else {
errno = 0;
-
- void (*old)(int);
- old = signal(sig, SIG_IGN);
- if(old == SIG_ERR) {
- error("Cannot overwrite signal handler for signal %d", sig);
- old = sig_handler;
- }
-
ret = kill(pid, sig);
-
- if(signal(sig, old) == SIG_ERR)
- error("Cannot restore signal handler for signal %d", sig);
-
if(ret == -1) {
switch(errno) {
case ESRCH:
setenv("NETDATA_PLUGINS_DIR", config_get("global", "plugins directory" , PLUGINS_DIR), 1);
setenv("NETDATA_WEB_DIR" , config_get("global", "web files directory", WEB_DIR) , 1);
setenv("NETDATA_CACHE_DIR" , config_get("global", "cache directory" , CACHE_DIR) , 1);
+ setenv("NETDATA_LIB_DIR" , config_get("global", "lib directory" , VARLIB_DIR) , 1);
setenv("NETDATA_LOG_DIR" , config_get("global", "log directory" , LOG_DIR) , 1);
setenv("NETDATA_HOST_PREFIX", config_get("global", "host access prefix" , "") , 1);
setenv("HOME" , config_get("global", "home directory" , CACHE_DIR) , 1);
// --------------------------------------------------------------------
+ // block signals while initializing threads.
+ // this causes the threads to block signals.
+ sigset_t sigset;
+ sigfillset(&sigset);
+
+ if(pthread_sigmask(SIG_BLOCK, &sigset, NULL) == -1) {
+ error("Could not block signals for threads");
+ }
+
+ // Catch signals which we want to use to quit savely
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, SIGINT);
+ sigaddset(&sa.sa_mask, SIGTERM);
+ sa.sa_handler = sig_handler;
+ sa.sa_flags = 0;
+ if(sigaction(SIGHUP, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGHUP");
+ }
+ if(sigaction(SIGINT, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGINT");
+ }
+ if(sigaction(SIGTERM, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGTERM");
+ }
+ // Ignore SIGPIPE completely.
+ // INFO: If we add signals here we have to unblock them
+ // at popen.c when running a external plugin.
+ sa.sa_handler = SIG_IGN;
+ if(sigaction(SIGPIPE, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGTERM");
+ }
+
+ // --------------------------------------------------------------------
+
i = pthread_attr_init(&attr);
if(i != 0)
fatal("pthread_attr_init() failed with code %d.", i);
// never become a problem
if(nice(20) == -1) error("Cannot lower my CPU priority.");
- if(become_daemon(dont_fork, 0, user, input_log_file, output_log_file, error_log_file, access_log_file, &access_fd, &stdaccess) == -1) {
+ if(become_daemon(dont_fork, 0, user, input_log_file, output_log_file, error_log_file, access_log_file, &access_fd, &stdaccess) == -1)
fatal("Cannot demonize myself.");
- exit(1);
- }
+#ifdef NETDATA_INTERNAL_CHECKS
if(debug_flags != 0) {
struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
if(setrlimit(RLIMIT_CORE, &rl) != 0)
info("Cannot request unlimited core dumps for debugging... Proceeding anyway...");
-
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
}
+#endif /* NETDATA_INTERNAL_CHECKS */
if(output_log_syslog || error_log_syslog || access_log_syslog)
openlog("netdata", LOG_PID, LOG_DAEMON);
info("NetData started on pid %d", getpid());
- // catch all signals
- for (i = 1 ; i < 65 ;i++) {
- switch(i) {
- case SIGKILL: // not catchable
- case SIGSTOP: // not catchable
- break;
-
- case SIGSEGV:
- case SIGFPE:
- case SIGCHLD:
- signal(i, SIG_DFL);
- break;
-
- default:
- signal(i, sig_handler);
- break;
- }
- }
-
// ------------------------------------------------------------------------
// get default pthread stack size
info("Successfully set pthread stacksize to %zu bytes", wanted_stacksize);
}
+ // --------------------------------------------------------------------
+ // initialize the registry
+
+ registry_init();
+
// ------------------------------------------------------------------------
// spawn the threads
else info("Not starting thread %s.", st->name);
}
- // for future use - the main thread
- while(1) {
- if(netdata_exit != 0) {
- netdata_exit++;
+ // ------------------------------------------------------------------------
+ // block signals while initializing threads.
+ sigset_t sigset;
+ sigfillset(&sigset);
- if(netdata_exit > 5) {
- netdata_cleanup_and_exit(0);
- exit(0);
- }
- }
- sleep(2);
+ if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
+ error("Could not unblock signals for threads");
}
- exit(0);
+ // Handle flags set in the signal handler.
+ while(1) {
+ pause();
+ if(netdata_exit) {
+ info("Exit main loop of netdata.");
+ netdata_cleanup_and_exit(0);
+ exit(0);
+ }
+ }
}
#ifndef NETDATA_MAIN_H
#define NETDATA_MAIN_H 1
-extern int netdata_exit;
+#include <signal.h>
+
+extern volatile sig_atomic_t netdata_exit;
extern void kill_childs(void);
extern int killpid(pid_t pid, int signal);
#include "rrd.h"
#include "plugin_proc.h"
#include "main.h"
+#include "registry.h"
void *proc_main(void *ptr)
{
if(!stcpu_thread) stcpu_thread = rrdset_find("netdata.plugin_proc_cpu");
if(!stcpu_thread) {
- stcpu_thread = rrdset_create("netdata", "plugin_proc_cpu", NULL, "proc.internal", NULL, "NetData Proc Plugin CPU usage", "milliseconds/s", 131000, rrd_update_every, RRDSET_TYPE_STACKED);
+ stcpu_thread = rrdset_create("netdata", "plugin_proc_cpu", NULL, "proc.internal", NULL, "NetData Proc Plugin CPU usage", "milliseconds/s", 132000, rrd_update_every, RRDSET_TYPE_STACKED);
rrddim_add(stcpu_thread, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL);
rrddim_add(stcpu_thread, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL);
if(!stclients) stclients = rrdset_find("netdata.clients");
if(!stclients) {
- stclients = rrdset_create("netdata", "clients", NULL, "netdata", NULL, "NetData Web Clients", "connected clients", 131000, rrd_update_every, RRDSET_TYPE_LINE);
+ stclients = rrdset_create("netdata", "clients", NULL, "netdata", NULL, "NetData Web Clients", "connected clients", 130100, rrd_update_every, RRDSET_TYPE_LINE);
rrddim_add(stclients, "clients", NULL, 1, 1, RRDDIM_ABSOLUTE);
}
if(!streqs) streqs = rrdset_find("netdata.requests");
if(!streqs) {
- streqs = rrdset_create("netdata", "requests", NULL, "netdata", NULL, "NetData Web Requests", "requests/s", 131100, rrd_update_every, RRDSET_TYPE_LINE);
+ streqs = rrdset_create("netdata", "requests", NULL, "netdata", NULL, "NetData Web Requests", "requests/s", 130200, rrd_update_every, RRDSET_TYPE_LINE);
rrddim_add(streqs, "requests", NULL, 1, 1, RRDDIM_INCREMENTAL);
}
if(!stbytes) stbytes = rrdset_find("netdata.net");
if(!stbytes) {
- stbytes = rrdset_create("netdata", "net", NULL, "netdata", NULL, "NetData Network Traffic", "kilobits/s", 131200, rrd_update_every, RRDSET_TYPE_AREA);
+ stbytes = rrdset_create("netdata", "net", NULL, "netdata", NULL, "NetData Network Traffic", "kilobits/s", 130300, rrd_update_every, RRDSET_TYPE_AREA);
rrddim_add(stbytes, "in", NULL, 8, 1024, RRDDIM_INCREMENTAL);
rrddim_add(stbytes, "out", NULL, -8, 1024, RRDDIM_INCREMENTAL);
rrddim_set(stbytes, "in", global_statistics.bytes_received);
rrddim_set(stbytes, "out", global_statistics.bytes_sent);
rrdset_done(stbytes);
+
+ // ----------------------------------------------------------------
+
+ registry_statistics();
}
}
avl_tree tc_device_root_index = {
NULL,
- tc_device_compare,
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#endif
-#endif
+ tc_device_compare
};
#define tc_device_index_add(st) avl_insert(&tc_device_root_index, (avl *)(st))
#define tc_device_index_del(st) avl_remove(&tc_device_root_index, (avl *)(st))
-static struct tc_device *tc_device_index_find(const char *id, uint32_t hash) {
+static inline struct tc_device *tc_device_index_find(const char *id, uint32_t hash) {
struct tc_device *result = NULL, tmp;
tmp.id = (char *)id;
tmp.hash = (hash)?hash:simple_hash(tmp.id);
- avl_search(&(tc_device_root_index), (avl *)&tmp, tc_device_iterator, (avl **)&result);
+ avl_search(&(tc_device_root_index), (avl *) &tmp, tc_device_iterator, (avl **) &result);
return result;
}
#define tc_class_index_add(st, rd) avl_insert(&((st)->classes_index), (avl *)(rd))
#define tc_class_index_del(st, rd) avl_remove(&((st)->classes_index), (avl *)(rd))
-static struct tc_class *tc_class_index_find(struct tc_device *st, const char *id, uint32_t hash) {
+static inline struct tc_class *tc_class_index_find(struct tc_device *st, const char *id, uint32_t hash) {
struct tc_class *result = NULL, tmp;
tmp.id = (char *)id;
tmp.hash = (hash)?hash:simple_hash(tmp.id);
- avl_search(&(st->classes_index), (avl *)&tmp, tc_class_iterator, (avl **)&result);
+ avl_search(&(st->classes_index), (avl *) &tmp, tc_class_iterator, (avl **) &result);
return result;
}
// ----------------------------------------------------------------------------
-static void tc_class_free(struct tc_device *n, struct tc_class *c) {
+static inline void tc_class_free(struct tc_device *n, struct tc_class *c) {
debug(D_TC_LOOP, "Removing from device '%s' class '%s', parentid '%s', leafid '%s', seen=%d", n->id, c->id, c->parentid?c->parentid:"", c->leafid?c->leafid:"", c->seen);
if(c->next) c->next->prev = c->prev;
free(c);
}
-static void tc_device_classes_cleanup(struct tc_device *d) {
+static inline void tc_device_classes_cleanup(struct tc_device *d) {
static int cleanup_every = 999;
if(cleanup_every > 0) {
}
}
-static void tc_device_commit(struct tc_device *d)
+static inline void tc_device_commit(struct tc_device *d)
{
static int enable_new_interfaces = -1;
tc_device_classes_cleanup(d);
}
-static void tc_device_set_class_name(struct tc_device *d, char *id, char *name)
+static inline void tc_device_set_class_name(struct tc_device *d, char *id, char *name)
{
struct tc_class *c = tc_class_index_find(d, id, 0);
if(c) {
}
}
-static void tc_device_set_device_name(struct tc_device *d, char *name) {
+static inline void tc_device_set_device_name(struct tc_device *d, char *name) {
if(d->name) free(d->name);
d->name = NULL;
}
}
-static void tc_device_set_device_family(struct tc_device *d, char *family) {
+static inline void tc_device_set_device_family(struct tc_device *d, char *family) {
if(d->family) free(d->family);
d->family = NULL;
// no need for null termination - it is already null
}
-static struct tc_device *tc_device_create(char *id)
+static inline struct tc_device *tc_device_create(char *id)
{
struct tc_device *d = tc_device_index_find(id, 0);
d->id = strdup(id);
d->hash = simple_hash(d->id);
- d->classes_index.root = NULL;
- d->classes_index.compar = tc_class_compare;
-
- int lock;
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- lock = pthread_mutex_init(&d->classes_index.mutex, NULL);
-#else
- lock = pthread_rwlock_init(&d->classes_index.rwlock, NULL);
-#endif
-#endif
- if(lock != 0)
- fatal("Failed to initialize plugin_tc mutex/rwlock, return code %d.", lock);
-
+ avl_init(&d->classes_index, tc_class_compare);
tc_device_index_add(d);
if(!tc_device_root) {
return(d);
}
-static struct tc_class *tc_class_add(struct tc_device *n, char *id, char *parentid, char *leafid)
+static inline struct tc_class *tc_class_add(struct tc_device *n, char *id, char *parentid, char *leafid)
{
struct tc_class *c = tc_class_index_find(n, id, 0);
return(c);
}
-static void tc_device_free(struct tc_device *n)
+static inline void tc_device_free(struct tc_device *n)
{
if(n->next) n->next->prev = n->prev;
if(n->prev) n->prev->next = n->next;
free(n);
}
-static void tc_device_free_all()
+static inline void tc_device_free_all()
{
while(tc_device_root)
tc_device_free(tc_device_root);
}
}
-static void tc_split_words(char *str, char **words, int max_words) {
+static inline void tc_split_words(char *str, char **words, int max_words) {
char *s = str;
int i = 0;
#endif
// reset all signals
- for (i = 1 ; i < 65 ;i++) if(i != SIGSEGV) signal(i, SIG_DFL);
+ {
+ sigset_t sigset;
+ sigfillset(&sigset);
+
+ if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
+ error("Could not block signals for threads");
+ }
+ // We only need to reset ignored signals.
+ // Signals with signal handlers are reset by default.
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = 0;
+ if(sigaction(SIGPIPE, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGTERM");
+ }
+ }
+
info("executing command: '%s' on pid %d.", command, getpid());
- execl("/bin/sh", "sh", "-c", command, NULL);
+ execl("/bin/sh", "sh", "-c", command, NULL);
exit(1);
}
#include "plugin_proc.h"
#include "log.h"
-#define MAX_INTERRUPTS 256
-#define MAX_INTERRUPT_CPUS 256
#define MAX_INTERRUPT_NAME 50
struct interrupt {
int used;
char *id;
char name[MAX_INTERRUPT_NAME + 1];
- unsigned long long value[MAX_INTERRUPT_CPUS];
unsigned long long total;
+ unsigned long long value[];
};
-static struct interrupt *alloc_interrupts(int lines) {
+// since each interrupt is variable in size
+// we use this to calculate its record size
+#define recordsize(cpus) (sizeof(struct interrupt) + (cpus * sizeof(unsigned long long)))
+
+// given a base, get a pointer to each record
+#define irrindex(base, line, cpus) ((struct interrupt *)&((char *)(base))[line * recordsize(cpus)])
+
+static inline struct interrupt *get_interrupts_array(int lines, int cpus) {
static struct interrupt *irrs = NULL;
- static int alloced = 0;
+ static int allocated = 0;
- if(lines < alloced) return irrs;
+ if(lines < allocated) return irrs;
else {
- irrs = (struct interrupt *)realloc(irrs, lines * sizeof(struct interrupt));
+ irrs = (struct interrupt *)realloc(irrs, lines * recordsize(cpus));
if(!irrs)
fatal("Cannot allocate memory for %d interrupts", lines);
- alloced = lines;
+ allocated = lines;
}
return irrs;
int do_proc_interrupts(int update_every, unsigned long long dt) {
static procfile *ff = NULL;
static int cpus = -1, do_per_core = -1;
-
struct interrupt *irrs = NULL;
if(dt) {};
if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0)
cpus++;
}
-
- if(cpus > MAX_INTERRUPT_CPUS) cpus = MAX_INTERRUPT_CPUS;
}
if(!cpus) {
}
// allocate the size we need;
- irrs = alloc_interrupts(lines);
+ irrs = get_interrupts_array(lines, cpus);
irrs[0].used = 0;
// loop through all lines
for(l = 1; l < lines ;l++) {
- struct interrupt *irr = &irrs[l];
+ struct interrupt *irr = irrindex(irrs, l, cpus);
irr->used = 0;
irr->total = 0;
st = rrdset_create("system", "interrupts", NULL, "interrupts", NULL, "System interrupts", "interrupts/s", 1000, update_every, RRDSET_TYPE_STACKED);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_add(st, irrs[l].id, irrs[l].name, 1, 1, RRDDIM_INCREMENTAL);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_add(st, irr->id, irr->name, 1, 1, RRDDIM_INCREMENTAL);
}
}
else rrdset_next(st);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_set(st, irrs[l].id, irrs[l].total);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_set(st, irr->id, irr->total);
}
rrdset_done(st);
st = rrdset_create("cpu", id, name, "interrupts", "cpu.interrupts", title, "interrupts/s", 2000 + c, update_every, RRDSET_TYPE_STACKED);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_add(st, irrs[l].id, irrs[l].name, 1, 1, RRDDIM_INCREMENTAL);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_add(st, irr->id, irr->name, 1, 1, RRDDIM_INCREMENTAL);
}
}
else rrdset_next(st);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_set(st, irrs[l].id, irrs[l].value[c]);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_set(st, irr->id, irr->value[c]);
}
rrdset_done(st);
}
#include "plugin_proc.h"
#include "log.h"
-#define MAX_INTERRUPTS 256
-#define MAX_INTERRUPT_CPUS 256
#define MAX_INTERRUPT_NAME 50
struct interrupt {
int used;
char *id;
char name[MAX_INTERRUPT_NAME + 1];
- unsigned long long value[MAX_INTERRUPT_CPUS];
unsigned long long total;
+ unsigned long long value[];
};
-static struct interrupt *alloc_interrupts(int lines) {
+// since each interrupt is variable in size
+// we use this to calculate its record size
+#define recordsize(cpus) (sizeof(struct interrupt) + (cpus * sizeof(unsigned long long)))
+
+// given a base, get a pointer to each record
+#define irrindex(base, line, cpus) ((struct interrupt *)&((char *)(base))[line * recordsize(cpus)])
+
+static inline struct interrupt *get_interrupts_array(int lines, int cpus) {
static struct interrupt *irrs = NULL;
- static int alloced = 0;
+ static int allocated = 0;
- if(lines < alloced) return irrs;
+ if(lines < allocated) return irrs;
else {
- irrs = (struct interrupt *)realloc(irrs, lines * sizeof(struct interrupt));
+ irrs = (struct interrupt *)realloc(irrs, lines * recordsize(cpus));
if(!irrs)
fatal("Cannot allocate memory for %d interrupts", lines);
- alloced = lines;
+ allocated = lines;
}
return irrs;
if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0)
cpus++;
}
-
- if(cpus > MAX_INTERRUPT_CPUS) cpus = MAX_INTERRUPT_CPUS;
}
if(!cpus) {
}
// allocate the size we need;
- irrs = alloc_interrupts(lines);
+ irrs = get_interrupts_array(lines, cpus);
irrs[0].used = 0;
// loop through all lines
for(l = 1; l < lines ;l++) {
- struct interrupt *irr = &irrs[l];
+ struct interrupt *irr = irrindex(irrs, l, cpus);
irr->used = 0;
irr->total = 0;
st = rrdset_create("system", "softirqs", NULL, "softirqs", NULL, "System softirqs", "softirqs/s", 950, update_every, RRDSET_TYPE_STACKED);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_add(st, irrs[l].id, irrs[l].name, 1, 1, RRDDIM_INCREMENTAL);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_add(st, irr->id, irr->name, 1, 1, RRDDIM_INCREMENTAL);
}
}
else rrdset_next(st);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_set(st, irrs[l].id, irrs[l].total);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_set(st, irr->id, irr->total);
}
rrdset_done(st);
// find if everything is zero
unsigned long long core_sum = 0 ;
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- core_sum += irrs[l].value[c];
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ core_sum += irr->value[c];
}
if(core_sum == 0) continue; // try next core
st = rrdset_create("cpu", id, name, "softirqs", "cpu.softirqs", title, "softirqs/s", 3000 + c, update_every, RRDSET_TYPE_STACKED);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_add(st, irrs[l].id, irrs[l].name, 1, 1, RRDDIM_INCREMENTAL);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_add(st, irr->id, irr->name, 1, 1, RRDDIM_INCREMENTAL);
}
}
else rrdset_next(st);
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- rrddim_set(st, irrs[l].id, irrs[l].value[c]);
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ rrddim_set(st, irr->id, irr->value[c]);
}
rrdset_done(st);
}
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <uuid/uuid.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "common.h"
+#include "dictionary.h"
+#include "appconfig.h"
+
+#include "web_client.h"
+#include "rrd.h"
+#include "rrd2json.h"
+#include "registry.h"
+
+
+// ----------------------------------------------------------------------------
+// TODO
+//
+// 1. the default tracking cookie expires in 1 year, but the persons are not
+// removed from the db - this means the database only grows - ideally the
+// database should be cleaned in registry_save() for both on-disk and
+// on-memory entries.
+//
+// Cleanup:
+// i. Find all the PERSONs that have expired cookie
+// ii. For each of their PERSON_URLs:
+// - decrement the linked MACHINE links
+// - if the linked MACHINE has no other links, remove the linked MACHINE too
+// - remove the PERSON_URL
+//
+// 2. add protection to prevent abusing the registry by flooding it with
+// requests to fill the memory and crash it.
+//
+// Possible protections:
+// - limit the number of URLs per person
+// - limit the number of URLs per machine
+// - limit the number of persons
+// - limit the number of machines
+// - [DONE] limit the size of URLs
+// - [DONE] limit the size of PERSON_URL names
+// - limit the number of requests that add data to the registry,
+// per client IP per hour
+
+
+
+#define REGISTRY_URL_FLAGS_DEFAULT 0x00
+#define REGISTRY_URL_FLAGS_EXPIRED 0x01
+
+#define DICTIONARY_FLAGS DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE | DICTIONARY_FLAG_NAME_LINK_DONT_CLONE
+
+// ----------------------------------------------------------------------------
+// COMMON structures
+
+struct registry {
+ int enabled;
+
+ char machine_guid[36 + 1];
+
+ // entries counters / statistics
+ unsigned long long persons_count;
+ unsigned long long machines_count;
+ unsigned long long usages_count;
+ unsigned long long urls_count;
+ unsigned long long persons_urls_count;
+ unsigned long long machines_urls_count;
+ unsigned long long log_count;
+
+ // memory counters / statistics
+ unsigned long long persons_memory;
+ unsigned long long machines_memory;
+ unsigned long long urls_memory;
+ unsigned long long persons_urls_memory;
+ unsigned long long machines_urls_memory;
+
+ // configuration
+ unsigned long long save_registry_every_entries;
+ char *registry_domain;
+ char *hostname;
+ char *registry_to_announce;
+ time_t persons_expiration; // seconds to expire idle persons
+
+ size_t max_url_length;
+ size_t max_name_length;
+
+ // file/path names
+ char *pathname;
+ char *db_filename;
+ char *log_filename;
+ char *machine_guid_filename;
+
+ // open files
+ FILE *log_fp;
+
+ // the database
+ DICTIONARY *persons; // dictionary of PERSON *, with key the PERSON.guid
+ DICTIONARY *machines; // dictionary of MACHINE *, with key the MACHINE.guid
+ DICTIONARY *urls; // dictionary of URL *, with key the URL.url
+
+ // concurrency locking
+ // we keep different locks for different things
+ // so that many tasks can be completed in parallel
+ pthread_mutex_t persons_lock;
+ pthread_mutex_t machines_lock;
+ pthread_mutex_t urls_lock;
+ pthread_mutex_t person_urls_lock;
+ pthread_mutex_t machine_urls_lock;
+ pthread_mutex_t log_lock;
+} registry;
+
+
+// ----------------------------------------------------------------------------
+// URL structures
+// Save memory by de-duplicating URLs
+// so instead of storing URLs all over the place
+// we store them here and we keep pointers elsewhere
+
+struct url {
+ uint32_t links; // the number of links to this URL - when none is left, we free it
+ uint16_t len; // the length of the URL in bytes
+ char url[1]; // the URL - dynamically allocated to more size
+};
+typedef struct url URL;
+
+
+// ----------------------------------------------------------------------------
+// MACHINE structures
+
+// For each MACHINE-URL pair we keep this
+struct machine_url {
+ URL *url; // de-duplicated URL
+// DICTIONARY *persons; // dictionary of PERSON *
+
+ uint8_t flags;
+ uint32_t first_t; // the first time we saw this
+ uint32_t last_t; // the last time we saw this
+ uint32_t usages; // how many times this has been accessed
+};
+typedef struct machine_url MACHINE_URL;
+
+// A machine
+struct machine {
+ char guid[36 + 1]; // the GUID
+
+ uint32_t links; // the number of PERSON_URLs linked to this machine
+
+ DICTIONARY *urls; // MACHINE_URL *
+
+ uint32_t first_t; // the first time we saw this
+ uint32_t last_t; // the last time we saw this
+ uint32_t usages; // how many times this has been accessed
+};
+typedef struct machine MACHINE;
+
+
+// ----------------------------------------------------------------------------
+// PERSON structures
+
+// for each PERSON-URL pair we keep this
+struct person_url {
+ URL *url; // de-duplicated URL
+ MACHINE *machine; // link the MACHINE of this URL
+
+ uint8_t flags;
+ uint32_t first_t; // the first time we saw this
+ uint32_t last_t; // the last time we saw this
+ uint32_t usages; // how many times this has been accessed
+
+ char name[1]; // the name of the URL, as known by the user
+ // dynamically allocated to fit properly
+};
+typedef struct person_url PERSON_URL;
+
+// A person
+struct person {
+ char guid[36 + 1]; // the person GUID
+
+ DICTIONARY *urls; // dictionary of PERSON_URL *
+
+ uint32_t first_t; // the first time we saw this
+ uint32_t last_t; // the last time we saw this
+ uint32_t usages; // how many times this has been accessed
+};
+typedef struct person PERSON;
+
+
+// ----------------------------------------------------------------------------
+// REGISTRY concurrency locking
+
+static inline void registry_persons_lock(void) {
+ pthread_mutex_lock(®istry.persons_lock);
+}
+
+static inline void registry_persons_unlock(void) {
+ pthread_mutex_unlock(®istry.persons_lock);
+}
+
+static inline void registry_machines_lock(void) {
+ pthread_mutex_lock(®istry.machines_lock);
+}
+
+static inline void registry_machines_unlock(void) {
+ pthread_mutex_unlock(®istry.machines_lock);
+}
+
+static inline void registry_urls_lock(void) {
+ pthread_mutex_lock(®istry.urls_lock);
+}
+
+static inline void registry_urls_unlock(void) {
+ pthread_mutex_unlock(®istry.urls_lock);
+}
+
+// ideally, we should not lock the whole registry for
+// updating a person's urls.
+// however, to save the memory required for keeping a
+// mutex (40 bytes) per person, we do...
+static inline void registry_person_urls_lock(PERSON *p) {
+ (void)p;
+ pthread_mutex_lock(®istry.person_urls_lock);
+}
+
+static inline void registry_person_urls_unlock(PERSON *p) {
+ (void)p;
+ pthread_mutex_unlock(®istry.person_urls_lock);
+}
+
+// ideally, we should not lock the whole registry for
+// updating a machine's urls.
+// however, to save the memory required for keeping a
+// mutex (40 bytes) per machine, we do...
+static inline void registry_machine_urls_lock(MACHINE *m) {
+ (void)m;
+ pthread_mutex_lock(®istry.machine_urls_lock);
+}
+
+static inline void registry_machine_urls_unlock(MACHINE *m) {
+ (void)m;
+ pthread_mutex_unlock(®istry.machine_urls_lock);
+}
+
+static inline void registry_log_lock(void) {
+ pthread_mutex_lock(®istry.log_lock);
+}
+
+static inline void registry_log_unlock(void) {
+ pthread_mutex_unlock(®istry.log_lock);
+}
+
+
+// ----------------------------------------------------------------------------
+// common functions
+
+// parse a GUID and re-generated to be always lower case
+// this is used as a protection against the variations of GUIDs
+static inline int registry_regenerate_guid(const char *guid, char *result) {
+ uuid_t uuid;
+ if(unlikely(uuid_parse(guid, uuid) == -1)) {
+ info("Registry: GUID '%s' is not a valid GUID.", guid);
+ return -1;
+ }
+ else {
+ uuid_unparse_lower(uuid, result);
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ if(strcmp(guid, result))
+ info("Registry: source GUID '%s' and re-generated GUID '%s' differ!", guid, result);
+#endif /* NETDATA_INTERNAL_CHECKS */
+ }
+
+ return 0;
+}
+
+// make sure the names of the machines / URLs do not contain any tabs
+// (which are used as our separator in the database files)
+// and are properly trimmed (before and after)
+static inline char *registry_fix_machine_name(char *name, size_t *len) {
+ char *s = name?name:"";
+
+ // skip leading spaces
+ while(*s && isspace(*s)) s++;
+
+ // make sure all spaces are a SPACE
+ char *t = s;
+ while(*t) {
+ if(unlikely(isspace(*t)))
+ *t = ' ';
+
+ t++;
+ }
+
+ // remove trailing spaces
+ while(--t >= s) {
+ if(*t == ' ')
+ *t = '\0';
+ else
+ break;
+ }
+ t++;
+
+ if(likely(len))
+ *len = (t - s);
+
+ return s;
+}
+
+static inline char *registry_fix_url(char *url, size_t *len) {
+ return registry_fix_machine_name(url, len);
+}
+
+
+// ----------------------------------------------------------------------------
+// forward definition of functions
+
+extern PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when);
+extern PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when);
+
+
+// ----------------------------------------------------------------------------
+// URL
+
+static inline URL *registry_url_allocate_nolock(const char *url, size_t urllen) {
+ // protection from too big URLs
+ if(urllen > registry.max_url_length)
+ urllen = registry.max_url_length;
+
+ debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): allocating %zu bytes", url, sizeof(URL) + urllen);
+ URL *u = malloc(sizeof(URL) + urllen);
+ if(!u) fatal("Cannot allocate %zu bytes for URL '%s'", sizeof(URL) + urllen);
+
+ // a simple strcpy() should do the job
+ // but I prefer to be safe, since the caller specified urllen
+ strncpy(u->url, url, urllen);
+ u->url[urllen] = '\0';
+
+ u->len = urllen;
+ u->links = 0;
+
+ registry.urls_memory += sizeof(URL) + urllen;
+
+ debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): indexing it", url);
+ dictionary_set(registry.urls, u->url, u, sizeof(URL));
+
+ return u;
+}
+
+static inline URL *registry_url_get(const char *url, size_t urllen) {
+ debug(D_REGISTRY, "Registry: registry_url_get('%s')", url);
+
+ registry_urls_lock();
+
+ URL *u = dictionary_get(registry.urls, url);
+ if(!u) {
+ u = registry_url_allocate_nolock(url, urllen);
+ registry.urls_count++;
+ }
+
+ registry_urls_unlock();
+
+ return u;
+}
+
+static inline void registry_url_link_nolock(URL *u) {
+ u->links++;
+ debug(D_REGISTRY, "Registry: registry_url_link_nolock('%s'): URL has now %u links", u->url, u->links);
+}
+
+static inline void registry_url_unlink_nolock(URL *u) {
+ u->links--;
+ if(!u->links) {
+ debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): No more links for this URL", u->url);
+ dictionary_del(registry.urls, u->url);
+ free(u);
+ }
+ else
+ debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): URL has %u links left", u->url, u->links);
+}
+
+
+// ----------------------------------------------------------------------------
+// MACHINE
+
+static inline MACHINE *registry_machine_find(const char *machine_guid) {
+ debug(D_REGISTRY, "Registry: registry_machine_find('%s')", machine_guid);
+ return dictionary_get(registry.machines, machine_guid);
+}
+
+static inline MACHINE_URL *registry_machine_url_allocate(MACHINE *m, URL *u, time_t when) {
+ debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): allocating %zu bytes", m->guid, u->url, sizeof(MACHINE_URL));
+
+ MACHINE_URL *mu = malloc(sizeof(MACHINE_URL));
+ if(!mu) fatal("registry_machine_link_to_url('%s', '%s'): cannot allocate %zu bytes.", m->guid, u->url, sizeof(MACHINE_URL));
+
+ // mu->persons = dictionary_create(DICTIONARY_FLAGS);
+ // dictionary_set(mu->persons, p->guid, p, sizeof(PERSON));
+
+ mu->first_t = mu->last_t = when;
+ mu->usages = 1;
+ mu->url = u;
+ mu->flags = REGISTRY_URL_FLAGS_DEFAULT;
+
+ registry.machines_urls_memory += sizeof(MACHINE_URL);
+
+ debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): indexing URL in machine", m->guid, u->url);
+ dictionary_set(m->urls, u->url, mu, sizeof(MACHINE_URL));
+ registry_url_link_nolock(u);
+
+ return mu;
+}
+
+static inline MACHINE *registry_machine_allocate(const char *machine_guid, time_t when) {
+ debug(D_REGISTRY, "Registry: registry_machine_allocate('%s'): creating new machine, sizeof(MACHINE)=%zu", machine_guid, sizeof(MACHINE));
+
+ MACHINE *m = malloc(sizeof(MACHINE));
+ if(!m) fatal("Registry: cannot allocate memory for new machine '%s'", machine_guid);
+
+ strncpy(m->guid, machine_guid, 36);
+ m->guid[36] = '\0';
+
+ debug(D_REGISTRY, "Registry: registry_machine_allocate('%s'): creating dictionary of urls", machine_guid);
+ m->urls = dictionary_create(DICTIONARY_FLAGS);
+
+ m->first_t = m->last_t = when;
+ m->usages = 0;
+
+ registry.machines_memory += sizeof(MACHINE);
+
+ registry.machines_count++;
+ dictionary_set(registry.machines, m->guid, m, sizeof(MACHINE));
+
+ return m;
+}
+
+// 1. validate machine GUID
+// 2. if it is valid, find it or create it and return it
+// 3. if it is not valid, return NULL
+static inline MACHINE *registry_machine_get(const char *machine_guid, time_t when) {
+ MACHINE *m = NULL;
+
+ registry_machines_lock();
+
+ if(likely(machine_guid && *machine_guid)) {
+ // validate it is a GUID
+ char buf[36 + 1];
+ if(unlikely(registry_regenerate_guid(machine_guid, buf) == -1))
+ info("Registry: machine guid '%s' is not a valid guid. Ignoring it.", machine_guid);
+ else {
+ machine_guid = buf;
+ m = registry_machine_find(machine_guid);
+ if(!m) m = registry_machine_allocate(machine_guid, when);
+ }
+ }
+
+ registry_machines_unlock();
+
+ return m;
+}
+
+
+// ----------------------------------------------------------------------------
+// PERSON
+
+static inline PERSON *registry_person_find(const char *person_guid) {
+ debug(D_REGISTRY, "Registry: registry_person_find('%s')", person_guid);
+ return dictionary_get(registry.persons, person_guid);
+}
+
+static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) {
+ // protection from too big names
+ if(namelen > registry.max_name_length)
+ namelen = registry.max_name_length;
+
+ debug(D_REGISTRY, "registry_person_url_allocate('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url,
+ sizeof(PERSON_URL) + namelen);
+
+ PERSON_URL *pu = malloc(sizeof(PERSON_URL) + namelen);
+ if(!pu) fatal("registry_person_url_allocate('%s', '%s', '%s'): cannot allocate %zu bytes.", p->guid, m->guid, u->url, sizeof(PERSON_URL) + namelen);
+
+ // a simple strcpy() should do the job
+ // but I prefer to be safe, since the caller specified urllen
+ strncpy(pu->name, name, namelen);
+ pu->name[namelen] = '\0';
+
+ pu->machine = m;
+ pu->first_t = pu->last_t = when;
+ pu->usages = 1;
+ pu->url = u;
+ pu->flags = REGISTRY_URL_FLAGS_DEFAULT;
+ m->links++;
+
+ registry.persons_urls_memory += sizeof(PERSON_URL) + namelen;
+
+ debug(D_REGISTRY, "registry_person_url_allocate('%s', '%s', '%s'): indexing URL in person", p->guid, m->guid, u->url);
+ dictionary_set(p->urls, u->url, pu, sizeof(PERSON_URL));
+ registry_url_link_nolock(u);
+
+ return pu;
+}
+
+static inline PERSON_URL *registry_person_url_reallocate(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when, PERSON_URL *pu) {
+ // this function is needed to change the name of a PERSON_URL
+
+ debug(D_REGISTRY, "registry_person_url_reallocate('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url,
+ sizeof(PERSON_URL) + namelen);
+
+ PERSON_URL *tpu = registry_person_url_allocate(p, m, u, name, namelen, when);
+ tpu->first_t = pu->first_t;
+ tpu->last_t = pu->last_t;
+ tpu->usages = pu->usages;
+
+ // ok, these are a hack - since the registry_person_url_allocate() is
+ // adding these, we have to subtract them
+ tpu->machine->links--;
+ registry.persons_urls_memory -= sizeof(PERSON_URL) + strlen(pu->name);
+ registry_url_unlink_nolock(u);
+
+ free(pu);
+
+ return tpu;
+}
+
+static inline PERSON *registry_person_allocate(const char *person_guid, time_t when) {
+ PERSON *p = NULL;
+
+ debug(D_REGISTRY, "Registry: registry_person_allocate('%s'): allocating new person, sizeof(PERSON)=%zu", (person_guid)?person_guid:"", sizeof(PERSON));
+
+ p = malloc(sizeof(PERSON));
+ if(!p) fatal("Registry: cannot allocate memory for new person.");
+
+ if(!person_guid) {
+ for (; ;) {
+ uuid_t uuid;
+ uuid_generate(uuid);
+ uuid_unparse_lower(uuid, p->guid);
+
+ debug(D_REGISTRY, "Registry: Checking if the generated person guid '%s' is unique", p->guid);
+ if (!dictionary_get(registry.persons, p->guid)) {
+ debug(D_REGISTRY, "Registry: generated person guid '%s' is unique", p->guid);
+ break;
+ }
+ else
+ info("Registry: generated person guid '%s' found in the registry. Retrying...", p->guid);
+ }
+ }
+ else {
+ strncpy(p->guid, person_guid, 36);
+ p->guid[36] = '\0';
+ }
+
+ debug(D_REGISTRY, "Registry: registry_person_allocate('%s'): creating dictionary of urls", p->guid);
+ p->urls = dictionary_create(DICTIONARY_FLAGS);
+
+ p->first_t = p->last_t = when;
+ p->usages = 0;
+
+ registry.persons_memory += sizeof(PERSON);
+
+ registry.persons_count++;
+ dictionary_set(registry.persons, p->guid, p, sizeof(PERSON));
+
+ return p;
+}
+
+
+// 1. validate person GUID
+// 2. if it is valid, find it
+// 3. if it is not valid, create a new one
+// 4. return it
+static inline PERSON *registry_person_get(const char *person_guid, time_t when) {
+ PERSON *p = NULL;
+
+ registry_persons_lock();
+
+ if(person_guid && *person_guid) {
+ char buf[36 + 1];
+ // validate it is a GUID
+ if(unlikely(registry_regenerate_guid(person_guid, buf) == -1))
+ info("Registry: person guid '%s' is not a valid guid. Ignoring it.", person_guid);
+ else {
+ person_guid = buf;
+ p = registry_person_find(person_guid);
+ if(!p) person_guid = NULL;
+ }
+ }
+
+ if(!p) p = registry_person_allocate(NULL, when);
+
+ registry_persons_unlock();
+
+ return p;
+}
+
+// ----------------------------------------------------------------------------
+// LINKING OF OBJECTS
+
+static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) {
+ debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): searching for URL in person", p->guid, m->guid, u->url);
+
+ registry_person_urls_lock(p);
+
+ PERSON_URL *pu = dictionary_get(p->urls, u->url);
+ if(!pu) {
+ debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
+ pu = registry_person_url_allocate(p, m, u, name, namelen, when);
+ registry.persons_urls_count++;
+ }
+ else {
+ debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): found", p->guid, m->guid, u->url);
+ pu->usages++;
+
+ if(pu->machine != m) {
+ MACHINE_URL *mu = dictionary_get(pu->machine->urls, u->url);
+ if(mu) {
+ info("registry_person_link_to_url('%s', '%s', '%s'): URL switched machines (old was '%s') - expiring it from previous machine.",
+ p->guid, m->guid, u->url, pu->machine->guid);
+ mu->flags |= REGISTRY_URL_FLAGS_EXPIRED;
+ }
+ else {
+ info("registry_person_link_to_url('%s', '%s', '%s'): URL switched machines (old was '%s') - but the URL is not linked to the old machine.",
+ p->guid, m->guid, u->url, pu->machine->guid);
+ }
+
+ pu->machine->links--;
+ pu->machine = m;
+ }
+
+ if(strcmp(pu->name, name)) {
+ // the name of the PERSON_URL has changed !
+ pu = registry_person_url_reallocate(p, m, u, name, namelen, when, pu);
+ }
+ }
+
+ p->usages++;
+ p->last_t = when;
+
+ if(pu->flags & REGISTRY_URL_FLAGS_EXPIRED) {
+ info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL. Re-enabling URL.", p->guid, m->guid, u->url);
+ pu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED;
+ }
+
+ registry_person_urls_unlock(p);
+
+ return pu;
+}
+
+static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) {
+ debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): searching for URL in machine", p->guid, m->guid, u->url);
+
+ registry_machine_urls_lock(m);
+
+ MACHINE_URL *mu = dictionary_get(m->urls, u->url);
+ if(!mu) {
+ debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url);
+ mu = registry_machine_url_allocate(m, u, when);
+ registry.machines_urls_count++;
+ }
+ else {
+ debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): found", p->guid, m->guid, u->url);
+ mu->usages++;
+ }
+
+ //debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): indexing person in machine", p->guid, m->guid, u->url);
+ //dictionary_set(mu->persons, p->guid, p, sizeof(PERSON));
+
+ m->usages++;
+ m->last_t = when;
+
+ if(mu->flags & REGISTRY_URL_FLAGS_EXPIRED) {
+ info("registry_machine_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url);
+ mu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED;
+ }
+
+ registry_machine_urls_unlock(m);
+
+ return mu;
+}
+
+// ----------------------------------------------------------------------------
+// REGISTRY LOG LOAD/SAVE
+
+static inline int registry_should_save_db(void) {
+ debug(D_REGISTRY, "log entries %llu, max %llu", registry.log_count, registry.save_registry_every_entries);
+ return registry.log_count > registry.save_registry_every_entries;
+}
+
+static inline void registry_log(const char action, PERSON *p, MACHINE *m, URL *u, char *name) {
+ if(likely(registry.log_fp)) {
+ // we lock only if the file is open
+ // to allow replaying the log at registry_log_load()
+ registry_log_lock();
+
+ if(unlikely(fprintf(registry.log_fp, "%c\t%08x\t%s\t%s\t%s\t%s\n",
+ action,
+ p->last_t,
+ p->guid,
+ m->guid,
+ name,
+ u->url) < 0))
+ error("Registry: failed to save log. Registry data may be lost in case of abnormal restart.");
+
+ // we increase the counter even on failures
+ // so that the registry will be saved periodically
+ registry.log_count++;
+
+ registry_log_unlock();
+
+ // this must be outside the log_lock(), or a deadlock will happen.
+ // registry_save() checks the same inside the log_lock, so only
+ // one thread will save the db
+ if(unlikely(registry_should_save_db()))
+ registry_save();
+ }
+}
+
+static inline int registry_log_open_nolock(void) {
+ if(registry.log_fp)
+ fclose(registry.log_fp);
+
+ registry.log_fp = fopen(registry.log_filename, "a");
+
+ if(registry.log_fp) {
+ if (setvbuf(registry.log_fp, NULL, _IOLBF, 0) != 0)
+ error("Cannot set line buffering on registry log file.");
+ return 0;
+ }
+
+ error("Cannot open registry log file '%s'. Registry data will be lost in case of netdata or server crash.", registry.log_filename);
+ return -1;
+}
+
+static inline void registry_log_close_nolock(void) {
+ if(registry.log_fp) {
+ fclose(registry.log_fp);
+ registry.log_fp = NULL;
+ }
+}
+
+static inline void registry_log_recreate_nolock(void) {
+ if(registry.log_fp != NULL) {
+ registry_log_close_nolock();
+
+ // open it with truncate
+ registry.log_fp = fopen(registry.log_filename, "w");
+ if(registry.log_fp) fclose(registry.log_fp);
+ else error("Cannot truncate registry log '%s'", registry.log_filename);
+
+ registry.log_fp = NULL;
+
+ registry_log_open_nolock();
+ }
+}
+
+int registry_log_load(void) {
+ char *s, buf[4096 + 1];
+ size_t line = -1;
+
+ // closing the log is required here
+ // otherwise we will append to it the values we read
+ registry_log_close_nolock();
+
+ debug(D_REGISTRY, "Registry: loading active db from: %s", registry.log_filename);
+ FILE *fp = fopen(registry.log_filename, "r");
+ if(!fp)
+ error("Registry: cannot open registry file: %s", registry.log_filename);
+ else {
+ line = 0;
+ size_t len = 0;
+ while ((s = fgets_trim_len(buf, 4096, fp, &len))) {
+ line++;
+
+ switch (s[0]) {
+ case 'A': // accesses
+ case 'D': // deletes
+
+ // verify it is valid
+ if (unlikely(len < 85 || s[1] != '\t' || s[10] != '\t' || s[47] != '\t' || s[84] != '\t')) {
+ error("Registry: log line %u is wrong (len = %zu).", line, len);
+ continue;
+ }
+ s[1] = s[10] = s[47] = s[84] = '\0';
+
+ // get the variables
+ time_t when = strtoul(&s[2], NULL, 16);
+ char *person_guid = &s[11];
+ char *machine_guid = &s[48];
+ char *name = &s[85];
+
+ // skip the name to find the url
+ char *url = name;
+ while(*url && *url != '\t') url++;
+ if(!*url) {
+ error("Registry: log line %u does not have a url.", line);
+ continue;
+ }
+ *url++ = '\0';
+
+ // make sure the person exists
+ // without this, a new person guid will be created
+ PERSON *p = registry_person_find(person_guid);
+ if(!p) p = registry_person_allocate(person_guid, when);
+
+ if(s[0] == 'A')
+ registry_request_access(p->guid, machine_guid, url, name, when);
+ else
+ registry_request_delete(p->guid, machine_guid, url, name, when);
+
+ break;
+
+ default:
+ error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s);
+ break;
+ }
+ }
+ }
+
+ // open the log again
+ registry_log_open_nolock();
+
+ return line;
+}
+
+
+// ----------------------------------------------------------------------------
+// REGISTRY REQUESTS
+
+PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
+ debug(D_REGISTRY, "registry_request_access('%s', '%s', '%s'): NEW REQUEST", (person_guid)?person_guid:"", machine_guid, url);
+
+ MACHINE *m = registry_machine_get(machine_guid, when);
+ if(!m) return NULL;
+
+ // make sure the name is valid
+ size_t namelen;
+ name = registry_fix_machine_name(name, &namelen);
+
+ size_t urllen;
+ url = registry_fix_url(url, &urllen);
+
+ URL *u = registry_url_get(url, urllen);
+ PERSON *p = registry_person_get(person_guid, when);
+
+ registry_person_link_to_url(p, m, u, name, namelen, when);
+ registry_machine_link_to_url(p, m, u, when);
+
+ registry_log('A', p, m, u, name);
+
+ registry.usages_count++;
+ return p;
+}
+
+// verify the person, the machine and the URL exist in our DB
+PERSON_URL *registry_verify_request(char *person_guid, char *machine_guid, char *url, PERSON **pp, MACHINE **mm) {
+ char pbuf[36 + 1], mbuf[36 + 1];
+
+ if(!person_guid || !*person_guid || !machine_guid || !*machine_guid || !url || !*url) {
+ info("Registry Request Verification: invalid request! person: '%s', machine '%s', url '%s'", person_guid?person_guid:"UNSET", machine_guid?machine_guid:"UNSET", url?url:"UNSET");
+ return NULL;
+ }
+
+ // normalize the url
+ url = registry_fix_url(url, NULL);
+
+ // make sure the person GUID is valid
+ if(registry_regenerate_guid(person_guid, pbuf) == -1) {
+ info("Registry Request Verification: invalid person GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+ return NULL;
+ }
+ person_guid = pbuf;
+
+ // make sure the machine GUID is valid
+ if(registry_regenerate_guid(machine_guid, mbuf) == -1) {
+ info("Registry Request Verification: invalid machine GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+ return NULL;
+ }
+ machine_guid = mbuf;
+
+ // make sure the machine exists
+ MACHINE *m = registry_machine_find(machine_guid);
+ if(!m) {
+ info("Registry Request Verification: machine not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+ return NULL;
+ }
+ if(mm) *mm = m;
+
+ // make sure the person exist
+ PERSON *p = registry_person_find(person_guid);
+ if(!p) {
+ info("Registry Request Verification: person not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+ return NULL;
+ }
+ if(pp) *pp = p;
+
+ PERSON_URL *pu = dictionary_get(p->urls, url);
+ if(!pu) {
+ info("Registry Request Verification: URL not found for person, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url);
+ return NULL;
+ }
+ return pu;
+}
+
+PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
+ (void)when;
+
+ PERSON *p = NULL;
+ MACHINE *m = NULL;
+ PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m);
+ if(!pu || !p || !m) return NULL;
+
+ // normalize the url
+ delete_url = registry_fix_url(delete_url, NULL);
+
+ // make sure the user is not deleting the url it uses
+ if(!strcmp(delete_url, pu->url->url)) {
+ info("Registry Delete Request: delete URL is the one currently accessed, person: '%s', machine '%s', url '%s', delete url '%s'", p->guid, m->guid, pu->url->url, delete_url);
+ return NULL;
+ }
+
+ registry_person_urls_lock(p);
+
+ PERSON_URL *dpu = dictionary_get(p->urls, delete_url);
+ if(!dpu) {
+ info("Registry Delete Request: URL not found for person: '%s', machine '%s', url '%s', delete url '%s'", p->guid, m->guid, pu->url->url, delete_url);
+ registry_person_urls_unlock(p);
+ return NULL;
+ }
+
+ registry_log('D', p, m, pu->url, dpu->url->url);
+
+ dictionary_del(p->urls, dpu->url->url);
+ registry_url_unlink_nolock(dpu->url);
+ free(dpu);
+
+ registry_person_urls_unlock(p);
+ return p;
+}
+
+
+// a structure to pass to the dictionary_get_all() callback handler
+struct machine_request_callback_data {
+ MACHINE *find_this_machine;
+ PERSON_URL *result;
+};
+
+// the callback function
+// this will be run for every PERSON_URL of this PERSON
+int machine_request_callback(void *entry, void *data) {
+ PERSON_URL *mypu = (PERSON_URL *)entry;
+ struct machine_request_callback_data *myrdata = (struct machine_request_callback_data *)data;
+
+ if(mypu->machine == myrdata->find_this_machine) {
+ myrdata->result = mypu;
+ return -1; // this will also stop the walk through
+ }
+
+ return 0; // continue
+}
+
+MACHINE *registry_request_machine(char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
+ (void)when;
+
+ char mbuf[36 + 1];
+
+ PERSON *p = NULL;
+ MACHINE *m = NULL;
+ PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m);
+ if(!pu || !p || !m) return NULL;
+
+ // make sure the machine GUID is valid
+ if(registry_regenerate_guid(request_machine, mbuf) == -1) {
+ info("Registry Machine URLs request: invalid machine GUID, person: '%s', machine '%s', url '%s', request machine '%s'", p->guid, m->guid, pu->url->url, request_machine);
+ return NULL;
+ }
+ request_machine = mbuf;
+
+ // make sure the machine exists
+ m = registry_machine_find(request_machine);
+ if(!m) {
+ info("Registry Machine URLs request: machine not found, person: '%s', machine '%s', url '%s', request machine '%s'", p->guid, m->guid, pu->url->url, request_machine);
+ return NULL;
+ }
+
+ // Verify the user has in the past accessed this machine
+ // We will walk through the PERSON_URLs to find the machine
+ // linking to our machine
+
+ // a structure to pass to the dictionary_get_all() callback handler
+ struct machine_request_callback_data rdata = { m, NULL };
+
+ // request a walk through on the dictionary
+ // no need for locking here, the underlying dictionary has its own
+ dictionary_get_all(p->urls, machine_request_callback, &rdata);
+
+ if(rdata.result)
+ return m;
+
+ return NULL;
+}
+
+
+// ----------------------------------------------------------------------------
+// REGISTRY JSON generation
+
+#define REGISTRY_STATUS_OK "ok"
+#define REGISTRY_STATUS_FAILED "failed"
+#define REGISTRY_STATUS_DISABLED "disabled"
+
+static inline void registry_set_person_cookie(struct web_client *w, PERSON *p) {
+ char edate[100];
+ time_t et = time(NULL) + registry.persons_expiration;
+ struct tm etmbuf, *etm = gmtime_r(&et, &etmbuf);
+ strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", etm);
+
+ if(registry.registry_domain && registry.registry_domain[0])
+ snprintf(w->cookie, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Domain=%s; Expires=%s", p->guid, registry.registry_domain, edate);
+ else
+ snprintf(w->cookie, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s", p->guid, edate);
+
+ w->cookie[COOKIE_MAX] = '\0';
+}
+
+static inline void registry_json_header(struct web_client *w, const char *action, const char *status) {
+ w->response.data->contenttype = CT_APPLICATION_JSON;
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "{\n\t\"action\": \"%s\",\n\t\"status\": \"%s\",\n\t\"hostname\": \"%s\",\n\t\"machine_guid\": \"%s\"",
+ action, status, registry.hostname, registry.machine_guid);
+}
+
+static inline void registry_json_footer(struct web_client *w) {
+ buffer_strcat(w->response.data, "\n}\n");
+}
+
+int registry_request_hello_json(struct web_client *w) {
+ registry_json_header(w, "hello", REGISTRY_STATUS_OK);
+
+ buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"",
+ registry.registry_to_announce);
+
+ registry_json_footer(w);
+ return 200;
+}
+
+static inline int registry_json_disabled(struct web_client *w, const char *action) {
+ registry_json_header(w, action, REGISTRY_STATUS_DISABLED);
+
+ buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"",
+ registry.registry_to_announce);
+
+ registry_json_footer(w);
+ return 200;
+}
+
+// structure used be the callbacks below
+struct registry_json_walk_person_urls_callback {
+ PERSON *p;
+ MACHINE *m;
+ struct web_client *w;
+ int count;
+};
+
+// callback for rendering PERSON_URLs
+static inline int registry_json_person_url_callback(void *entry, void *data) {
+ PERSON_URL *pu = (PERSON_URL *)entry;
+ struct registry_json_walk_person_urls_callback *c = (struct registry_json_walk_person_urls_callback *)data;
+ struct web_client *w = c->w;
+
+ if(unlikely(c->count++))
+ buffer_strcat(w->response.data, ",");
+
+ buffer_sprintf(w->response.data, "\n\t\t[ \"%s\", \"%s\", %u000, %u, \"%s\" ]",
+ pu->machine->guid, pu->url->url, pu->last_t, pu->usages, pu->name);
+
+ return 1;
+}
+
+// callback for rendering MACHINE_URLs
+static inline int registry_json_machine_url_callback(void *entry, void *data) {
+ MACHINE_URL *mu = (MACHINE_URL *)entry;
+ struct registry_json_walk_person_urls_callback *c = (struct registry_json_walk_person_urls_callback *)data;
+ struct web_client *w = c->w;
+ MACHINE *m = c->m;
+
+ if(unlikely(c->count++))
+ buffer_strcat(w->response.data, ",");
+
+ buffer_sprintf(w->response.data, "\n\t\t[ \"%s\", \"%s\", %u000, %u ]",
+ m->guid, mu->url->url, mu->last_t, mu->usages);
+
+ return 1;
+}
+
+
+// the main method for registering an access
+int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
+ if(!registry.enabled)
+ return registry_json_disabled(w, "access");
+
+ PERSON *p = registry_request_access(person_guid, machine_guid, url, name, when);
+ if(!p) {
+ registry_json_header(w, "access", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 412;
+ }
+
+ // set the cookie
+ registry_set_person_cookie(w, p);
+
+ // generate the response
+ registry_json_header(w, "access", REGISTRY_STATUS_OK);
+
+ buffer_sprintf(w->response.data, ",\n\t\"person_guid\": \"%s\",\n\t\"urls\": [", p->guid);
+ struct registry_json_walk_person_urls_callback c = { p, NULL, w, 0 };
+ dictionary_get_all(p->urls, registry_json_person_url_callback, &c);
+ buffer_strcat(w->response.data, "\n\t]\n");
+
+ registry_json_footer(w);
+ return 200;
+}
+
+// the main method for deleting a URL from a person
+int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
+ if(!registry.enabled)
+ return registry_json_disabled(w, "delete");
+
+ PERSON *p = registry_request_delete(person_guid, machine_guid, url, delete_url, when);
+ if(!p) {
+ registry_json_header(w, "delete", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 412;
+ }
+
+ // generate the response
+ registry_json_header(w, "delete", REGISTRY_STATUS_OK);
+ registry_json_footer(w);
+ return 200;
+}
+
+// the main method for searching the URLs of a netdata
+int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
+ if(!registry.enabled)
+ return registry_json_disabled(w, "search");
+
+ MACHINE *m = registry_request_machine(person_guid, machine_guid, url, request_machine, when);
+ if(!m) {
+ registry_json_header(w, "search", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 404;
+ }
+
+ registry_json_header(w, "search", REGISTRY_STATUS_OK);
+
+ buffer_strcat(w->response.data, ",\n\t\"urls\": [");
+ struct registry_json_walk_person_urls_callback c = { NULL, m, w, 0 };
+ dictionary_get_all(m->urls, registry_json_machine_url_callback, &c);
+ buffer_strcat(w->response.data, "\n\t]\n");
+
+ registry_json_footer(w);
+ return 200;
+}
+
+
+int registry_person_url_callback_verify_machine_exists(void *entry, void *machine) {
+ PERSON_URL *pu = (PERSON_URL *)entry;
+ MACHINE *m = (MACHINE *)machine;
+
+ if(pu->machine == m)
+ return 1;
+ else
+ return 0;
+}
+
+// the main method for switching user identity
+int registry_request_switch_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *new_person_guid, time_t when) {
+ (void)url;
+ (void)when;
+
+ if(!registry.enabled)
+ return registry_json_disabled(w, "switch");
+
+ PERSON *op = registry_person_find(person_guid);
+ if(!op) {
+ registry_json_header(w, "switch", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 430;
+ }
+
+ PERSON *np = registry_person_find(new_person_guid);
+ if(!np) {
+ registry_json_header(w, "switch", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 431;
+ }
+
+ MACHINE *m = registry_machine_find(machine_guid);
+ if(!m) {
+ registry_json_header(w, "switch", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 432;
+ }
+
+ // verify the old person has access to this machine
+ int count = dictionary_get_all(op->urls, registry_person_url_callback_verify_machine_exists, m);
+ if(!count) {
+ registry_json_header(w, "switch", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 433;
+ }
+
+ // verify the new person has access to this machine
+ count = dictionary_get_all(np->urls, registry_person_url_callback_verify_machine_exists, m);
+ if(!count) {
+ registry_json_header(w, "switch", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 434;
+ }
+
+ // set the cookie of the new person
+ // the user just switched identity
+ registry_set_person_cookie(w, np);
+
+ // generate the response
+ registry_json_header(w, "switch", REGISTRY_STATUS_OK);
+ buffer_sprintf(w->response.data, ",\n\t\"person_guid\": \"%s\"", np->guid);
+ registry_json_footer(w);
+ return 200;
+}
+
+
+// ----------------------------------------------------------------------------
+// REGISTRY THIS MACHINE UNIQUE ID
+
+char *registry_get_this_machine_guid(void) {
+ if(likely(registry.machine_guid[0]))
+ return registry.machine_guid;
+
+ // read it from disk
+ int fd = open(registry.machine_guid_filename, O_RDONLY);
+ if(fd != -1) {
+ char buf[36 + 1];
+ if(read(fd, buf, 36) != 36)
+ error("Failed to read machine GUID from '%s'", registry.machine_guid_filename);
+ else {
+ buf[36] = '\0';
+ if(registry_regenerate_guid(buf, registry.machine_guid) == -1) {
+ error("Failed to validate machine GUID '%s' from '%s'. Ignoring it - this might mean this netdata will appear as duplicate in the registry.",
+ buf, registry.machine_guid_filename);
+
+ registry.machine_guid[0] = '\0';
+ }
+ }
+ close(fd);
+ }
+
+ // generate a new one?
+ if(!registry.machine_guid[0]) {
+ int count = 10, ret = 0;
+ uuid_t uuid;
+
+ // for some reason it reports unsafe generation the first time
+ while(count-- && (ret = uuid_generate_time_safe(uuid)) == -1) ;
+ uuid_unparse_lower(uuid, registry.machine_guid);
+ registry.machine_guid[36] = '\0';
+
+ if(ret == -1)
+ info("Warning: generated machine GUID '%s' was not generated using a safe method.", registry.machine_guid);
+
+ // save it
+ fd = open(registry.machine_guid_filename, O_WRONLY|O_CREAT|O_TRUNC, 444);
+ if(fd == -1)
+ fatal("Cannot create unique machine id file '%s'. Please fix this.", registry.machine_guid_filename);
+
+ if(write(fd, registry.machine_guid, 36) != 36)
+ fatal("Cannot write the unique machine id file '%s'. Please fix this.", registry.machine_guid_filename);
+
+ close(fd);
+ }
+
+ return registry.machine_guid;
+}
+
+
+// ----------------------------------------------------------------------------
+// REGISTRY LOAD/SAVE
+
+int registry_machine_save_url(void *entry, void *file) {
+ MACHINE_URL *mu = entry;
+ FILE *fp = file;
+
+ debug(D_REGISTRY, "Registry: registry_machine_save_url('%s')", mu->url->url);
+
+ int ret = fprintf(fp, "V\t%08x\t%08x\t%08x\t%02x\t%s\n",
+ mu->first_t,
+ mu->last_t,
+ mu->usages,
+ mu->flags,
+ mu->url->url
+ );
+
+ // error handling is done at registry_save()
+
+ return ret;
+}
+
+int registry_machine_save(void *entry, void *file) {
+ MACHINE *m = entry;
+ FILE *fp = file;
+
+ debug(D_REGISTRY, "Registry: registry_machine_save('%s')", m->guid);
+
+ int ret = fprintf(fp, "M\t%08x\t%08x\t%08x\t%s\n",
+ m->first_t,
+ m->last_t,
+ m->usages,
+ m->guid
+ );
+
+ if(ret >= 0) {
+ int ret2 = dictionary_get_all(m->urls, registry_machine_save_url, fp);
+ if(ret2 < 0) return ret2;
+ ret += ret2;
+ }
+
+ // error handling is done at registry_save()
+
+ return ret;
+}
+
+static inline int registry_person_save_url(void *entry, void *file) {
+ PERSON_URL *pu = entry;
+ FILE *fp = file;
+
+ debug(D_REGISTRY, "Registry: registry_person_save_url('%s')", pu->url->url);
+
+ int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\t%s\n",
+ pu->first_t,
+ pu->last_t,
+ pu->usages,
+ pu->flags,
+ pu->machine->guid,
+ pu->name,
+ pu->url->url
+ );
+
+ // error handling is done at registry_save()
+
+ return ret;
+}
+
+static inline int registry_person_save(void *entry, void *file) {
+ PERSON *p = entry;
+ FILE *fp = file;
+
+ debug(D_REGISTRY, "Registry: registry_person_save('%s')", p->guid);
+
+ int ret = fprintf(fp, "P\t%08x\t%08x\t%08x\t%s\n",
+ p->first_t,
+ p->last_t,
+ p->usages,
+ p->guid
+ );
+
+ if(ret >= 0) {
+ int ret2 = dictionary_get_all(p->urls, registry_person_save_url, fp);
+ if (ret2 < 0) return ret2;
+ ret += ret2;
+ }
+
+ // error handling is done at registry_save()
+
+ return ret;
+}
+
+int registry_save(void) {
+ if(!registry.enabled) return -1;
+
+ // make sure the log is not updated
+ registry_log_lock();
+
+ if(unlikely(!registry_should_save_db())) {
+ registry_log_unlock();
+ return -2;
+ }
+
+ char tmp_filename[FILENAME_MAX + 1];
+ char old_filename[FILENAME_MAX + 1];
+
+ snprintf(old_filename, FILENAME_MAX, "%s.old", registry.db_filename);
+ snprintf(tmp_filename, FILENAME_MAX, "%s.tmp", registry.db_filename);
+
+ debug(D_REGISTRY, "Registry: Creating file '%s'", tmp_filename);
+ FILE *fp = fopen(tmp_filename, "w");
+ if(!fp) {
+ error("Registry: Cannot create file: %s", tmp_filename);
+ registry_log_unlock();
+ return -1;
+ }
+
+ // dictionary_get_all() has its own locking, so this is safe to do
+
+ debug(D_REGISTRY, "Saving all machines");
+ int bytes1 = dictionary_get_all(registry.machines, registry_machine_save, fp);
+ if(bytes1 < 0) {
+ error("Registry: Cannot save registry machines - return value %d", bytes1);
+ fclose(fp);
+ registry_log_unlock();
+ return bytes1;
+ }
+ debug(D_REGISTRY, "Registry: saving machines took %d bytes", bytes1);
+
+ debug(D_REGISTRY, "Saving all persons");
+ int bytes2 = dictionary_get_all(registry.persons, registry_person_save, fp);
+ if(bytes2 < 0) {
+ error("Registry: Cannot save registry persons - return value %d", bytes2);
+ fclose(fp);
+ registry_log_unlock();
+ return bytes2;
+ }
+ debug(D_REGISTRY, "Registry: saving persons took %d bytes", bytes2);
+
+ // save the totals
+ fprintf(fp, "T\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\n",
+ registry.persons_count,
+ registry.machines_count,
+ registry.usages_count + 1, // this is required - it is lost on db rotation
+ registry.urls_count,
+ registry.persons_urls_count,
+ registry.machines_urls_count
+ );
+
+ fclose(fp);
+
+ errno = 0;
+
+ // remove the .old db
+ debug(D_REGISTRY, "Registry: Removing old db '%s'", old_filename);
+ if(unlink(old_filename) == -1 && errno != ENOENT)
+ error("Registry: cannot remove old registry file '%s'", old_filename);
+
+ // rename the db to .old
+ debug(D_REGISTRY, "Registry: Link current db '%s' to .old: '%s'", registry.db_filename, old_filename);
+ if(link(registry.db_filename, old_filename) == -1 && errno != ENOENT)
+ error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename, registry.db_filename);
+
+ else {
+ // remove the database (it is saved in .old)
+ debug(D_REGISTRY, "Registry: removing db '%s'", registry.db_filename);
+ if (unlink(registry.db_filename) == -1 && errno != ENOENT)
+ error("Registry: cannot remove old registry file '%s'", registry.db_filename);
+
+ // move the .tmp to make it active
+ debug(D_REGISTRY, "Registry: linking tmp db '%s' to active db '%s'", tmp_filename, registry.db_filename);
+ if (link(tmp_filename, registry.db_filename) == -1) {
+ error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", tmp_filename,
+ registry.db_filename);
+
+ // move the .old back
+ debug(D_REGISTRY, "Registry: linking old db '%s' to active db '%s'", old_filename, registry.db_filename);
+ if(link(old_filename, registry.db_filename) == -1)
+ error("Registry: cannot move file '%s' to '%s'. Recovering the old registry DB failed!", old_filename, registry.db_filename);
+ }
+ else {
+ debug(D_REGISTRY, "Registry: removing tmp db '%s'", tmp_filename);
+ if(unlink(tmp_filename) == -1)
+ error("Registry: cannot remove tmp registry file '%s'", tmp_filename);
+
+ // it has been moved successfully
+ // discard the current registry log
+ registry_log_recreate_nolock();
+
+ registry.log_count = 0;
+ }
+ }
+
+ // continue operations
+ registry_log_unlock();
+
+ return -1;
+}
+
+static inline size_t registry_load(void) {
+ char *s, buf[4096 + 1];
+ PERSON *p = NULL;
+ MACHINE *m = NULL;
+ URL *u = NULL;
+ size_t line = 0;
+
+ debug(D_REGISTRY, "Registry: loading active db from: '%s'", registry.db_filename);
+ FILE *fp = fopen(registry.db_filename, "r");
+ if(!fp) {
+ error("Registry: cannot open registry file: '%s'", registry.db_filename);
+ return 0;
+ }
+
+ size_t len = 0;
+ buf[4096] = '\0';
+ while((s = fgets_trim_len(buf, 4096, fp, &len))) {
+ line++;
+
+ debug(D_REGISTRY, "Registry: read line %zu to length %zu: %s", line, len, s);
+ switch(*s) {
+ case 'T': // totals
+ if(unlikely(len != 103 || s[1] != '\t' || s[18] != '\t' || s[35] != '\t' || s[52] != '\t' || s[69] != '\t' || s[86] != '\t' || s[103] != '\0')) {
+ error("Registry totals line %u is wrong (len = %zu).", line, len);
+ continue;
+ }
+ registry.persons_count = strtoull(&s[2], NULL, 16);
+ registry.machines_count = strtoull(&s[19], NULL, 16);
+ registry.usages_count = strtoull(&s[36], NULL, 16);
+ registry.urls_count = strtoull(&s[53], NULL, 16);
+ registry.persons_urls_count = strtoull(&s[70], NULL, 16);
+ registry.machines_urls_count = strtoull(&s[87], NULL, 16);
+ break;
+
+ case 'P': // person
+ m = NULL;
+ // verify it is valid
+ if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
+ error("Registry person line %u is wrong (len = %zu).", line, len);
+ continue;
+ }
+
+ s[1] = s[10] = s[19] = s[28] = '\0';
+ p = registry_person_allocate(&s[29], strtoul(&s[2], NULL, 16));
+ p->last_t = strtoul(&s[11], NULL, 16);
+ p->usages = strtoul(&s[20], NULL, 16);
+ debug(D_REGISTRY, "Registry loaded person '%s', first: %u, last: %u, usages: %u", p->guid, p->first_t, p->last_t, p->usages);
+ break;
+
+ case 'M': // machine
+ p = NULL;
+ // verify it is valid
+ if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) {
+ error("Registry person line %u is wrong (len = %zu).", line, len);
+ continue;
+ }
+
+ s[1] = s[10] = s[19] = s[28] = '\0';
+ m = registry_machine_allocate(&s[29], strtoul(&s[2], NULL, 16));
+ m->last_t = strtoul(&s[11], NULL, 16);
+ m->usages = strtoul(&s[20], NULL, 16);
+ debug(D_REGISTRY, "Registry loaded machine '%s', first: %u, last: %u, usages: %u", m->guid, m->first_t, m->last_t, m->usages);
+ break;
+
+ case 'U': // person URL
+ if(unlikely(!p)) {
+ error("Registry: ignoring line %zu, no person loaded: %s", line, s);
+ continue;
+ }
+
+ // verify it is valid
+ if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t') {
+ error("Registry person URL line %u is wrong (len = %zu).", line, len);
+ continue;
+ }
+
+ s[1] = s[10] = s[19] = s[28] = s[31] = s[68] = '\0';
+
+ // skip the name to find the url
+ char *url = &s[69];
+ while(*url && *url != '\t') url++;
+ if(!*url) {
+ error("Registry person URL line %u does not have a url.", line);
+ continue;
+ }
+ *url++ = '\0';
+
+ u = registry_url_allocate_nolock(url, strlen(url));
+
+ time_t first_t = strtoul(&s[2], NULL, 16);
+
+ m = registry_machine_find(&s[32]);
+ if(!m) m = registry_machine_allocate(&s[32], first_t);
+
+ PERSON_URL *pu = registry_person_url_allocate(p, m, u, &s[69], strlen(&s[69]), first_t);
+ pu->last_t = strtoul(&s[11], NULL, 16);
+ pu->usages = strtoul(&s[20], NULL, 16);
+ pu->flags = strtoul(&s[29], NULL, 16);
+ debug(D_REGISTRY, "Registry loaded person URL '%s' with name '%s' of machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, pu->name, m->guid, pu->first_t, pu->last_t, pu->usages, pu->flags);
+ break;
+
+ case 'V': // machine URL
+ if(unlikely(!m)) {
+ error("Registry: ignoring line %zu, no machine loaded: %s", line, s);
+ continue;
+ }
+
+ // verify it is valid
+ if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t') {
+ error("Registry person URL line %u is wrong (len = %zu).", line, len);
+ continue;
+ }
+
+ s[1] = s[10] = s[19] = s[28] = s[31] = '\0';
+ u = registry_url_allocate_nolock(&s[32], strlen(&s[32]));
+
+ MACHINE_URL *mu = registry_machine_url_allocate(m, u, strtoul(&s[2], NULL, 16));
+ mu->last_t = strtoul(&s[11], NULL, 16);
+ mu->usages = strtoul(&s[20], NULL, 16);
+ mu->flags = strtoul(&s[29], NULL, 16);
+ debug(D_REGISTRY, "Registry loaded machine URL '%s', machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, m->guid, mu->first_t, mu->last_t, mu->usages, mu->flags);
+ break;
+
+ default:
+ error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.db_filename, s);
+ break;
+ }
+ }
+ fclose(fp);
+
+ return line;
+}
+
+// ----------------------------------------------------------------------------
+// REGISTRY
+
+int registry_init(void) {
+ char filename[FILENAME_MAX + 1];
+
+ // registry enabled?
+ registry.enabled = config_get_boolean("registry", "enabled", 0);
+
+ // pathnames
+ registry.pathname = config_get("registry", "registry db directory", VARLIB_DIR "/registry");
+ if(mkdir(registry.pathname, 0755) == -1 && errno != EEXIST) {
+ error("Cannot create directory '%s'. Registry disabled.", registry.pathname);
+ registry.enabled = 0;
+ return -1;
+ }
+
+ // filenames
+ snprintf(filename, FILENAME_MAX, "%s/netdata.public.unique.id", registry.pathname);
+ registry.machine_guid_filename = config_get("registry", "netdata unique id file", filename);
+ registry_get_this_machine_guid();
+
+ snprintf(filename, FILENAME_MAX, "%s/registry.db", registry.pathname);
+ registry.db_filename = config_get("registry", "registry db file", filename);
+
+ snprintf(filename, FILENAME_MAX, "%s/registry-log.db", registry.pathname);
+ registry.log_filename = config_get("registry", "registry log file", filename);
+
+ // configuration options
+ registry.save_registry_every_entries = config_get_number("registry", "registry save db every new entries", 1000000);
+ registry.persons_expiration = config_get_number("registry", "registry expire idle persons days", 365) * 86400;
+ registry.registry_domain = config_get("registry", "registry domain", "");
+ registry.registry_to_announce = config_get("registry", "registry to announce", "//registry.my-netdata.io");
+ registry.hostname = config_get("registry", "registry hostname", config_get("global", "hostname", hostname));
+
+ registry.max_url_length = config_get_number("registry", "max URL length", 1024);
+ registry.max_name_length = config_get_number("registry", "max URL name length", 50);
+
+
+ // initialize entries counters
+ registry.persons_count = 0;
+ registry.machines_count = 0;
+ registry.usages_count = 0;
+ registry.urls_count = 0;
+ registry.persons_urls_count = 0;
+ registry.machines_urls_count = 0;
+
+ // initialize memory counters
+ registry.persons_memory = 0;
+ registry.machines_memory = 0;
+ registry.urls_memory = 0;
+ registry.persons_urls_memory = 0;
+ registry.machines_urls_memory = 0;
+
+ // initialize locks
+ pthread_mutex_init(®istry.persons_lock, NULL);
+ pthread_mutex_init(®istry.machines_lock, NULL);
+ pthread_mutex_init(®istry.urls_lock, NULL);
+ pthread_mutex_init(®istry.person_urls_lock, NULL);
+ pthread_mutex_init(®istry.machine_urls_lock, NULL);
+
+ // create dictionaries
+ registry.persons = dictionary_create(DICTIONARY_FLAGS);
+ registry.machines = dictionary_create(DICTIONARY_FLAGS);
+ registry.urls = dictionary_create(DICTIONARY_FLAGS);
+
+ // load the registry database
+ if(registry.enabled) {
+ registry_log_open_nolock();
+ registry_load();
+ registry_log_load();
+ }
+
+ return 0;
+}
+
+void registry_free(void) {
+ if(!registry.enabled) return;
+
+ // we need to destroy the dictionaries ourselves
+ // since the dictionaries use memory we allocated
+
+ while(registry.persons->values_index.root) {
+ PERSON *p = ((NAME_VALUE *)registry.persons->values_index.root)->value;
+
+ // fprintf(stderr, "\nPERSON: '%s', first: %u, last: %u, usages: %u\n", p->guid, p->first_t, p->last_t, p->usages);
+
+ while(p->urls->values_index.root) {
+ PERSON_URL *pu = ((NAME_VALUE *)p->urls->values_index.root)->value;
+
+ // fprintf(stderr, "\tURL: '%s', first: %u, last: %u, usages: %u, flags: 0x%02x\n", pu->url->url, pu->first_t, pu->last_t, pu->usages, pu->flags);
+
+ debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", pu->url->url, p->guid);
+ dictionary_del(p->urls, pu->url->url);
+
+ debug(D_REGISTRY, "Registry: unlinking url '%s' from person", pu->url->url);
+ registry_url_unlink_nolock(pu->url);
+
+ debug(D_REGISTRY, "Registry: freeing person url");
+ free(pu);
+ }
+
+ debug(D_REGISTRY, "Registry: deleting person '%s' from persons registry", p->guid);
+ dictionary_del(registry.persons, p->guid);
+
+ debug(D_REGISTRY, "Registry: destroying URL dictionary of person '%s'", p->guid);
+ dictionary_destroy(p->urls);
+
+ debug(D_REGISTRY, "Registry: freeing person '%s'", p->guid);
+ free(p);
+ }
+
+ while(registry.machines->values_index.root) {
+ MACHINE *m = ((NAME_VALUE *)registry.machines->values_index.root)->value;
+
+ // fprintf(stderr, "\nMACHINE: '%s', first: %u, last: %u, usages: %u\n", m->guid, m->first_t, m->last_t, m->usages);
+
+ while(m->urls->values_index.root) {
+ MACHINE_URL *mu = ((NAME_VALUE *)m->urls->values_index.root)->value;
+
+ // fprintf(stderr, "\tURL: '%s', first: %u, last: %u, usages: %u, flags: 0x%02x\n", mu->url->url, mu->first_t, mu->last_t, mu->usages, mu->flags);
+
+ //debug(D_REGISTRY, "Registry: destroying persons dictionary from url '%s'", mu->url->url);
+ //dictionary_destroy(mu->persons);
+
+ debug(D_REGISTRY, "Registry: deleting url '%s' from person '%s'", mu->url->url, m->guid);
+ dictionary_del(m->urls, mu->url->url);
+
+ debug(D_REGISTRY, "Registry: unlinking url '%s' from machine", mu->url->url);
+ registry_url_unlink_nolock(mu->url);
+
+ debug(D_REGISTRY, "Registry: freeing machine url");
+ free(mu);
+ }
+
+ debug(D_REGISTRY, "Registry: deleting machine '%s' from machines registry", m->guid);
+ dictionary_del(registry.machines, m->guid);
+
+ debug(D_REGISTRY, "Registry: destroying URL dictionary of machine '%s'", m->guid);
+ dictionary_destroy(m->urls);
+
+ debug(D_REGISTRY, "Registry: freeing machine '%s'", m->guid);
+ free(m);
+ }
+
+ // and free the memory of remaining dictionary structures
+
+ debug(D_REGISTRY, "Registry: destroying persons dictionary");
+ dictionary_destroy(registry.persons);
+
+ debug(D_REGISTRY, "Registry: destroying machines dictionary");
+ dictionary_destroy(registry.machines);
+
+ debug(D_REGISTRY, "Registry: destroying urls dictionary");
+ dictionary_destroy(registry.urls);
+}
+
+// ----------------------------------------------------------------------------
+// STATISTICS
+
+void registry_statistics(void) {
+ if(!registry.enabled) return;
+
+ static RRDSET *sts = NULL, *stc = NULL, *stm = NULL;
+
+ if(!sts) sts = rrdset_find("netdata.registry_sessions");
+ if(!sts) {
+ sts = rrdset_create("netdata", "registry_sessions", NULL, "registry", NULL, "NetData Registry Sessions", "session", 131000, rrd_update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(sts, "sessions", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ }
+ else rrdset_next(sts);
+
+ rrddim_set(sts, "sessions", registry.usages_count);
+ rrdset_done(sts);
+
+ // ------------------------------------------------------------------------
+
+ if(!stc) stc = rrdset_find("netdata.registry_entries");
+ if(!stc) {
+ stc = rrdset_create("netdata", "registry_entries", NULL, "registry", NULL, "NetData Registry Entries", "entries", 131100, rrd_update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(stc, "persons", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ rrddim_add(stc, "machines", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ rrddim_add(stc, "urls", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ rrddim_add(stc, "persons_urls", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ rrddim_add(stc, "machines_urls", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ }
+ else rrdset_next(stc);
+
+ rrddim_set(stc, "persons", registry.persons_count);
+ rrddim_set(stc, "machines", registry.machines_count);
+ rrddim_set(stc, "urls", registry.urls_count);
+ rrddim_set(stc, "persons_urls", registry.persons_urls_count);
+ rrddim_set(stc, "machines_urls", registry.machines_urls_count);
+ rrdset_done(stc);
+
+ // ------------------------------------------------------------------------
+
+ if(!stm) stm = rrdset_find("netdata.registry_mem");
+ if(!stm) {
+ stm = rrdset_create("netdata", "registry_mem", NULL, "registry", NULL, "NetData Registry Memory", "KB", 131300, rrd_update_every, RRDSET_TYPE_STACKED);
+
+ rrddim_add(stm, "persons", NULL, 1, 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(stm, "machines", NULL, 1, 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(stm, "urls", NULL, 1, 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(stm, "persons_urls", NULL, 1, 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(stm, "machines_urls", NULL, 1, 1024, RRDDIM_ABSOLUTE);
+ }
+ else rrdset_next(stm);
+
+ rrddim_set(stm, "persons", registry.persons_memory + registry.persons_count * sizeof(NAME_VALUE) + sizeof(DICTIONARY));
+ rrddim_set(stm, "machines", registry.machines_memory + registry.machines_count * sizeof(NAME_VALUE) + sizeof(DICTIONARY));
+ rrddim_set(stm, "urls", registry.urls_memory + registry.urls_count * sizeof(NAME_VALUE) + sizeof(DICTIONARY));
+ rrddim_set(stm, "persons_urls", registry.persons_urls_memory + registry.persons_count * sizeof(DICTIONARY) + registry.persons_urls_count * sizeof(NAME_VALUE));
+ rrddim_set(stm, "machines_urls", registry.machines_urls_memory + registry.machines_count * sizeof(DICTIONARY) + registry.machines_urls_count * sizeof(NAME_VALUE));
+ rrdset_done(stm);
+}
+
+
+#ifdef REGISTRY_STANDALONE_TESTS
+
+// ----------------------------------------------------------------------------
+// TESTS
+
+int test1(int argc, char **argv) {
+
+ void print_stats(uint32_t requests, unsigned long long start, unsigned long long end) {
+ fprintf(stderr, " > SPEED: %u requests served in %0.2f seconds ( >>> %llu per second <<< )\n",
+ requests, (end-start) / 1000000.0, (unsigned long long)requests * 1000000ULL / (end-start));
+
+ fprintf(stderr, " > DB : persons %llu, machines %llu, unique URLs %llu, accesses %llu, URLs: for persons %llu, for machines %llu\n",
+ registry.persons_count, registry.machines_count, registry.urls_count, registry.usages_count,
+ registry.persons_urls_count, registry.machines_urls_count);
+ }
+
+ (void) argc;
+ (void) argv;
+
+ uint32_t u, users = 1000000;
+ uint32_t m, machines = 200000;
+ uint32_t machines2 = machines * 2;
+
+ char **users_guids = malloc(users * sizeof(char *));
+ char **machines_guids = malloc(machines2 * sizeof(char *));
+ char **machines_urls = malloc(machines2 * sizeof(char *));
+ unsigned long long start;
+
+ registry_init();
+
+ fprintf(stderr, "Generating %u machine guids\n", machines2);
+ for(m = 0; m < machines2 ;m++) {
+ uuid_t uuid;
+ machines_guids[m] = malloc(36+1);
+ uuid_generate(uuid);
+ uuid_unparse(uuid, machines_guids[m]);
+
+ char buf[FILENAME_MAX + 1];
+ snprintf(buf, FILENAME_MAX, "http://%u.netdata.rocks/", m+1);
+ machines_urls[m] = strdup(buf);
+
+ // fprintf(stderr, "\tmachine %u: '%s', url: '%s'\n", m + 1, machines_guids[m], machines_urls[m]);
+ }
+
+ start = timems();
+ fprintf(stderr, "\nGenerating %u users accessing %u machines\n", users, machines);
+ m = 0;
+ time_t now = time(NULL);
+ for(u = 0; u < users ; u++) {
+ if(++m == machines) m = 0;
+
+ PERSON *p = registry_request_access(NULL, machines_guids[m], machines_urls[m], "test", now);
+ users_guids[u] = p->guid;
+ }
+ print_stats(u, start, timems());
+
+ start = timems();
+ fprintf(stderr, "\nAll %u users accessing again the same %u servers\n", users, machines);
+ m = 0;
+ now = time(NULL);
+ for(u = 0; u < users ; u++) {
+ if(++m == machines) m = 0;
+
+ PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
+
+ if(p->guid != users_guids[u])
+ fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
+ }
+ print_stats(u, start, timems());
+
+ start = timems();
+ fprintf(stderr, "\nAll %u users accessing a new server, out of the %u servers\n", users, machines);
+ m = 1;
+ now = time(NULL);
+ for(u = 0; u < users ; u++) {
+ if(++m == machines) m = 0;
+
+ PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now);
+
+ if(p->guid != users_guids[u])
+ fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid);
+ }
+ print_stats(u, start, timems());
+
+ start = timems();
+ fprintf(stderr, "\n%u random users accessing a random server, out of the %u servers\n", users, machines);
+ now = time(NULL);
+ for(u = 0; u < users ; u++) {
+ uint32_t tu = random() * users / RAND_MAX;
+ uint32_t tm = random() * machines / RAND_MAX;
+
+ PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
+
+ if(p->guid != users_guids[tu])
+ fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
+ }
+ print_stats(u, start, timems());
+
+ start = timems();
+ fprintf(stderr, "\n%u random users accessing a random server, out of %u servers\n", users, machines2);
+ now = time(NULL);
+ for(u = 0; u < users ; u++) {
+ uint32_t tu = random() * users / RAND_MAX;
+ uint32_t tm = random() * machines2 / RAND_MAX;
+
+ PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now);
+
+ if(p->guid != users_guids[tu])
+ fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
+ }
+ print_stats(u, start, timems());
+
+ for(m = 0; m < 10; m++) {
+ start = timems();
+ fprintf(stderr,
+ "\n%u random user accesses to a random server, out of %u servers,\n > using 1/10000 with a random url, 1/1000 with a mismatched url\n",
+ users * 2, machines2);
+ now = time(NULL);
+ for (u = 0; u < users * 2; u++) {
+ uint32_t tu = random() * users / RAND_MAX;
+ uint32_t tm = random() * machines2 / RAND_MAX;
+
+ char *url = machines_urls[tm];
+ char buf[FILENAME_MAX + 1];
+ if (random() % 10000 == 1234) {
+ snprintf(buf, FILENAME_MAX, "http://random.%ld.netdata.rocks/", random());
+ url = buf;
+ }
+ else if (random() % 1000 == 123)
+ url = machines_urls[random() * machines2 / RAND_MAX];
+
+ PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], url, "test", now);
+
+ if (p->guid != users_guids[tu])
+ fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid);
+ }
+ print_stats(u, start, timems());
+ }
+
+ fprintf(stderr, "\n\nSAVE\n");
+ start = timems();
+ registry_save();
+ print_stats(registry.persons_count, start, timems());
+
+ fprintf(stderr, "\n\nCLEANUP\n");
+ start = timems();
+ registry_free();
+ print_stats(registry.persons_count, start, timems());
+ return 0;
+}
+
+// ----------------------------------------------------------------------------
+// TESTING
+
+int main(int argc, char **argv) {
+ // debug_flags = 0xFFFFFFFF;
+ // test1(argc, argv);
+ // exit(0);
+
+ (void)argc;
+ (void)argv;
+
+
+ PERSON *p1, *p2;
+
+ fprintf(stderr, "\n\nINITIALIZATION\n");
+
+ registry_init();
+
+ int i = 2;
+
+ fprintf(stderr, "\n\nADDING ENTRY\n");
+ p1 = registry_request_access("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
+
+ if(0)
+ while(i--) {
+#ifdef REGISTRY_STDOUT_DUMP
+ fprintf(stderr, "\n\nADDING ENTRY\n");
+#endif /* REGISTRY_STDOUT_DUMP */
+ p1 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL));
+
+#ifdef REGISTRY_STDOUT_DUMP
+ fprintf(stderr, "\n\nADDING ANOTHER URL\n");
+#endif /* REGISTRY_STDOUT_DUMP */
+ p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", "test", time(NULL));
+
+#ifdef REGISTRY_STDOUT_DUMP
+ fprintf(stderr, "\n\nADDING ANOTHER URL\n");
+#endif /* REGISTRY_STDOUT_DUMP */
+ p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
+
+#ifdef REGISTRY_STDOUT_DUMP
+ fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
+#endif /* REGISTRY_STDOUT_DUMP */
+ p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL));
+
+#ifdef REGISTRY_STDOUT_DUMP
+ fprintf(stderr, "\n\nADDING ANOTHER PERSON\n");
+#endif /* REGISTRY_STDOUT_DUMP */
+ p2 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
+
+#ifdef REGISTRY_STDOUT_DUMP
+ fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n");
+#endif /* REGISTRY_STDOUT_DUMP */
+ p2 = registry_request_access(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL));
+ }
+
+ fprintf(stderr, "\n\nSAVE\n");
+ registry_save();
+
+ fprintf(stderr, "\n\nCLEANUP\n");
+ registry_free();
+ return 0;
+}
+
+#endif /* REGISTRY_STANDALONE_TESTS */
--- /dev/null
+#include "web_client.h"
+
+#ifndef NETDATA_REGISTRY_H
+#define NETDATA_REGISTRY_H 1
+
+#define NETDATA_REGISTRY_COOKIE_NAME "netdata_registry_id"
+
+extern int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when);
+extern int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when);
+extern int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when);
+extern int registry_request_switch_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *new_person_guid, time_t when);
+extern int registry_request_hello_json(struct web_client *w);
+
+extern int registry_init(void);
+extern void registry_free(void);
+extern int registry_save(void);
+
+extern char *registry_get_this_machine_guid(void);
+
+extern void registry_statistics(void);
+
+
+#endif /* NETDATA_REGISTRY_H */
else return strcmp(((RRDSET *)a)->id, ((RRDSET *)b)->id);
}
-avl_tree rrdset_root_index = {
- NULL,
- rrdset_compare,
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#endif
-#endif
+avl_tree_lock rrdset_root_index = {
+ { NULL, rrdset_compare },
+ AVL_LOCK_INITIALIZER
};
-#define rrdset_index_add(st) avl_insert(&rrdset_root_index, (avl *)(st))
-#define rrdset_index_del(st) avl_remove(&rrdset_root_index, (avl *)(st))
+#define rrdset_index_add(st) avl_insert_lock(&rrdset_root_index, (avl *)(st))
+#define rrdset_index_del(st) avl_remove_lock(&rrdset_root_index, (avl *)(st))
static RRDSET *rrdset_index_find(const char *id, uint32_t hash) {
RRDSET *result = NULL, tmp;
strncpyz(tmp.id, id, RRD_ID_LENGTH_MAX);
tmp.hash = (hash)?hash:simple_hash(tmp.id);
- avl_search(&(rrdset_root_index), (avl *)&tmp, rrdset_iterator, (avl **)&result);
+ avl_search_lock(&(rrdset_root_index), (avl *) &tmp, rrdset_iterator, (avl **) &result);
return result;
}
else return strcmp(A->name, B->name);
}
-avl_tree rrdset_root_index_name = {
- NULL,
- rrdset_compare_name,
-#ifndef AVL_WITHOUT_PTHREADS
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#endif
-#endif
+avl_tree_lock rrdset_root_index_name = {
+ { NULL, rrdset_compare_name },
+ AVL_LOCK_INITIALIZER
};
int rrdset_index_add_name(RRDSET *st) {
// fprintf(stderr, "ADDING: %s (name: %s)\n", st->id, st->name);
- return avl_insert(&rrdset_root_index_name, (avl *)(&st->avlname));
+ return avl_insert_lock(&rrdset_root_index_name, (avl *) (&st->avlname));
}
-#define rrdset_index_del_name(st) avl_remove(&rrdset_root_index_name, (avl *)(&st->avlname))
+#define rrdset_index_del_name(st) avl_remove_lock(&rrdset_root_index_name, (avl *)(&st->avlname))
static RRDSET *rrdset_index_find_name(const char *name, uint32_t hash) {
void *result = NULL;
tmp.hash_name = (hash)?hash:simple_hash(tmp.name);
// fprintf(stderr, "SEARCHING: %s\n", name);
- avl_search(&(rrdset_root_index_name), (avl *)(&(tmp.avlname)), rrdset_iterator_name, (avl **)&result);
+ avl_search_lock(&(rrdset_root_index_name), (avl *) (&(tmp.avlname)), rrdset_iterator_name, (avl **) &result);
if(result) {
RRDSET *st = rrdset_from_avlname(result);
if(strcmp(st->magic, RRDSET_MAGIC))
else return strcmp(((RRDDIM *)a)->id, ((RRDDIM *)b)->id);
}
-#define rrddim_index_add(st, rd) avl_insert(&((st)->dimensions_index), (avl *)(rd))
-#define rrddim_index_del(st,rd ) avl_remove(&((st)->dimensions_index), (avl *)(rd))
+#define rrddim_index_add(st, rd) avl_insert_lock(&((st)->dimensions_index), (avl *)(rd))
+#define rrddim_index_del(st,rd ) avl_remove_lock(&((st)->dimensions_index), (avl *)(rd))
static RRDDIM *rrddim_index_find(RRDSET *st, const char *id, uint32_t hash) {
RRDDIM *result = NULL, tmp;
strncpyz(tmp.id, id, RRD_ID_LENGTH_MAX);
tmp.hash = (hash)?hash:simple_hash(tmp.id);
- avl_search(&(st->dimensions_index), (avl *)&tmp, rrddim_iterator, (avl **)&result);
+ avl_search_lock(&(st->dimensions_index), (avl *) &tmp, rrddim_iterator, (avl **) &result);
return result;
}
st->gap_when_lost_iterations_above = (int) (
config_get_number(st->id, "gap when lost iterations above", RRD_DEFAULT_GAP_INTERPOLATIONS) + 2);
- avl_init(&st->dimensions_index, rrddim_compare);
+ avl_init_lock(&st->dimensions_index, rrddim_compare);
pthread_rwlock_init(&st->rwlock, NULL);
pthread_rwlock_wrlock(&rrdset_root_rwlock);
// ------------------------------------------------------------------------
// the dimensions
- avl_tree dimensions_index; // the root of the dimensions index
+ avl_tree_lock dimensions_index; // the root of the dimensions index
RRDDIM *dimensions; // the actual data for every dimension
};
typedef struct rrdset RRDSET;
if(!stcpu_thread) stcpu_thread = rrdset_find("netdata.plugin_cgroups_cpu");
if(!stcpu_thread) {
- stcpu_thread = rrdset_create("netdata", "plugin_cgroups_cpu", NULL, "proc.internal", NULL, "NetData CGroups Plugin CPU usage", "milliseconds/s", 131000, rrd_update_every, RRDSET_TYPE_STACKED);
+ stcpu_thread = rrdset_create("netdata", "plugin_cgroups_cpu", NULL, "proc.internal", NULL, "NetData CGroups Plugin CPU usage", "milliseconds/s", 132000, rrd_update_every, RRDSET_TYPE_STACKED);
rrddim_add(stcpu_thread, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL);
rrddim_add(stcpu_thread, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL);
#include "global_statistics.h"
#include "rrd.h"
#include "rrd2json.h"
+#include "registry.h"
#include "web_client.h"
#include "../config.h"
return NULL;
}
+ w->origin[0] = '*';
w->wait_receive = 1;
if(web_clients) web_clients->prev = w;
}
w->last_url[0] = '\0';
+ w->cookie[0] = '\0';
+ w->origin[0] = '*';
+ w->origin[1] = '\0';
w->mode = WEB_CLIENT_MODE_NORMAL;
+ w->enable_gzip = 0;
+ w->keepalive = 0;
+ if(w->decoded_url) {
+ free(w->decoded_url);
+ w->decoded_url = NULL;
+ }
buffer_reset(w->response.header_output);
buffer_reset(w->response.header);
if(!name || !*name) continue;
if(!value || !*value) continue;
- debug(D_WEB_CLIENT, "%llu: API v1 query param '%s' with value '%s'", w->id, name, value);
+ debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
// name and value are now the parameters
// they are not null and not empty
return ret;
}
+int web_client_api_request_v1_registry(struct web_client *w, char *url)
+{
+ char person_guid[36 + 1] = "";
+
+ debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
+
+ char *cookie = strstr(w->response.data->buffer, " " NETDATA_REGISTRY_COOKIE_NAME "=");
+ if(cookie) {
+ strncpy(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME) + 1], 36);
+ person_guid[36] = '\0';
+ }
+
+ char action = '\0';
+ char *machine_guid = NULL,
+ *machine_url = NULL,
+ *url_name = NULL,
+ *search_machine_guid = NULL,
+ *delete_url = NULL,
+ *to_person_guid = NULL;
+
+ while(url) {
+ char *value = mystrsep(&url, "?&[]");
+ if (!value || !*value) continue;
+
+ char *name = mystrsep(&value, "=");
+ if (!name || !*name) continue;
+ if (!value || !*value) continue;
+
+ debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
+
+ if(!strcmp(name, "action")) {
+ if(!strcmp(value, "access")) action = 'A';
+ else if(!strcmp(value, "hello")) action = 'H';
+ else if(!strcmp(value, "delete")) action = 'D';
+ else if(!strcmp(value, "search")) action = 'S';
+ else if(!strcmp(value, "switch")) action = 'W';
+ }
+ else if(!strcmp(name, "machine"))
+ machine_guid = value;
+
+ else if(!strcmp(name, "url"))
+ machine_url = value;
+
+ else if(action == 'A') {
+ if(!strcmp(name, "name"))
+ url_name = value;
+ }
+ else if(action == 'D') {
+ if(!strcmp(name, "delete_url"))
+ delete_url = value;
+ }
+ else if(action == 'S') {
+ if(!strcmp(name, "for"))
+ search_machine_guid = value;
+ }
+ else if(action == 'W') {
+ if(!strcmp(name, "to"))
+ to_person_guid = value;
+ }
+ }
+
+ if(action == 'A' && (!machine_guid || !machine_url || !url_name)) {
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')",
+ machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", url_name?url_name:"UNSET");
+ return 400;
+ }
+ else if(action == 'D' && (!machine_guid || !machine_url || !delete_url)) {
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')",
+ machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
+ return 400;
+ }
+ else if(action == 'S' && (!machine_guid || !machine_url || !search_machine_guid)) {
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')",
+ machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET");
+ return 400;
+ }
+ else if(action == 'W' && (!machine_guid || !machine_url || !to_person_guid)) {
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')",
+ machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
+ return 400;
+ }
+
+ switch(action) {
+ case 'A':
+ return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, time(NULL));
+
+ case 'D':
+ return registry_request_delete_json(w, person_guid, machine_guid, machine_url, delete_url, time(NULL));
+
+ case 'S':
+ return registry_request_search_json(w, person_guid, machine_guid, machine_url, search_machine_guid, time(NULL));
+
+ case 'W':
+ return registry_request_switch_json(w, person_guid, machine_guid, machine_url, to_person_guid, time(NULL));
+
+ case 'H':
+ return registry_request_hello_json(w);
+
+ default:
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
+ return 400;
+ }
+
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Invalid or no registry action.");
+ return 400;
+}
+
int web_client_api_request_v1(struct web_client *w, char *url)
{
+ static uint32_t data_hash = 0, chart_hash = 0, charts_hash = 0, registry_hash = 0;
+
+ if(unlikely(data_hash == 0)) {
+ data_hash = simple_hash("data");
+ chart_hash = simple_hash("chart");
+ charts_hash = simple_hash("charts");
+ registry_hash = simple_hash("registry");
+ }
+
// get the command
char *tok = mystrsep(&url, "/?&");
if(tok && *tok) {
debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
+ uint32_t hash = simple_hash(tok);
- if(strcmp(tok, "data") == 0)
+ if(hash == data_hash && !strcmp(tok, "data"))
return web_client_api_request_v1_data(w, url);
- else if(strcmp(tok, "chart") == 0)
+
+ else if(hash == chart_hash && !strcmp(tok, "chart"))
return web_client_api_request_v1_chart(w, url);
- else if(strcmp(tok, "charts") == 0)
+
+ else if(hash == charts_hash && !strcmp(tok, "charts"))
return web_client_api_request_v1_charts(w, url);
+
+ else if(hash == registry_hash && !strcmp(tok, "registry"))
+ return web_client_api_request_v1_registry(w, url);
+
else {
buffer_flush(w->response.data);
buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok);
}
*/
-void web_client_process(struct web_client *w) {
- int code = 500;
- ssize_t bytes;
- int enable_gzip = 0;
- w->wait_receive = 0;
+static inline char *http_header_parse(struct web_client *w, char *s) {
+ char *e = s;
- // check if we have an empty line (end of HTTP header)
- if(strstr(w->response.data->buffer, "\r\n\r\n")) {
- global_statistics_lock();
- global_statistics.web_requests++;
- global_statistics_unlock();
+ // find the :
+ while(*e && *e != ':') e++;
+ if(!*e || e[1] != ' ') return e;
- gettimeofday(&w->tv_in, NULL);
- debug(D_WEB_DATA, "%llu: Processing data buffer of %d bytes: '%s'.", w->id, w->response.data->len, w->response.data->buffer);
+ // get the name
+ *e = '\0';
- // check if the client requested keep-alive HTTP
- if(strcasestr(w->response.data->buffer, "Connection: keep-alive")) w->keepalive = 1;
- else w->keepalive = 0;
+ // find the value
+ char *v, *ve;
+ v = ve = e + 2;
+ // find the \r
+ while(*ve && *ve != '\r') ve++;
+ if(!*ve || ve[1] != '\n') {
+ *e = ':';
+ return ve;
+ }
+
+ // terminate the value
+ *ve = '\0';
+
+ // fprintf(stderr, "HEADER: '%s' = '%s'\n", s, v);
+
+ if(!strcasecmp(s, "Origin")) {
+ strncpy(w->origin, v, ORIGIN_MAX);
+ }
+ else if(!strcasecmp(s, "Connection")) {
+ if(strcasestr(v, "keep-alive"))
+ w->keepalive = 1;
+ }
#ifdef NETDATA_WITH_ZLIB
- // check if the client accepts deflate
- if(web_enable_gzip && strstr(w->response.data->buffer, "gzip"))
- enable_gzip = 1;
-#endif // NETDATA_WITH_ZLIB
+ else if(!strcasecmp(s, "Accept-Encoding")) {
+ if(web_enable_gzip && strcasestr(v, "gzip")) {
+ w->enable_gzip = 1;
+ }
+ }
+#endif /* NETDATA_WITH_ZLIB */
+
+ *e = ':';
+ *ve = '\r';
+ return ve;
+}
- int datasource_type = DATASOURCE_DATATABLE_JSONP;
- //if(strstr(w->response.data->buffer, "X-DataSource-Auth"))
- // datasource_type = DATASOURCE_GOOGLE_JSON;
+// http_request_validate()
+// returns:
+// = 0 : all good, process the request
+// > 0 : request is complete, but is not supported
+// < 0 : request is incomplete - wait for more data
- char *buf = (char *)buffer_tostring(w->response.data);
- char *tok = strsep(&buf, " \r\n");
- char *url = NULL;
- char *pointer_to_free = NULL; // keep url_decode() allocated buffer
+static inline int http_request_validate(struct web_client *w) {
+ char *s = w->response.data->buffer, *encoded_url = NULL;
+ // is is a valid request?
+ if(!strncmp(s, "GET ", 4)) {
+ encoded_url = s = &s[4];
w->mode = WEB_CLIENT_MODE_NORMAL;
+ }
+ else if(!strncmp(s, "OPTIONS ", 8)) {
+ encoded_url = s = &s[8];
+ w->mode = WEB_CLIENT_MODE_OPTIONS;
+ }
+ else {
+ w->wait_receive = 0;
+ return 1;
+ }
+
+ // find the SPACE + "HTTP/"
+ while(*s) {
+ // find the space
+ while (*s && *s != ' ') s++;
+
+ // is it SPACE + "HTTP/" ?
+ if(*s && !strncmp(s, " HTTP/", 6)) break;
+ else s++;
+ }
+
+ // incomplete requests
+ if(!*s) {
+ w->wait_receive = 1;
+ return -2;
+ }
+
+ // we have the end of encoded_url - remember it
+ char *ue = s;
+
+ while(*s) {
+ // find a line feed
+ while (*s && *s != '\r') s++;
+
+ // did we reach the end?
+ if(unlikely(!*s)) break;
+
+ // is it \r\n ?
+ if (likely(s[1] == '\n')) {
+
+ // is it again \r\n ? (header end)
+ if(unlikely(s[2] == '\r' && s[3] == '\n')) {
+ // a valid complete HTTP request found
- if(buf && strcmp(tok, "GET") == 0) {
- tok = strsep(&buf, " \r\n");
- pointer_to_free = url = url_decode(tok);
- debug(D_WEB_CLIENT, "%llu: Processing HTTP GET on url '%s'.", w->id, url);
+ *ue = '\0';
+ w->decoded_url = url_decode(encoded_url);
+ *ue = ' ';
+
+ w->wait_receive = 0;
+ return 0;
+ }
+
+ // another header line
+ s = http_header_parse(w, &s[2]);
}
- else if(buf && strcmp(tok, "OPTIONS") == 0) {
- tok = strsep(&buf, " \r\n");
- pointer_to_free = url = url_decode(tok);
- debug(D_WEB_CLIENT, "%llu: Processing HTTP OPTIONS on url '%s'.", w->id, url);
- w->mode = WEB_CLIENT_MODE_OPTIONS;
+ else s++;
+ }
+
+ // incomplete request
+ w->wait_receive = 1;
+ return -3;
+}
+
+void web_client_process(struct web_client *w) {
+ int code = 500;
+ ssize_t bytes;
+
+ int what_to_do = http_request_validate(w);
+
+ // wait for more data
+ if(what_to_do < 0) {
+ if(w->response.data->len > TOO_BIG_REQUEST) {
+ strcpy(w->last_url, "too big request");
+
+ debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zd bytes).", w->id, w->response.data->len);
+
+ code = 400;
+ buffer_flush(w->response.data);
+ buffer_sprintf(w->response.data, "Received request is too big (%zd bytes).\r\n", w->response.data->len);
}
- else if (buf && strcmp(tok, "POST") == 0) {
- w->keepalive = 0;
- tok = strsep(&buf, " \r\n");
- pointer_to_free = url = url_decode(tok);
- debug(D_WEB_CLIENT, "%llu: I don't know how to handle POST with form data. Assuming it is a GET on url '%s'.", w->id, url);
+ else {
+ // wait for more data
+ return;
}
+ }
+ else if(what_to_do > 0) {
+ strcpy(w->last_url, "not a valid response");
- w->last_url[0] = '\0';
+ debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer);
+<<<<<<< HEAD
+ code = 500;
+ buffer_flush(w->response.data);
+ buffer_strcat(w->response.data, "I don't understand you...\r\n");
+ }
+ else { // what_to_do == 0
+ gettimeofday(&w->tv_in, NULL);
+
+ global_statistics_lock();
+ global_statistics.web_requests++;
+ global_statistics_unlock();
+=======
if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
strncpyz(w->last_url, url, URL_MAX);
+>>>>>>> fredericopissarra/changes
+ // copy the URL - we are going to overwrite parts of it
+ // FIXME -- we should avoid it
+ strncpy(w->last_url, w->decoded_url, URL_MAX);
+ w->last_url[URL_MAX] = '\0';
+
+ if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
code = 200;
w->response.data->contenttype = CT_TEXT_PLAIN;
buffer_flush(w->response.data);
buffer_strcat(w->response.data, "OK");
}
- else if(url) {
+ else {
#ifdef NETDATA_WITH_ZLIB
- if(enable_gzip)
+ if(w->enable_gzip)
web_client_enable_deflate(w);
#endif
+<<<<<<< HEAD
+ char *url = w->decoded_url;
+ char *tok = mystrsep(&url, "/?");
+=======
strncpyz(w->last_url, url, URL_MAX);
tok = mystrsep(&url, "/?");
+>>>>>>> fredericopissarra/changes
if(tok && *tok) {
debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
if(strcmp(tok, "api") == 0) {
// the client is requesting api access
- datasource_type = DATASOURCE_JSON;
code = web_client_api_request(w, url);
}
-#ifdef NETDATA_INTERNAL_CHECKS
- else if(strcmp(tok, "exit") == 0) {
- netdata_exit = 1;
+ else if(strcmp(tok, "netdata.conf") == 0) {
code = 200;
+ debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id);
+
w->response.data->contenttype = CT_TEXT_PLAIN;
buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "will do");
+ generate_config(w->response.data, 0);
}
-#endif
else if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
- // the client is requesting rrd data
- datasource_type = DATASOURCE_JSON;
- code = web_client_data_request(w, url, datasource_type);
+ // the client is requesting rrd data -- OLD API
+ code = web_client_data_request(w, url, DATASOURCE_JSON);
}
else if(strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource"
- // the client is requesting google datasource
- code = web_client_data_request(w, url, datasource_type);
+ // the client is requesting google datasource -- OLD API
+ code = web_client_data_request(w, url, DATASOURCE_DATATABLE_JSONP);
}
else if(strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph"
- // the client is requesting an rrd graph
+ // the client is requesting an rrd graph -- OLD API
// get the name of the data to show
tok = mystrsep(&url, "/?&");
buffer_strcat(w->response.data, "Graph name?\r\n");
}
}
+ else if(strcmp(tok, "list") == 0) {
+ // OLD API
+ code = 200;
+
+ debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
+
+ buffer_flush(w->response.data);
+ RRDSET *st = rrdset_root;
+
+ for ( ; st ; st = st->next )
+ buffer_sprintf(w->response.data, "%s\n", st->name);
+ }
+ else if(strcmp(tok, "all.json") == 0) {
+ // OLD API
+ code = 200;
+ debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id);
+
+ w->response.data->contenttype = CT_APPLICATION_JSON;
+ buffer_flush(w->response.data);
+ rrd_stats_all_json(w->response.data);
+ }
#ifdef NETDATA_INTERNAL_CHECKS
+ else if(strcmp(tok, "exit") == 0) {
+ code = 200;
+ w->response.data->contenttype = CT_TEXT_PLAIN;
+ buffer_flush(w->response.data);
+
+ if(!netdata_exit)
+ buffer_strcat(w->response.data, "ok, will do...");
+ else
+ buffer_strcat(w->response.data, "I am doing it already");
+
+ netdata_exit = 1;
+ }
else if(strcmp(tok, "debug") == 0) {
buffer_flush(w->response.data);
// just leave the buffer as is
// it will be copied back to the client
}
-#endif
- else if(strcmp(tok, "list") == 0) {
- code = 200;
-
- debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
-
- buffer_flush(w->response.data);
- RRDSET *st = rrdset_root;
-
- for ( ; st ; st = st->next )
- buffer_sprintf(w->response.data, "%s\n", st->name);
- }
- else if(strcmp(tok, "all.json") == 0) {
- code = 200;
- debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id);
-
- w->response.data->contenttype = CT_APPLICATION_JSON;
- buffer_flush(w->response.data);
- rrd_stats_all_json(w->response.data);
- }
- else if(strcmp(tok, "netdata.conf") == 0) {
- code = 200;
- debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id);
-
- w->response.data->contenttype = CT_TEXT_PLAIN;
- buffer_flush(w->response.data);
- generate_config(w->response.data, 0);
- }
+#endif /* NETDATA_INTERNAL_CHECKS */
else {
char filename[FILENAME_MAX+1];
url = filename;
code = mysendfile(w, (tok && *tok)?tok:"/");
}
}
- else {
- strcpy(w->last_url, "not a valid response");
-
- if(buf) debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, buf);
-
- code = 500;
- buffer_flush(w->response.data);
- buffer_strcat(w->response.data, "I don't understand you...\r\n");
- }
-
- // free url_decode() buffer
- if(pointer_to_free) {
- free(pointer_to_free);
- pointer_to_free = NULL;
- }
- }
- else if(w->response.data->len > TOO_BIG_REQUEST) {
- strcpy(w->last_url, "too big request");
-
- debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zd bytes).", w->id, w->response.data->len);
-
- code = 400;
- buffer_flush(w->response.data);
- buffer_sprintf(w->response.data, "Received request is too big (%zd bytes).\r\n", w->response.data->len);
- }
- else {
- // wait for more data
- w->wait_receive = 1;
- return;
}
gettimeofday(&w->tv_ready, NULL);
code_msg = "Not Found";
break;
+ case 412:
+ code_msg = "Preconditions Failed";
+ break;
+
default:
code_msg = "Internal Server Error";
break;
"HTTP/1.1 %d %s\r\n"
"Connection: %s\r\n"
"Server: NetData Embedded HTTP Server\r\n"
- "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Origin: %s\r\n"
+ "Access-Control-Allow-Credentials: true\r\n"
"Content-Type: %s\r\n"
"Date: %s\r\n"
, code, code_msg
, w->keepalive?"keep-alive":"close"
+ , w->origin
, content_type_string
, date
);
+ if(w->cookie[0]) {
+ buffer_sprintf(w->response.header_output,
+ "Set-Cookie: %s\r\n",
+ w->cookie);
+ }
+
if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
buffer_strcat(w->response.header_output,
"Access-Control-Allow-Methods: GET, OPTIONS\r\n"
- "Access-Control-Allow-Headers: accept, x-requested-with\r\n"
+ "Access-Control-Allow-Headers: accept, x-requested-with, origin, content-type, cookie\r\n"
"Access-Control-Max-Age: 1209600\r\n" // 86400 * 14
);
}
#include <netdb.h>
#include "web_buffer.h"
+#include "dictionary.h"
#define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
extern int web_client_timeout;
#define URL_MAX 8192
#define ZLIB_CHUNK 16384
#define HTTP_RESPONSE_HEADER_SIZE 4096
+#define COOKIE_MAX 1024
+#define ORIGIN_MAX 1024
struct response {
BUFFER *header; // our response header
struct timeval tv_in, tv_ready;
+ char cookie[COOKIE_MAX+1];
+ char origin[ORIGIN_MAX+1];
+
int mode;
int keepalive;
+ int enable_gzip;
+ char *decoded_url;
struct sockaddr_storage clientaddr;
Group=root
PIDFile=@localstatedir_POST@/run/netdata.pid
ExecStart=@sbindir_POST@/netdata -pidfile @localstatedir_POST@/run/netdata.pid
-ExecStop=/bin/kill -SIGTERM $MAINPID
+KillMode=mixed
+KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
<!-- <script> netdataServer = "http://box:19999"; </script> -->
<!-- load the dashboard manager - it will do the rest -->
- <script type="text/javascript" src="dashboard.js"></script>
+ <script type="text/javascript" src="dashboard.js?v35"></script>
// var netdataNoBootstrap = true; // do not load bootstrap
// var netdataDontStart = true; // do not start the thread to process the charts
// var netdataErrorCallback = null; // Callback function that will be invoked upon error
+// var netdataNoRegistry = true; // Don't update the registry for this access
+// var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
+// the URLs from the registry
//
// You can also set the default netdata server, using the following.
// When this variable is not set, we assume the page is hosted on your
403: { message: "Chart library not enabled/is failed", alert: false },
404: { message: "Chart not found", alert: false },
405: { message: "Cannot download charts index from server", alert: true },
- 406: { message: "Invalid charts index downloaded from server", alert: true }
+ 406: { message: "Invalid charts index downloaded from server", alert: true },
+ 407: { message: "Cannot HELLO netdata server", alert: false },
+ 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
+ 409: { message: "Cannot ACCESS netdata registry", alert: false },
+ 410: { message: "Netdata registry ACCESS failed", alert: false },
+ 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
+ 412: { message: "Netdata registry DELETE failed", alert: false },
+ 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
+ 414: { message: "Netdata registry SWITCH failed", alert: false }
};
NETDATA.errorLast = {
code: 0,
$('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
NETDATA.parseDom(NETDATA.chartRefresher);
+
+ // Registry initialization
+ setTimeout(NETDATA.registry.init, 1000);
};
// ----------------------------------------------------------------------------------------------------------------
};
// ----------------------------------------------------------------------------------------------------------------
- // Start up
+ // Load required JS libraries and CSS
NETDATA.requiredJs = [
{
NETDATA.loadRequiredCSS(++index);
};
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // Registry of netdata hosts
+
+ NETDATA.registry = {
+ server: null, // the netdata registry server
+ person_guid: null, // the unique ID of this browser / user
+ machine_guid: null, // the unique ID the netdata server that served dashboard.js
+ hostname: null, // the hostname of the netdata server that served dashboard.js
+ urls: null, // the user's other URLs
+
+ parsePersonUrls: function(person_urls) {
+ if(person_urls) {
+ NETDATA.registry.urls = {};
+
+ // sort based on the timestamp of the last access
+ function pu_comparator_asc(a, b) {
+ if (a[2] < b[2]) return -1;
+ if (a[2] > b[2]) return 1;
+ return 0;
+ }
+
+ var apu = person_urls.sort(pu_comparator_asc);
+ var len = apu.length;
+ while(len--) {
+ if(typeof NETDATA.registry.urls[apu[len][0]] === 'undefined') {
+ NETDATA.registry.urls[apu[len][0]] = {
+ guid: apu[len][0],
+ url: apu[len][1],
+ last_t: apu[len][2],
+ accesses: apu[len][3],
+ name: apu[len][4],
+ alternate_urls: new Array()
+ };
+ }
+ else
+ NETDATA.registry.urls[apu[len][0]].alternate_urls.push(apu[len][1]);
+ }
+ }
+
+ if(typeof netdataRegistryCallback === 'function')
+ netdataRegistryCallback(NETDATA.registry.urls);
+ },
+
+ init: function() {
+ if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
+ return;
+
+ NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
+ if(data) {
+ NETDATA.registry.server = data.registry;
+ NETDATA.registry.machine_guid = data.machine_guid;
+ NETDATA.registry.hostname = data.hostname;
+
+ NETDATA.registry.access(10, function (person_urls) {
+ NETDATA.registry.parsePersonUrls(person_urls);
+
+ });
+ }
+ });
+ },
+
+ hello: function(host, callback) {
+ // send HELLO to a netdata server:
+ // 1. verifies the server is reachable
+ // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
+ $.ajax({
+ url: host + '/api/v1/registry?action=hello',
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(typeof callback === 'function')
+ callback(data);
+ })
+ .fail(function() {
+ NETDATA.error(407, host);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ },
+
+ access: function(max_redirects, callback) {
+ // send ACCESS to a netdata registry:
+ // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
+ // 2. it responds with a list of netdata servers we know
+ // the registry identifies us using a cookie it sets the first time we access it
+ // the registry may respond with a redirect URL to send us to another registry
+ $.ajax({
+ url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ var redirect = null;
+ if(typeof data.registry === 'string')
+ redirect = data.registry;
+
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(data === null && redirect !== null && max_redirects > 0) {
+ NETDATA.registry.server = redirect;
+ NETDATA.registry.access(max_redirects - 1, callback);
+ }
+ else {
+ if(typeof data.person_guid === 'string')
+ NETDATA.registry.person_guid = data.person_guid;
+
+ if(typeof callback === 'function')
+ callback(data.urls);
+ }
+ })
+ .fail(function() {
+ NETDATA.error(410, NETDATA.registry.server);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ },
+
+ delete: function(delete_url, callback) {
+ // send DELETE to a netdata registry:
+ $.ajax({
+ url: NETDATA.registry.server + '/api/v1/registry?action=delete&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(typeof callback === 'function')
+ callback(data);
+ })
+ .fail(function() {
+ NETDATA.error(412, NETDATA.registry.server);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ },
+
+ switch: function(new_person_guid, callback) {
+ // impersonate
+ $.ajax({
+ url: NETDATA.registry.server + '/api/v1/registry?action=switch&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&to=' + new_person_guid,
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(typeof callback === 'function')
+ callback(data);
+ })
+ .fail(function() {
+ NETDATA.error(414, NETDATA.registry.server);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ }
+ };
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // Boot it!
+
NETDATA.errorReset();
NETDATA.loadRequiredCSS(0);
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">\r
<meta name="author" content="costa@tsaousis.gr">\r
\r
- <script type="text/javascript" src="dashboard.js"></script>\r
+ <script type="text/javascript" src="dashboard.js?v35"></script>\r
</head>\r
<body>\r
\r
and that you have chown it to be owned by netdata:netdata
-->
<!-- <script type="text/javascript" src="http://my.server:19999/dashboard.js"></script> -->
- <script type="text/javascript" src="dashboard.js"></script>
+ <script type="text/javascript" src="dashboard.js?v35"></script>
<script>
// --- OPTIONS FOR THE CHARTS --
<div class="mygauge-combo">
<div class="mygauge">
<div data-netdata="netdata.requests"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-title="EU - London"
data-chart-library="gauge"
data-width="100%"
</div>
<div class="mygauge-button">
<br/> <br/>
- <button type="button" class="btn btn-default" data-toggle="button" aria-pressed="false" autocomplete="off" onclick="window.location='//netdata1.firehol.org/default.html'" style="font-size: 1.5vw;">Enter London!</button>
+ <button type="button" class="btn btn-default" data-toggle="button" aria-pressed="false" autocomplete="off" onclick="window.location='//london.my-netdata.io/default.html'" style="font-size: 1.5vw;">Enter London!</button>
<div style="font-size: 1vw;">
Donated by DigitalOcean.com
</div>
<div class="mygauge-combo">
<div class="mygauge">
<div data-netdata="netdata.requests"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-title="US - Atlanta"
data-chart-library="gauge"
data-width="100%"
</div>
<div class="mygauge-button">
<br/> <br/>
- <button type="button" class="btn btn-default" data-toggle="button" aria-pressed="false" autocomplete="off" onclick="window.location='//netdata2.firehol.org/default.html'" style="font-size: 1.5vw;">Enter Atlanta!</button>
+ <button type="button" class="btn btn-default" data-toggle="button" aria-pressed="false" autocomplete="off" onclick="window.location='//atlanta.my-netdata.io/default.html'" style="font-size: 1.5vw;">Enter Atlanta!</button>
<div style="font-size: 1vw;">
Donated by CDN77.com
</div>
<div class="mygauge-combo">
<div class="mygauge">
<div data-netdata="netdata.requests"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-title="EU - Greece"
data-chart-library="gauge"
data-width="100%"
</div>
<div class="mygauge-button">
<br/> <br/>
- <button type="button" class="btn btn-default" data-toggle="button" aria-pressed="false" autocomplete="off" onclick="window.location='//netdata3.firehol.org/default.html'" style="font-size: 1.5vw;">Come to Greece!</button>
+ <button type="button" class="btn btn-default" data-toggle="button" aria-pressed="false" autocomplete="off" onclick="window.location='//athens.my-netdata.io/default.html'" style="font-size: 1.5vw;">Come to Greece!</button>
<div style="font-size: 0.8vw;">
</div>
</div>
<div data-netdata="nginx.requests"
data-dimensions="requests"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="nginx.requests"
data-dimensions="requests"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="nginx.requests"
data-dimensions="requests"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="nginx.connections"
data-dimensions="active"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="nginx.connections"
data-dimensions="active"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="nginx.connections"
data-dimensions="active"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
<div role="tabpanel" class="tab-pane active" id="outbout">
<div class="myfullchart">
<div data-netdata="tc.world_out"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-chart-library="dygraph"
data-title="EU - London, traffic we send per service"
data-width="100%"
<div class="myfullchart">
<div data-netdata="tc.world_out"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-chart-library="dygraph"
data-title="US - Atlanta, traffic we send per service"
data-width="100%"
<div class="myfullchart">
<div data-netdata="tc.world_out"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-chart-library="dygraph"
data-title="EU - Greece, traffic we send per service"
data-width="100%"
<div role="tabpanel" class="tab-pane" id="inbound">
<div class="myfullchart">
<div data-netdata="tc.world_in"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-chart-library="dygraph"
data-title="EU - London, traffic we receive per service"
data-width="100%"
<div class="myfullchart">
<div data-netdata="tc.world_in"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-chart-library="dygraph"
data-title="US - Atlanta, traffic we receive per service"
data-width="100%"
<div class="myfullchart">
<div data-netdata="tc.world_in"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-chart-library="dygraph"
data-title="EU - Greece, traffic we receive per service"
data-width="100%"
</div>
<div data-netdata="netfilter.synproxy_syn_received"
data-dimensions="received"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="netfilter.synproxy_syn_received"
data-dimensions="received"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="netfilter.synproxy_syn_received"
data-dimensions="received"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
<div style="padding-top: 1vw;">
<div class="myfullchart">
<div data-netdata="system.cpu"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-chart-library="dygraph"
data-title="EU - London, CPU Usage"
data-width="100%"
<div class="myfullchart">
<div data-netdata="system.cpu"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-chart-library="dygraph"
data-title="US - Atlanta, CPU Usage"
data-width="100%"
<div class="myfullchart">
<div data-netdata="system.cpu"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-chart-library="dygraph"
data-title="EU - Greece, CPU Usage"
data-width="100%"
</div>
<div data-netdata="users.cpu"
data-dimensions="netdata"
- data-host="//netdata1.firehol.org"
+ data-host="//london.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="users.cpu"
data-dimensions="netdata"
- data-host="//netdata2.firehol.org"
+ data-host="//atlanta.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
</div>
<div data-netdata="users.cpu"
data-dimensions="netdata"
- data-host="//netdata3.firehol.org"
+ data-host="//athens.my-netdata.io"
data-chart-library="dygraph"
data-dygraph-theme="sparkline"
data-dygraph-type="area"
font-weight: 500;
}
+ .dropdown-menu {
+ min-width: 200px;
+ }
+ .dropdown-menu.columns-2 {
+ margin: 0;
+ padding: 0;
+ width: 400px;
+ }
+ .dropdown-menu li a {
+ padding: 5px 15px;
+ font-weight: 300;
+ }
+ .dropdown-menu.multi-column {
+ overflow-x: hidden;
+ }
+ .multi-column-dropdown {
+ list-style: none;
+ padding: 0;
+ }
+ .multi-column-dropdown li a {
+ display: block;
+ clear: both;
+ line-height: 1.428571429;
+ white-space: normal;
+ }
+ .multi-column-dropdown li a:hover {
+ text-decoration: none;
+ color: #f5f5f5;
+ background-color: #262626;
+ }
+
/* Back to top (hidden on mobile) */
.back-to-top,
.dashboard-theme-toggle {
else
return ret;
}
+
var netdataTheme = getTheme('slate');
function setTheme(theme) {
return saveLocalStorage('netdataTheme', theme);
}
+
+ var netdataRegistryCallback = function(urls) {
+ var el = '';
+ var a1 = '';
+ var found = 0;
+
+ if(urls) {
+ $.each(urls, function(i, u) {
+ if(u.guid !== NETDATA.registry.machine_guid) {
+ found++;
+ el += '<li id="registry_server_' + u.guid + '"><a href="' + u.url + '">' + u.name + '</a></li>';
+ a1 += '<li id="registry_action_' + u.guid + '"><a href="#" onclick="deleteRegistryModalHandler(\'' + u.guid + '\',\'' + u.name + '\',\'' + u.url + '\'); return false;"><i class="fa fa-trash-o" aria-hidden="true" style="color: #999;"></i></a></li>';
+ }
+ });
+ }
+
+ if(!found) {
+ if(urls)
+ el += '<li><a href="https://github.com/firehol/netdata/wiki/mynetdata-menu-item" style="color: #666;" target="_blank">your netdata server list is empty...</a></li>';
+ else
+ el += '<li><a href="https://github.com/firehol/netdata/wiki/mynetdata-menu-item" style="color: #666;" target="_blank">failed to contact the registry...</a></li>';
+
+ a1 += '<li><a href="#"> </a></li>';
+
+ el += '<li role="separator" class="divider"></li>' +
+ '<li><a href="//london.netdata.rocks/default.html">EU - London (DigitalOcean.com)</a></li>' +
+ '<li><a href="//atlanta.netdata.rocks/default.html">US - Atlanta (CDN77.com)</a></li>' +
+ '<li><a href="//athens.netdata.rocks/default.html">EU - Athens</a></li>';
+ a1 += '<li role="separator" class="divider"></li>' +
+ '<li><a href="#"> </a></li>' +
+ '<li><a href="#"> </a></li>'+
+ '<li><a href="#"> </a></li>';
+ }
+
+ el += '<li role="separator" class="divider"></li>';
+ a1 += '<li role="separator" class="divider"></li>';
+
+ el += '<li><a href="https://github.com/firehol/netdata/wiki/mynetdata-menu-item" style="color: #999;" target="_blank">What is this?</a></li>';
+ a1 += '<li><a href="#" style="color: #999;" onclick="switchRegistryModalHandler(); return false;"><i class="fa fa-cog" aria-hidden="true" style="color: #999;"></i></a></li>'
+
+ document.getElementById('mynetdata_servers').innerHTML = el;
+ document.getElementById('mynetdata_actions1').innerHTML = a1;
+
+ document.getElementById('mynetdata_servers2').innerHTML = el;
+ };
+
</script>
<!-- load the dashboard manager - it will do the rest -->
- <script type="text/javascript" src="dashboard.js?v34"></script>
+ <script type="text/javascript" src="dashboard.js?v35"></script>
</head>
<body data-spy="scroll" data-target="#sidebar">
<nav class="navbar navbar-default navbar-fixed-top" role="banner">
<div class="container">
+ <nav id="mynetdata_nav" class="collapse navbar-collapse navbar-left hidden-sm hidden-xs" role="navigation" style="padding-right: 20px;">
+ <ul class="nav navbar-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="current_view">my-netdata <strong class="caret"></strong></a>
+ <ul class="dropdown-menu scrollable-menu inpagemenu multi-column columns-2" role="menu">
+ <div class="row">
+ <div class="col-sm-6" style="width: 85%; padding-right: 0;">
+ <ul id="mynetdata_servers" class="multi-column-dropdown">
+ </ul>
+ </div>
+ <div class="col-sm-3 hidden-xs" style="width: 15%; padding-left: 0;">
+ <ul id="mynetdata_actions1" class="multi-column-dropdown">
+ </ul>
+ </div>
+ </div>
+ </ul>
+ </li>
+ </ul>
+ </nav>
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
</button>
<a href="/" class="navbar-brand" id="hostname">netdata</a>
</div>
- <nav id="demosites_nav" class="collapse navbar-collapse navbar-left" role="navigation">
+ <nav class="collapse navbar-collapse navbar-right" role="navigation">
<ul class="nav navbar-nav">
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="current_view">demo sites <strong class="caret"></strong></a>
+ <li class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal"><i class="fa fa-cog"></i> settings</a></li>
+ <li class="hidden-sm"><a href="https://github.com/firehol/netdata/wiki" class="btn" target="_blank"><i class="fa fa-github"></i> community</a></li>
+ <li class="hidden-sm" id="updateButton"><a href="#" class="btn" data-toggle="modal" data-target="#updateModal"><i class="fa fa-cloud-download"></i> update</a></li>
+ <li class="hidden-sm"><a href="#" class="btn" data-toggle="modal" data-target="#helpModal"><i class="fa fa-question-circle"></i> help</a></li>
+ <li class="dropdown hidden-md hidden-lg hidden-xs">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="current_view">Menu <strong class="caret"></strong></a>
<ul class="dropdown-menu scrollable-menu inpagemenu" role="menu">
- <li id="demo_eu"><a href="//netdata1.firehol.org?nowelcome">EU - London (DigitalOcean.com)</a></li>
- <li id="demo_us"><a href="//netdata2.firehol.org?nowelcome">US - Atlanta (CDN77.com)</a></li>
- <li id="demo_gr"><a href="//netdata3.firehol.org?nowelcome">EU - Greece</a></li>
- <li role="separator" class="divider"></li>
- <li id="demo_tv"><a href="tv.html">TV Dashboard for 2 servers</a></li>
- <li id="demosites"><a href="demosites.html">Dashboard for monitoring netdata demo sites</a></li>
+ <li><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal"><i class="fa fa-cog"></i> settings</a></li>
+ <li><a href="https://github.com/firehol/netdata/wiki" class="btn" target="_blank"><i class="fa fa-github"></i> community</a></li>
+ <li><a href="#" class="btn" data-toggle="modal" data-target="#helpModal"><i class="fa fa-question-circle"></i> help</a></li>
+ </ul>
+ </li>
+ <li class="dropdown hidden-sm hidden-md hidden-lg">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="current_view">my-netdata <strong class="caret"></strong></a>
+ <ul id="mynetdata_servers2" class="dropdown-menu scrollable-menu inpagemenu" role="menu">
</ul>
</li>
</ul>
</nav>
- <nav class="collapse navbar-collapse navbar-right" role="navigation">
- <ul class="nav navbar-nav">
- <li><a href="#" class="btn" data-toggle="modal" data-target="#optionsModal"><i class="fa fa-cog"></i> settings</a></li>
- <li><a href="https://github.com/firehol/netdata/wiki" class="btn" target="_blank"><i class="fa fa-github"></i> community</a></li>
- <li id="updateButton"><a href="#" class="btn" data-toggle="modal" data-target="#updateModal"><i class="fa fa-cloud-download"></i> update</a></li>
-<!-- <li><a href="old/" class="btn" target="_blank"><i class="fa fa-step-backward"></i> old dashboard</a></li> -->
- <li><a href="#" class="btn" data-toggle="modal" data-target="#helpModal"><i class="fa fa-question-circle"></i> help</a></li>
-<!-- <li><a href="#sec">Visualize</a></li>
- <li><a href="#sec">Prototype</a></li>
---> </ul>
- </nav>
+ </nav>
</div>
</nav>
</div>
</div>
-<script>
+ <div class="modal fade" id="deleteRegistryModal" tabindex="-1" role="dialog" aria-labelledby="deleteRegistryModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
+ <h4 class="modal-title" id="deleteRegistryModalLabel">Delete <span id="deleteRegistryServerName"></span>?</h4>
+ </div>
+ <div class="modal-body">
+ You are about to delete, from your personal list of netdata servers, the following server:
+ <p style="text-align: center; padding-top: 10px; padding-bottom: 10px; line-height: 2;">
+ <b><span id="deleteRegistryServerName2"></span></b>
+ <br/>
+ <b><span id="deleteRegistryServerURL"></span></b>
+ </p>
+ Are you sure you want to do this?
+ <br/>
+ <div style="padding: 10px;"></div>
+ <small>Keep in mind, this server will be added back if and when you visit it again.</small>
+ <br/>
+ <div id="deleteRegistryResponse" style="display: block; width: 100%; text-align: center; padding-top: 20px; color: #fff;"></div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success" data-dismiss="modal">keep it</button>
+ <a href="#" onclick="notifyForDeleteRegistry(true);" type="button" class="btn btn-danger">delete it</a>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="modal fade" id="switchRegistryModal" tabindex="-1" role="dialog" aria-labelledby="switchRegistryModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
+ <h4 class="modal-title" id="switchRegistryModalLabel">Switch Netdata Registry Identity</h4>
+ </div>
+ <div class="modal-body">
+ You can copy and paste the following ID to all your browsers (e.g. work and home).
+ <br/>
+ All the browsers with the same ID will identify <b>you</b>, so please don't share this with others.
+ <p style="text-align: center; padding-top: 10px; padding-bottom: 10px; line-height: 2;">
+ <form action="#">
+ <input type="text" class="form-control" id="switchRegistryPersonGUID" placeholder="your personal ID" maxlength="36" autocomplete="off" style="text-align: center; font-size: 1.4em;">
+ </form>
+ </p>
+ Either copy this ID and paste it to another browser, or paste here the ID you have taken from another browser.
+ <p style="padding-top: 10px;"><small>
+ Keep in mind that:
+ <ul>
+ <li>when you switch ID, your previous ID will be lost forever - this is irreversible.</li>
+ <li>both IDs (your old and the new) must list this netdata at their personal lists.</li>
+ <li>both IDs have to be known by the registry: <b><span id="switchRegistryURL"></span></b>.</li>
+ <li>to get a new ID, just clear your browser cookies.</li>
+ </ul>
+ </small></p>
+ <div id="switchRegistryResponse" style="display: block; width: 100%; text-align: center; padding-top: 20px; color: #fff;"></div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success" data-dismiss="modal">cancel</button>
+ <a href="#" onclick="notifyForSwitchRegistry(true);" type="button" class="btn btn-danger">impersonate</a>
+ </div>
+ </div>
+ </div>
+ </div>
+<script>
var this_is_demo = null;
function isdemo() {
if(this_is_demo !== null) return this_is_demo;
try {
if(typeof document.location.hostname === 'string') {
- if(document.location.hostname === 'netdata.firehol.org' || document.location.hostname === 'netdata1.firehol.org') {
- document.getElementById("demo_eu").className = "active";
- this_is_demo = true;
- }
- else if(document.location.hostname === 'netdata2.firehol.org') {
- document.getElementById("demo_us").className = "active";
- this_is_demo = true;
- }
- else if(document.location.hostname === 'netdata3.firehol.org') {
- document.getElementById("demo_gr").className = "active";
- this_is_demo = true;
- }
+ if(document.location.hostname.endsWith('.my-netdata.io') ||
+ document.location.hostname.endsWith('.mynetdata.io') ||
+ document.location.hostname.endsWith('.netdata.rocks') ||
+ document.location.hostname.endsWith('.firehol.org') ||
+ document.location.hostname.endsWith('.netdata.online'))
+ this_is_demo = true;
}
-
- if(!this_is_demo)
- document.getElementById("demosites_nav").style.visibility = "hidden";
}
catch(error) {
;
document.getElementById('masthead').style.display = 'block';
}
+function switchRegistryModalHandler() {
+ document.getElementById('switchRegistryPersonGUID').value = NETDATA.registry.person_guid;
+ document.getElementById('switchRegistryURL').innerHTML = NETDATA.registry.server;
+ $('#switchRegistryModal').modal('show');
+}
+
+function notifyForSwitchRegistry() {
+ var n = document.getElementById('switchRegistryPersonGUID').value;
+
+ if(n !== '' && n.length === 36) {
+ NETDATA.registry.switch(n, function(result) {
+ if(result !== null) {
+ $('#switchRegistryModal').modal('hide');
+ NETDATA.registry.init();
+ }
+ else {
+ document.getElementById('switchRegistryResponse').innerHTML = "<b>Sorry! The registry rejected your request.</b>";
+ }
+ });
+ }
+ else
+ document.getElementById('switchRegistryResponse').innerHTML = "<b>The ID you have entered is not a GUID.</b>";
+}
+
+var deleteRegistryUrl = null;
+function deleteRegistryModalHandler(guid, name, url) {
+ deleteRegistryUrl = url;
+ document.getElementById('deleteRegistryServerName').innerHTML = name;
+ document.getElementById('deleteRegistryServerName2').innerHTML = name;
+ document.getElementById('deleteRegistryServerURL').innerHTML = url;
+ $('#deleteRegistryModal').modal('show');
+}
+
+function notifyForDeleteRegistry() {
+ if(deleteRegistryUrl) {
+ NETDATA.registry.delete(deleteRegistryUrl, function(result) {
+ if(result !== null) {
+ deleteRegistryUrl = null;
+ $('#deleteRegistryModal').modal('hide');
+ NETDATA.registry.init();
+ }
+ else {
+ document.getElementById('deleteRegistryResponse').innerHTML = "<b>Sorry! this command was rejected by the registry server.</b>";
+ }
+ });
+ }
+}
+
var options = {
sparklines_registry: {},
submenu_names: {},
notifyForUpdate(true);
});
+ $('#deleteRegistryModal').on('hidden.bs.modal', function() {
+ deleteRegistryGuid = null;
+ });
+
if(isdemo()) {
if(!nowelcome) {
setTimeout(function() {
and that you have chown it to be owned by netdata:netdata
-->
<!-- <script type="text/javascript" src="http://my.server:19999/dashboard.js"></script> -->
- <script type="text/javascript" src="dashboard.js"></script>
+ <script type="text/javascript" src="dashboard.js?v35"></script>
<script>
// Set options for TV operation