]> arthur.barton.de Git - netdata.git/commitdiff
Merge remote-tracking branch 'fredericopissarra/changes' into registry
authorCosta Tsaousis <costa@tsaousis.gr>
Fri, 13 May 2016 17:08:47 +0000 (20:08 +0300)
committerCosta Tsaousis <costa@tsaousis.gr>
Fri, 13 May 2016 17:08:47 +0000 (20:08 +0300)
Conflicts:
src/common.c
src/web_client.c

39 files changed:
.travis.yml
CMakeLists.txt
README.md
configure.ac
netdata-installer.sh
profile/benchmark-dictionary.c
src/Makefile.am
src/appconfig.c
src/apps_plugin.c
src/avl.c
src/avl.h
src/common.c
src/common.h
src/daemon.c
src/dictionary.c
src/dictionary.h
src/log.c
src/log.h
src/main.c
src/main.h
src/plugin_proc.c
src/plugin_tc.c
src/popen.c
src/proc_interrupts.c
src/proc_softirqs.c
src/registry.c [new file with mode: 0644]
src/registry.h [new file with mode: 0644]
src/rrd.c
src/rrd.h
src/sys_fs_cgroup.c
src/web_client.c
src/web_client.h
system/netdata.service.in
web/dashboard.html
web/dashboard.js
web/demo.html
web/demosites.html
web/index.html
web/tv.html

index f2fc25f3a678116671addecac27c6df3be266113..0f3670ba8fc615eee04f235ca8daed408c1d6a2f 100644 (file)
@@ -4,7 +4,7 @@ compiler:
  - 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
index 1c42a9138cc7beca15fa03b0cf323b90399bd622..967df77e244b0d6046e79fecbfa7b4ba3b961ba9 100755 (executable)
@@ -91,7 +91,7 @@ set(APPS_PLUGIN_SOURCE_FILES
 
 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})
index 59ef9eadd1e0fb312855c5979e7b0f72baa03984..3c53bbf424d0641928aa40f3591aae1cfe76fa72 100644 (file)
--- a/README.md
+++ b/README.md
@@ -138,3 +138,7 @@ It should run on **any Linux** system. It has been tested on:
 ## 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")
+
index 177b6ddbd54266624aa2c78a96687742c138d1a7..fd69e71d16738262715bd1b2c08ad0b4c03ed3c3 100644 (file)
@@ -92,6 +92,10 @@ if test -z "${MATH_LIBS}"; then
        )
 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],
@@ -139,6 +143,7 @@ fi
 
 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"])
index cc27a61c246ec496d9ecb1696781eafe1897637a..d8c073ae57771730df547f789723327b0594e263 100755 (executable)
@@ -11,6 +11,7 @@ then
 fi
 
 LC_ALL=C
+umask 022
 
 # you can set CFLAGS before running installer
 CFLAGS="${CFLAGS--O3}"
@@ -66,7 +67,7 @@ If you need to pass different CFLAGS, use something like this:
 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:
 
@@ -120,6 +121,7 @@ It will be installed at these locations:
   - 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
 
@@ -247,15 +249,16 @@ You many need to check these:
 
 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:
 
@@ -407,6 +410,7 @@ defport=19999
 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" )"
@@ -425,7 +429,7 @@ if [ ! -d "${NETDATA_RUN_DIR}" ]
 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
@@ -716,6 +720,7 @@ else
        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
index 163f44257f82e7aded614e56bcacad8f75937489..8c414db8e7c4c02c0b55b583821785d02e434719 100644 (file)
@@ -22,14 +22,14 @@ struct myvalue {
 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;
 
        // ------------------------------------------------------------------------
 
index 9226e4c6ca686d7c7d330b66bc82da90fb47c763..69d1a2fe5a37216d85378a47fe554091d1b05fd2 100644 (file)
@@ -4,6 +4,7 @@
 MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
 
 AM_CPPFLAGS = \
+       -DVARLIB_DIR="\"$(varlibdir)\"" \
        -DCACHE_DIR="\"$(cachedir)\"" \
        -DCONFIG_DIR="\"$(configdir)\"" \
        -DLOG_DIR="\"$(logdir)\"" \
@@ -58,6 +59,7 @@ netdata_SOURCES = \
        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 \
index 770f315e2eb51c78a3fa880cabedfd28f8bfcfe2..c164f232e0d35137d469cc558085c5de59e6d762 100644 (file)
@@ -1,3 +1,20 @@
+
+/*
+ * 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
@@ -45,7 +62,7 @@ struct config {
        char *name;
 
        struct config_value *values;
-       avl_tree values_index;
+       avl_tree_lock values_index;
 
        struct config *next;
 
@@ -54,7 +71,35 @@ struct config {
 
 
 // ----------------------------------------------------------------------------
-// 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; }
 
@@ -64,20 +109,21 @@ static int config_value_compare(void* a, void* b) {
        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; }
 
@@ -87,63 +133,32 @@ static int config_compare(void* a, void* b) {
        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);
 
@@ -154,14 +169,11 @@ struct config *config_create(const char *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) {
@@ -170,98 +182,43 @@ struct config *config_create(const char *section)
        }
        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)
@@ -270,8 +227,8 @@ char *config_get(const char *section, const char *name, const char *default_valu
 
        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) {
@@ -348,7 +305,7 @@ const char *config_set_default(const char *section, const char *name, const char
 
        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);
@@ -376,8 +333,8 @@ const char *config_set(const char *section, const char *name, const char *value)
 
        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);
@@ -415,6 +372,94 @@ int config_set_boolean(const char *section, const char *name, int 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;
@@ -440,9 +485,9 @@ void generate_config(BUFFER *wb, int only_changed)
                                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;
 
@@ -451,15 +496,13 @@ void generate_config(BUFFER *wb, int only_changed)
                                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;
@@ -470,7 +513,7 @@ void generate_config(BUFFER *wb, int only_changed)
 
                                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)) {
@@ -478,10 +521,9 @@ void generate_config(BUFFER *wb, int only_changed)
                                        }
                                        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();
        }
 }
-
index d795d3dcbddddba4f97e0861ac19c43214165a1f..8a330ae75e28e997dd7a9f630b9a25397e5339e2 100644 (file)
 #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
@@ -997,14 +1001,7 @@ int file_descriptor_iterator(avl *a) { if(a) {}; return 0; }
 
 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) {
@@ -1017,7 +1014,7 @@ static struct file_descriptor *file_descriptor_find(const char *name, uint32_t h
        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;
 }
 
@@ -2412,6 +2409,15 @@ int main(int argc, char **argv)
        }
        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;
index fd4fb1420637e0668597cbe94803f48b0b4622da..db1984945380407f8b8100e76096fa8f5cd3bc93 100644 (file)
--- a/src/avl.c
+++ b/src/avl.c
@@ -19,9 +19,6 @@
 #include "avl.h"
 #include "log.h"
 
-/* Private methods */
-int _avl_removeroot(avl_tree* t);
-
 /* Swing to the left
  * Warning: no balance maintainance
  */
@@ -69,7 +66,7 @@ void avl_nasty(avl* root) {
  * 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;
@@ -86,7 +83,7 @@ int _avl_insert(avl_tree* t, avl* a) {
                        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;
@@ -117,7 +114,7 @@ int _avl_insert(avl_tree* t, avl* a) {
                        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;
@@ -144,36 +141,16 @@ int _avl_insert(avl_tree* t, avl* a) {
                }
        }
 }
-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 */
@@ -181,7 +158,7 @@ int _avl_remove(avl_tree* t, avl* a) {
                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++) {
@@ -215,7 +192,7 @@ int _avl_remove(avl_tree* t, avl* a) {
                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--) {
@@ -246,31 +223,10 @@ int _avl_remove(avl_tree* t, avl* a) {
        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) {
@@ -296,7 +252,7 @@ int _avl_removeroot(avl_tree* t) {
                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;
@@ -306,33 +262,12 @@ int _avl_removeroot(avl_tree* t) {
        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;
@@ -349,7 +284,7 @@ int _avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret) {
                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;
                }
@@ -366,7 +301,7 @@ int _avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret) {
                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;
                }
@@ -374,17 +309,34 @@ int _avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret) {
        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);
@@ -392,21 +344,12 @@ int avl_range(avl_tree* t, avl* a, avl* b, int (*iter)(avl*), avl** ret) {
        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;
@@ -421,5 +364,32 @@ void avl_init(avl_tree* t, int (*compar)(void* a, void* b)) {
                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;
 }
index 2d1fbc5376cb65933945cd7e77fcbfe4d959301e..04e95ea80c2501cd4aa83cd7c5eb4cc0a231faff 100644 (file)
--- a/src/avl.h
+++ b/src/avl.h
 
 #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 */
@@ -32,8 +41,13 @@ typedef struct avl {
 
 /* 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
@@ -42,7 +56,7 @@ typedef struct avl_tree {
        pthread_rwlock_t rwlock;
 #endif /* AVL_LOCK_WITH_MUTEX */
 #endif /* AVL_WITHOUT_PTHREADS */
-} avl_tree;
+} avl_tree_lock;
 
 /* Public methods */
 
@@ -50,35 +64,41 @@ typedef struct avl_tree {
  * 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 */
index 460c8d284f57f5819cd2a205871858cbeeb056fa..ea94ecc33baead4af53305c687ee01749dc96e45 100644 (file)
@@ -776,6 +776,26 @@ pid_t gettid(void)
        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;
index f2a861f7f2277436dabf7d4fb8df916eb104306f..1460d922314f9ce7a46be88298392296f3601680 100644 (file)
@@ -1,6 +1,7 @@
 #include <stdarg.h>
 #include <sys/time.h>
 #include <sys/resource.h>
+#include <stdio.h>
 
 #ifndef NETDATA_COMMON_H
 #define NETDATA_COMMON_H 1
@@ -43,6 +44,8 @@ extern pid_t gettid(void);
 
 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
index 6b671bee1fa7a6f4b1ea7bed26576202e1ebe0b9..2a56ae0cce38765a52b00f4026f4b867fe1977bf 100644 (file)
@@ -30,49 +30,8 @@ int pidfd = -1;
 
 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)
@@ -208,6 +167,8 @@ int become_daemon(int dont_fork, int close_all_files, const char *user, const ch
                                *access_fd = -1;
                                return -1;
                        }
+                       if(setvbuf(*access_fp, NULL, _IOLBF, 0) != 0)
+                               error("Cannot set line buffering on access.log");
                }
        }
 
@@ -247,10 +208,6 @@ int become_daemon(int dont_fork, int close_all_files, const char *user, const ch
                }
        }
 
-       signal(SIGCHLD,  SIG_IGN);
-       signal(SIGHUP,   SIG_IGN);
-       signal(SIGWINCH, SIG_IGN);
-
        // fork() again
        if(!dont_fork) {
                int i = fork();
@@ -301,6 +258,10 @@ int become_daemon(int dont_fork, int close_all_files, const char *user, const ch
                        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);
@@ -310,6 +271,10 @@ int become_daemon(int dont_fork, int close_all_files, const char *user, const ch
                        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);
index 7c6a8342d04b49906e8938ce057a97389d76b689..b2151ff6d813f688f0c543951a272fe7aef82849 100644 (file)
@@ -1,6 +1,7 @@
 #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; }
 
@@ -31,36 +57,27 @@ static inline NAME_VALUE *dictionary_name_value_index_find_nolock(DICTIONARY *di
        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);
 
@@ -74,15 +91,10 @@ static NAME_VALUE *dictionary_name_value_create_nolock(DICTIONARY *dict, const c
                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;
 }
 
@@ -93,15 +105,21 @@ static void dictionary_name_value_destroy_nolock(DICTIONARY *dict, NAME_VALUE *n
 
        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.");
@@ -137,27 +155,26 @@ void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t val
 
        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;
 
@@ -167,10 +184,13 @@ void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t val
                        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;
 }
 
@@ -190,20 +210,66 @@ void *dictionary_get(DICTIONARY *dict, const char *name) {
        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;
 }
index e9edab4c231d0d54fa2954d86862c54a74e21479..575f2827105c443644bfe6349769383bab58db47 100644 (file)
@@ -11,30 +11,35 @@ typedef struct name_value {
 
        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 */
index 2db0c662e3bad87442147222da47d081ff4e55e3..717126a69e14f74854c5d029250f0735c33ec7ef 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -131,7 +131,7 @@ void debug_int( const char *file, const char *function, const unsigned long line
        vfprintf( stdout, fmt, args );
        va_end( args );
        fprintf(stdout, "\n");
-       fflush( stdout );
+       // fflush( stdout );
 
        if(output_log_syslog) {
                va_start( args, fmt );
@@ -229,7 +229,7 @@ void log_access( const char *fmt, ... )
                vfprintf( stdaccess, fmt, args );
                va_end( args );
                fprintf( stdaccess, "\n");
-               fflush( stdaccess );
+               // fflush( stdaccess );
        }
 
        if(access_log_syslog) {
index 1607826e6748b1ac81d226e2056772c3538d2257..3f811d9fbe93bf3160a14049590b0ceb8356607f 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -26,6 +26,7 @@
 #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
index aebeee1b012e65bbe0a9da32733bdb5dd18e35e4..ec3c59ca79f937af6c3db3d0d924730f75b4c0b0 100644 (file)
 #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)
 {
@@ -122,19 +123,7 @@ int killpid(pid_t pid, int sig)
        }
        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:
@@ -274,6 +263,7 @@ int main(int argc, char **argv)
        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);
@@ -409,6 +399,42 @@ int main(int argc, char **argv)
 
                // --------------------------------------------------------------------
 
+               // 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);
@@ -470,18 +496,17 @@ int main(int argc, char **argv)
        // 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);
@@ -489,25 +514,6 @@ int main(int argc, char **argv)
        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
 
@@ -519,6 +525,11 @@ int main(int argc, char **argv)
                        info("Successfully set pthread stacksize to %zu bytes", wanted_stacksize);
        }
 
+       // --------------------------------------------------------------------
+       // initialize the registry
+
+       registry_init();
+
        // ------------------------------------------------------------------------
        // spawn the threads
 
@@ -541,18 +552,22 @@ int main(int argc, char **argv)
                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);
+               }
+       }
 }
index 6a90efd9d42f6f9e47915af081963b4da4fa4a2b..d9edda58e6c71574c3fe78dc05ccfbf486431f31 100644 (file)
@@ -1,7 +1,9 @@
 #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);
index 632ec1e86c25c202993d8179b5f89e881a06b9f7..a147d971f8128609d1e9416309ef7e35138e8765 100644 (file)
@@ -14,6 +14,7 @@
 #include "rrd.h"
 #include "plugin_proc.h"
 #include "main.h"
+#include "registry.h"
 
 void *proc_main(void *ptr)
 {
@@ -240,7 +241,7 @@ 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);
@@ -270,7 +271,7 @@ void *proc_main(void *ptr)
 
                        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);
                        }
@@ -283,7 +284,7 @@ void *proc_main(void *ptr)
 
                        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);
                        }
@@ -296,7 +297,7 @@ void *proc_main(void *ptr)
 
                        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);
@@ -306,6 +307,10 @@ void *proc_main(void *ptr)
                        rrddim_set(stbytes, "in", global_statistics.bytes_received);
                        rrddim_set(stbytes, "out", global_statistics.bytes_sent);
                        rrdset_done(stbytes);
+
+                       // ----------------------------------------------------------------
+
+                       registry_statistics();
                }
        }
 
index 3f57e034716c054c5fd8013de6ca02cbfd45ce7d..511dd1622ddf8124e390c883a024e44d89348fcf 100644 (file)
@@ -93,25 +93,18 @@ static int tc_device_compare(void* a, void* b) {
 
 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;
 }
 
@@ -130,18 +123,18 @@ static int tc_class_compare(void* a, void* b) {
 #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;
@@ -161,7 +154,7 @@ static void tc_class_free(struct tc_device *n, struct tc_class *c) {
        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) {
@@ -186,7 +179,7 @@ static void tc_device_classes_cleanup(struct tc_device *d) {
        }
 }
 
-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;
 
@@ -296,7 +289,7 @@ static void tc_device_commit(struct tc_device *d)
        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) {
@@ -310,7 +303,7 @@ static void tc_device_set_class_name(struct tc_device *d, char *id, char *name)
        }
 }
 
-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;
 
@@ -320,7 +313,7 @@ static void tc_device_set_device_name(struct tc_device *d, char *name) {
        }
 }
 
-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;
 
@@ -331,7 +324,7 @@ static void tc_device_set_device_family(struct tc_device *d, char *family) {
        // 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);
 
@@ -347,20 +340,7 @@ static struct tc_device *tc_device_create(char *id)
                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) {
@@ -376,7 +356,7 @@ static struct tc_device *tc_device_create(char *id)
        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);
 
@@ -418,7 +398,7 @@ static struct tc_class *tc_class_add(struct tc_device *n, char *id, char *parent
        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;
@@ -438,7 +418,7 @@ static void tc_device_free(struct tc_device *n)
        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);
@@ -459,7 +439,7 @@ static inline int tc_space(char c) {
        }
 }
 
-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;
 
index 882a4cc5a6ba88537327cd4f53d8b0413d71ca43..06f27c0b7c4c6ec69a5a4342145137a436cdc98a 100644 (file)
@@ -114,10 +114,27 @@ FILE *mypopen(const char *command, pid_t *pidptr)
 #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);
 }
 
index 8264546cc7ef61cab0a85934d02e34ff0be16b81..ad00c2022af532178f03cdc1c5b6cc5fd9788fb8 100644 (file)
 #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;
@@ -44,7 +49,6 @@ static struct interrupt *alloc_interrupts(int lines) {
 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) {};
@@ -76,8 +80,6 @@ int do_proc_interrupts(int update_every, unsigned long long dt) {
                        if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0)
                                cpus++;
                }
-
-               if(cpus > MAX_INTERRUPT_CPUS) cpus = MAX_INTERRUPT_CPUS;
        }
 
        if(!cpus) {
@@ -86,12 +88,12 @@ int do_proc_interrupts(int update_every, unsigned long long dt) {
        }
 
        // 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;
 
@@ -139,15 +141,17 @@ int do_proc_interrupts(int update_every, unsigned long long dt) {
                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);
 
@@ -166,15 +170,17 @@ int do_proc_interrupts(int update_every, unsigned long long dt) {
                                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);
                }
index f350a54dc08c0fd5c140883b6e17c9f8efe0f20d..96b5d3d30224fa42b18ce5917d838b4135bc7ee3 100644 (file)
 #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;
@@ -76,8 +81,6 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
                        if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0)
                                cpus++;
                }
-
-               if(cpus > MAX_INTERRUPT_CPUS) cpus = MAX_INTERRUPT_CPUS;
        }
 
        if(!cpus) {
@@ -86,12 +89,12 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
        }
 
        // 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;
 
@@ -129,15 +132,17 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
                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);
 
@@ -153,8 +158,9 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
                                // 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
 
@@ -164,15 +170,17 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
                                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);
                }
diff --git a/src/registry.c b/src/registry.c
new file mode 100644 (file)
index 0000000..4e88dc6
--- /dev/null
@@ -0,0 +1,2057 @@
+#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(&registry.persons_lock);
+}
+
+static inline void registry_persons_unlock(void) {
+       pthread_mutex_unlock(&registry.persons_lock);
+}
+
+static inline void registry_machines_lock(void) {
+       pthread_mutex_lock(&registry.machines_lock);
+}
+
+static inline void registry_machines_unlock(void) {
+       pthread_mutex_unlock(&registry.machines_lock);
+}
+
+static inline void registry_urls_lock(void) {
+       pthread_mutex_lock(&registry.urls_lock);
+}
+
+static inline void registry_urls_unlock(void) {
+       pthread_mutex_unlock(&registry.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(&registry.person_urls_lock);
+}
+
+static inline void registry_person_urls_unlock(PERSON *p) {
+       (void)p;
+       pthread_mutex_unlock(&registry.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(&registry.machine_urls_lock);
+}
+
+static inline void registry_machine_urls_unlock(MACHINE *m) {
+       (void)m;
+       pthread_mutex_unlock(&registry.machine_urls_lock);
+}
+
+static inline void registry_log_lock(void) {
+       pthread_mutex_lock(&registry.log_lock);
+}
+
+static inline void registry_log_unlock(void) {
+       pthread_mutex_unlock(&registry.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(&registry.persons_lock, NULL);
+       pthread_mutex_init(&registry.machines_lock, NULL);
+       pthread_mutex_init(&registry.urls_lock, NULL);
+       pthread_mutex_init(&registry.person_urls_lock, NULL);
+       pthread_mutex_init(&registry.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 */
diff --git a/src/registry.h b/src/registry.h
new file mode 100644 (file)
index 0000000..d95383b
--- /dev/null
@@ -0,0 +1,23 @@
+#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 */
index d502e52a67516339ded57b37b242a30f11f40c9c..5971afb2f71d8daf3da497d3c42b6dc856ac3ca1 100644 (file)
--- a/src/rrd.c
+++ b/src/rrd.c
@@ -50,27 +50,20 @@ static int rrdset_compare(void* a, void* b) {
        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;
 }
 
@@ -92,24 +85,17 @@ static int rrdset_compare_name(void* a, void* b) {
        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;
@@ -118,7 +104,7 @@ static RRDSET *rrdset_index_find_name(const char *name, uint32_t hash) {
        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))
@@ -143,15 +129,15 @@ static int rrddim_compare(void* a, void* b) {
        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;
 }
 
@@ -455,7 +441,7 @@ RRDSET *rrdset_create(const char *type, const char *id, const char *name, const
        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);
index 4211b7d563d103fabe8eaaa8d65dd666efa30884..60eeeb04ffe65226cd565567d65dd3c92d7e4cc4 100644 (file)
--- a/src/rrd.h
+++ b/src/rrd.h
@@ -246,7 +246,7 @@ struct rrdset {
        // ------------------------------------------------------------------------
        // 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;
index 4c091d544e2fdd9279ef05d132de533a52491637..9f3d3f0fdbc0fa17fcbbcc998182ed878cb83411 100644 (file)
@@ -1292,7 +1292,7 @@ void *cgroups_main(void *ptr)
 
                        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);
index 2c07e9876acb2dd025d2dfc4662eb88146a3b683..f1e368ff6923337ef38f1ccb806b9645f0db7847 100644 (file)
@@ -26,6 +26,7 @@
 #include "global_statistics.h"
 #include "rrd.h"
 #include "rrd2json.h"
+#include "registry.h"
 
 #include "web_client.h"
 #include "../config.h"
@@ -128,6 +129,7 @@ struct web_client *web_client_create(int listener)
                return NULL;
        }
 
+       w->origin[0] = '*';
        w->wait_receive = 1;
 
        if(web_clients) web_clients->prev = w;
@@ -173,8 +175,17 @@ void web_client_reset(struct web_client *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);
@@ -644,7 +655,7 @@ int web_client_api_request_v1_data(struct web_client *w, char *url)
                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
@@ -784,19 +795,148 @@ cleanup:
        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);
@@ -1043,108 +1183,221 @@ cleanup:
 }
 */
 
-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, "/?&");
@@ -1174,7 +1427,40 @@ void web_client_process(struct web_client *w) {
                                                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);
 
@@ -1216,34 +1502,7 @@ void web_client_process(struct web_client *w) {
                                        // 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;
@@ -1262,35 +1521,6 @@ void web_client_process(struct web_client *w) {
                                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);
@@ -1411,6 +1641,10 @@ void web_client_process(struct web_client *w) {
                        code_msg = "Not Found";
                        break;
 
+               case 412:
+                       code_msg = "Preconditions Failed";
+                       break;
+
                default:
                        code_msg = "Internal Server Error";
                        break;
@@ -1424,19 +1658,27 @@ void web_client_process(struct web_client *w) {
                "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
                        );
        }
index 3823dbc9120c094ac87e3d77bb4cf6b5a812b1e5..de1e1ec51bb68a36ba6c3e05589e223143f435e9 100644 (file)
@@ -11,6 +11,7 @@
 #include <netdb.h>
 
 #include "web_buffer.h"
+#include "dictionary.h"
 
 #define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
 extern int web_client_timeout;
@@ -26,6 +27,8 @@ extern int web_enable_gzip;
 #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
@@ -58,8 +61,13 @@ struct web_client {
 
        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;
 
index bc26cc9dcc83aec3df4adcf5e062af9f098cde3f..91db6122d8c6a0f8d2ede7f43bdc7c92ebf62aaa 100644 (file)
@@ -9,7 +9,8 @@ User=root
 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]
index fd505078d8b54b5d16f7105c20016add559d6848..41bd4cbd9812518ee8ae7c3304820160dadc10a0 100644 (file)
@@ -646,4 +646,4 @@ So, to avoid flashing the charts, we destroy and re-create the charts on each up
        <!-- <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>
index c05d1546027bf117f445795cbd1b9b594271f22b..85620a7b42c78e8b9bc78ee4e5068c3f62f08033 100644 (file)
@@ -12,6 +12,9 @@
 // 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);
 
index 8a6c0c12954cad3ec4aff04326eed15fe3fcedb7..341156b26949592bc3cabef5b74577739549bcfd 100644 (file)
@@ -11,7 +11,7 @@
        <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
index caad81b20935b2a49f6f639f50c535c2f07e45eb..703407766980740cecde7e3fb2c62a6682bafaaf 100644 (file)
@@ -42,7 +42,7 @@
                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 --
@@ -184,7 +184,7 @@ body {
                        <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%"
@@ -195,7 +195,7 @@ body {
                                </div>
                                <div class="mygauge-button">
                                        <br/>&nbsp;<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>
@@ -204,7 +204,7 @@ body {
                        <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%"
@@ -215,7 +215,7 @@ body {
                                </div>
                                <div class="mygauge-button">
                                        <br/>&nbsp;<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>
@@ -224,7 +224,7 @@ body {
                        <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%"
@@ -235,7 +235,7 @@ body {
                                </div>
                                <div class="mygauge-button">
                                        <br/>&nbsp;<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;">
                                                &nbsp;
                                        </div>
@@ -287,7 +287,7 @@ body {
                                </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"
@@ -307,7 +307,7 @@ body {
                                </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"
@@ -327,7 +327,7 @@ body {
                                </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"
@@ -349,7 +349,7 @@ body {
                                </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"
@@ -369,7 +369,7 @@ body {
                                </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"
@@ -389,7 +389,7 @@ body {
                                </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"
@@ -426,7 +426,7 @@ body {
                <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%"
@@ -437,7 +437,7 @@ body {
 
                        <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%"
@@ -449,7 +449,7 @@ body {
 
                        <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%"
@@ -461,7 +461,7 @@ body {
                <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%"
@@ -473,7 +473,7 @@ body {
 
                        <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%"
@@ -485,7 +485,7 @@ body {
 
                        <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%"
@@ -516,7 +516,7 @@ body {
                        </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"
@@ -536,7 +536,7 @@ body {
                        </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"
@@ -556,7 +556,7 @@ body {
                        </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"
@@ -581,7 +581,7 @@ body {
        <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%"
@@ -592,7 +592,7 @@ body {
 
                <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%"
@@ -603,7 +603,7 @@ body {
 
                <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%"
@@ -636,7 +636,7 @@ body {
                        </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"
@@ -656,7 +656,7 @@ body {
                        </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"
@@ -676,7 +676,7 @@ body {
                        </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"
index 90db3eeb54fbac61e6ed277ac65919a55ff7dbb9..df48f2fa6682fb30db5010a5760a05e89352e865 100644 (file)
                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="#">&nbsp;</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="#">&nbsp;</a></li>' +
+                                               '<li><a href="#">&nbsp;</a></li>'+
+                                               '<li><a href="#">&nbsp;</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">&times;</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">&times;</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;
@@ -796,22 +953,13 @@ function isdemo() {
 
        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) {
                ;
@@ -824,6 +972,54 @@ if(isdemo()) {
        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: {},
@@ -2008,6 +2204,10 @@ function finalizePage() {
                notifyForUpdate(true);
        });
 
+       $('#deleteRegistryModal').on('hidden.bs.modal', function() {
+               deleteRegistryGuid = null;
+       });
+
        if(isdemo()) {
                if(!nowelcome) {
                        setTimeout(function() {
index 1588dd302c9929689c33ac508de09033b2c52b59..2dd42654e0c9e3a8a9f79b5331ecfdb1f28cd56f 100644 (file)
@@ -40,7 +40,7 @@
                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