summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLennart Weller <lhw@ring0.de>2016-05-25 10:36:27 +0000
committerLennart Weller <lhw@ring0.de>2016-05-25 10:36:27 +0000
commit39ecaf8d995e3fefea54f260b8b42bbb9cc806d6 (patch)
tree02ecee3bf4d7dad9dfad2e1799616716f5ffbf03 /src
parentbroken logrotate (diff)
parentImported Upstream version 1.2.0 (diff)
downloadnetdata-39ecaf8d995e3fefea54f260b8b42bbb9cc806d6.tar.xz
netdata-39ecaf8d995e3fefea54f260b8b42bbb9cc806d6.zip
Merge tag 'upstream/1.2.0'
Upstream version 1.2.0
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am16
-rw-r--r--src/Makefile.in34
-rw-r--r--src/appconfig.c341
-rw-r--r--src/apps_plugin.c151
-rw-r--r--src/avl.c184
-rw-r--r--src/avl.h42
-rw-r--r--src/common.c368
-rw-r--r--src/common.h14
-rw-r--r--src/daemon.c84
-rw-r--r--src/dictionary.c245
-rw-r--r--src/dictionary.h26
-rw-r--r--src/log.c3
-rw-r--r--src/log.h3
-rw-r--r--src/main.c121
-rw-r--r--src/main.h4
-rw-r--r--src/plugin_proc.c57
-rw-r--r--src/plugin_tc.c66
-rw-r--r--src/plugins_d.c18
-rw-r--r--src/popen.c21
-rw-r--r--src/proc_diskstats.c68
-rw-r--r--src/proc_interrupts.c69
-rw-r--r--src/proc_loadavg.c2
-rw-r--r--src/proc_meminfo.c2
-rw-r--r--src/proc_net_dev.c4
-rw-r--r--src/proc_net_ip_vs_stats.c2
-rw-r--r--src/proc_net_netstat.c2
-rw-r--r--src/proc_net_rpc_nfsd.c2
-rw-r--r--src/proc_net_snmp.c2
-rw-r--r--src/proc_net_snmp6.c2
-rw-r--r--src/proc_net_stat_conntrack.c2
-rw-r--r--src/proc_net_stat_synproxy.c2
-rw-r--r--src/proc_self_mountinfo.c231
-rw-r--r--src/proc_self_mountinfo.h42
-rw-r--r--src/proc_softirqs.c67
-rw-r--r--src/proc_stat.c2
-rw-r--r--src/proc_sys_kernel_random_entropy_avail.c2
-rw-r--r--src/proc_vmstat.c2
-rw-r--r--src/procfile.c6
-rw-r--r--src/registry.c1838
-rw-r--r--src/registry.h23
-rw-r--r--src/rrd.c95
-rw-r--r--src/rrd.h2
-rw-r--r--src/rrd2json.c57
-rw-r--r--src/storage_number.c6
-rw-r--r--src/sys_fs_cgroup.c1310
-rw-r--r--src/sys_kernel_mm_ksm.c20
-rw-r--r--src/unit_test.c6
-rw-r--r--src/url.c28
-rw-r--r--src/web_buffer.c155
-rw-r--r--src/web_buffer.h2
-rw-r--r--src/web_client.c513
-rw-r--r--src/web_client.h9
-rw-r--r--src/web_server.c4
53 files changed, 5314 insertions, 1063 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index a6808f424..e9fc8f332 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,6 +4,7 @@
MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
AM_CPPFLAGS = \
+ -DVARLIB_DIR="\"$(varlibdir)\"" \
-DCACHE_DIR="\"$(cachedir)\"" \
-DCONFIG_DIR="\"$(configdir)\"" \
-DLOG_DIR="\"$(logdir)\"" \
@@ -15,6 +16,7 @@ AM_CFLAGS = \
$(OPTIONAL_MATH_CFLAGS) \
$(OPTIONAL_NFACCT_CLFAGS) \
$(OPTIONAL_ZLIB_CFLAGS) \
+ $(OPTIONAL_UUID_CFLAGS) \
$(NULL)
sbin_PROGRAMS = netdata
@@ -52,10 +54,13 @@ netdata_SOURCES = \
proc_net_stat_conntrack.c \
proc_net_stat_synproxy.c \
proc_stat.c \
+ proc_self_mountinfo.c proc_self_mountinfo.h \
proc_sys_kernel_random_entropy_avail.c \
proc_vmstat.c \
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 \
@@ -69,6 +74,7 @@ netdata_LDADD = \
$(OPTIONAL_MATH_LIBS) \
$(OPTIONAL_NFACCT_LIBS) \
$(OPTIONAL_ZLIB_LIBS) \
+ $(OPTIONAL_UUID_LIBS) \
$(NULL)
apps_plugin_SOURCES = \
@@ -82,12 +88,16 @@ apps_plugin_SOURCES = \
install-data-hook:
if [ `id -u` == 0 ]; then \
chown root '$(DESTDIR)$(pluginsdir)/apps.plugin' && \
- chmod 4755 '$(DESTDIR)$(pluginsdir)/apps.plugin'; \
+ chmod 0755 '$(DESTDIR)$(pluginsdir)/apps.plugin' && \
+ ( setcap cap_dac_read_search,cap_sys_ptrace+ep '$(DESTDIR)$(pluginsdir)/apps.plugin' || \
+ chmod 4755 '$(DESTDIR)$(pluginsdir)/apps.plugin' ); \
else \
echo; \
echo "ATTENTION"; \
echo; \
- echo "setuid bit of $(pluginsdir)/apps.plugin must be set, please execute as root:"; \
- echo "chown root '$(pluginsdir)/apps.plugin' && chmod 4755 '$(pluginsdir)/apps.plugin'"; \
+ echo "$(pluginsdir)/apps.plugin requires escalated capabilities:"; \
+ echo "sudo chown root '$(DESTDIR)$(pluginsdir)/apps.plugin'"; \
+ echo "sudo chmod 0755 '$(DESTDIR)$(pluginsdir)/apps.plugin'"; \
+ echo "sudo setcap cap_dac_read_search,cap_sys_ptrace+ep '$(DESTDIR)$(pluginsdir)/apps.plugin'"; \
echo; \
fi
diff --git a/src/Makefile.in b/src/Makefile.in
index 20aa81ef0..11b68946e 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -113,15 +113,17 @@ am_netdata_OBJECTS = appconfig.$(OBJEXT) avl.$(OBJEXT) \
proc_net_rpc_nfsd.$(OBJEXT) proc_net_snmp.$(OBJEXT) \
proc_net_snmp6.$(OBJEXT) proc_net_stat_conntrack.$(OBJEXT) \
proc_net_stat_synproxy.$(OBJEXT) proc_stat.$(OBJEXT) \
+ proc_self_mountinfo.$(OBJEXT) \
proc_sys_kernel_random_entropy_avail.$(OBJEXT) \
proc_vmstat.$(OBJEXT) sys_kernel_mm_ksm.$(OBJEXT) \
- procfile.$(OBJEXT) rrd.$(OBJEXT) rrd2json.$(OBJEXT) \
- storage_number.$(OBJEXT) unit_test.$(OBJEXT) url.$(OBJEXT) \
- web_buffer.$(OBJEXT) web_client.$(OBJEXT) web_server.$(OBJEXT)
+ sys_fs_cgroup.$(OBJEXT) procfile.$(OBJEXT) registry.$(OBJEXT) \
+ rrd.$(OBJEXT) rrd2json.$(OBJEXT) storage_number.$(OBJEXT) \
+ unit_test.$(OBJEXT) url.$(OBJEXT) web_buffer.$(OBJEXT) \
+ web_client.$(OBJEXT) web_server.$(OBJEXT)
netdata_OBJECTS = $(am_netdata_OBJECTS)
am__DEPENDENCIES_1 =
netdata_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
- $(am__DEPENDENCIES_1)
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
am__v_P_0 = false
@@ -249,6 +251,8 @@ OPTIONAL_MATH_CLFAGS = @OPTIONAL_MATH_CLFAGS@
OPTIONAL_MATH_LIBS = @OPTIONAL_MATH_LIBS@
OPTIONAL_NFACCT_CLFAGS = @OPTIONAL_NFACCT_CLFAGS@
OPTIONAL_NFACCT_LIBS = @OPTIONAL_NFACCT_LIBS@
+OPTIONAL_UUID_CLFAGS = @OPTIONAL_UUID_CLFAGS@
+OPTIONAL_UUID_LIBS = @OPTIONAL_UUID_LIBS@
OPTIONAL_ZLIB_CLFAGS = @OPTIONAL_ZLIB_CLFAGS@
OPTIONAL_ZLIB_LIBS = @OPTIONAL_ZLIB_LIBS@
PACKAGE = @PACKAGE@
@@ -270,6 +274,8 @@ PTHREAD_LIBS = @PTHREAD_LIBS@
SET_MAKE = @SET_MAKE@
SHELL = @SHELL@
STRIP = @STRIP@
+UUID_CFLAGS = @UUID_CFLAGS@
+UUID_LIBS = @UUID_LIBS@
VERSION = @VERSION@
ZLIB_CFLAGS = @ZLIB_CFLAGS@
ZLIB_LIBS = @ZLIB_LIBS@
@@ -330,6 +336,7 @@ target_alias = @target_alias@
top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
+varlibdir = @varlibdir@
webdir = @webdir@
#
@@ -337,6 +344,7 @@ webdir = @webdir@
#
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
AM_CPPFLAGS = \
+ -DVARLIB_DIR="\"$(varlibdir)\"" \
-DCACHE_DIR="\"$(cachedir)\"" \
-DCONFIG_DIR="\"$(configdir)\"" \
-DLOG_DIR="\"$(logdir)\"" \
@@ -349,6 +357,7 @@ AM_CFLAGS = \
$(OPTIONAL_MATH_CFLAGS) \
$(OPTIONAL_NFACCT_CLFAGS) \
$(OPTIONAL_ZLIB_CFLAGS) \
+ $(OPTIONAL_UUID_CFLAGS) \
$(NULL)
dist_cache_DATA = .keep
@@ -383,10 +392,13 @@ netdata_SOURCES = \
proc_net_stat_conntrack.c \
proc_net_stat_synproxy.c \
proc_stat.c \
+ proc_self_mountinfo.c proc_self_mountinfo.h \
proc_sys_kernel_random_entropy_avail.c \
proc_vmstat.c \
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 \
@@ -401,6 +413,7 @@ netdata_LDADD = \
$(OPTIONAL_MATH_LIBS) \
$(OPTIONAL_NFACCT_LIBS) \
$(OPTIONAL_ZLIB_LIBS) \
+ $(OPTIONAL_UUID_LIBS) \
$(NULL)
apps_plugin_SOURCES = \
@@ -572,14 +585,17 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_net_snmp6.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_net_stat_conntrack.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_net_stat_synproxy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_self_mountinfo.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_softirqs.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_stat.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_sys_kernel_random_entropy_avail.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_vmstat.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/procfile.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/registry.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrd.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrd2json.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/storage_number.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sys_fs_cgroup.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sys_kernel_mm_ksm.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unit_test.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/url.Po@am__quote@
@@ -858,13 +874,17 @@ uninstall-am: uninstall-dist_cacheDATA uninstall-dist_logDATA \
install-data-hook:
if [ `id -u` == 0 ]; then \
chown root '$(DESTDIR)$(pluginsdir)/apps.plugin' && \
- chmod 4755 '$(DESTDIR)$(pluginsdir)/apps.plugin'; \
+ chmod 0755 '$(DESTDIR)$(pluginsdir)/apps.plugin' && \
+ ( setcap cap_dac_read_search,cap_sys_ptrace+ep '$(DESTDIR)$(pluginsdir)/apps.plugin' || \
+ chmod 4755 '$(DESTDIR)$(pluginsdir)/apps.plugin' ); \
else \
echo; \
echo "ATTENTION"; \
echo; \
- echo "setuid bit of $(pluginsdir)/apps.plugin must be set, please execute as root:"; \
- echo "chown root '$(pluginsdir)/apps.plugin' && chmod 4755 '$(pluginsdir)/apps.plugin'"; \
+ echo "$(pluginsdir)/apps.plugin requires escalated capabilities:"; \
+ echo "sudo chown root '$(DESTDIR)$(pluginsdir)/apps.plugin'"; \
+ echo "sudo chmod 0755 '$(DESTDIR)$(pluginsdir)/apps.plugin'"; \
+ echo "sudo setcap cap_dac_read_search,cap_sys_ptrace+ep '$(DESTDIR)$(pluginsdir)/apps.plugin'"; \
echo; \
fi
diff --git a/src/appconfig.c b/src/appconfig.c
index 73b946508..0ec4cad32 100644
--- a/src/appconfig.c
+++ b/src/appconfig.c
@@ -1,3 +1,11 @@
+
+/*
+ * TODO
+ *
+ * 1. Re-write this using DICTIONARY
+ *
+ */
+
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
@@ -12,7 +20,7 @@
#define CONFIG_FILE_LINE_MAX ((CONFIG_MAX_NAME + CONFIG_MAX_VALUE + 1024) * 2)
-pthread_rwlock_t config_rwlock = PTHREAD_RWLOCK_INITIALIZER;
+pthread_mutex_t config_mutex = PTHREAD_MUTEX_INITIALIZER;
// ----------------------------------------------------------------------------
// definitions
@@ -25,15 +33,14 @@ pthread_rwlock_t config_rwlock = PTHREAD_RWLOCK_INITIALIZER;
struct config_value {
avl avl; // the index - this has to be first!
+ uint8_t flags;
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;
char *value;
- uint8_t flags;
-
- struct config_value *next;
+ struct config_value *next; // config->mutex protects just this
};
struct config {
@@ -44,19 +51,38 @@ struct config {
char *name;
- struct config_value *values;
- avl_tree values_index;
+ struct config *next; // gloabl config_mutex protects just this
- struct config *next;
+ struct config_value *values;
+ avl_tree_lock values_index;
- pthread_rwlock_t rwlock;
+ pthread_mutex_t mutex; // this locks only the writers, to ensure atomic updates
+ // readers are protected using the rwlock in avl_tree_lock
} *config_root = NULL;
// ----------------------------------------------------------------------------
-// config value
+// locking
+
+static inline void config_global_write_lock(void) {
+ pthread_mutex_lock(&config_mutex);
+}
+
+static inline void config_global_unlock(void) {
+ pthread_mutex_unlock(&config_mutex);
+}
+
+static inline void config_section_write_lock(struct config *co) {
+ pthread_mutex_lock(&co->mutex);
+}
+
+static inline void config_section_unlock(struct config *co) {
+ pthread_mutex_unlock(&co->mutex);
+}
-static int config_value_iterator(avl *a) { if(a) {}; return 0; }
+
+// ----------------------------------------------------------------------------
+// config name-value index
static int config_value_compare(void* a, void* b) {
if(((struct config_value *)a)->hash < ((struct config_value *)b)->hash) return -1;
@@ -64,22 +90,20 @@ 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;
+ struct config_value tmp;
tmp.hash = (hash)?hash:simple_hash(name);
tmp.name = (char *)name;
- avl_search(&(co->values_index), (avl *)&tmp, config_value_iterator, (avl **)&result);
- return result;
+ return (struct config_value *)avl_search_lock(&(co->values_index), (avl *) &tmp);
}
-// ----------------------------------------------------------------------------
-// config
-static int config_iterator(avl *a) { if(a) {}; return 0; }
+// ----------------------------------------------------------------------------
+// config sections index
static int config_compare(void* a, void* b) {
if(((struct config *)a)->hash < ((struct config *)b)->hash) return -1;
@@ -87,61 +111,31 @@ 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,
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#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;
+ struct config tmp;
tmp.hash = (hash)?hash:simple_hash(name);
tmp.name = (char *)name;
- avl_search(&config_root_index, (avl *)&tmp, config_iterator, (avl **)&result);
- return result;
+ return (struct config *)avl_search_lock(&config_root_index, (avl *) &tmp);
}
-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);
@@ -152,114 +146,52 @@ 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()
-
- pthread_rwlock_wrlock(&config_rwlock);
-
+ config_global_write_lock();
struct config *co2 = config_root;
if(co2) {
while (co2->next) co2 = co2->next;
co2->next = co;
}
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++;
-
- co = config_find_section(s);
- if(!co) co = config_create(s);
-
- continue;
- }
+// ----------------------------------------------------------------------------
+// config name-value methods
- if(!co) {
- // line outside a section
- error("Ignoring line %d ('%s'), it is outside all sections.", line, s);
- continue;
- }
+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);
- 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++;
+ struct config_value *cv = calloc(1, sizeof(struct config_value));
+ if(!cv) fatal("Cannot allocate config_value");
- name = trim(name);
- value = trim(value);
+ cv->name = strdup(name);
+ if(!cv->name) fatal("Cannot allocate config.name");
+ cv->hash = simple_hash(cv->name);
- if(!name) {
- error("Ignoring line %d, name is empty.", line);
- continue;
- }
- if(!value) {
- debug(D_CONFIG, "Ignoring line %d, value is empty.", line);
- continue;
- }
+ cv->value = strdup(value);
+ if(!cv->value) fatal("Cannot allocate config.value");
- 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;
+ config_section_write_lock(co);
+ struct config_value *cv2 = co->values;
+ if(cv2) {
+ while (cv2->next) cv2 = cv2->next;
+ cv2->next = cv;
}
+ else co->values = cv;
+ config_section_unlock(co);
- fclose(fp);
-
- return 1;
+ return cv;
}
char *config_get(const char *section, const char *name, const char *default_value)
@@ -268,8 +200,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) {
@@ -346,7 +278,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);
@@ -374,8 +306,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);
@@ -413,6 +345,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;
@@ -438,9 +458,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;
@@ -449,15 +469,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;
@@ -468,7 +486,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)) {
@@ -476,10 +494,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();
}
}
-
diff --git a/src/apps_plugin.c b/src/apps_plugin.c
index e8a6f43ae..0bcdfcf50 100644
--- a/src/apps_plugin.c
+++ b/src/apps_plugin.c
@@ -39,12 +39,14 @@
#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
-unsigned long long Hertz = 1;
-
long processors = 1;
long pid_max = 32768;
int debug = 0;
@@ -221,7 +223,7 @@ long get_system_cpus(void) {
int processors = 0;
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/proc/stat", host_prefix);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/stat", host_prefix);
ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
if(!ff) return 1;
@@ -250,7 +252,7 @@ long get_system_pid_max(void) {
long mpid = 32768;
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", host_prefix);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", host_prefix);
ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
if(!ff) return mpid;
@@ -267,28 +269,6 @@ long get_system_pid_max(void) {
return mpid;
}
-unsigned long long get_system_hertz(void)
-{
- unsigned long long myhz = 1;
-
-#ifdef _SC_CLK_TCK
- if((myhz = (unsigned long long int) sysconf(_SC_CLK_TCK)) > 0) {
- return myhz;
- }
-#endif
-
-#ifdef HZ
- myhz = HZ; /* <asm/param.h> */
-#else /* HZ */
- /* If 32-bit or big-endian (not Alpha or ia64), assume HZ is 100. */
- hz = (sizeof(long)==sizeof(int) || htons(999)==999) ? 100UL : 1024UL;
-#endif /* HZ */
-
- error("Unknown HZ value. Assuming %llu.", myhz);
- return myhz;
-}
-
-
// ----------------------------------------------------------------------------
// target
// target is the structure that process data are aggregated
@@ -397,20 +377,20 @@ struct target *get_users_target(uid_t uid)
return NULL;
}
- snprintf(w->compare, MAX_COMPARE_NAME, "%d", uid);
+ snprintfz(w->compare, MAX_COMPARE_NAME, "%d", uid);
w->comparehash = simple_hash(w->compare);
w->comparelen = strlen(w->compare);
- snprintf(w->id, MAX_NAME, "%d", uid);
+ snprintfz(w->id, MAX_NAME, "%d", uid);
w->idhash = simple_hash(w->id);
struct passwd *pw = getpwuid(uid);
if(!pw)
- snprintf(w->name, MAX_NAME, "%d", uid);
+ snprintfz(w->name, MAX_NAME, "%d", uid);
else
- snprintf(w->name, MAX_NAME, "%s", pw->pw_name);
+ snprintfz(w->name, MAX_NAME, "%s", pw->pw_name);
- netdata_fix_id(w->name);
+ netdata_fix_chart_name(w->name);
w->uid = uid;
@@ -435,20 +415,20 @@ struct target *get_groups_target(gid_t gid)
return NULL;
}
- snprintf(w->compare, MAX_COMPARE_NAME, "%d", gid);
+ snprintfz(w->compare, MAX_COMPARE_NAME, "%d", gid);
w->comparehash = simple_hash(w->compare);
w->comparelen = strlen(w->compare);
- snprintf(w->id, MAX_NAME, "%d", gid);
+ snprintfz(w->id, MAX_NAME, "%d", gid);
w->idhash = simple_hash(w->id);
struct group *gr = getgrgid(gid);
if(!gr)
- snprintf(w->name, MAX_NAME, "%d", gid);
+ snprintfz(w->name, MAX_NAME, "%d", gid);
else
- snprintf(w->name, MAX_NAME, "%s", gr->gr_name);
+ snprintfz(w->name, MAX_NAME, "%s", gr->gr_name);
- netdata_fix_id(w->name);
+ netdata_fix_chart_name(w->name);
w->gid = gid;
@@ -488,12 +468,12 @@ struct target *get_apps_groups_target(const char *id, struct target *target)
return NULL;
}
- strncpy(w->id, nid, MAX_NAME);
+ strncpyz(w->id, nid, MAX_NAME);
w->idhash = simple_hash(w->id);
- strncpy(w->name, nid, MAX_NAME);
+ strncpyz(w->name, nid, MAX_NAME);
- strncpy(w->compare, nid, MAX_COMPARE_NAME);
+ strncpyz(w->compare, nid, MAX_COMPARE_NAME);
int len = strlen(w->compare);
if(w->compare[len - 1] == '*') {
w->compare[len - 1] = '\0';
@@ -531,7 +511,7 @@ int read_apps_groups_conf(const char *name)
{
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/apps_%s.conf", config_dir, name);
+ snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", config_dir, name);
if(unlikely(debug))
fprintf(stderr, "apps.plugin: process groups file: '%s'\n", filename);
@@ -583,8 +563,7 @@ int read_apps_groups_conf(const char *name)
t++;
}
- strncpy(w->name, t, MAX_NAME);
- w->name[MAX_NAME] = '\0';
+ strncpyz(w->name, t, MAX_NAME);
w->hidden = thidden;
w->debug = tdebug;
@@ -606,7 +585,7 @@ int read_apps_groups_conf(const char *name)
if(!apps_groups_default_target)
error("Cannot create default target");
else
- strncpy(apps_groups_default_target->name, "other", MAX_NAME);
+ strncpyz(apps_groups_default_target->name, "other", MAX_NAME);
return 0;
}
@@ -796,7 +775,7 @@ void del_pid_entry(pid_t pid)
int read_proc_pid_cmdline(struct pid_stat *p) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/proc/%d/cmdline", host_prefix, p->pid);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", host_prefix, p->pid);
int fd = open(filename, O_RDONLY, 0666);
if(unlikely(fd == -1)) return 1;
@@ -806,8 +785,7 @@ int read_proc_pid_cmdline(struct pid_stat *p) {
if(bytes <= 0) {
// copy the command to the command line
- strncpy(p->cmdline, p->comm, MAX_CMDLINE);
- p->cmdline[MAX_CMDLINE] = '\0';
+ strncpyz(p->cmdline, p->comm, MAX_CMDLINE);
return 0;
}
@@ -824,7 +802,7 @@ int read_proc_pid_cmdline(struct pid_stat *p) {
int read_proc_pid_ownership(struct pid_stat *p) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/proc/%d", host_prefix, p->pid);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d", host_prefix, p->pid);
// ----------------------------------------
// read uid and gid
@@ -844,7 +822,7 @@ int read_proc_pid_stat(struct pid_stat *p) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/proc/%d/stat", host_prefix, p->pid);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/stat", host_prefix, p->pid);
// ----------------------------------------
@@ -866,8 +844,7 @@ int read_proc_pid_stat(struct pid_stat *p) {
// parse the process name
unsigned int i = 0;
- strncpy(p->comm, procfile_lineword(ff, 0, 1), MAX_COMPARE_NAME);
- p->comm[MAX_COMPARE_NAME] = '\0';
+ strncpyz(p->comm, procfile_lineword(ff, 0, 1), MAX_COMPARE_NAME);
// p->pid = atol(procfile_lineword(ff, 0, 0+i));
// comm is at 1
@@ -926,7 +903,7 @@ int read_proc_pid_statm(struct pid_stat *p) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/proc/%d/statm", host_prefix, p->pid);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/statm", host_prefix, p->pid);
ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
if(!ff) return 1;
@@ -956,7 +933,7 @@ int read_proc_pid_io(struct pid_stat *p) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s/proc/%d/io", host_prefix, p->pid);
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/io", host_prefix, p->pid);
ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
if(!ff) return 1;
@@ -1024,18 +1001,11 @@ 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) {
- struct file_descriptor *result = NULL, tmp;
+ struct file_descriptor tmp;
tmp.hash = (hash)?hash:simple_hash(name);
tmp.name = name;
tmp.count = 0;
@@ -1044,8 +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);
- return result;
+ return (struct file_descriptor *)avl_search(&all_files_index, (avl *) &tmp);
}
#define file_descriptor_add(fd) avl_insert(&all_files_index, (avl *)(fd))
@@ -1211,7 +1180,7 @@ int file_descriptor_find_or_add(const char *name)
int read_pid_file_descriptors(struct pid_stat *p) {
char dirname[FILENAME_MAX+1];
- snprintf(dirname, FILENAME_MAX, "%s/proc/%d/fd", host_prefix, p->pid);
+ snprintfz(dirname, FILENAME_MAX, "%s/proc/%d/fd", host_prefix, p->pid);
DIR *fds = opendir(dirname);
if(fds) {
int c;
@@ -1235,7 +1204,7 @@ int read_pid_file_descriptors(struct pid_stat *p) {
if(debug) fprintf(stderr, "apps.plugin: extending fd memory slots for %s from %d to %d\n", p->comm, p->fds_size, fdid + 100);
p->fds = realloc(p->fds, (fdid + 100) * sizeof(int));
if(!p->fds) {
- error("Cannot re-allocate fds for %s", p->comm);
+ fatal("Cannot re-allocate fds for %s", p->comm);
break;
}
@@ -1304,7 +1273,7 @@ int collect_data_for_all_processes_from_proc(void)
{
char dirname[FILENAME_MAX + 1];
- snprintf(dirname, FILENAME_MAX, "%s/proc", host_prefix);
+ snprintfz(dirname, FILENAME_MAX, "%s/proc", host_prefix);
DIR *dir = opendir(dirname);
if(!dir) return 0;
@@ -2267,7 +2236,7 @@ void send_charts_updates_to_netdata(struct target *root, const char *type, const
for (w = root; w ; w = w->next) {
if(w->target || (!w->processes && !w->exposed)) continue;
- fprintf(stdout, "DIMENSION %s '' incremental 100 %llu %s\n", w->name, Hertz, w->hidden ? "hidden,noreset" : "noreset");
+ fprintf(stdout, "DIMENSION %s '' incremental 100 %u %s\n", w->name, hz, w->hidden ? "hidden,noreset" : "noreset");
}
fprintf(stdout, "CHART %s.mem '' '%s Dedicated Memory (w/o shared)' 'MB' mem %s.mem stacked 20003 %d\n", type, title, type, update_every);
@@ -2295,14 +2264,14 @@ void send_charts_updates_to_netdata(struct target *root, const char *type, const
for (w = root; w ; w = w->next) {
if(w->target || (!w->processes && !w->exposed)) continue;
- fprintf(stdout, "DIMENSION %s '' incremental 100 %llu noreset\n", w->name, Hertz * processors);
+ fprintf(stdout, "DIMENSION %s '' incremental 100 %ld noreset\n", w->name, hz * processors);
}
fprintf(stdout, "CHART %s.cpu_system '' '%s CPU System Time (%ld%% = %ld core%s)' 'cpu time %%' cpu %s.cpu_system stacked 20021 %d\n", type, title, (processors * 100), processors, (processors>1)?"s":"", type, update_every);
for (w = root; w ; w = w->next) {
if(w->target || (!w->processes && !w->exposed)) continue;
- fprintf(stdout, "DIMENSION %s '' incremental 100 %llu noreset\n", w->name, Hertz * processors);
+ fprintf(stdout, "DIMENSION %s '' incremental 100 %ld noreset\n", w->name, hz * processors);
}
fprintf(stdout, "CHART %s.major_faults '' '%s Major Page Faults (swap read)' 'page faults/s' swap %s.major_faults stacked 20010 %d\n", type, title, type, update_every);
@@ -2411,12 +2380,6 @@ void parse_args(int argc, char **argv)
}
}
-unsigned long long sutime() {
- struct timeval now;
- gettimeofday(&now, NULL);
- return now.tv_sec * 1000000ULL + now.tv_usec;
-}
-
int main(int argc, char **argv)
{
// debug_flags = D_PROCFILE;
@@ -2427,6 +2390,10 @@ int main(int argc, char **argv)
// disable syslog for apps.plugin
error_log_syslog = 0;
+ // set errors flood protection to 100 logs per hour
+ error_log_errors_per_period = 100;
+ error_log_throttle_period = 3600;
+
host_prefix = getenv("NETDATA_HOST_PREFIX");
if(host_prefix == NULL) {
info("NETDATA_HOST_PREFIX is not passed from netdata");
@@ -2441,13 +2408,22 @@ 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;
time_t started_t = time(NULL);
time_t current_t;
- Hertz = get_system_hertz();
+ get_HZ();
pid_max = get_system_pid_max();
processors = get_system_cpus();
@@ -2460,16 +2436,14 @@ int main(int argc, char **argv)
exit(1);
}
- fprintf(stdout, "CHART netdata.apps_cpu '' 'Apps Plugin CPU' 'milliseconds/s' apps.plugin netdata.apps_cpu stacked 140000 %d\n", update_every);
- fprintf(stdout, "DIMENSION user '' incremental 1 %d\n", 1000);
- fprintf(stdout, "DIMENSION system '' incremental 1 %d\n", 1000);
-
- fprintf(stdout, "CHART netdata.apps_files '' 'Apps Plugin Files' 'files/s' apps.plugin netdata.apps_files line 140001 %d\n", update_every);
- fprintf(stdout, "DIMENSION files '' incremental 1 1\n");
- fprintf(stdout, "DIMENSION pids '' absolute 1 1\n");
- fprintf(stdout, "DIMENSION fds '' absolute 1 1\n");
- fprintf(stdout, "DIMENSION targets '' absolute 1 1\n");
-
+ fprintf(stdout, "CHART netdata.apps_cpu '' 'Apps Plugin CPU' 'milliseconds/s' apps.plugin netdata.apps_cpu stacked 140000 %1$d\n"
+ "DIMENSION user '' incremental 1 1000\n"
+ "DIMENSION system '' incremental 1 1000\n"
+ "CHART netdata.apps_files '' 'Apps Plugin Files' 'files/s' apps.plugin netdata.apps_files line 140001 %1$d\n"
+ "DIMENSION files '' incremental 1 1\n"
+ "DIMENSION pids '' absolute 1 1\n"
+ "DIMENSION fds '' absolute 1 1\n"
+ "DIMENSION targets '' absolute 1 1\n", update_every);
#ifndef PROFILING_MODE
unsigned long long sunext = (time(NULL) - (time(NULL) % update_every) + update_every) * 1000000ULL;
@@ -2480,11 +2454,11 @@ int main(int argc, char **argv)
for(;1; counter++) {
#ifndef PROFILING_MODE
// delay until it is our time to run
- while((sunow = sutime()) < sunext)
+ while((sunow = timems()) < sunext)
usleep((useconds_t)(sunext - sunow));
// find the next time we need to run
- while(sutime() > sunext)
+ while(timems() > sunext)
sunext += update_every * 1000000ULL;
#endif /* PROFILING_MODE */
@@ -2508,7 +2482,6 @@ int main(int argc, char **argv)
send_collected_data_to_netdata(groups_root_target, "groups", dt);
if(debug) fprintf(stderr, "apps.plugin: done Loop No %llu\n", counter);
- fflush(NULL);
current_t = time(NULL);
diff --git a/src/avl.c b/src/avl.c
index fd4fb1420..067b0b361 100644
--- 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,57 @@ 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) {
+/* high performance searching - by ktsaou */
+avl *avl_search(avl_tree *t, avl *a) {
+ avl *root = t->root;
+
+ while(root) {
+ int x = t->compar(root, a);
+
+ if(x > 0) {
+ root = root->left;
+ continue;
+ }
+
+ if(x < 0) {
+ root = root->right;
+ continue;
+ }
+
+ return root;
+ }
+
+ return NULL;
+}
+
+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 +367,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 +387,39 @@ 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 */
+}
+
+avl *avl_search_lock(avl_tree_lock *t, avl *a) {
+ avl_read_lock(t);
+ avl *ret = avl_search(&t->avl_tree, a);
+ avl_unlock(t);
+ return ret;
+}
+
+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;
}
diff --git a/src/avl.h b/src/avl.h
index 2d1fbc537..5397b196e 100644
--- a/src/avl.h
+++ b/src/avl.h
@@ -17,10 +17,19 @@
#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);
+avl *avl_search_lock(avl_tree_lock *t, avl *a);
+avl *avl_search(avl_tree *t, avl *a);
-/* 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 */
diff --git a/src/common.c b/src/common.c
index cb74b6335..a2b0d940f 100644
--- a/src/common.c
+++ b/src/common.c
@@ -20,7 +20,14 @@
char *global_host_prefix = "";
int enable_ksm = 1;
-unsigned char netdata_keys_map[256] = {
+// time(NULL) in milliseconds
+unsigned long long timems(void) {
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ return now.tv_sec * 1000000ULL + now.tv_usec;
+}
+
+unsigned char netdata_map_chart_names[256] = {
[0] = '\0', //
[1] = '_', //
[2] = '_', //
@@ -281,8 +288,273 @@ unsigned char netdata_keys_map[256] = {
// make sure the supplied string
// is good for a netdata chart/dimension ID/NAME
-void netdata_fix_id(char *s) {
- while((*s = netdata_keys_map[(unsigned char)*s])) s++;
+void netdata_fix_chart_name(char *s) {
+ while((*s = netdata_map_chart_names[(unsigned char)*s])) s++;
+}
+
+unsigned char netdata_map_chart_ids[256] = {
+ [0] = '\0', //
+ [1] = '_', //
+ [2] = '_', //
+ [3] = '_', //
+ [4] = '_', //
+ [5] = '_', //
+ [6] = '_', //
+ [7] = '_', //
+ [8] = '_', //
+ [9] = '_', //
+ [10] = '_', //
+ [11] = '_', //
+ [12] = '_', //
+ [13] = '_', //
+ [14] = '_', //
+ [15] = '_', //
+ [16] = '_', //
+ [17] = '_', //
+ [18] = '_', //
+ [19] = '_', //
+ [20] = '_', //
+ [21] = '_', //
+ [22] = '_', //
+ [23] = '_', //
+ [24] = '_', //
+ [25] = '_', //
+ [26] = '_', //
+ [27] = '_', //
+ [28] = '_', //
+ [29] = '_', //
+ [30] = '_', //
+ [31] = '_', //
+ [32] = '_', //
+ [33] = '_', // !
+ [34] = '_', // "
+ [35] = '_', // #
+ [36] = '_', // $
+ [37] = '_', // %
+ [38] = '_', // &
+ [39] = '_', // '
+ [40] = '_', // (
+ [41] = '_', // )
+ [42] = '_', // *
+ [43] = '_', // +
+ [44] = '.', // ,
+ [45] = '-', // -
+ [46] = '.', // .
+ [47] = '_', // /
+ [48] = '0', // 0
+ [49] = '1', // 1
+ [50] = '2', // 2
+ [51] = '3', // 3
+ [52] = '4', // 4
+ [53] = '5', // 5
+ [54] = '6', // 6
+ [55] = '7', // 7
+ [56] = '8', // 8
+ [57] = '9', // 9
+ [58] = '_', // :
+ [59] = '_', // ;
+ [60] = '_', // <
+ [61] = '_', // =
+ [62] = '_', // >
+ [63] = '_', // ?
+ [64] = '_', // @
+ [65] = 'a', // A
+ [66] = 'b', // B
+ [67] = 'c', // C
+ [68] = 'd', // D
+ [69] = 'e', // E
+ [70] = 'f', // F
+ [71] = 'g', // G
+ [72] = 'h', // H
+ [73] = 'i', // I
+ [74] = 'j', // J
+ [75] = 'k', // K
+ [76] = 'l', // L
+ [77] = 'm', // M
+ [78] = 'n', // N
+ [79] = 'o', // O
+ [80] = 'p', // P
+ [81] = 'q', // Q
+ [82] = 'r', // R
+ [83] = 's', // S
+ [84] = 't', // T
+ [85] = 'u', // U
+ [86] = 'v', // V
+ [87] = 'w', // W
+ [88] = 'x', // X
+ [89] = 'y', // Y
+ [90] = 'z', // Z
+ [91] = '_', // [
+ [92] = '/', // backslash
+ [93] = '_', // ]
+ [94] = '_', // ^
+ [95] = '_', // _
+ [96] = '_', // `
+ [97] = 'a', // a
+ [98] = 'b', // b
+ [99] = 'c', // c
+ [100] = 'd', // d
+ [101] = 'e', // e
+ [102] = 'f', // f
+ [103] = 'g', // g
+ [104] = 'h', // h
+ [105] = 'i', // i
+ [106] = 'j', // j
+ [107] = 'k', // k
+ [108] = 'l', // l
+ [109] = 'm', // m
+ [110] = 'n', // n
+ [111] = 'o', // o
+ [112] = 'p', // p
+ [113] = 'q', // q
+ [114] = 'r', // r
+ [115] = 's', // s
+ [116] = 't', // t
+ [117] = 'u', // u
+ [118] = 'v', // v
+ [119] = 'w', // w
+ [120] = 'x', // x
+ [121] = 'y', // y
+ [122] = 'z', // z
+ [123] = '_', // {
+ [124] = '_', // |
+ [125] = '_', // }
+ [126] = '_', // ~
+ [127] = '_', //
+ [128] = '_', //
+ [129] = '_', //
+ [130] = '_', //
+ [131] = '_', //
+ [132] = '_', //
+ [133] = '_', //
+ [134] = '_', //
+ [135] = '_', //
+ [136] = '_', //
+ [137] = '_', //
+ [138] = '_', //
+ [139] = '_', //
+ [140] = '_', //
+ [141] = '_', //
+ [142] = '_', //
+ [143] = '_', //
+ [144] = '_', //
+ [145] = '_', //
+ [146] = '_', //
+ [147] = '_', //
+ [148] = '_', //
+ [149] = '_', //
+ [150] = '_', //
+ [151] = '_', //
+ [152] = '_', //
+ [153] = '_', //
+ [154] = '_', //
+ [155] = '_', //
+ [156] = '_', //
+ [157] = '_', //
+ [158] = '_', //
+ [159] = '_', //
+ [160] = '_', //
+ [161] = '_', //
+ [162] = '_', //
+ [163] = '_', //
+ [164] = '_', //
+ [165] = '_', //
+ [166] = '_', //
+ [167] = '_', //
+ [168] = '_', //
+ [169] = '_', //
+ [170] = '_', //
+ [171] = '_', //
+ [172] = '_', //
+ [173] = '_', //
+ [174] = '_', //
+ [175] = '_', //
+ [176] = '_', //
+ [177] = '_', //
+ [178] = '_', //
+ [179] = '_', //
+ [180] = '_', //
+ [181] = '_', //
+ [182] = '_', //
+ [183] = '_', //
+ [184] = '_', //
+ [185] = '_', //
+ [186] = '_', //
+ [187] = '_', //
+ [188] = '_', //
+ [189] = '_', //
+ [190] = '_', //
+ [191] = '_', //
+ [192] = '_', //
+ [193] = '_', //
+ [194] = '_', //
+ [195] = '_', //
+ [196] = '_', //
+ [197] = '_', //
+ [198] = '_', //
+ [199] = '_', //
+ [200] = '_', //
+ [201] = '_', //
+ [202] = '_', //
+ [203] = '_', //
+ [204] = '_', //
+ [205] = '_', //
+ [206] = '_', //
+ [207] = '_', //
+ [208] = '_', //
+ [209] = '_', //
+ [210] = '_', //
+ [211] = '_', //
+ [212] = '_', //
+ [213] = '_', //
+ [214] = '_', //
+ [215] = '_', //
+ [216] = '_', //
+ [217] = '_', //
+ [218] = '_', //
+ [219] = '_', //
+ [220] = '_', //
+ [221] = '_', //
+ [222] = '_', //
+ [223] = '_', //
+ [224] = '_', //
+ [225] = '_', //
+ [226] = '_', //
+ [227] = '_', //
+ [228] = '_', //
+ [229] = '_', //
+ [230] = '_', //
+ [231] = '_', //
+ [232] = '_', //
+ [233] = '_', //
+ [234] = '_', //
+ [235] = '_', //
+ [236] = '_', //
+ [237] = '_', //
+ [238] = '_', //
+ [239] = '_', //
+ [240] = '_', //
+ [241] = '_', //
+ [242] = '_', //
+ [243] = '_', //
+ [244] = '_', //
+ [245] = '_', //
+ [246] = '_', //
+ [247] = '_', //
+ [248] = '_', //
+ [249] = '_', //
+ [250] = '_', //
+ [251] = '_', //
+ [252] = '_', //
+ [253] = '_', //
+ [254] = '_', //
+ [255] = '_' //
+};
+
+// make sure the supplied string
+// is good for a netdata chart/dimension ID/NAME
+void netdata_fix_chart_id(char *s) {
+ while((*s = netdata_map_chart_ids[(unsigned char)*s])) s++;
}
/*
@@ -310,8 +582,11 @@ uint32_t simple_hash(const char *name) {
// FNV-1a algorithm
while (*s) {
// multiply by the 32 bit FNV magic prime mod 2^32
- // gcc optimized
- hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
+ // NOTE: No need to optimize with left shifts.
+ // GCC will use imul instruction anyway.
+ // Tested with 'gcc -O3 -S'
+ //hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
+ hval *= 16777619;
// xor the bottom with the current octet
hval ^= (uint32_t)*s++;
@@ -346,9 +621,15 @@ uint32_t simple_hash(const char *name) {
void strreverse(char* begin, char* end)
{
- char aux;
- while (end > begin)
- aux = *end, *end-- = *begin, *begin++ = aux;
+ char aux;
+
+ while (end > begin)
+ {
+ // clearer code.
+ aux = *end;
+ *end-- = *begin;
+ *begin++ = aux;
+ }
}
char *mystrsep(char **ptr, char *s)
@@ -361,17 +642,22 @@ char *mystrsep(char **ptr, char *s)
char *trim(char *s)
{
// skip leading spaces
+ // and 'comments' as well!?
while(*s && isspace(*s)) s++;
if(!*s || *s == '#') return NULL;
// skip tailing spaces
- long c = (long) strlen(s) - 1;
- while(c >= 0 && isspace(s[c])) {
- s[c] = '\0';
- c--;
+ // this way is way faster. Writes only one NUL char.
+ ssize_t l = strlen(s);
+ if (--l >= 0)
+ {
+ char *p = s + l;
+ while (p > s && isspace(*p)) p--;
+ *++p = '\0';
}
- if(c < 0) return NULL;
+
if(!*s) return NULL;
+
return s;
}
@@ -438,7 +724,7 @@ int savememory(const char *filename, void *mem, unsigned long size)
{
char tmpfilename[FILENAME_MAX + 1];
- snprintf(tmpfilename, FILENAME_MAX, "%s.%ld.tmp", filename, (long)getpid());
+ snprintfz(tmpfilename, FILENAME_MAX, "%s.%ld.tmp", filename, (long)getpid());
int fd = open(tmpfilename, O_RDWR|O_CREAT|O_NOATIME, 0664);
if(fd < 0) {
@@ -490,3 +776,57 @@ 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 *dst, const char *src, size_t n) {
+ char *p = dst;
+
+ while(*src && n--)
+ *dst++ = *src++;
+
+ *dst = '\0';
+
+ return p;
+}
+
+int vsnprintfz(char *dst, size_t n, const char *fmt, va_list args) {
+ int size;
+
+ size = vsnprintf(dst, n, fmt, args);
+
+ if(unlikely((size_t)size > n)) {
+ // there is bug in vsnprintf() and it returns
+ // a number higher to len, but it does not
+ // overflow the buffer.
+ size = n;
+ }
+
+ dst[size] = '\0';
+ return size;
+}
+
+int snprintfz(char *dst, size_t n, const char *fmt, ...) {
+ va_list args;
+
+ va_start(args, fmt);
+ return vsnprintfz(dst, n, fmt, args);
+ va_end(args);
+}
diff --git a/src/common.h b/src/common.h
index e9987af72..c94f1cde5 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,5 +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
@@ -15,13 +17,18 @@
#define abs(x) ((x < 0)? -x : x)
#define usecdiff(now, last) (((((now)->tv_sec * 1000000ULL) + (now)->tv_usec) - (((last)->tv_sec * 1000000ULL) + (last)->tv_usec)))
-extern void netdata_fix_id(char *s);
+extern void netdata_fix_chart_id(char *s);
+extern void netdata_fix_chart_name(char *s);
extern uint32_t simple_hash(const char *name);
extern void strreverse(char* begin, char* end);
extern char *mystrsep(char **ptr, char *s);
extern char *trim(char *s);
+extern char *strncpyz(char *dst, const char *src, size_t n);
+extern int vsnprintfz(char *dst, size_t n, const char *fmt, va_list args);
+extern int snprintfz(char *dst, size_t n, const char *fmt, ...);
+
extern void *mymmap(const char *filename, size_t size, int flags, int ksm);
extern int savememory(const char *filename, void *mem, unsigned long size);
@@ -31,12 +38,15 @@ extern char *global_host_prefix;
extern int enable_ksm;
/* Number of ticks per second */
-#define HZ myhz
extern unsigned int hz;
extern void get_HZ(void);
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
diff --git a/src/daemon.c b/src/daemon.c
index 9dcf32f0b..2a56ae0cc 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -10,6 +10,7 @@
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
+#include <grp.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/stat.h>
@@ -29,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)
@@ -85,6 +45,21 @@ int become_user(const char *username)
uid_t uid = pw->pw_uid;
gid_t gid = pw->pw_gid;
+ int ngroups = sysconf(_SC_NGROUPS_MAX);
+ gid_t *supplementary_groups = NULL;
+ if(ngroups) {
+ supplementary_groups = malloc(sizeof(gid_t) * ngroups);
+ if(supplementary_groups) {
+ if(getgrouplist(username, gid, supplementary_groups, &ngroups) == -1) {
+ error("Cannot get supplementary groups of user '%s'.", username);
+ free(supplementary_groups);
+ supplementary_groups = NULL;
+ ngroups = 0;
+ }
+ }
+ else fatal("Cannot allocate memory for %d supplementary groups", ngroups);
+ }
+
if(pidfile[0] && getuid() != uid) {
// we are dropping privileges
if(chown(pidfile, uid, gid) != 0)
@@ -102,6 +77,15 @@ int become_user(const char *username)
pidfd = -1;
}
+ if(supplementary_groups && ngroups) {
+ if(setgroups(ngroups, supplementary_groups) == -1)
+ error("Cannot set supplementary groups for user '%s'", username);
+
+ free(supplementary_groups);
+ supplementary_groups = NULL;
+ ngroups = 0;
+ }
+
if(setresgid(gid, gid, gid) != 0) {
error("Cannot switch to user's %s group (gid: %d).", username, gid);
return -1;
@@ -183,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");
}
}
@@ -222,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();
@@ -276,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);
@@ -285,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);
diff --git a/src/dictionary.c b/src/dictionary.c
index 31f4d52e1..1543f4d0e 100644
--- a/src/dictionary.c
+++ b/src/dictionary.c
@@ -1,6 +1,7 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
+
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
@@ -12,9 +13,32 @@
#include "dictionary.h"
// ----------------------------------------------------------------------------
-// name_value index
+// dictionary locks
-static int name_value_iterator(avl *a) { if(a) {}; return 0; }
+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_compare(void* a, void* b) {
if(((NAME_VALUE *)a)->hash < ((NAME_VALUE *)b)->hash) return -1;
@@ -22,93 +46,100 @@ static int name_value_compare(void* a, void* b) {
else return strcmp(((NAME_VALUE *)a)->name, ((NAME_VALUE *)b)->name);
}
-#define name_value_index_add(dict, cv) avl_insert(&((dict)->values_index), (avl *)(cv))
-#define name_value_index_del(dict, cv) avl_remove(&((dict)->values_index), (avl *)(cv))
+#define dictionary_name_value_index_add_nolock(dict, nv) do { (dict)->inserts++; avl_insert(&((dict)->values_index), (avl *)(nv)); } while(0)
+#define dictionary_name_value_index_del_nolock(dict, nv) do { (dict)->deletes++; avl_remove(&(dict->values_index), (avl *)(nv)); } while(0)
-static NAME_VALUE *dictionary_name_value_index_find(DICTIONARY *dict, const char *name, uint32_t hash) {
- NAME_VALUE *result = NULL, tmp;
+static inline NAME_VALUE *dictionary_name_value_index_find_nolock(DICTIONARY *dict, const char *name, uint32_t hash) {
+ NAME_VALUE tmp;
tmp.hash = (hash)?hash:simple_hash(name);
tmp.name = (char *)name;
- avl_search(&(dict->values_index), (avl *)&tmp, name_value_iterator, (avl **)&result);
- return result;
+ dict->searches++;
+ return (NAME_VALUE *)avl_search(&(dict->values_index), (avl *) &tmp);
}
// ----------------------------------------------------------------------------
+// internal methods
-static NAME_VALUE *dictionary_name_value_create(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
- debug(D_DICTIONARY, "Creating name value entry for name '%s', value '%s'.", name, value);
+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'.", name);
NAME_VALUE *nv = calloc(1, sizeof(NAME_VALUE));
- if(!nv) {
- fatal("Cannot allocate name_value of size %z", sizeof(NAME_VALUE));
- exit(1);
+ if(unlikely(!nv)) fatal("Cannot allocate name_value of size %z", sizeof(NAME_VALUE));
+
+ 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->name = strdup(name);
- if(!nv->name) fatal("Cannot allocate name_value.name of size %z", strlen(name));
- nv->hash = simple_hash(nv->name);
+ nv->hash = (hash)?hash:simple_hash(nv->name);
- nv->value = malloc(value_len);
- if(!nv->value) fatal("Cannot allocate name_value.value of size %z", value_len);
- memcpy(nv->value, value, value_len);
+ if(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE)
+ nv->value = value;
+ else {
+ nv->value = malloc(value_len);
+ if (unlikely(!nv->value))
+ fatal("Cannot allocate name_value.value of size %z", value_len);
- // link it
- pthread_rwlock_wrlock(&dict->rwlock);
- nv->next = dict->values;
- dict->values = nv;
- pthread_rwlock_unlock(&dict->rwlock);
+ memcpy(nv->value, value, value_len);
+ }
// index it
- name_value_index_add(dict, nv);
+ dictionary_name_value_index_add_nolock(dict, nv);
+ dict->entries++;
return nv;
}
-static void dictionary_name_value_destroy(DICTIONARY *dict, NAME_VALUE *nv) {
+static void dictionary_name_value_destroy_nolock(DICTIONARY *dict, NAME_VALUE *nv) {
debug(D_DICTIONARY, "Destroying name value entry for name '%s'.", nv->name);
- pthread_rwlock_wrlock(&dict->rwlock);
- if(dict->values == nv) dict->values = nv->next;
- else {
- NAME_VALUE *n = dict->values;
- while(n && n->next && n->next != nv) nv = nv->next;
- if(!n || n->next != nv) {
- fatal("Cannot find name_value with name '%s' in dictionary.", nv->name);
- exit(1);
- }
- n->next = nv->next;
- nv->next = NULL;
+ dictionary_name_value_index_del_nolock(dict, nv);
+
+ dict->entries--;
+
+ 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);
}
- pthread_rwlock_unlock(&dict->rwlock);
- free(nv->value);
free(nv);
}
// ----------------------------------------------------------------------------
+// API - basic methods
-DICTIONARY *dictionary_create(void) {
+DICTIONARY *dictionary_create(uint32_t flags) {
debug(D_DICTIONARY, "Creating dictionary.");
DICTIONARY *dict = calloc(1, sizeof(DICTIONARY));
- if(!dict) {
- fatal("Cannot allocate DICTIONARY");
- exit(1);
- }
+ if(unlikely(!dict)) fatal("Cannot allocate DICTIONARY");
avl_init(&dict->values_index, name_value_compare);
pthread_rwlock_init(&dict->rwlock, NULL);
+ dict->flags = flags;
+
return dict;
}
void dictionary_destroy(DICTIONARY *dict) {
debug(D_DICTIONARY, "Destroying dictionary.");
- pthread_rwlock_wrlock(&dict->rwlock);
- while(dict->values) dictionary_name_value_destroy(dict, dict->values);
- pthread_rwlock_unlock(&dict->rwlock);
+ dictionary_write_lock(dict);
+
+ while(dict->values_index.root)
+ dictionary_name_value_destroy_nolock(dict, (NAME_VALUE *)dict->values_index.root);
+
+ dictionary_unlock(dict);
free(dict);
}
@@ -118,39 +149,55 @@ void dictionary_destroy(DICTIONARY *dict) {
void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name);
- pthread_rwlock_rdlock(&dict->rwlock);
- NAME_VALUE *nv = dictionary_name_value_index_find(dict, name, 0);
- pthread_rwlock_unlock(&dict->rwlock);
- if(!nv) {
+ uint32_t hash = simple_hash(name);
+
+ 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);
- nv = dictionary_name_value_create(dict, name, value, value_len);
- if(!nv) {
+
+ nv = dictionary_name_value_create_nolock(dict, name, value, value_len, hash);
+ if(unlikely(!nv))
fatal("Cannot create name_value.");
- exit(1);
- }
- return nv->value;
}
else {
debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", name);
- pthread_rwlock_wrlock(&dict->rwlock);
- void *old = nv->value;
- nv->value = malloc(value_len);
- if(!nv->value) fatal("Cannot allocate value of size %z", value_len);
- memcpy(nv->value, value, value_len);
- pthread_rwlock_unlock(&dict->rwlock);
- free(old);
+
+ 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;
+
+ if(unlikely(!nv->value))
+ fatal("Cannot allocate value of size %z", value_len);
+
+ 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;
}
void *dictionary_get(DICTIONARY *dict, const char *name) {
debug(D_DICTIONARY, "GET dictionary entry with name '%s'.", name);
- pthread_rwlock_rdlock(&dict->rwlock);
- NAME_VALUE *nv = dictionary_name_value_index_find(dict, name, 0);
- pthread_rwlock_unlock(&dict->rwlock);
- if(!nv) {
+ dictionary_read_lock(dict);
+ NAME_VALUE *nv = dictionary_name_value_index_find_nolock(dict, name, 0);
+ dictionary_unlock(dict);
+
+ if(unlikely(!nv)) {
debug(D_DICTIONARY, "Not found dictionary entry with name '%s'.", name);
return NULL;
}
@@ -158,3 +205,67 @@ void *dictionary_get(DICTIONARY *dict, const char *name) {
debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
return nv->value;
}
+
+int dictionary_del(DICTIONARY *dict, const char *name) {
+ int ret;
+
+ debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name);
+
+ 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);
+ ret = -1;
+ }
+ else {
+ debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
+ dictionary_name_value_destroy_nolock(dict, nv);
+ ret = 0;
+ }
+
+ 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;
+}
diff --git a/src/dictionary.h b/src/dictionary.h
index 9822b23c2..575f28271 100644
--- a/src/dictionary.h
+++ b/src/dictionary.h
@@ -1,4 +1,7 @@
+#include <pthread.h>
+
#include "web_buffer.h"
+#include "avl.h"
#ifndef NETDATA_DICTIONARY_H
#define NETDATA_DICTIONARY_H 1
@@ -10,20 +13,33 @@ typedef struct name_value {
// we first compare hashes, and only if the hashes are equal we do string comparisons
char *name;
- char *value;
-
- struct name_value *next;
+ void *value;
} NAME_VALUE;
typedef struct dictionary {
- NAME_VALUE *values;
avl_tree values_index;
+
+ uint8_t flags;
+
+ unsigned long long inserts;
+ unsigned long long deletes;
+ unsigned long long searches;
+ unsigned long long entries;
+
pthread_rwlock_t rwlock;
} DICTIONARY;
-extern DICTIONARY *dictionary_create(void);
+#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 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 */
diff --git a/src/log.c b/src/log.c
index e5e0ad2c4..717126a69 100644
--- a/src/log.c
+++ b/src/log.c
@@ -131,6 +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 );
if(output_log_syslog) {
va_start( args, fmt );
@@ -228,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) {
diff --git a/src/log.h b/src/log.h
index e882af386..3f811d9fb 100644
--- a/src/log.h
+++ b/src/log.h
@@ -1,5 +1,6 @@
#include <stdio.h>
#include <stdarg.h>
+#include <time.h>
#ifndef NETDATA_LOG_H
#define NETDATA_LOG_H 1
@@ -24,6 +25,8 @@
#define D_RRD_CALLS 0x00020000
#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
diff --git a/src/main.c b/src/main.c
index ad24debfa..ec3c59ca7 100644
--- a/src/main.c
+++ b/src/main.c
@@ -34,11 +34,13 @@
#include "plugin_checks.h"
#include "plugin_proc.h"
#include "plugin_nfacct.h"
+#include "registry.h"
#include "main.h"
-#include "../config.h"
-int netdata_exit = 0;
+extern void *cgroups_main(void *ptr);
+
+volatile sig_atomic_t netdata_exit = 0;
void netdata_cleanup_and_exit(int ret)
{
@@ -84,6 +86,7 @@ struct netdata_static_thread static_threads[] = {
{"tc", "plugins", "tc", 1, NULL, NULL, tc_main},
{"idlejitter", "plugins", "idlejitter", 1, NULL, NULL, cpuidlejitter_main},
{"proc", "plugins", "proc", 1, NULL, NULL, proc_main},
+ {"cgroups", "plugins", "cgroups", 1, NULL, NULL, cgroups_main},
#ifdef INTERNAL_PLUGIN_NFACCT
// nfacct requires root access
@@ -120,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:
@@ -239,8 +230,7 @@ int main(int argc, char **argv)
else if(strcmp(argv[i], "-nodaemon") == 0 || strcmp(argv[i], "-nd") == 0) dont_fork = 1;
else if(strcmp(argv[i], "-pidfile") == 0 && (i+1) < argc) {
i++;
- strncpy(pidfile, argv[i], FILENAME_MAX);
- pidfile[FILENAME_MAX] = '\0';
+ strncpyz(pidfile, argv[i], FILENAME_MAX);
}
else if(strcmp(argv[i], "--unittest") == 0) {
rrd_update_every = 1;
@@ -273,8 +263,10 @@ 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);
// avoid extended to stat(/etc/localtime)
// http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux
@@ -400,13 +392,49 @@ int main(int argc, char **argv)
// let the plugins know the min update_every
{
- char buf[50];
- snprintf(buf, 50, "%d", rrd_update_every);
+ char buf[51];
+ snprintfz(buf, 50, "%d", rrd_update_every);
setenv("NETDATA_UPDATE_EVERY", buf, 1);
}
// --------------------------------------------------------------------
+ // block signals while initializing threads.
+ // this causes the threads to block signals.
+ sigset_t sigset;
+ sigfillset(&sigset);
+
+ if(pthread_sigmask(SIG_BLOCK, &sigset, NULL) == -1) {
+ error("Could not block signals for threads");
+ }
+
+ // Catch signals which we want to use to quit savely
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, SIGINT);
+ sigaddset(&sa.sa_mask, SIGTERM);
+ sa.sa_handler = sig_handler;
+ sa.sa_flags = 0;
+ if(sigaction(SIGHUP, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGHUP");
+ }
+ if(sigaction(SIGINT, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGINT");
+ }
+ if(sigaction(SIGTERM, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGTERM");
+ }
+ // Ignore SIGPIPE completely.
+ // INFO: If we add signals here we have to unblock them
+ // at popen.c when running a external plugin.
+ sa.sa_handler = SIG_IGN;
+ if(sigaction(SIGPIPE, &sa, NULL) == -1) {
+ error("Failed to change signal handler for SIGTERM");
+ }
+
+ // --------------------------------------------------------------------
+
i = pthread_attr_init(&attr);
if(i != 0)
fatal("pthread_attr_init() failed with code %d.", i);
@@ -468,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);
@@ -487,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
@@ -517,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
@@ -539,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);
+ }
+ }
}
diff --git a/src/main.h b/src/main.h
index 6a90efd9d..d9edda58e 100644
--- a/src/main.h
+++ b/src/main.h
@@ -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);
diff --git a/src/plugin_proc.c b/src/plugin_proc.c
index 4cd20afc5..a147d971f 100644
--- a/src/plugin_proc.c
+++ b/src/plugin_proc.c
@@ -14,12 +14,7 @@
#include "rrd.h"
#include "plugin_proc.h"
#include "main.h"
-
-unsigned long long sutime() {
- struct timeval now;
- gettimeofday(&now, NULL);
- return now.tv_sec * 1000000ULL + now.tv_usec;
-}
+#include "registry.h"
void *proc_main(void *ptr)
{
@@ -88,11 +83,11 @@ void *proc_main(void *ptr)
if(unlikely(netdata_exit)) break;
// delay until it is our time to run
- while((sunow = sutime()) < sunext)
+ while((sunow = timems()) < sunext)
usleep((useconds_t)(sunext - sunow));
// find the next time we need to run
- while(sutime() > sunext)
+ while(timems() > sunext)
sunext += rrd_update_every * 1000000ULL;
if(unlikely(netdata_exit)) break;
@@ -102,7 +97,7 @@ void *proc_main(void *ptr)
if(!vdo_sys_kernel_mm_ksm) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_kernel_mm_ksm().");
- sunow = sutime();
+ sunow = timems();
vdo_sys_kernel_mm_ksm = do_sys_kernel_mm_ksm(rrd_update_every, (sutime_sys_kernel_mm_ksm > 0)?sunow - sutime_sys_kernel_mm_ksm:0ULL);
sutime_sys_kernel_mm_ksm = sunow;
}
@@ -110,7 +105,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_loadavg) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_loadavg().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_loadavg = do_proc_loadavg(rrd_update_every, (sutime_proc_loadavg > 0)?sunow - sutime_proc_loadavg:0ULL);
sutime_proc_loadavg = sunow;
}
@@ -118,7 +113,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_interrupts) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_interrupts().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_interrupts = do_proc_interrupts(rrd_update_every, (sutime_proc_interrupts > 0)?sunow - sutime_proc_interrupts:0ULL);
sutime_proc_interrupts = sunow;
}
@@ -126,7 +121,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_softirqs) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_softirqs().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_softirqs = do_proc_softirqs(rrd_update_every, (sutime_proc_softirqs > 0)?sunow - sutime_proc_softirqs:0ULL);
sutime_proc_softirqs = sunow;
}
@@ -134,7 +129,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_sys_kernel_random_entropy_avail) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_sys_kernel_random_entropy_avail().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_sys_kernel_random_entropy_avail = do_proc_sys_kernel_random_entropy_avail(rrd_update_every, (sutime_proc_sys_kernel_random_entropy_avail > 0)?sunow - sutime_proc_sys_kernel_random_entropy_avail:0ULL);
sutime_proc_sys_kernel_random_entropy_avail = sunow;
}
@@ -142,7 +137,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_dev) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_dev().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_dev = do_proc_net_dev(rrd_update_every, (sutime_proc_net_dev > 0)?sunow - sutime_proc_net_dev:0ULL);
sutime_proc_net_dev = sunow;
}
@@ -150,7 +145,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_diskstats) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_diskstats().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_diskstats = do_proc_diskstats(rrd_update_every, (sutime_proc_diskstats > 0)?sunow - sutime_proc_diskstats:0ULL);
sutime_proc_diskstats = sunow;
}
@@ -158,7 +153,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_snmp) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_snmp = do_proc_net_snmp(rrd_update_every, (sutime_proc_net_snmp > 0)?sunow - sutime_proc_net_snmp:0ULL);
sutime_proc_net_snmp = sunow;
}
@@ -166,7 +161,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_snmp6) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp6().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_snmp6 = do_proc_net_snmp6(rrd_update_every, (sutime_proc_net_snmp6 > 0)?sunow - sutime_proc_net_snmp6:0ULL);
sutime_proc_net_snmp6 = sunow;
}
@@ -174,7 +169,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_netstat) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_netstat().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_netstat = do_proc_net_netstat(rrd_update_every, (sutime_proc_net_netstat > 0)?sunow - sutime_proc_net_netstat:0ULL);
sutime_proc_net_netstat = sunow;
}
@@ -182,7 +177,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_stat_conntrack) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_stat_conntrack().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_stat_conntrack = do_proc_net_stat_conntrack(rrd_update_every, (sutime_proc_net_stat_conntrack > 0)?sunow - sutime_proc_net_stat_conntrack:0ULL);
sutime_proc_net_stat_conntrack = sunow;
}
@@ -190,7 +185,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_ip_vs_stats) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_ip_vs_stats().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_ip_vs_stats = do_proc_net_ip_vs_stats(rrd_update_every, (sutime_proc_net_ip_vs_stats > 0)?sunow - sutime_proc_net_ip_vs_stats:0ULL);
sutime_proc_net_ip_vs_stats = sunow;
}
@@ -198,7 +193,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_stat_synproxy) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_stat_synproxy().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_stat_synproxy = do_proc_net_stat_synproxy(rrd_update_every, (sutime_proc_net_stat_synproxy > 0)?sunow - sutime_proc_net_stat_synproxy:0ULL);
sutime_proc_net_stat_synproxy = sunow;
}
@@ -206,7 +201,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_stat) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_stat().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_stat = do_proc_stat(rrd_update_every, (sutime_proc_stat > 0)?sunow - sutime_proc_stat:0ULL);
sutime_proc_stat = sunow;
}
@@ -214,7 +209,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_meminfo) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_meminfo().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_meminfo = do_proc_meminfo(rrd_update_every, (sutime_proc_meminfo > 0)?sunow - sutime_proc_meminfo:0ULL);
sutime_proc_meminfo = sunow;
}
@@ -222,7 +217,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_vmstat) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_vmstat().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_vmstat = do_proc_vmstat(rrd_update_every, (sutime_proc_vmstat > 0)?sunow - sutime_proc_vmstat:0ULL);
sutime_proc_vmstat = sunow;
}
@@ -230,7 +225,7 @@ void *proc_main(void *ptr)
if(!vdo_proc_net_rpc_nfsd) {
debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_rpc_nfsd().");
- sunow = sutime();
+ sunow = timems();
vdo_proc_net_rpc_nfsd = do_proc_net_rpc_nfsd(rrd_update_every, (sutime_proc_net_rpc_nfsd > 0)?sunow - sutime_proc_net_rpc_nfsd:0ULL);
sutime_proc_net_rpc_nfsd = sunow;
}
@@ -246,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);
@@ -276,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);
}
@@ -289,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);
}
@@ -302,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);
@@ -312,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();
}
}
diff --git a/src/plugin_tc.c b/src/plugin_tc.c
index 2c7a55cee..3d3e35217 100644
--- a/src/plugin_tc.c
+++ b/src/plugin_tc.c
@@ -83,8 +83,6 @@ struct tc_device *tc_device_root = NULL;
// ----------------------------------------------------------------------------
// tc_device index
-static int tc_device_iterator(avl *a) { if(a) {}; return 0; }
-
static int tc_device_compare(void* a, void* b) {
if(((struct tc_device *)a)->hash < ((struct tc_device *)b)->hash) return -1;
else if(((struct tc_device *)a)->hash > ((struct tc_device *)b)->hash) return 1;
@@ -93,32 +91,24 @@ static int tc_device_compare(void* a, void* b) {
avl_tree tc_device_root_index = {
NULL,
- tc_device_compare,
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#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) {
- struct tc_device *result = NULL, tmp;
+static inline struct tc_device *tc_device_index_find(const char *id, uint32_t hash) {
+ struct tc_device 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);
- return result;
+ return (struct tc_device *)avl_search(&(tc_device_root_index), (avl *)&tmp);
}
// ----------------------------------------------------------------------------
// tc_class index
-static int tc_class_iterator(avl *a) { if(a) {}; return 0; }
-
static int tc_class_compare(void* a, void* b) {
if(((struct tc_class *)a)->hash < ((struct tc_class *)b)->hash) return -1;
else if(((struct tc_class *)a)->hash > ((struct tc_class *)b)->hash) return 1;
@@ -128,18 +118,17 @@ 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) {
- struct tc_class *result = NULL, tmp;
+static inline struct tc_class *tc_class_index_find(struct tc_device *st, const char *id, uint32_t hash) {
+ struct tc_class 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);
- return result;
+ return (struct tc_class *)avl_search(&(st->classes_index), (avl *) &tmp);
}
// ----------------------------------------------------------------------------
-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;
@@ -159,7 +148,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) {
@@ -184,7 +173,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;
@@ -239,7 +228,7 @@ static void tc_device_commit(struct tc_device *d)
}
char var_name[CONFIG_MAX_NAME + 1];
- snprintf(var_name, CONFIG_MAX_NAME, "qos for %s", d->id);
+ snprintfz(var_name, CONFIG_MAX_NAME, "qos for %s", d->id);
if(config_get_boolean("plugin:tc", var_name, enable_new_interfaces)) {
RRDSET *st = rrdset_find_bytype(RRD_TYPE_TC, d->id);
if(!st) {
@@ -294,7 +283,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) {
@@ -308,7 +297,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;
@@ -318,7 +307,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;
@@ -329,7 +318,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);
@@ -345,18 +334,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;
-#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
- 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) {
@@ -372,7 +350,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);
@@ -414,7 +392,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;
@@ -434,7 +412,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);
@@ -455,7 +433,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;
@@ -531,7 +509,7 @@ void *tc_main(void *ptr)
struct tc_device *device = NULL;
struct tc_class *class = NULL;
- snprintf(buffer, TC_LINE_MAX, "exec %s %d", config_get("plugin:tc", "script to run to get tc values", PLUGINS_DIR "/tc-qos-helper.sh"), rrd_update_every);
+ snprintfz(buffer, TC_LINE_MAX, "exec %s %d", config_get("plugin:tc", "script to run to get tc values", PLUGINS_DIR "/tc-qos-helper.sh"), rrd_update_every);
debug(D_TC_LOOP, "executing '%s'", buffer);
// fp = popen(buffer, "r");
fp = mypopen(buffer, &tc_child_pid);
@@ -586,7 +564,7 @@ void *tc_main(void *ptr)
char leafbuf[20 + 1] = "";
if(leafid && leafid[strlen(leafid) - 1] == ':') {
- strncpy(leafbuf, leafid, 20 - 1);
+ strncpyz(leafbuf, leafid, 20 - 1);
strcat(leafbuf, "1");
leafid = leafbuf;
}
diff --git a/src/plugins_d.c b/src/plugins_d.c
index b8524d99c..0ccbd36e4 100644
--- a/src/plugins_d.c
+++ b/src/plugins_d.c
@@ -181,7 +181,7 @@ void *pluginsd_worker_thread(void *arg)
if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: '%s' is setting dimension %s/%s to %s", cd->fullfilename, st->id, dimension, value?value:"<nothing>");
- if(value) rrddim_set(st, dimension, atoll(value));
+ if(value) rrddim_set(st, dimension, strtoll(value, NULL, 0));
count++;
}
@@ -311,11 +311,11 @@ void *pluginsd_worker_thread(void *arg)
}
long multiplier = 1;
- if(multiplier_s && *multiplier_s) multiplier = atol(multiplier_s);
+ if(multiplier_s && *multiplier_s) multiplier = strtol(multiplier_s, NULL, 0);
if(unlikely(!multiplier)) multiplier = 1;
long divisor = 1;
- if(likely(divisor_s && *divisor_s)) divisor = atol(divisor_s);
+ if(likely(divisor_s && *divisor_s)) divisor = strtol(divisor_s, NULL, 0);
if(unlikely(!divisor)) divisor = 1;
if(unlikely(!algorithm || !*algorithm)) algorithm = "absolute";
@@ -351,7 +351,7 @@ void *pluginsd_worker_thread(void *arg)
#ifdef DETACH_PLUGINS_FROM_NETDATA
else if(likely(hash == MYPID_HASH && !strcmp(s, "MYPID"))) {
char *pid_s = words[1];
- pid_t pid = atol(pid_s);
+ pid_t pid = strtod(pid_s, NULL, 0);
if(likely(pid)) cd->pid = pid;
debug(D_PLUGINSD, "PLUGINSD: %s is on pid %d", cd->id, cd->pid);
@@ -470,7 +470,7 @@ void *pluginsd_main(void *ptr)
}
char pluginname[CONFIG_MAX_NAME + 1];
- snprintf(pluginname, CONFIG_MAX_NAME, "%.*s", (int)(len - PLUGINSD_FILE_SUFFIX_LEN), file->d_name);
+ snprintfz(pluginname, CONFIG_MAX_NAME, "%.*s", (int)(len - PLUGINSD_FILE_SUFFIX_LEN), file->d_name);
int enabled = config_get_boolean("plugins", pluginname, automatic_run);
if(unlikely(!enabled)) {
@@ -493,17 +493,17 @@ void *pluginsd_main(void *ptr)
cd = calloc(sizeof(struct plugind), 1);
if(unlikely(!cd)) fatal("Cannot allocate memory for plugin.");
- snprintf(cd->id, CONFIG_MAX_NAME, "plugin:%s", pluginname);
+ snprintfz(cd->id, CONFIG_MAX_NAME, "plugin:%s", pluginname);
- strncpy(cd->filename, file->d_name, FILENAME_MAX);
- snprintf(cd->fullfilename, FILENAME_MAX, "%s/%s", dir_name, cd->filename);
+ strncpyz(cd->filename, file->d_name, FILENAME_MAX);
+ snprintfz(cd->fullfilename, FILENAME_MAX, "%s/%s", dir_name, cd->filename);
cd->enabled = enabled;
cd->update_every = (int) config_get_number(cd->id, "update every", rrd_update_every);
cd->started_t = time(NULL);
char *def = "";
- snprintf(cd->cmd, PLUGINSD_CMD_MAX, "exec %s %d %s", cd->fullfilename, cd->update_every, config_get(cd->id, "command options", def));
+ snprintfz(cd->cmd, PLUGINSD_CMD_MAX, "exec %s %d %s", cd->fullfilename, cd->update_every, config_get(cd->id, "command options", def));
// link it
if(likely(pluginsd_root)) cd->next = pluginsd_root;
diff --git a/src/popen.c b/src/popen.c
index 882a4cc5a..06f27c0b7 100644
--- a/src/popen.c
+++ b/src/popen.c
@@ -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);
}
diff --git a/src/proc_diskstats.c b/src/proc_diskstats.c
index c2b84aae1..c62a1351c 100644
--- a/src/proc_diskstats.c
+++ b/src/proc_diskstats.c
@@ -15,17 +15,21 @@
#include "rrd.h"
#include "plugin_proc.h"
+#include "proc_self_mountinfo.h"
+
#define RRD_TYPE_DISK "disk"
struct disk {
unsigned long major;
unsigned long minor;
int partition_id; // -1 = this is not a partition
+ char *family;
struct disk *next;
} *disk_root = NULL;
struct disk *get_disk(unsigned long major, unsigned long minor) {
static char path_find_block_device_partition[FILENAME_MAX + 1] = "";
+ static struct mountinfo *mountinfo_root = NULL;
struct disk *d;
// search for it in our RAM list.
@@ -42,8 +46,8 @@ struct disk *get_disk(unsigned long major, unsigned long minor) {
if(unlikely(!path_find_block_device_partition[0])) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/dev/block/%lu:%lu/partition");
- snprintf(path_find_block_device_partition, FILENAME_MAX, "%s", config_get("plugin:proc:/proc/diskstats", "path to get block device partition", filename));
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/dev/block/%lu:%lu/partition");
+ snprintfz(path_find_block_device_partition, FILENAME_MAX, "%s", config_get("plugin:proc:/proc/diskstats", "path to get block device partition", filename));
}
// not found
@@ -68,7 +72,7 @@ struct disk *get_disk(unsigned long major, unsigned long minor) {
// find if it is a partition
// by reading /sys/dev/block/MAJOR:MINOR/partition
char buffer[FILENAME_MAX + 1];
- snprintf(buffer, FILENAME_MAX, path_find_block_device_partition, major, minor);
+ snprintfz(buffer, FILENAME_MAX, path_find_block_device_partition, major, minor);
int fd = open(buffer, O_RDONLY, 0666);
if(likely(fd != -1)) {
@@ -81,6 +85,28 @@ struct disk *get_disk(unsigned long major, unsigned long minor) {
}
// if the /partition file does not exist, it is a disk, not a partition
+ // ------------------------------------------------------------------------
+ // check if we can find its mount point
+
+ // mountinfo_find() can be called with NULL mountinfo_root
+ struct mountinfo *mi = mountinfo_find(mountinfo_root, d->major, d->minor);
+ if(unlikely(!mi)) {
+ // mountinfo_free() can be called with NULL mountinfo_root
+ mountinfo_free(mountinfo_root);
+
+ // re-read mountinfo in case something changed
+ mountinfo_root = mountinfo_read();
+
+ // search again for this disk
+ mi = mountinfo_find(mountinfo_root, d->major, d->minor);
+ }
+
+ if(mi)
+ d->family = strdup(mi->mount_point);
+ // no need to check for NULL
+ else
+ d->family = NULL;
+
return d;
}
@@ -102,15 +128,15 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/diskstats");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/diskstats");
ff = procfile_open(config_get("plugin:proc:/proc/diskstats", "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
if(!path_to_get_hw_sector_size[0]) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/block/%s/queue/hw_sector_size");
- snprintf(path_to_get_hw_sector_size, FILENAME_MAX, "%s", config_get("plugin:proc:/proc/diskstats", "path to get h/w sector size", filename));
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/block/%s/queue/hw_sector_size");
+ snprintfz(path_to_get_hw_sector_size, FILENAME_MAX, "%s", config_get("plugin:proc:/proc/diskstats", "path to get h/w sector size", filename));
}
ff = procfile_readall(ff);
@@ -189,6 +215,9 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
else
def_enabled = 0;
+ char *family = d->family;
+ if(!family) family = disk;
+
/*
switch(major) {
case 9: // MDs
@@ -316,7 +345,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
// check which charts are enabled for this disk
{
char var_name[4096 + 1];
- snprintf(var_name, 4096, "plugin:proc:/proc/diskstats:%s", disk);
+ snprintfz(var_name, 4096, "plugin:proc:/proc/diskstats:%s", disk);
def_enabled = config_get_boolean_ondemand(var_name, "enabled", def_enabled);
if(def_enabled == CONFIG_ONDEMAND_NO) continue;
if(def_enabled == CONFIG_ONDEMAND_ONDEMAND && !reads && !writes) continue;
@@ -354,13 +383,12 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
char tf[FILENAME_MAX + 1], *t;
char ssfilename[FILENAME_MAX + 1];
- strncpy(tf, disk, FILENAME_MAX);
- tf[FILENAME_MAX] = '\0';
+ strncpyz(tf, disk, FILENAME_MAX);
// replace all / with !
while((t = strchr(tf, '/'))) *t = '!';
- snprintf(ssfilename, FILENAME_MAX, path_to_get_hw_sector_size, tf);
+ snprintfz(ssfilename, FILENAME_MAX, path_to_get_hw_sector_size, tf);
FILE *fpss = fopen(ssfilename, "r");
if(fpss) {
char ssbuffer[1025];
@@ -379,7 +407,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
}
else error("Cannot read sector size for device %s from %s. Assuming 512.", disk, ssfilename);
- st = rrdset_create(RRD_TYPE_DISK, disk, NULL, disk, "disk.io", "Disk I/O Bandwidth", "kilobytes/s", 2000, update_every, RRDSET_TYPE_AREA);
+ st = rrdset_create(RRD_TYPE_DISK, disk, NULL, family, "disk.io", "Disk I/O Bandwidth", "kilobytes/s", 2000, update_every, RRDSET_TYPE_AREA);
rrddim_add(st, "reads", NULL, sector_size, 1024, RRDDIM_INCREMENTAL);
rrddim_add(st, "writes", NULL, sector_size * -1, 1024, RRDDIM_INCREMENTAL);
@@ -396,7 +424,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_ops) {
st = rrdset_find_bytype("disk_ops", disk);
if(!st) {
- st = rrdset_create("disk_ops", disk, NULL, disk, "disk.ops", "Disk Completed I/O Operations", "operations/s", 2001, update_every, RRDSET_TYPE_LINE);
+ st = rrdset_create("disk_ops", disk, NULL, family, "disk.ops", "Disk Completed I/O Operations", "operations/s", 2001, update_every, RRDSET_TYPE_LINE);
st->isdetail = 1;
rrddim_add(st, "reads", NULL, 1, 1, RRDDIM_INCREMENTAL);
@@ -414,7 +442,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_qops) {
st = rrdset_find_bytype("disk_qops", disk);
if(!st) {
- st = rrdset_create("disk_qops", disk, NULL, disk, "disk.qops", "Disk Current I/O Operations", "operations", 2002, update_every, RRDSET_TYPE_LINE);
+ st = rrdset_create("disk_qops", disk, NULL, family, "disk.qops", "Disk Current I/O Operations", "operations", 2002, update_every, RRDSET_TYPE_LINE);
st->isdetail = 1;
rrddim_add(st, "operations", NULL, 1, 1, RRDDIM_ABSOLUTE);
@@ -430,7 +458,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_backlog) {
st = rrdset_find_bytype("disk_backlog", disk);
if(!st) {
- st = rrdset_create("disk_backlog", disk, NULL, disk, "disk.backlog", "Disk Backlog", "backlog (ms)", 2003, update_every, RRDSET_TYPE_AREA);
+ st = rrdset_create("disk_backlog", disk, NULL, family, "disk.backlog", "Disk Backlog", "backlog (ms)", 2003, update_every, RRDSET_TYPE_AREA);
st->isdetail = 1;
rrddim_add(st, "backlog", NULL, 1, 10, RRDDIM_INCREMENTAL);
@@ -446,7 +474,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_util) {
st = rrdset_find_bytype("disk_util", disk);
if(!st) {
- st = rrdset_create("disk_util", disk, NULL, disk, "disk.util", "Disk Utilization Time", "% of time working", 2004, update_every, RRDSET_TYPE_AREA);
+ st = rrdset_create("disk_util", disk, NULL, family, "disk.util", "Disk Utilization Time", "% of time working", 2004, update_every, RRDSET_TYPE_AREA);
st->isdetail = 1;
rrddim_add(st, "utilization", NULL, 1, 10, RRDDIM_INCREMENTAL);
@@ -462,7 +490,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_mops) {
st = rrdset_find_bytype("disk_mops", disk);
if(!st) {
- st = rrdset_create("disk_mops", disk, NULL, disk, "disk.mops", "Disk Merged Operations", "merged operations/s", 2021, update_every, RRDSET_TYPE_LINE);
+ st = rrdset_create("disk_mops", disk, NULL, family, "disk.mops", "Disk Merged Operations", "merged operations/s", 2021, update_every, RRDSET_TYPE_LINE);
st->isdetail = 1;
rrddim_add(st, "reads", NULL, 1, 1, RRDDIM_INCREMENTAL);
@@ -480,7 +508,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_iotime) {
st = rrdset_find_bytype("disk_iotime", disk);
if(!st) {
- st = rrdset_create("disk_iotime", disk, NULL, disk, "disk.iotime", "Disk Total I/O Time", "milliseconds/s", 2022, update_every, RRDSET_TYPE_LINE);
+ st = rrdset_create("disk_iotime", disk, NULL, family, "disk.iotime", "Disk Total I/O Time", "milliseconds/s", 2022, update_every, RRDSET_TYPE_LINE);
st->isdetail = 1;
rrddim_add(st, "reads", NULL, 1, 1, RRDDIM_INCREMENTAL);
@@ -501,7 +529,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_iotime && ddo_ops) {
st = rrdset_find_bytype("disk_await", disk);
if(!st) {
- st = rrdset_create("disk_await", disk, NULL, disk, "disk.await", "Average Completed I/O Operation Time", "ms per operation", 2005, update_every, RRDSET_TYPE_LINE);
+ st = rrdset_create("disk_await", disk, NULL, family, "disk.await", "Average Completed I/O Operation Time", "ms per operation", 2005, update_every, RRDSET_TYPE_LINE);
st->isdetail = 1;
rrddim_add(st, "reads", NULL, 1, 1, RRDDIM_ABSOLUTE);
@@ -517,7 +545,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_io && ddo_ops) {
st = rrdset_find_bytype("disk_avgsz", disk);
if(!st) {
- st = rrdset_create("disk_avgsz", disk, NULL, disk, "disk.avgsz", "Average Completed I/O Operation Bandwidth", "kilobytes per operation", 2006, update_every, RRDSET_TYPE_AREA);
+ st = rrdset_create("disk_avgsz", disk, NULL, family, "disk.avgsz", "Average Completed I/O Operation Bandwidth", "kilobytes per operation", 2006, update_every, RRDSET_TYPE_AREA);
st->isdetail = 1;
rrddim_add(st, "reads", NULL, sector_size, 1024, RRDDIM_ABSOLUTE);
@@ -533,7 +561,7 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
if(ddo_util && ddo_ops) {
st = rrdset_find_bytype("disk_svctm", disk);
if(!st) {
- st = rrdset_create("disk_svctm", disk, NULL, disk, "disk.svctm", "Average Service Time", "ms per operation", 2007, update_every, RRDSET_TYPE_LINE);
+ st = rrdset_create("disk_svctm", disk, NULL, family, "disk.svctm", "Average Service Time", "ms per operation", 2007, update_every, RRDSET_TYPE_LINE);
st->isdetail = 1;
rrddim_add(st, "svctm", NULL, 1, 1, RRDDIM_ABSOLUTE);
diff --git a/src/proc_interrupts.c b/src/proc_interrupts.c
index 6704ef1a5..ad00c2022 100644
--- a/src/proc_interrupts.c
+++ b/src/proc_interrupts.c
@@ -13,29 +13,34 @@
#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) {};
@@ -53,7 +57,7 @@ int do_proc_interrupts(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/interrupts");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/interrupts");
ff = procfile_open(config_get("plugin:proc:/proc/interrupts", "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
@@ -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;
@@ -116,18 +118,15 @@ int do_proc_interrupts(int update_every, unsigned long long dt) {
}
if(isdigit(irr->id[0]) && (uint32_t)(cpus + 2) < words) {
- strncpy(irr->name, procfile_lineword(ff, l, words - 1), MAX_INTERRUPT_NAME);
- irr->name[MAX_INTERRUPT_NAME] = '\0';
+ strncpyz(irr->name, procfile_lineword(ff, l, words - 1), MAX_INTERRUPT_NAME);
int nlen = strlen(irr->name);
if(nlen < (MAX_INTERRUPT_NAME-1)) {
irr->name[nlen] = '_';
- strncpy(&irr->name[nlen + 1], irr->id, MAX_INTERRUPT_NAME - nlen);
- irr->name[MAX_INTERRUPT_NAME] = '\0';
+ strncpyz(&irr->name[nlen + 1], irr->id, MAX_INTERRUPT_NAME - nlen);
}
}
else {
- strncpy(irr->name, irr->id, MAX_INTERRUPT_NAME);
- irr->name[MAX_INTERRUPT_NAME] = '\0';
+ strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME);
}
irr->used = 1;
@@ -142,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);
@@ -158,26 +159,28 @@ int do_proc_interrupts(int update_every, unsigned long long dt) {
int c;
for(c = 0; c < cpus ; c++) {
- char id[256];
- snprintf(id, 256, "cpu%d_interrupts", c);
+ char id[256+1];
+ snprintfz(id, 256, "cpu%d_interrupts", c);
st = rrdset_find_bytype("cpu", id);
if(!st) {
- char name[256], title[256];
- snprintf(name, 256, "cpu%d_interrupts", c);
- snprintf(title, 256, "CPU%d Interrupts", c);
+ char name[256+1], title[256+1];
+ snprintfz(name, 256, "cpu%d_interrupts", c);
+ snprintfz(title, 256, "CPU%d Interrupts", c);
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);
}
diff --git a/src/proc_loadavg.c b/src/proc_loadavg.c
index cd7edc832..c8e893b99 100644
--- a/src/proc_loadavg.c
+++ b/src/proc_loadavg.c
@@ -20,7 +20,7 @@ int do_proc_loadavg(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/loadavg");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/loadavg");
ff = procfile_open(config_get("plugin:proc:/proc/loadavg", "filename to monitor", filename), " \t,:|/", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_meminfo.c b/src/proc_meminfo.c
index dbd43369f..611b4ed21 100644
--- a/src/proc_meminfo.c
+++ b/src/proc_meminfo.c
@@ -32,7 +32,7 @@ int do_proc_meminfo(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/meminfo");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/meminfo");
ff = procfile_open(config_get("plugin:proc:/proc/meminfo", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_net_dev.c b/src/proc_net_dev.c
index 5070ab817..12d8078c7 100644
--- a/src/proc_net_dev.c
+++ b/src/proc_net_dev.c
@@ -20,7 +20,7 @@ int do_proc_net_dev(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/dev");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/dev");
ff = procfile_open(config_get("plugin:proc:/proc/net/dev", "filename to monitor", filename), " \t,:|", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
@@ -86,7 +86,7 @@ int do_proc_net_dev(int update_every, unsigned long long dt) {
// check if the user wants it
{
char var_name[512 + 1];
- snprintf(var_name, 512, "plugin:proc:/proc/net/dev:%s", iface);
+ snprintfz(var_name, 512, "plugin:proc:/proc/net/dev:%s", iface);
default_enable = config_get_boolean_ondemand(var_name, "enabled", default_enable);
if(default_enable == CONFIG_ONDEMAND_NO) continue;
if(default_enable == CONFIG_ONDEMAND_ONDEMAND && !rbytes && !tbytes) continue;
diff --git a/src/proc_net_ip_vs_stats.c b/src/proc_net_ip_vs_stats.c
index 8c2ece7d3..ffb5da7b5 100644
--- a/src/proc_net_ip_vs_stats.c
+++ b/src/proc_net_ip_vs_stats.c
@@ -25,7 +25,7 @@ int do_proc_net_ip_vs_stats(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/ip_vs_stats");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/ip_vs_stats");
ff = procfile_open(config_get("plugin:proc:/proc/net/ip_vs_stats", "filename to monitor", filename), " \t,:|", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_net_netstat.c b/src/proc_net_netstat.c
index 859cf9053..c8c12c1db 100644
--- a/src/proc_net_netstat.c
+++ b/src/proc_net_netstat.c
@@ -27,7 +27,7 @@ int do_proc_net_netstat(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/netstat");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/netstat");
ff = procfile_open(config_get("plugin:proc:/proc/net/netstat", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_net_rpc_nfsd.c b/src/proc_net_rpc_nfsd.c
index 12949f5d2..6c6dd7066 100644
--- a/src/proc_net_rpc_nfsd.c
+++ b/src/proc_net_rpc_nfsd.c
@@ -142,7 +142,7 @@ int do_proc_net_rpc_nfsd(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/rpc/nfsd");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/rpc/nfsd");
ff = procfile_open(config_get("plugin:proc:/proc/net/rpc/nfsd", "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_net_snmp.c b/src/proc_net_snmp.c
index 742b4cfc7..e0ac6a263 100644
--- a/src/proc_net_snmp.c
+++ b/src/proc_net_snmp.c
@@ -36,7 +36,7 @@ int do_proc_net_snmp(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/snmp");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/snmp");
ff = procfile_open(config_get("plugin:proc:/proc/net/snmp", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_net_snmp6.c b/src/proc_net_snmp6.c
index e7fadf573..885835a8c 100644
--- a/src/proc_net_snmp6.c
+++ b/src/proc_net_snmp6.c
@@ -254,7 +254,7 @@ int do_proc_net_snmp6(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/snmp6");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/snmp6");
ff = procfile_open(config_get("plugin:proc:/proc/net/snmp6", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_net_stat_conntrack.c b/src/proc_net_stat_conntrack.c
index f7e5c45b2..7d754a1d9 100644
--- a/src/proc_net_stat_conntrack.c
+++ b/src/proc_net_stat_conntrack.c
@@ -31,7 +31,7 @@ int do_proc_net_stat_conntrack(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/stat/nf_conntrack");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/stat/nf_conntrack");
ff = procfile_open(config_get("plugin:proc:/proc/net/stat/nf_conntrack", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_net_stat_synproxy.c b/src/proc_net_stat_synproxy.c
index 62296d78a..508b7d3b4 100644
--- a/src/proc_net_stat_synproxy.c
+++ b/src/proc_net_stat_synproxy.c
@@ -28,7 +28,7 @@ int do_proc_net_stat_synproxy(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/stat/synproxy");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/net/stat/synproxy");
ff = procfile_open(config_get("plugin:proc:/proc/net/stat/synproxy", "filename to monitor", filename), " \t,:|", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_self_mountinfo.c b/src/proc_self_mountinfo.c
new file mode 100644
index 000000000..45630b4c0
--- /dev/null
+++ b/src/proc_self_mountinfo.c
@@ -0,0 +1,231 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "common.h"
+#include "log.h"
+#include "appconfig.h"
+
+#include "proc_self_mountinfo.h"
+
+// find the mount info with the given major:minor
+// in the supplied linked list of mountinfo structures
+struct mountinfo *mountinfo_find(struct mountinfo *root, unsigned long major, unsigned long minor) {
+ struct mountinfo *mi;
+
+ for(mi = root; mi ; mi = mi->next)
+ if(mi->major == major && mi->minor == minor)
+ return mi;
+
+ return NULL;
+}
+
+// find the mount info with the given filesystem and mount_source
+// in the supplied linked list of mountinfo structures
+struct mountinfo *mountinfo_find_by_filesystem_mount_source(struct mountinfo *root, const char *filesystem, const char *mount_source) {
+ struct mountinfo *mi;
+ uint32_t filesystem_hash = simple_hash(filesystem), mount_source_hash = simple_hash(mount_source);
+
+ for(mi = root; mi ; mi = mi->next)
+ if(mi->filesystem
+ && mi->mount_source
+ && mi->filesystem_hash == filesystem_hash
+ && mi->mount_source_hash == mount_source_hash
+ && !strcmp(mi->filesystem, filesystem)
+ && !strcmp(mi->mount_source, mount_source))
+ return mi;
+
+ return NULL;
+}
+
+struct mountinfo *mountinfo_find_by_filesystem_super_option(struct mountinfo *root, const char *filesystem, const char *super_options) {
+ struct mountinfo *mi;
+ uint32_t filesystem_hash = simple_hash(filesystem);
+
+ size_t solen = strlen(super_options);
+
+ for(mi = root; mi ; mi = mi->next)
+ if(mi->filesystem
+ && mi->super_options
+ && mi->filesystem_hash == filesystem_hash
+ && !strcmp(mi->filesystem, filesystem)) {
+
+ // super_options is a comma separated list
+ char *s = mi->super_options, *e;
+ while(*s) {
+ e = ++s;
+ while(*e && *e != ',') e++;
+
+ size_t len = e - s;
+ if(len == solen && !strncmp(s, super_options, len))
+ return mi;
+
+ if(*e == ',') s = ++e;
+ else s = e;
+ }
+ }
+
+ return NULL;
+}
+
+
+// free a linked list of mountinfo structures
+void mountinfo_free(struct mountinfo *mi) {
+ if(unlikely(!mi))
+ return;
+
+ if(likely(mi->next))
+ mountinfo_free(mi->next);
+
+ if(mi->root) free(mi->root);
+ if(mi->mount_point) free(mi->mount_point);
+ if(mi->mount_options) free(mi->mount_options);
+
+/*
+ if(mi->optional_fields_count) {
+ int i;
+ for(i = 0; i < mi->optional_fields_count ; i++)
+ free(*mi->optional_fields[i]);
+ }
+ free(mi->optional_fields);
+*/
+ free(mi->filesystem);
+ free(mi->mount_source);
+ free(mi->super_options);
+ free(mi);
+}
+
+// read the whole mountinfo into a linked list
+struct mountinfo *mountinfo_read() {
+ procfile *ff = NULL;
+
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/proc/self/mountinfo", global_host_prefix);
+ ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
+ if(!ff) {
+ snprintfz(filename, FILENAME_MAX, "%s/proc/1/mountinfo", global_host_prefix);
+ ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
+ if(!ff) return NULL;
+ }
+
+ ff = procfile_readall(ff);
+ if(!ff) return NULL;
+
+ struct mountinfo *root = NULL, *last = NULL, *mi = NULL;
+
+ unsigned long l, lines = procfile_lines(ff);
+ for(l = 0; l < lines ;l++) {
+ if(procfile_linewords(ff, l) < 5)
+ continue;
+
+ mi = malloc(sizeof(struct mountinfo));
+ if(unlikely(!mi)) fatal("Cannot allocate memory for mountinfo");
+
+ if(unlikely(!root))
+ root = last = mi;
+ else
+ last->next = mi;
+
+ last = mi;
+ mi->next = NULL;
+
+ unsigned long w = 0;
+ mi->id = strtoul(procfile_lineword(ff, l, w), NULL, 10); w++;
+ mi->parentid = strtoul(procfile_lineword(ff, l, w), NULL, 10); w++;
+
+ char *major = procfile_lineword(ff, l, w), *minor; w++;
+ for(minor = major; *minor && *minor != ':' ;minor++) ;
+ *minor = '\0';
+ minor++;
+
+ mi->major = strtoul(major, NULL, 10);
+ mi->minor = strtoul(minor, NULL, 10);
+
+ mi->root = strdup(procfile_lineword(ff, l, w)); w++;
+ if(unlikely(!mi->root)) fatal("Cannot allocate memory");
+ mi->root_hash = simple_hash(mi->root);
+
+ mi->mount_point = strdup(procfile_lineword(ff, l, w)); w++;
+ if(unlikely(!mi->mount_point)) fatal("Cannot allocate memory");
+ mi->mount_point_hash = simple_hash(mi->mount_point);
+
+ mi->mount_options = strdup(procfile_lineword(ff, l, w)); w++;
+ if(unlikely(!mi->mount_options)) fatal("Cannot allocate memory");
+
+ // count the optional fields
+/*
+ unsigned long wo = w;
+*/
+ mi->optional_fields_count = 0;
+ char *s = procfile_lineword(ff, l, w);
+ while(*s && *s != '-') {
+ w++;
+ s = procfile_lineword(ff, l, w);
+ mi->optional_fields_count++;
+ }
+
+/*
+ if(unlikely(mi->optional_fields_count)) {
+ // we have some optional fields
+ // read them into a new array of pointers;
+
+ mi->optional_fields = malloc(mi->optional_fields_count * sizeof(char *));
+ if(unlikely(!mi->optional_fields))
+ fatal("Cannot allocate memory for %d mountinfo optional fields", mi->optional_fields_count);
+
+ int i;
+ for(i = 0; i < mi->optional_fields_count ; i++) {
+ *mi->optional_fields[wo] = strdup(procfile_lineword(ff, l, w));
+ if(!mi->optional_fields[wo]) fatal("Cannot allocate memory");
+ wo++;
+ }
+ }
+ else
+ mi->optional_fields = NULL;
+*/
+
+ if(likely(*s == '-')) {
+ w++;
+
+ mi->filesystem = strdup(procfile_lineword(ff, l, w)); w++;
+ if(!mi->filesystem) fatal("Cannot allocate memory");
+ mi->filesystem_hash = simple_hash(mi->filesystem);
+
+ mi->mount_source = strdup(procfile_lineword(ff, l, w)); w++;
+ if(!mi->mount_source) fatal("Cannot allocate memory");
+ mi->mount_source_hash = simple_hash(mi->mount_source);
+
+ mi->super_options = strdup(procfile_lineword(ff, l, w)); w++;
+ if(!mi->super_options) fatal("Cannot allocate memory");
+ }
+ else {
+ mi->filesystem = NULL;
+ mi->mount_source = NULL;
+ mi->super_options = NULL;
+ }
+
+/*
+ info("MOUNTINFO: %u %u %u:%u root '%s', mount point '%s', mount options '%s', filesystem '%s', mount source '%s', super options '%s'",
+ mi->id,
+ mi->parentid,
+ mi->major,
+ mi->minor,
+ mi->root,
+ mi->mount_point,
+ mi->mount_options,
+ mi->filesystem,
+ mi->mount_source,
+ mi->super_options
+ );
+*/
+ }
+
+ procfile_close(ff);
+ return root;
+}
diff --git a/src/proc_self_mountinfo.h b/src/proc_self_mountinfo.h
new file mode 100644
index 000000000..51712a58a
--- /dev/null
+++ b/src/proc_self_mountinfo.h
@@ -0,0 +1,42 @@
+#include "procfile.h"
+
+#ifndef NETDATA_PROC_SELF_MOUNTINFO_H
+#define NETDATA_PROC_SELF_MOUNTINFO_H 1
+
+struct mountinfo {
+ long id; // mount ID: unique identifier of the mount (may be reused after umount(2)).
+ long parentid; // parent ID: ID of parent mount (or of self for the top of the mount tree).
+ unsigned long major; // major:minor: value of st_dev for files on filesystem (see stat(2)).
+ unsigned long minor;
+
+ char *root; // root: root of the mount within the filesystem.
+ uint32_t root_hash;
+
+ char *mount_point; // mount point: mount point relative to the process's root.
+ uint32_t mount_point_hash;
+
+ char *mount_options; // mount options: per-mount options.
+
+ int optional_fields_count;
+/*
+ char ***optional_fields; // optional fields: zero or more fields of the form "tag[:value]".
+*/
+ char *filesystem; // filesystem type: name of filesystem in the form "type[.subtype]".
+ uint32_t filesystem_hash;
+
+ char *mount_source; // mount source: filesystem-specific information or "none".
+ uint32_t mount_source_hash;
+
+ char *super_options; // super options: per-superblock options.
+
+ struct mountinfo *next;
+};
+
+extern struct mountinfo *mountinfo_find(struct mountinfo *root, unsigned long major, unsigned long minor);
+extern struct mountinfo *mountinfo_find_by_filesystem_mount_source(struct mountinfo *root, const char *filesystem, const char *mount_source);
+extern struct mountinfo *mountinfo_find_by_filesystem_super_option(struct mountinfo *root, const char *filesystem, const char *super_options);
+
+extern void mountinfo_free(struct mountinfo *mi);
+extern struct mountinfo *mountinfo_read();
+
+#endif /* NETDATA_PROC_SELF_MOUNTINFO_H */ \ No newline at end of file
diff --git a/src/proc_softirqs.c b/src/proc_softirqs.c
index c3a75f600..96b5d3d30 100644
--- a/src/proc_softirqs.c
+++ b/src/proc_softirqs.c
@@ -13,29 +13,34 @@
#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;
@@ -53,7 +58,7 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/softirqs");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/softirqs");
ff = procfile_open(config_get("plugin:proc:/proc/softirqs", "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
@@ -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;
@@ -115,8 +118,7 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
irr->total += irr->value[c];
}
- strncpy(irr->name, irr->id, MAX_INTERRUPT_NAME);
- irr->name[MAX_INTERRUPT_NAME] = '\0';
+ strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME);
irr->used = 1;
}
@@ -130,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);
@@ -146,34 +150,37 @@ int do_proc_softirqs(int update_every, unsigned long long dt) {
int c;
for(c = 0; c < cpus ; c++) {
- char id[256];
- snprintf(id, 256, "cpu%d_softirqs", c);
+ char id[256+1];
+ snprintfz(id, 256, "cpu%d_softirqs", c);
st = rrdset_find_bytype("cpu", id);
if(!st) {
// find if everything is zero
unsigned long long core_sum = 0 ;
for(l = 0; l < lines ;l++) {
- if(!irrs[l].used) continue;
- core_sum += irrs[l].value[c];
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(!irr->used) continue;
+ core_sum += irr->value[c];
}
if(core_sum == 0) continue; // try next core
- char name[256], title[256];
- snprintf(name, 256, "cpu%d_softirqs", c);
- snprintf(title, 256, "CPU%d softirqs", c);
+ char name[256+1], title[256+1];
+ snprintfz(name, 256, "cpu%d_softirqs", c);
+ snprintfz(title, 256, "CPU%d softirqs", c);
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/proc_stat.c b/src/proc_stat.c
index 47f994b52..154ba167d 100644
--- a/src/proc_stat.c
+++ b/src/proc_stat.c
@@ -30,7 +30,7 @@ int do_proc_stat(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/stat");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/stat");
ff = procfile_open(config_get("plugin:proc:/proc/stat", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_sys_kernel_random_entropy_avail.c b/src/proc_sys_kernel_random_entropy_avail.c
index be9070aca..d7d1e8261 100644
--- a/src/proc_sys_kernel_random_entropy_avail.c
+++ b/src/proc_sys_kernel_random_entropy_avail.c
@@ -17,7 +17,7 @@ int do_proc_sys_kernel_random_entropy_avail(int update_every, unsigned long long
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/sys/kernel/random/entropy_avail");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/sys/kernel/random/entropy_avail");
ff = procfile_open(config_get("plugin:proc:/proc/sys/kernel/random/entropy_avail", "filename to monitor", filename), "", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/proc_vmstat.c b/src/proc_vmstat.c
index c8222390c..7b20ed8cf 100644
--- a/src/proc_vmstat.c
+++ b/src/proc_vmstat.c
@@ -214,7 +214,7 @@ int do_proc_vmstat(int update_every, unsigned long long dt) {
if(!ff) {
char filename[FILENAME_MAX + 1];
- snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/vmstat");
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/proc/vmstat");
ff = procfile_open(config_get("plugin:proc:/proc/vmstat", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff) return 1;
diff --git a/src/procfile.c b/src/procfile.c
index 1fc33ef5f..291f14519 100644
--- a/src/procfile.c
+++ b/src/procfile.c
@@ -445,8 +445,7 @@ procfile *procfile_open(const char *filename, const char *separators, uint32_t f
return NULL;
}
- strncpy(ff->filename, filename, FILENAME_MAX);
- ff->filename[FILENAME_MAX] = '\0';
+ strncpyz(ff->filename, filename, FILENAME_MAX);
ff->fd = fd;
ff->size = size;
@@ -479,8 +478,7 @@ procfile *procfile_reopen(procfile *ff, const char *filename, const char *separa
return NULL;
}
- strncpy(ff->filename, filename, FILENAME_MAX);
- ff->filename[FILENAME_MAX] = '\0';
+ strncpyz(ff->filename, filename, FILENAME_MAX);
ff->flags = flags;
diff --git a/src/registry.c b/src/registry.c
new file mode 100644
index 000000000..f39ce3e2e
--- /dev/null
+++ b/src/registry.c
@@ -0,0 +1,1838 @@
+#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
+ strncpyz(u->url, url, urllen);
+
+ 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);
+
+ strncpyz(m->guid, machine_guid, 36);
+
+ 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
+ strncpyz(pu->name, name, namelen);
+
+ 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
+ strncpyz(p->guid, person_guid, 36);
+
+ 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(likely(pu->last_t < when)) pu->last_t = when;
+
+ 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++;
+ if(likely(p->last_t < when)) 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++;
+ if(likely(mu->last_t < when)) mu->last_t = when;
+ }
+
+ //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++;
+ if(likely(m->last_t < when)) 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);
+
+ snprintfz(w->cookie1, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s", p->guid, edate);
+
+ if(registry.registry_domain && registry.registry_domain[0])
+ snprintfz(w->cookie2, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Domain=%s; Expires=%s", p->guid, registry.registry_domain, edate);
+}
+
+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;
+}
+
+// structure used be the callbacks below
+struct registry_person_url_callback_verify_machine_exists_data {
+ MACHINE *m;
+ int count;
+};
+
+int registry_person_url_callback_verify_machine_exists(void *entry, void *data) {
+ struct registry_person_url_callback_verify_machine_exists_data *d = (struct registry_person_url_callback_verify_machine_exists_data *)data;
+ PERSON_URL *pu = (PERSON_URL *)entry;
+ MACHINE *m = d->m;
+
+ if(pu->machine == m)
+ d->count++;
+
+ 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;
+ }
+
+ struct registry_person_url_callback_verify_machine_exists_data data = { m, 0 };
+
+ // verify the old person has access to this machine
+ dictionary_get_all(op->urls, registry_person_url_callback_verify_machine_exists, &data);
+ if(!data.count) {
+ registry_json_header(w, "switch", REGISTRY_STATUS_FAILED);
+ registry_json_footer(w);
+ return 433;
+ }
+
+ // verify the new person has access to this machine
+ data.count = 0;
+ dictionary_get_all(np->urls, registry_person_url_callback_verify_machine_exists, &data);
+ if(!data.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]) {
+ uuid_t uuid;
+
+ uuid_generate_time(uuid);
+ uuid_unparse_lower(uuid, registry.machine_guid);
+ registry.machine_guid[36] = '\0';
+
+ // 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];
+
+ snprintfz(old_filename, FILENAME_MAX, "%s.old", registry.db_filename);
+ snprintfz(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
+ snprintfz(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();
+
+ snprintfz(filename, FILENAME_MAX, "%s/registry.db", registry.pathname);
+ registry.db_filename = config_get("registry", "registry db file", filename);
+
+ snprintfz(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", "https://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);
+}
diff --git a/src/registry.h b/src/registry.h
new file mode 100644
index 000000000..d95383b5d
--- /dev/null
+++ b/src/registry.h
@@ -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 */
diff --git a/src/rrd.c b/src/rrd.c
index 86ff39687..ee23da0c2 100644
--- a/src/rrd.c
+++ b/src/rrd.c
@@ -42,35 +42,26 @@ int rrd_memory_mode = RRD_MEMORY_MODE_SAVE;
// ----------------------------------------------------------------------------
// RRDSET index
-static int rrdset_iterator(avl *a) { if(a) {}; return 0; }
-
static int rrdset_compare(void* a, void* b) {
if(((RRDSET *)a)->hash < ((RRDSET *)b)->hash) return -1;
else if(((RRDSET *)a)->hash > ((RRDSET *)b)->hash) return 1;
else return strcmp(((RRDSET *)a)->id, ((RRDSET *)b)->id);
}
-avl_tree rrdset_root_index = {
- NULL,
- rrdset_compare,
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#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;
- strncpy(tmp.id, id, RRD_ID_LENGTH_MAX);
- tmp.id[RRD_ID_LENGTH_MAX] = '\0';
+ RRDSET 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);
- return result;
+ return (RRDSET *)avl_search_lock(&(rrdset_root_index), (avl *) &tmp);
}
// ----------------------------------------------------------------------------
@@ -78,8 +69,6 @@ static RRDSET *rrdset_index_find(const char *id, uint32_t hash) {
#define rrdset_from_avlname(avlname_ptr) ((RRDSET *)((avlname_ptr) - offsetof(RRDSET, avlname)))
-static int rrdset_iterator_name(avl *a) { if(a) {}; return 0; }
-
static int rrdset_compare_name(void* a, void* b) {
RRDSET *A = rrdset_from_avlname(a);
RRDSET *B = rrdset_from_avlname(b);
@@ -91,22 +80,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,
-#ifdef AVL_LOCK_WITH_MUTEX
- PTHREAD_MUTEX_INITIALIZER
-#else
- PTHREAD_RWLOCK_INITIALIZER
-#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;
@@ -115,7 +99,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);
+ result = avl_search_lock(&(rrdset_root_index_name), (avl *) (&(tmp.avlname)));
if(result) {
RRDSET *st = rrdset_from_avlname(result);
if(strcmp(st->magic, RRDSET_MAGIC))
@@ -132,25 +116,21 @@ static RRDSET *rrdset_index_find_name(const char *name, uint32_t hash) {
// ----------------------------------------------------------------------------
// RRDDIM index
-static int rrddim_iterator(avl *a) { if(a) {}; return 0; }
-
static int rrddim_compare(void* a, void* b) {
if(((RRDDIM *)a)->hash < ((RRDDIM *)b)->hash) return -1;
else if(((RRDDIM *)a)->hash > ((RRDDIM *)b)->hash) return 1;
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;
- strncpy(tmp.id, id, RRD_ID_LENGTH_MAX);
- tmp.id[RRD_ID_LENGTH_MAX] = '\0';
+ RRDDIM 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);
- return result;
+ return (RRDDIM *)avl_search_lock(&(st->dimensions_index), (avl *) &tmp);
}
// ----------------------------------------------------------------------------
@@ -222,8 +202,8 @@ int rrd_memory_mode_id(const char *name)
int rrddim_algorithm_id(const char *name)
{
- if(strcmp(name, RRDDIM_ABSOLUTE_NAME) == 0) return RRDDIM_ABSOLUTE;
if(strcmp(name, RRDDIM_INCREMENTAL_NAME) == 0) return RRDDIM_INCREMENTAL;
+ if(strcmp(name, RRDDIM_ABSOLUTE_NAME) == 0) return RRDDIM_ABSOLUTE;
if(strcmp(name, RRDDIM_PCENT_OVER_ROW_TOTAL_NAME) == 0) return RRDDIM_PCENT_OVER_ROW_TOTAL;
if(strcmp(name, RRDDIM_PCENT_OVER_DIFF_TOTAL_NAME) == 0) return RRDDIM_PCENT_OVER_DIFF_TOTAL;
return RRDDIM_ABSOLUTE;
@@ -277,7 +257,7 @@ void rrdset_set_name(RRDSET *st, const char *name)
char b[CONFIG_MAX_VALUE + 1];
char n[RRD_ID_LENGTH_MAX + 1];
- snprintf(n, RRD_ID_LENGTH_MAX, "%s.%s", st->type, name);
+ snprintfz(n, RRD_ID_LENGTH_MAX, "%s.%s", st->type, name);
rrdset_strncpy_name(b, n, CONFIG_MAX_VALUE);
st->name = config_get(st->id, "name", b);
st->hash_name = simple_hash(st->name);
@@ -299,7 +279,7 @@ char *rrdset_cache_dir(const char *id)
char n[FILENAME_MAX + 1];
rrdset_strncpy_name(b, id, FILENAME_MAX);
- snprintf(n, FILENAME_MAX, "%s/%s", cache_dir, b);
+ snprintfz(n, FILENAME_MAX, "%s/%s", cache_dir, b);
ret = config_get(id, "cache directory", n);
if(rrd_memory_mode == RRD_MEMORY_MODE_MAP || rrd_memory_mode == RRD_MEMORY_MODE_SAVE) {
@@ -351,7 +331,7 @@ RRDSET *rrdset_create(const char *type, const char *id, const char *name, const
char fullfilename[FILENAME_MAX + 1];
RRDSET *st = NULL;
- snprintf(fullid, RRD_ID_LENGTH_MAX, "%s.%s", type, id);
+ snprintfz(fullid, RRD_ID_LENGTH_MAX, "%s.%s", type, id);
st = rrdset_find(fullid);
if(st) {
@@ -371,7 +351,7 @@ RRDSET *rrdset_create(const char *type, const char *id, const char *name, const
debug(D_RRD_CALLS, "Creating RRD_STATS for '%s.%s'.", type, id);
- snprintf(fullfilename, FILENAME_MAX, "%s/main.db", cache_dir);
+ snprintfz(fullfilename, FILENAME_MAX, "%s/main.db", cache_dir);
if(rrd_memory_mode != RRD_MEMORY_MODE_RAM) st = (RRDSET *)mymmap(fullfilename, size, ((rrd_memory_mode == RRD_MEMORY_MODE_MAP)?MAP_SHARED:MAP_PRIVATE), 0);
if(st) {
if(strcmp(st->magic, RRDSET_MAGIC) != 0) {
@@ -453,7 +433,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);
@@ -463,7 +443,7 @@ RRDSET *rrdset_create(const char *type, const char *id, const char *name, const
{
char varvalue[CONFIG_MAX_VALUE + 1];
- snprintf(varvalue, CONFIG_MAX_VALUE, "%s (%s)", title?title:"", st->name);
+ snprintfz(varvalue, CONFIG_MAX_VALUE, "%s (%s)", title?title:"", st->name);
st->title = config_get(st->id, "title", varvalue);
}
@@ -489,7 +469,7 @@ RRDDIM *rrddim_add(RRDSET *st, const char *id, const char *name, long multiplier
debug(D_RRD_CALLS, "Adding dimension '%s/%s'.", st->id, id);
rrdset_strncpy_name(filename, id, FILENAME_MAX);
- snprintf(fullfilename, FILENAME_MAX, "%s/%s.db", st->cache_dir, filename);
+ snprintfz(fullfilename, FILENAME_MAX, "%s/%s.db", st->cache_dir, filename);
if(rrd_memory_mode != RRD_MEMORY_MODE_RAM) rd = (RRDDIM *)mymmap(fullfilename, size, ((rrd_memory_mode == RRD_MEMORY_MODE_MAP)?MAP_SHARED:MAP_PRIVATE), 1);
if(rd) {
struct timeval now;
@@ -561,19 +541,19 @@ RRDDIM *rrddim_add(RRDSET *st, const char *id, const char *name, long multiplier
strcpy(rd->magic, RRDDIMENSION_MAGIC);
strcpy(rd->cache_filename, fullfilename);
- strncpy(rd->id, id, RRD_ID_LENGTH_MAX);
+ strncpyz(rd->id, id, RRD_ID_LENGTH_MAX);
rd->hash = simple_hash(rd->id);
- snprintf(varname, CONFIG_MAX_NAME, "dim %s name", rd->id);
+ snprintfz(varname, CONFIG_MAX_NAME, "dim %s name", rd->id);
rd->name = config_get(st->id, varname, (name && *name)?name:rd->id);
- snprintf(varname, CONFIG_MAX_NAME, "dim %s algorithm", rd->id);
+ snprintfz(varname, CONFIG_MAX_NAME, "dim %s algorithm", rd->id);
rd->algorithm = rrddim_algorithm_id(config_get(st->id, varname, rrddim_algorithm_name(algorithm)));
- snprintf(varname, CONFIG_MAX_NAME, "dim %s multiplier", rd->id);
+ snprintfz(varname, CONFIG_MAX_NAME, "dim %s multiplier", rd->id);
rd->multiplier = config_get_number(st->id, varname, multiplier);
- snprintf(varname, CONFIG_MAX_NAME, "dim %s divisor", rd->id);
+ snprintfz(varname, CONFIG_MAX_NAME, "dim %s divisor", rd->id);
rd->divisor = config_get_number(st->id, varname, divisor);
if(!rd->divisor) rd->divisor = 1;
@@ -604,7 +584,7 @@ void rrddim_set_name(RRDSET *st, RRDDIM *rd, const char *name)
debug(D_RRD_CALLS, "rrddim_set_name() %s.%s", st->name, rd->name);
char varname[CONFIG_MAX_NAME + 1];
- snprintf(varname, CONFIG_MAX_NAME, "dim %s name", rd->id);
+ snprintfz(varname, CONFIG_MAX_NAME, "dim %s name", rd->id);
config_set_default(st->id, varname, name);
}
@@ -724,12 +704,10 @@ RRDSET *rrdset_find_bytype(const char *type, const char *id)
char buf[RRD_ID_LENGTH_MAX + 1];
- strncpy(buf, type, RRD_ID_LENGTH_MAX - 1);
- buf[RRD_ID_LENGTH_MAX - 1] = '\0';
+ strncpyz(buf, type, RRD_ID_LENGTH_MAX - 1);
strcat(buf, ".");
int len = (int) strlen(buf);
- strncpy(&buf[len], id, (size_t) (RRD_ID_LENGTH_MAX - len));
- buf[RRD_ID_LENGTH_MAX] = '\0';
+ strncpyz(&buf[len], id, (size_t) (RRD_ID_LENGTH_MAX - len));
return(rrdset_find(buf));
}
@@ -846,7 +824,8 @@ unsigned long long rrdset_done(RRDSET *st)
pthread_rwlock_rdlock(&st->rwlock);
// enable the chart, if it was disabled
- st->enabled = 1;
+ if(unlikely(rrd_delete_unupdated_dimensions) && !st->enabled)
+ st->enabled = 1;
// check if the chart has a long time to be updated
if(unlikely(st->usec_since_last_update > st->entries * st->update_every * 1000000ULL)) {
diff --git a/src/rrd.h b/src/rrd.h
index 4211b7d56..60eeeb04f 100644
--- 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;
diff --git a/src/rrd2json.c b/src/rrd2json.c
index 88a750443..e0bd06670 100644
--- a/src/rrd2json.c
+++ b/src/rrd2json.c
@@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
+#include <math.h>
#include "log.h"
#include "common.h"
@@ -681,18 +682,18 @@ static void rrdr2json(RRDR *r, BUFFER *wb, uint32_t options, int datatable)
sq[0] = '"';
}
row_annotations = 1;
- snprintf(pre_date, 100, " {%sc%s:[{%sv%s:%s", kq, kq, kq, kq, sq);
- snprintf(post_date, 100, "%s}", sq);
- snprintf(pre_label, 100, ",\n {%sid%s:%s%s,%slabel%s:%s", kq, kq, sq, sq, kq, kq, sq);
- snprintf(post_label, 100, "%s,%spattern%s:%s%s,%stype%s:%snumber%s}", sq, kq, kq, sq, sq, kq, kq, sq, sq);
- snprintf(pre_value, 100, ",{%sv%s:", kq, kq);
- snprintf(post_value, 100, "}");
- snprintf(post_line, 100, "]}");
- snprintf(data_begin, 100, "\n ],\n %srows%s:\n [\n", kq, kq);
- snprintf(finish, 100, "\n ]\n}");
-
- snprintf(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq);
- snprintf(normal_annotation, 200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq);
+ snprintfz(pre_date, 100, " {%sc%s:[{%sv%s:%s", kq, kq, kq, kq, sq);
+ snprintfz(post_date, 100, "%s}", sq);
+ snprintfz(pre_label, 100, ",\n {%sid%s:%s%s,%slabel%s:%s", kq, kq, sq, sq, kq, kq, sq);
+ snprintfz(post_label, 100, "%s,%spattern%s:%s%s,%stype%s:%snumber%s}", sq, kq, kq, sq, sq, kq, kq, sq, sq);
+ snprintfz(pre_value, 100, ",{%sv%s:", kq, kq);
+ strcpy(post_value, "}");
+ strcpy(post_line, "]}");
+ snprintfz(data_begin, 100, "\n ],\n %srows%s:\n [\n", kq, kq);
+ strcpy(finish, "\n ]\n}");
+
+ snprintfz(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq);
+ snprintfz(normal_annotation, 200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq);
buffer_sprintf(wb, "{\n %scols%s:\n [\n", kq, kq, kq, kq);
buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%stime%s,%spattern%s:%s%s,%stype%s:%sdatetime%s},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq);
@@ -716,18 +717,18 @@ static void rrdr2json(RRDR *r, BUFFER *wb, uint32_t options, int datatable)
dates_with_new = 1;
}
if( options & RRDR_OPTION_OBJECTSROWS )
- snprintf(pre_date, 100, " { ");
+ strcpy(pre_date, " { ");
else
- snprintf(pre_date, 100, " [ ");
- snprintf(pre_label, 100, ", \"");
- snprintf(post_label, 100, "\"");
- snprintf(pre_value, 100, ", ");
+ strcpy(pre_date, " [ ");
+ strcpy(pre_label, ", \"");
+ strcpy(post_label, "\"");
+ strcpy(pre_value, ", ");
if( options & RRDR_OPTION_OBJECTSROWS )
- snprintf(post_line, 100, "}");
+ strcpy(post_line, "}");
else
- snprintf(post_line, 100, "]");
- snprintf(data_begin, 100, "],\n %sdata%s:\n [\n", kq, kq);
- snprintf(finish, 100, "\n ]\n}");
+ strcpy(post_line, "]");
+ snprintfz(data_begin, 100, "],\n %sdata%s:\n [\n", kq, kq);
+ strcpy(finish, "\n ]\n}");
buffer_sprintf(wb, "{\n %slabels%s: [", kq, kq);
buffer_sprintf(wb, "%stime%s", sq, sq);
@@ -1450,7 +1451,7 @@ RRDR *rrd2rrdr(RRDSET *st, long points, long long after, long long before, int g
switch(group_method) {
case GROUP_MAX:
- if(unlikely(abs(value) > abs(group_values[c])))
+ if(unlikely(fabsl(value) > fabsl(group_values[c])))
group_values[c] = value;
break;
@@ -1742,12 +1743,12 @@ time_t rrd_stats_json(int type, RRDSET *st, BUFFER *wb, long points, long group,
// -------------------------------------------------------------------------
// prepare various strings, to speed up the loop
- char overflow_annotation[201]; snprintf(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq);
- char normal_annotation[201]; snprintf(normal_annotation, 200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq);
- char pre_date[51]; snprintf(pre_date, 50, " {%sc%s:[{%sv%s:%s", kq, kq, kq, kq, sq);
- char post_date[21]; snprintf(post_date, 20, "%s}", sq);
- char pre_value[21]; snprintf(pre_value, 20, ",{%sv%s:", kq, kq);
- char post_value[21]; snprintf(post_value, 20, "}");
+ char overflow_annotation[201]; snprintfz(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq);
+ char normal_annotation[201]; snprintfz(normal_annotation, 200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq);
+ char pre_date[51]; snprintfz(pre_date, 50, " {%sc%s:[{%sv%s:%s", kq, kq, kq, kq, sq);
+ char post_date[21]; snprintfz(post_date, 20, "%s}", sq);
+ char pre_value[21]; snprintfz(pre_value, 20, ",{%sv%s:", kq, kq);
+ char post_value[21]; strcpy(post_value, "}");
// -------------------------------------------------------------------------
diff --git a/src/storage_number.c b/src/storage_number.c
index 225cf0348..b5c5f4067 100644
--- a/src/storage_number.c
+++ b/src/storage_number.c
@@ -129,7 +129,7 @@ static char *print_calculated_number_lu_r(char *str, unsigned long uvalue) {
char *wstr = str;
// print each digit
- do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10);
+ do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10);
return wstr;
}
@@ -137,7 +137,7 @@ static char *print_calculated_number_llu_r(char *str, unsigned long long uvalue)
char *wstr = str;
// print each digit
- do *wstr++ = (char)(48 + (uvalue % 10)); while((uvalue /= 10) && uvalue > (unsigned long long)0xffffffff);
+ do *wstr++ = (char)('0' + (uvalue % 10)); while((uvalue /= 10) && uvalue > (unsigned long long)0xffffffff);
if(uvalue) return print_calculated_number_lu_r(wstr, uvalue);
return wstr;
}
@@ -164,7 +164,7 @@ int print_calculated_number(char *str, calculated_number value)
else
wstr = print_calculated_number_lu_r(str, uvalue);
#else
- do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10);
+ do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10);
#endif
// make sure we have 6 bytes at least
diff --git a/src/sys_fs_cgroup.c b/src/sys_fs_cgroup.c
new file mode 100644
index 000000000..9f3d3f0fd
--- /dev/null
+++ b/src/sys_fs_cgroup.c
@@ -0,0 +1,1310 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+#include "common.h"
+#include "appconfig.h"
+#include "procfile.h"
+#include "log.h"
+#include "rrd.h"
+#include "main.h"
+#include "popen.h"
+#include "proc_self_mountinfo.h"
+
+// ----------------------------------------------------------------------------
+// cgroup globals
+
+static int cgroup_enable_cpuacct_stat = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_cpuacct_usage = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_memory = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_blkio = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_new_cgroups_detected_at_runtime = 1;
+static int cgroup_check_for_new_every = 10;
+static char *cgroup_cpuacct_base = NULL;
+static char *cgroup_blkio_base = NULL;
+static char *cgroup_memory_base = NULL;
+
+static int cgroup_root_count = 0;
+static int cgroup_root_max = 500;
+static int cgroup_max_depth = 0;
+
+void read_cgroup_plugin_configuration() {
+ cgroup_check_for_new_every = config_get_number("plugin:cgroups", "check for new cgroups every", cgroup_check_for_new_every);
+
+ cgroup_enable_cpuacct_stat = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct stat", cgroup_enable_cpuacct_stat);
+ cgroup_enable_cpuacct_usage = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct usage", cgroup_enable_cpuacct_usage);
+ cgroup_enable_memory = config_get_boolean_ondemand("plugin:cgroups", "enable memory", cgroup_enable_memory);
+ cgroup_enable_blkio = config_get_boolean_ondemand("plugin:cgroups", "enable blkio", cgroup_enable_blkio);
+
+ char filename[FILENAME_MAX + 1], *s;
+ struct mountinfo *mi, *root = mountinfo_read();
+
+ mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "cpuacct");
+ if(!mi) mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "cpuacct");
+ if(!mi) s = "/sys/fs/cgroup/cpuacct";
+ else s = mi->mount_point;
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, s);
+ cgroup_cpuacct_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/cpuacct", filename);
+
+ mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "blkio");
+ if(!mi) mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "blkio");
+ if(!mi) s = "/sys/fs/cgroup/blkio";
+ else s = mi->mount_point;
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, s);
+ cgroup_blkio_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/blkio", filename);
+
+ mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "memory");
+ if(!mi) mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "memory");
+ if(!mi) s = "/sys/fs/cgroup/memory";
+ else s = mi->mount_point;
+ snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, s);
+ cgroup_memory_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/memory", filename);
+
+ cgroup_root_max = config_get_number("plugin:cgroups", "max cgroups to allow", cgroup_root_max);
+ cgroup_max_depth = config_get_number("plugin:cgroups", "max cgroups depth to monitor", cgroup_max_depth);
+
+ cgroup_enable_new_cgroups_detected_at_runtime = config_get_boolean("plugin:cgroups", "enable new cgroups detected at run time", cgroup_enable_new_cgroups_detected_at_runtime);
+
+ mountinfo_free(root);
+}
+
+// ----------------------------------------------------------------------------
+// cgroup objects
+
+struct blkio {
+ int updated;
+
+ char *filename;
+
+ unsigned long long Read;
+ unsigned long long Write;
+/*
+ unsigned long long Sync;
+ unsigned long long Async;
+ unsigned long long Total;
+*/
+};
+
+// https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
+struct memory {
+ int updated;
+
+ char *filename;
+
+ int has_dirty_swap;
+
+ unsigned long long cache;
+ unsigned long long rss;
+ unsigned long long rss_huge;
+ unsigned long long mapped_file;
+ unsigned long long writeback;
+ unsigned long long dirty;
+ unsigned long long swap;
+ unsigned long long pgpgin;
+ unsigned long long pgpgout;
+ unsigned long long pgfault;
+ unsigned long long pgmajfault;
+/*
+ unsigned long long inactive_anon;
+ unsigned long long active_anon;
+ unsigned long long inactive_file;
+ unsigned long long active_file;
+ unsigned long long unevictable;
+ unsigned long long hierarchical_memory_limit;
+ unsigned long long total_cache;
+ unsigned long long total_rss;
+ unsigned long long total_rss_huge;
+ unsigned long long total_mapped_file;
+ unsigned long long total_writeback;
+ unsigned long long total_dirty;
+ unsigned long long total_swap;
+ unsigned long long total_pgpgin;
+ unsigned long long total_pgpgout;
+ unsigned long long total_pgfault;
+ unsigned long long total_pgmajfault;
+ unsigned long long total_inactive_anon;
+ unsigned long long total_active_anon;
+ unsigned long long total_inactive_file;
+ unsigned long long total_active_file;
+ unsigned long long total_unevictable;
+*/
+};
+
+// https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
+struct cpuacct_stat {
+ int updated;
+
+ char *filename;
+
+ unsigned long long user;
+ unsigned long long system;
+};
+
+// https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
+struct cpuacct_usage {
+ int updated;
+
+ char *filename;
+
+ unsigned int cpus;
+ unsigned long long *cpu_percpu;
+};
+
+struct cgroup {
+ int available; // found in the filesystem
+ int enabled; // enabled in the config
+
+ char *id;
+ uint32_t hash;
+
+ char *chart_id;
+ char *chart_title;
+
+ struct cpuacct_stat cpuacct_stat;
+ struct cpuacct_usage cpuacct_usage;
+
+ struct memory memory;
+
+ struct blkio io_service_bytes; // bytes
+ struct blkio io_serviced; // operations
+
+ struct blkio throttle_io_service_bytes; // bytes
+ struct blkio throttle_io_serviced; // operations
+
+ struct blkio io_merged; // operations
+ struct blkio io_queued; // operations
+
+ struct cgroup *next;
+
+} *cgroup_root = NULL;
+
+// ----------------------------------------------------------------------------
+// read values from /sys
+
+void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) {
+ static procfile *ff = NULL;
+
+ static uint32_t user_hash = 0;
+ static uint32_t system_hash = 0;
+
+ if(unlikely(user_hash == 0)) {
+ user_hash = simple_hash("user");
+ system_hash = simple_hash("system");
+ }
+
+ cp->updated = 0;
+ if(cp->filename) {
+ ff = procfile_reopen(ff, cp->filename, NULL, PROCFILE_FLAG_DEFAULT);
+ if(!ff) return;
+
+ ff = procfile_readall(ff);
+ if(!ff) return;
+
+ unsigned long i, lines = procfile_lines(ff);
+
+ if(lines < 1) {
+ error("File '%s' should have 1+ lines.", cp->filename);
+ return;
+ }
+
+ for(i = 0; i < lines ; i++) {
+ char *s = procfile_lineword(ff, i, 0);
+ uint32_t hash = simple_hash(s);
+
+ if(hash == user_hash && !strcmp(s, "user"))
+ cp->user = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == system_hash && !strcmp(s, "system"))
+ cp->system = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+ }
+
+ cp->updated = 1;
+
+ // fprintf(stderr, "READ '%s': user: %llu, system: %llu\n", cp->filename, cp->user, cp->system);
+ }
+}
+
+void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
+ static procfile *ff = NULL;
+
+ ca->updated = 0;
+ if(ca->filename) {
+ ff = procfile_reopen(ff, ca->filename, NULL, PROCFILE_FLAG_DEFAULT);
+ if(!ff) return;
+
+ ff = procfile_readall(ff);
+ if(!ff) return;
+
+ if(procfile_lines(ff) < 1) {
+ error("File '%s' should have 1+ lines but has %d.", ca->filename, procfile_lines(ff));
+ return;
+ }
+
+ unsigned long i = procfile_linewords(ff, 0);
+ if(i <= 0) return;
+
+ // we may have 1 more CPU reported
+ while(i > 0) {
+ char *s = procfile_lineword(ff, 0, i - 1);
+ if(!*s) i--;
+ else break;
+ }
+
+ if(i != ca->cpus) {
+ free(ca->cpu_percpu);
+
+ ca->cpu_percpu = malloc(sizeof(unsigned long long) * i);
+ if(!ca->cpu_percpu)
+ fatal("Cannot allocate memory (%z bytes)", sizeof(unsigned long long) * i);
+
+ ca->cpus = i;
+ }
+
+ for(i = 0; i < ca->cpus ;i++) {
+ ca->cpu_percpu[i] = strtoull(procfile_lineword(ff, 0, i), NULL, 10);
+ // fprintf(stderr, "READ '%s': cpu%d/%d: %llu ('%s')\n", ca->filename, i, ca->cpus, ca->cpu_percpu[i], procfile_lineword(ff, 0, i));
+ }
+
+ ca->updated = 1;
+ }
+}
+
+void cgroup_read_blkio(struct blkio *io) {
+ static procfile *ff = NULL;
+
+ static uint32_t Read_hash = 0;
+ static uint32_t Write_hash = 0;
+/*
+ static uint32_t Sync_hash = 0;
+ static uint32_t Async_hash = 0;
+ static uint32_t Total_hash = 0;
+*/
+
+ if(unlikely(Read_hash == 0)) {
+ Read_hash = simple_hash("Read");
+ Write_hash = simple_hash("Write");
+/*
+ Sync_hash = simple_hash("Sync");
+ Async_hash = simple_hash("Async");
+ Total_hash = simple_hash("Total");
+*/
+ }
+
+ io->updated = 0;
+ if(io->filename) {
+ ff = procfile_reopen(ff, io->filename, NULL, PROCFILE_FLAG_DEFAULT);
+ if(!ff) return;
+
+ ff = procfile_readall(ff);
+ if(!ff) return;
+
+ unsigned long i, lines = procfile_lines(ff);
+
+ if(lines < 1) {
+ error("File '%s' should have 1+ lines.", io->filename);
+ return;
+ }
+
+ io->Read = 0;
+ io->Write = 0;
+/*
+ io->Sync = 0;
+ io->Async = 0;
+ io->Total = 0;
+*/
+
+ for(i = 0; i < lines ; i++) {
+ char *s = procfile_lineword(ff, i, 1);
+ uint32_t hash = simple_hash(s);
+
+ if(hash == Read_hash && !strcmp(s, "Read"))
+ io->Read += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+ else if(hash == Write_hash && !strcmp(s, "Write"))
+ io->Write += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+/*
+ else if(hash == Sync_hash && !strcmp(s, "Sync"))
+ io->Sync += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+ else if(hash == Async_hash && !strcmp(s, "Async"))
+ io->Async += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+ else if(hash == Total_hash && !strcmp(s, "Total"))
+ io->Total += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+*/
+ }
+
+ io->updated = 1;
+ // fprintf(stderr, "READ '%s': Read: %llu, Write: %llu, Sync: %llu, Async: %llu, Total: %llu\n", io->filename, io->Read, io->Write, io->Sync, io->Async, io->Total);
+ }
+}
+
+void cgroup_read_memory(struct memory *mem) {
+ static procfile *ff = NULL;
+
+ static uint32_t cache_hash = 0;
+ static uint32_t rss_hash = 0;
+ static uint32_t rss_huge_hash = 0;
+ static uint32_t mapped_file_hash = 0;
+ static uint32_t writeback_hash = 0;
+ static uint32_t dirty_hash = 0;
+ static uint32_t swap_hash = 0;
+ static uint32_t pgpgin_hash = 0;
+ static uint32_t pgpgout_hash = 0;
+ static uint32_t pgfault_hash = 0;
+ static uint32_t pgmajfault_hash = 0;
+/*
+ static uint32_t inactive_anon_hash = 0;
+ static uint32_t active_anon_hash = 0;
+ static uint32_t inactive_file_hash = 0;
+ static uint32_t active_file_hash = 0;
+ static uint32_t unevictable_hash = 0;
+ static uint32_t hierarchical_memory_limit_hash = 0;
+ static uint32_t total_cache_hash = 0;
+ static uint32_t total_rss_hash = 0;
+ static uint32_t total_rss_huge_hash = 0;
+ static uint32_t total_mapped_file_hash = 0;
+ static uint32_t total_writeback_hash = 0;
+ static uint32_t total_dirty_hash = 0;
+ static uint32_t total_swap_hash = 0;
+ static uint32_t total_pgpgin_hash = 0;
+ static uint32_t total_pgpgout_hash = 0;
+ static uint32_t total_pgfault_hash = 0;
+ static uint32_t total_pgmajfault_hash = 0;
+ static uint32_t total_inactive_anon_hash = 0;
+ static uint32_t total_active_anon_hash = 0;
+ static uint32_t total_inactive_file_hash = 0;
+ static uint32_t total_active_file_hash = 0;
+ static uint32_t total_unevictable_hash = 0;
+*/
+ if(unlikely(cache_hash == 0)) {
+ cache_hash = simple_hash("cache");
+ rss_hash = simple_hash("rss");
+ rss_huge_hash = simple_hash("rss_huge");
+ mapped_file_hash = simple_hash("mapped_file");
+ writeback_hash = simple_hash("writeback");
+ dirty_hash = simple_hash("dirty");
+ swap_hash = simple_hash("swap");
+ pgpgin_hash = simple_hash("pgpgin");
+ pgpgout_hash = simple_hash("pgpgout");
+ pgfault_hash = simple_hash("pgfault");
+ pgmajfault_hash = simple_hash("pgmajfault");
+/*
+ inactive_anon_hash = simple_hash("inactive_anon");
+ active_anon_hash = simple_hash("active_anon");
+ inactive_file_hash = simple_hash("inactive_file");
+ active_file_hash = simple_hash("active_file");
+ unevictable_hash = simple_hash("unevictable");
+ hierarchical_memory_limit_hash = simple_hash("hierarchical_memory_limit");
+ total_cache_hash = simple_hash("total_cache");
+ total_rss_hash = simple_hash("total_rss");
+ total_rss_huge_hash = simple_hash("total_rss_huge");
+ total_mapped_file_hash = simple_hash("total_mapped_file");
+ total_writeback_hash = simple_hash("total_writeback");
+ total_dirty_hash = simple_hash("total_dirty");
+ total_swap_hash = simple_hash("total_swap");
+ total_pgpgin_hash = simple_hash("total_pgpgin");
+ total_pgpgout_hash = simple_hash("total_pgpgout");
+ total_pgfault_hash = simple_hash("total_pgfault");
+ total_pgmajfault_hash = simple_hash("total_pgmajfault");
+ total_inactive_anon_hash = simple_hash("total_inactive_anon");
+ total_active_anon_hash = simple_hash("total_active_anon");
+ total_inactive_file_hash = simple_hash("total_inactive_file");
+ total_active_file_hash = simple_hash("total_active_file");
+ total_unevictable_hash = simple_hash("total_unevictable");
+*/
+ }
+
+ mem->updated = 0;
+ if(mem->filename) {
+ ff = procfile_reopen(ff, mem->filename, NULL, PROCFILE_FLAG_DEFAULT);
+ if(!ff) return;
+
+ ff = procfile_readall(ff);
+ if(!ff) return;
+
+ unsigned long i, lines = procfile_lines(ff);
+
+ if(lines < 1) {
+ error("File '%s' should have 1+ lines.", mem->filename);
+ return;
+ }
+
+ for(i = 0; i < lines ; i++) {
+ char *s = procfile_lineword(ff, i, 0);
+ uint32_t hash = simple_hash(s);
+
+ if(hash == cache_hash && !strcmp(s, "cache"))
+ mem->cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == rss_hash && !strcmp(s, "rss"))
+ mem->rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == rss_huge_hash && !strcmp(s, "rss_huge"))
+ mem->rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == mapped_file_hash && !strcmp(s, "mapped_file"))
+ mem->mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == writeback_hash && !strcmp(s, "writeback"))
+ mem->writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == dirty_hash && !strcmp(s, "dirty")) {
+ mem->dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+ mem->has_dirty_swap = 1;
+ }
+
+ else if(hash == swap_hash && !strcmp(s, "swap")) {
+ mem->swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+ mem->has_dirty_swap = 1;
+ }
+
+ else if(hash == pgpgin_hash && !strcmp(s, "pgpgin"))
+ mem->pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == pgpgout_hash && !strcmp(s, "pgpgout"))
+ mem->pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == pgfault_hash && !strcmp(s, "pgfault"))
+ mem->pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == pgmajfault_hash && !strcmp(s, "pgmajfault"))
+ mem->pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+/*
+ else if(hash == inactive_anon_hash && !strcmp(s, "inactive_anon"))
+ mem->inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == active_anon_hash && !strcmp(s, "active_anon"))
+ mem->active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == inactive_file_hash && !strcmp(s, "inactive_file"))
+ mem->inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == active_file_hash && !strcmp(s, "active_file"))
+ mem->active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == unevictable_hash && !strcmp(s, "unevictable"))
+ mem->unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == hierarchical_memory_limit_hash && !strcmp(s, "hierarchical_memory_limit"))
+ mem->hierarchical_memory_limit = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_cache_hash && !strcmp(s, "total_cache"))
+ mem->total_cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_rss_hash && !strcmp(s, "total_rss"))
+ mem->total_rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_rss_huge_hash && !strcmp(s, "total_rss_huge"))
+ mem->total_rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_mapped_file_hash && !strcmp(s, "total_mapped_file"))
+ mem->total_mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_writeback_hash && !strcmp(s, "total_writeback"))
+ mem->total_writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_dirty_hash && !strcmp(s, "total_dirty"))
+ mem->total_dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_swap_hash && !strcmp(s, "total_swap"))
+ mem->total_swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_pgpgin_hash && !strcmp(s, "total_pgpgin"))
+ mem->total_pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_pgpgout_hash && !strcmp(s, "total_pgpgout"))
+ mem->total_pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_pgfault_hash && !strcmp(s, "total_pgfault"))
+ mem->total_pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_pgmajfault_hash && !strcmp(s, "total_pgmajfault"))
+ mem->total_pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_inactive_anon_hash && !strcmp(s, "total_inactive_anon"))
+ mem->total_inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_active_anon_hash && !strcmp(s, "total_active_anon"))
+ mem->total_active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_inactive_file_hash && !strcmp(s, "total_inactive_file"))
+ mem->total_inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_active_file_hash && !strcmp(s, "total_active_file"))
+ mem->total_active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+ else if(hash == total_unevictable_hash && !strcmp(s, "total_unevictable"))
+ mem->total_unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+*/
+ }
+
+ // fprintf(stderr, "READ: '%s', cache: %llu, rss: %llu, rss_huge: %llu, mapped_file: %llu, writeback: %llu, dirty: %llu, swap: %llu, pgpgin: %llu, pgpgout: %llu, pgfault: %llu, pgmajfault: %llu, inactive_anon: %llu, active_anon: %llu, inactive_file: %llu, active_file: %llu, unevictable: %llu, hierarchical_memory_limit: %llu, total_cache: %llu, total_rss: %llu, total_rss_huge: %llu, total_mapped_file: %llu, total_writeback: %llu, total_dirty: %llu, total_swap: %llu, total_pgpgin: %llu, total_pgpgout: %llu, total_pgfault: %llu, total_pgmajfault: %llu, total_inactive_anon: %llu, total_active_anon: %llu, total_inactive_file: %llu, total_active_file: %llu, total_unevictable: %llu\n", mem->filename, mem->cache, mem->rss, mem->rss_huge, mem->mapped_file, mem->writeback, mem->dirty, mem->swap, mem->pgpgin, mem->pgpgout, mem->pgfault, mem->pgmajfault, mem->inactive_anon, mem->active_anon, mem->inactive_file, mem->active_file, mem->unevictable, mem->hierarchical_memory_limit, mem->total_cache, mem->total_rss, mem->total_rss_huge, mem->total_mapped_file, mem->total_writeback, mem->total_dirty, mem->total_swap, mem->total_pgpgin, mem->total_pgpgout, mem->total_pgfault, mem->total_pgmajfault, mem->total_inactive_anon, mem->total_active_anon, mem->total_inactive_file, mem->total_active_file, mem->total_unevictable);
+
+ mem->updated = 1;
+ }
+}
+
+void cgroup_read(struct cgroup *cg) {
+ debug(D_CGROUP, "reading metrics for cgroups '%s'", cg->id);
+
+ cgroup_read_cpuacct_stat(&cg->cpuacct_stat);
+ cgroup_read_cpuacct_usage(&cg->cpuacct_usage);
+ cgroup_read_memory(&cg->memory);
+ cgroup_read_blkio(&cg->io_service_bytes);
+ cgroup_read_blkio(&cg->io_serviced);
+ cgroup_read_blkio(&cg->throttle_io_service_bytes);
+ cgroup_read_blkio(&cg->throttle_io_serviced);
+ cgroup_read_blkio(&cg->io_merged);
+ cgroup_read_blkio(&cg->io_queued);
+}
+
+void read_all_cgroups(struct cgroup *root) {
+ debug(D_CGROUP, "reading metrics for all cgroups");
+
+ struct cgroup *cg;
+
+ for(cg = root; cg ; cg = cg->next)
+ if(cg->enabled && cg->available)
+ cgroup_read(cg);
+}
+
+// ----------------------------------------------------------------------------
+// add/remove/find cgroup objects
+
+#define CGROUP_CHARTID_LINE_MAX 1024
+
+void cgroup_get_chart_id(struct cgroup *cg) {
+ debug(D_CGROUP, "getting the name of cgroup '%s'", cg->id);
+
+ pid_t cgroup_pid;
+ char buffer[CGROUP_CHARTID_LINE_MAX + 1];
+
+ snprintfz(buffer, CGROUP_CHARTID_LINE_MAX, "exec %s '%s'",
+ config_get("plugin:cgroups", "script to get cgroup names", PLUGINS_DIR "/cgroup-name.sh"), cg->chart_id);
+
+ debug(D_CGROUP, "executing command '%s' for cgroup '%s'", buffer, cg->id);
+ FILE *fp = mypopen(buffer, &cgroup_pid);
+ if(!fp) {
+ error("CGROUP: Cannot popen(\"%s\", \"r\").", buffer);
+ return;
+ }
+ debug(D_CGROUP, "reading from command '%s' for cgroup '%s'", buffer, cg->id);
+ char *s = fgets(buffer, CGROUP_CHARTID_LINE_MAX, fp);
+ debug(D_CGROUP, "closing command for cgroup '%s'", cg->id);
+ mypclose(fp, cgroup_pid);
+ debug(D_CGROUP, "closed command for cgroup '%s'", cg->id);
+
+ if(s && *s && *s != '\n') {
+ debug(D_CGROUP, "cgroup '%s' should be renamed to '%s'", cg->id, s);
+
+ trim(s);
+
+ free(cg->chart_title);
+ cg->chart_title = strdup(s);
+ if(!cg->chart_title)
+ fatal("CGROUP: Cannot allocate memory for chart name of cgroup '%s' chart name: '%s'", cg->id, s);
+
+ netdata_fix_chart_name(cg->chart_title);
+
+ free(cg->chart_id);
+ cg->chart_id = strdup(s);
+ if(!cg->chart_id)
+ fatal("CGROUP: Cannot allocate memory for chart id of cgroup '%s' chart id: '%s'", cg->id, s);
+
+ netdata_fix_chart_id(cg->chart_id);
+
+ debug(D_CGROUP, "cgroup '%s' renamed to '%s' (title: '%s')", cg->id, cg->chart_id, cg->chart_title);
+ }
+ else debug(D_CGROUP, "cgroup '%s' is not to be renamed (will be shown as '%s')", cg->id, cg->chart_id);
+}
+
+struct cgroup *cgroup_add(const char *id) {
+ debug(D_CGROUP, "adding cgroup '%s'", id);
+
+ if(cgroup_root_count >= cgroup_root_max) {
+ info("Maximum number of cgroups reached (%d). Not adding cgroup '%s'", cgroup_root_count, id);
+ return NULL;
+ }
+
+ int def = cgroup_enable_new_cgroups_detected_at_runtime;
+ const char *chart_id = id;
+ if(!*chart_id) {
+ chart_id = "/";
+
+ // disable by default the root cgroup
+ def = 0;
+ debug(D_CGROUP, "cgroup '%s' is the root container (by default %s)", id, (def)?"enabled":"disabled");
+ }
+ else {
+ if(*chart_id == '/') chart_id++;
+
+ size_t len = strlen(chart_id);
+
+ // disable by default the parent cgroup
+ // for known cgroup managers
+ if(!strcmp(chart_id, "lxc") ||
+ !strcmp(chart_id, "docker") ||
+ !strcmp(chart_id, "libvirt") ||
+ !strcmp(chart_id, "qemu") ||
+ !strcmp(chart_id, "systemd") ||
+ !strcmp(chart_id, "system.slice") ||
+ !strcmp(chart_id, "machine.slice") ||
+ !strcmp(chart_id, "user") ||
+ !strcmp(chart_id, "system") ||
+ !strcmp(chart_id, "machine") ||
+ // starts with them
+ (len > 6 && !strncmp(chart_id, "user/", 6)) ||
+ (len > 11 && !strncmp(chart_id, "user.slice/", 11)) ||
+ // ends with them
+ (len > 5 && !strncmp(&chart_id[len - 5], ".user", 5)) ||
+ (len > 5 && !strncmp(&chart_id[len - 5], ".swap", 5)) ||
+ (len > 6 && !strncmp(&chart_id[len - 6], ".slice", 6)) ||
+ (len > 6 && !strncmp(&chart_id[len - 6], ".mount", 6)) ||
+ (len > 8 && !strncmp(&chart_id[len - 8], ".session", 8)) ||
+ (len > 8 && !strncmp(&chart_id[len - 8], ".service", 8)) ||
+ (len > 10 && !strncmp(&chart_id[len - 10], ".partition", 10))
+ ) {
+ def = 0;
+ debug(D_CGROUP, "cgroup '%s' is %s (by default)", id, (def)?"enabled":"disabled");
+ }
+ }
+
+ struct cgroup *cg = calloc(1, sizeof(struct cgroup));
+ if(!cg) fatal("Cannot allocate memory for cgroup '%s'", id);
+
+ debug(D_CGROUP, "adding cgroup '%s'", id);
+
+ cg->id = strdup(id);
+ if(!cg->id) fatal("Cannot allocate memory for cgroup '%s'", id);
+
+ cg->hash = simple_hash(cg->id);
+
+ cg->chart_id = strdup(chart_id);
+ if(!cg->chart_id) fatal("Cannot allocate memory for cgroup '%s'", id);
+
+ cg->chart_title = strdup(chart_id);
+ if(!cg->chart_title) fatal("Cannot allocate memory for cgroup '%s'", id);
+
+ if(!cgroup_root)
+ cgroup_root = cg;
+ else {
+ // append it
+ struct cgroup *e;
+ for(e = cgroup_root; e->next ;e = e->next) ;
+ e->next = cg;
+ }
+
+ cgroup_root_count++;
+
+ // fix the name by calling the external script
+ cgroup_get_chart_id(cg);
+
+ char option[FILENAME_MAX + 1];
+ snprintfz(option, FILENAME_MAX, "enable cgroup %s", cg->chart_title);
+ cg->enabled = config_get_boolean("plugin:cgroups", option, def);
+
+ debug(D_CGROUP, "Added cgroup '%s' with chart id '%s' and title '%s' as %s (default was %s)", cg->id, cg->chart_id, cg->chart_title, (cg->enabled)?"enabled":"disabled", (def)?"enabled":"disabled");
+
+ return cg;
+}
+
+void cgroup_free(struct cgroup *cg) {
+ debug(D_CGROUP, "Removing cgroup '%s' with chart id '%s' (was %s and %s)", cg->id, cg->chart_id, (cg->enabled)?"enabled":"disabled", (cg->available)?"available":"not available");
+
+ free(cg->cpuacct_usage.cpu_percpu);
+
+ free(cg->cpuacct_stat.filename);
+ free(cg->cpuacct_usage.filename);
+ free(cg->memory.filename);
+ free(cg->io_service_bytes.filename);
+ free(cg->io_serviced.filename);
+ free(cg->throttle_io_service_bytes.filename);
+ free(cg->throttle_io_serviced.filename);
+ free(cg->io_merged.filename);
+ free(cg->io_queued.filename);
+
+ free(cg->id);
+ free(cg->chart_id);
+ free(cg->chart_title);
+ free(cg);
+
+ cgroup_root_count--;
+}
+
+// find if a given cgroup exists
+struct cgroup *cgroup_find(const char *id) {
+ debug(D_CGROUP, "searching for cgroup '%s'", id);
+
+ uint32_t hash = simple_hash(id);
+
+ struct cgroup *cg;
+ for(cg = cgroup_root; cg ; cg = cg->next) {
+ if(hash == cg->hash && strcmp(id, cg->id) == 0)
+ break;
+ }
+
+ debug(D_CGROUP, "cgroup_find('%s') %s", id, (cg)?"found":"not found");
+ return cg;
+}
+
+// ----------------------------------------------------------------------------
+// detect running cgroups
+
+// callback for find_file_in_subdirs()
+void found_subdir_in_dir(const char *dir) {
+ debug(D_CGROUP, "examining cgroup dir '%s'", dir);
+
+ struct cgroup *cg = cgroup_find(dir);
+ if(!cg) {
+ if(*dir && cgroup_max_depth > 0) {
+ int depth = 0;
+ const char *s;
+
+ for(s = dir; *s ;s++)
+ if(unlikely(*s == '/'))
+ depth++;
+
+ if(depth > cgroup_max_depth) {
+ info("cgroup '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
+ return;
+ }
+ }
+ debug(D_CGROUP, "will add dir '%s' as cgroup", dir);
+ cg = cgroup_add(dir);
+ }
+
+ if(cg) cg->available = 1;
+}
+
+void find_dir_in_subdirs(const char *base, const char *this, void (*callback)(const char *)) {
+ int enabled = -1;
+ if(!this) this = base;
+ size_t dirlen = strlen(this), baselen = strlen(base);
+ const char *relative_path = &this[baselen];
+
+ DIR *dir = opendir(this);
+ if(!dir) return;
+
+ callback(relative_path);
+
+ struct dirent *de = NULL;
+ while((de = readdir(dir))) {
+ if(de->d_type == DT_DIR
+ && (
+ (de->d_name[0] == '.' && de->d_name[1] == '\0')
+ || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
+ ))
+ continue;
+
+ debug(D_CGROUP, "examining '%s/%s'", this, de->d_name);
+
+ if(de->d_type == DT_DIR) {
+ if(enabled == -1) {
+ const char *r = relative_path;
+ if(*r == '\0') r = "/";
+ else if (*r == '/') r++;
+
+ // we check for this option here
+ // so that the config will not have settings
+ // for leaf directories
+ char option[FILENAME_MAX + 1];
+ snprintfz(option, FILENAME_MAX, "search for cgroups under %s", r);
+ option[FILENAME_MAX] = '\0';
+ enabled = config_get_boolean("plugin:cgroups", option, 1);
+ }
+
+ if(enabled) {
+ char *s = malloc(dirlen + strlen(de->d_name) + 2);
+ if(s) {
+ strcpy(s, this);
+ strcat(s, "/");
+ strcat(s, de->d_name);
+ find_dir_in_subdirs(base, s, callback);
+ free(s);
+ }
+ }
+ }
+ }
+
+ closedir(dir);
+}
+
+void mark_all_cgroups_as_not_available() {
+ debug(D_CGROUP, "marking all cgroups as not available");
+
+ struct cgroup *cg;
+
+ // mark all as not available
+ for(cg = cgroup_root; cg ; cg = cg->next)
+ cg->available = 0;
+}
+
+void cleanup_all_cgroups() {
+ struct cgroup *cg = cgroup_root, *last = NULL;
+
+ for(; cg ;) {
+ if(!cg->available) {
+
+ if(!last)
+ cgroup_root = cg->next;
+ else
+ last->next = cg->next;
+
+ cgroup_free(cg);
+
+ if(!last)
+ cg = cgroup_root;
+ else
+ cg = last->next;
+ }
+ else {
+ last = cg;
+ cg = cg->next;
+ }
+ }
+}
+
+void find_all_cgroups() {
+ debug(D_CGROUP, "searching for cgroups");
+
+ mark_all_cgroups_as_not_available();
+
+ if(cgroup_enable_cpuacct_stat || cgroup_enable_cpuacct_usage)
+ find_dir_in_subdirs(cgroup_cpuacct_base, NULL, found_subdir_in_dir);
+
+ if(cgroup_enable_blkio)
+ find_dir_in_subdirs(cgroup_blkio_base, NULL, found_subdir_in_dir);
+
+ if(cgroup_enable_memory)
+ find_dir_in_subdirs(cgroup_memory_base, NULL, found_subdir_in_dir);
+
+ // remove any non-existing cgroups
+ cleanup_all_cgroups();
+
+ struct cgroup *cg;
+ for(cg = cgroup_root; cg ; cg = cg->next) {
+ // fprintf(stderr, " >>> CGROUP '%s' (%u - %s) with name '%s'\n", cg->id, cg->hash, cg->available?"available":"stopped", cg->name);
+
+ if(unlikely(!cg->available))
+ continue;
+
+ debug(D_CGROUP, "checking paths for cgroup '%s'", cg->id);
+
+ // check for newly added cgroups
+ // and update the filenames they read
+ char filename[FILENAME_MAX + 1];
+ if(cgroup_enable_cpuacct_stat && !cg->cpuacct_stat.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/cpuacct.stat", cgroup_cpuacct_base, cg->id);
+ cg->cpuacct_stat.filename = strdup(filename);
+ debug(D_CGROUP, "cpuacct.stat filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_stat.filename);
+ }
+ if(cgroup_enable_cpuacct_usage && !cg->cpuacct_usage.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/cpuacct.usage_percpu", cgroup_cpuacct_base, cg->id);
+ cg->cpuacct_usage.filename = strdup(filename);
+ debug(D_CGROUP, "cpuacct.usage_percpu filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_usage.filename);
+ }
+ if(cgroup_enable_memory && !cg->memory.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
+ cg->memory.filename = strdup(filename);
+ debug(D_CGROUP, "memory.stat filename for cgroup '%s': '%s'", cg->id, cg->memory.filename);
+ }
+ if(cgroup_enable_blkio) {
+ if(!cg->io_service_bytes.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
+ cg->io_service_bytes.filename = strdup(filename);
+ debug(D_CGROUP, "io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->io_service_bytes.filename);
+ }
+ if(!cg->io_serviced.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
+ cg->io_serviced.filename = strdup(filename);
+ debug(D_CGROUP, "io_serviced filename for cgroup '%s': '%s'", cg->id, cg->io_serviced.filename);
+ }
+ if(!cg->throttle_io_service_bytes.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
+ cg->throttle_io_service_bytes.filename = strdup(filename);
+ debug(D_CGROUP, "throttle_io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_service_bytes.filename);
+ }
+ if(!cg->throttle_io_serviced.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
+ cg->throttle_io_serviced.filename = strdup(filename);
+ debug(D_CGROUP, "throttle_io_serviced filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_serviced.filename);
+ }
+ if(!cg->io_merged.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
+ cg->io_merged.filename = strdup(filename);
+ debug(D_CGROUP, "io_merged filename for cgroup '%s': '%s'", cg->id, cg->io_merged.filename);
+ }
+ if(!cg->io_queued.filename) {
+ snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
+ cg->io_queued.filename = strdup(filename);
+ debug(D_CGROUP, "io_queued filename for cgroup '%s': '%s'", cg->id, cg->io_queued.filename);
+ }
+ }
+ }
+
+ debug(D_CGROUP, "done searching for cgroups");
+ return;
+}
+
+// ----------------------------------------------------------------------------
+// generate charts
+
+#define CHART_TITLE_MAX 300
+
+void update_cgroup_charts(int update_every) {
+ debug(D_CGROUP, "updating cgroups charts");
+
+ char type[RRD_ID_LENGTH_MAX + 1];
+ char title[CHART_TITLE_MAX + 1];
+
+ struct cgroup *cg;
+ RRDSET *st;
+
+ for(cg = cgroup_root; cg ; cg = cg->next) {
+ if(!cg->available || !cg->enabled)
+ continue;
+
+ if(cg->id[0] == '\0')
+ strcpy(type, "cgroup_root");
+ else if(cg->id[0] == '/')
+ snprintfz(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->chart_id);
+ else
+ snprintfz(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->chart_id);
+
+ netdata_fix_chart_id(type);
+
+ if(cg->cpuacct_stat.updated) {
+ st = rrdset_find_bytype(type, "cpu");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "CPU Usage for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "cpu", NULL, "cpu", "cgroup.cpu", title, "%", 40000, update_every, RRDSET_TYPE_STACKED);
+
+ rrddim_add(st, "user", NULL, 100, hz, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "system", NULL, 100, hz, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "user", cg->cpuacct_stat.user);
+ rrddim_set(st, "system", cg->cpuacct_stat.system);
+ rrdset_done(st);
+ }
+
+ if(cg->cpuacct_usage.updated) {
+ char id[RRD_ID_LENGTH_MAX + 1];
+ unsigned int i;
+
+ st = rrdset_find_bytype(type, "cpu_per_core");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "CPU Usage Per Core for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "cpu_per_core", NULL, "cpu", "cgroup.cpu_per_core", title, "%", 40100, update_every, RRDSET_TYPE_STACKED);
+
+ for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
+ snprintfz(id, CHART_TITLE_MAX, "cpu%d", i);
+ rrddim_add(st, id, NULL, 100, 1000000, RRDDIM_INCREMENTAL);
+ }
+ }
+ else rrdset_next(st);
+
+ for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
+ snprintfz(id, CHART_TITLE_MAX, "cpu%d", i);
+ rrddim_set(st, id, cg->cpuacct_usage.cpu_percpu[i]);
+ }
+ rrdset_done(st);
+ }
+
+ if(cg->memory.updated) {
+ if(cg->memory.cache + cg->memory.rss + cg->memory.rss_huge + cg->memory.mapped_file > 0) {
+ st = rrdset_find_bytype(type, "mem");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Memory Usage for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "mem", NULL, "mem", "cgroup.mem", title, "MB", 40200, update_every,
+ RRDSET_TYPE_STACKED);
+
+ rrddim_add(st, "cache", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "rss", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+ if(cg->memory.has_dirty_swap)
+ rrddim_add(st, "swap", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "rss_huge", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "mapped_file", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "cache", cg->memory.cache);
+ rrddim_set(st, "rss", cg->memory.rss);
+ if(cg->memory.has_dirty_swap)
+ rrddim_set(st, "swap", cg->memory.swap);
+ rrddim_set(st, "rss_huge", cg->memory.rss_huge);
+ rrddim_set(st, "mapped_file", cg->memory.mapped_file);
+ rrdset_done(st);
+ }
+
+ st = rrdset_find_bytype(type, "writeback");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Writeback Memory for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "writeback", NULL, "mem", "cgroup.writeback", title, "MB", 40300,
+ update_every, RRDSET_TYPE_AREA);
+
+ if(cg->memory.has_dirty_swap)
+ rrddim_add(st, "dirty", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "writeback", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ if(cg->memory.has_dirty_swap)
+ rrddim_set(st, "dirty", cg->memory.dirty);
+ rrddim_set(st, "writeback", cg->memory.writeback);
+ rrdset_done(st);
+
+ if(cg->memory.pgpgin + cg->memory.pgpgout > 0) {
+ st = rrdset_find_bytype(type, "mem_activity");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Memory Activity for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "mem_activity", NULL, "mem", "cgroup.mem_activity", title, "MB/s",
+ 40400, update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "pgpgin", "in", sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "pgpgout", "out", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "pgpgin", cg->memory.pgpgin);
+ rrddim_set(st, "pgpgout", cg->memory.pgpgout);
+ rrdset_done(st);
+ }
+
+ if(cg->memory.pgfault + cg->memory.pgmajfault > 0) {
+ st = rrdset_find_bytype(type, "pgfaults");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Memory Page Faults for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "pgfaults", NULL, "mem", "cgroup.pgfaults", title, "MB/s", 40500,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "pgfault", NULL, sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "pgmajfault", "swap", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "pgfault", cg->memory.pgfault);
+ rrddim_set(st, "pgmajfault", cg->memory.pgmajfault);
+ rrdset_done(st);
+ }
+ }
+
+ if(cg->io_service_bytes.updated && cg->io_service_bytes.Read + cg->io_service_bytes.Write > 0) {
+ st = rrdset_find_bytype(type, "io");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "I/O Bandwidth (all disks) for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "read", cg->io_service_bytes.Read);
+ rrddim_set(st, "write", cg->io_service_bytes.Write);
+ rrdset_done(st);
+ }
+
+ if(cg->io_serviced.updated && cg->io_serviced.Read + cg->io_serviced.Write > 0) {
+ st = rrdset_find_bytype(type, "serviced_ops");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Serviced I/O Operations (all disks) for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "serviced_ops", NULL, "disk", "cgroup.serviced_ops", title, "operations/s", 41200,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "read", cg->io_serviced.Read);
+ rrddim_set(st, "write", cg->io_serviced.Write);
+ rrdset_done(st);
+ }
+
+ if(cg->throttle_io_service_bytes.updated && cg->throttle_io_service_bytes.Read + cg->throttle_io_service_bytes.Write > 0) {
+ st = rrdset_find_bytype(type, "io");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Throttle I/O Bandwidth (all disks) for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "read", cg->throttle_io_service_bytes.Read);
+ rrddim_set(st, "write", cg->throttle_io_service_bytes.Write);
+ rrdset_done(st);
+ }
+
+
+ if(cg->throttle_io_serviced.updated && cg->throttle_io_serviced.Read + cg->throttle_io_serviced.Write > 0) {
+ st = rrdset_find_bytype(type, "throttle_serviced_ops");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Throttle Serviced I/O Operations (all disks) for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "throttle_serviced_ops", NULL, "disk", "cgroup.throttle_serviced_ops", title, "operations/s", 41200,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "read", cg->throttle_io_serviced.Read);
+ rrddim_set(st, "write", cg->throttle_io_serviced.Write);
+ rrdset_done(st);
+ }
+
+ if(cg->io_queued.updated) {
+ st = rrdset_find_bytype(type, "queued_ops");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Queued I/O Operations (all disks) for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "queued_ops", NULL, "disk", "cgroup.queued_ops", title, "operations", 42000,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "read", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "write", NULL, -1, 1, RRDDIM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "read", cg->io_queued.Read);
+ rrddim_set(st, "write", cg->io_queued.Write);
+ rrdset_done(st);
+ }
+
+ if(cg->io_merged.updated && cg->io_merged.Read + cg->io_merged.Write > 0) {
+ st = rrdset_find_bytype(type, "merged_ops");
+ if(!st) {
+ snprintfz(title, CHART_TITLE_MAX, "Merged I/O Operations (all disks) for cgroup %s", cg->chart_title);
+ st = rrdset_create(type, "merged_ops", NULL, "disk", "cgroup.merged_ops", title, "operations/s", 42100,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+ rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "read", cg->io_merged.Read);
+ rrddim_set(st, "write", cg->io_merged.Write);
+ rrdset_done(st);
+ }
+ }
+
+ debug(D_CGROUP, "done updating cgroups charts");
+}
+
+// ----------------------------------------------------------------------------
+// cgroups main
+
+int do_sys_fs_cgroup(int update_every, unsigned long long dt) {
+ static int cgroup_global_config_read = 0;
+ static time_t last_run = 0;
+ time_t now = time(NULL);
+
+ if(dt) {};
+
+ if(unlikely(!cgroup_global_config_read)) {
+ read_cgroup_plugin_configuration();
+ cgroup_global_config_read = 1;
+ }
+
+ if(unlikely(cgroup_enable_new_cgroups_detected_at_runtime && now - last_run > cgroup_check_for_new_every)) {
+ find_all_cgroups();
+ last_run = now;
+ }
+
+ read_all_cgroups(cgroup_root);
+ update_cgroup_charts(update_every);
+
+ return 0;
+}
+
+void *cgroups_main(void *ptr)
+{
+ if(ptr) { ; }
+
+ info("CGROUP Plugin thread created with task id %d", gettid());
+
+ if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
+ error("Cannot set pthread cancel type to DEFERRED.");
+
+ if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
+ error("Cannot set pthread cancel state to ENABLE.");
+
+ struct rusage thread;
+
+ // when ZERO, attempt to do it
+ int vdo_sys_fs_cgroup = 0;
+ int vdo_cpu_netdata = !config_get_boolean("plugin:cgroups", "cgroups plugin resources", 1);
+
+ // keep track of the time each module was called
+ unsigned long long sutime_sys_fs_cgroup = 0ULL;
+
+ // the next time we will run - aligned properly
+ unsigned long long sunext = (time(NULL) - (time(NULL) % rrd_update_every) + rrd_update_every) * 1000000ULL;
+ unsigned long long sunow;
+
+ RRDSET *stcpu_thread = NULL;
+
+ for(;1;) {
+ if(unlikely(netdata_exit)) break;
+
+ // delay until it is our time to run
+ while((sunow = timems()) < sunext)
+ usleep((useconds_t)(sunext - sunow));
+
+ // find the next time we need to run
+ while(timems() > sunext)
+ sunext += rrd_update_every * 1000000ULL;
+
+ if(unlikely(netdata_exit)) break;
+
+ // BEGIN -- the job to be done
+
+ if(!vdo_sys_fs_cgroup) {
+ debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_fs_cgroup().");
+ sunow = timems();
+ vdo_sys_fs_cgroup = do_sys_fs_cgroup(rrd_update_every, (sutime_sys_fs_cgroup > 0)?sunow - sutime_sys_fs_cgroup:0ULL);
+ sutime_sys_fs_cgroup = sunow;
+ }
+ if(unlikely(netdata_exit)) break;
+
+ // END -- the job is done
+
+ // --------------------------------------------------------------------
+
+ if(!vdo_cpu_netdata) {
+ getrusage(RUSAGE_THREAD, &thread);
+
+ 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", 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);
+ }
+ else rrdset_next(stcpu_thread);
+
+ rrddim_set(stcpu_thread, "user" , thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec);
+ rrddim_set(stcpu_thread, "system", thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec);
+ rrdset_done(stcpu_thread);
+ }
+ }
+
+ pthread_exit(NULL);
+ return NULL;
+}
diff --git a/src/sys_kernel_mm_ksm.c b/src/sys_kernel_mm_ksm.c
index 822e0d41a..928ac8c62 100644
--- a/src/sys_kernel_mm_ksm.c
+++ b/src/sys_kernel_mm_ksm.c
@@ -41,32 +41,32 @@ int do_sys_kernel_mm_ksm(int update_every, unsigned long long dt) {
page_size = sysconf(_SC_PAGESIZE);
if(!ff_pages_shared) {
- snprintf(values[PAGES_SHARED].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_shared");
- snprintf(values[PAGES_SHARED].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_shared", values[PAGES_SHARED].filename));
+ snprintfz(values[PAGES_SHARED].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_shared");
+ snprintfz(values[PAGES_SHARED].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_shared", values[PAGES_SHARED].filename));
ff_pages_shared = procfile_open(values[PAGES_SHARED].filename, " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff_pages_sharing) {
- snprintf(values[PAGES_SHARING].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_sharing");
- snprintf(values[PAGES_SHARING].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_sharing", values[PAGES_SHARING].filename));
+ snprintfz(values[PAGES_SHARING].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_sharing");
+ snprintfz(values[PAGES_SHARING].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_sharing", values[PAGES_SHARING].filename));
ff_pages_sharing = procfile_open(values[PAGES_SHARING].filename, " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff_pages_unshared) {
- snprintf(values[PAGES_UNSHARED].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_unshared");
- snprintf(values[PAGES_UNSHARED].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_unshared", values[PAGES_UNSHARED].filename));
+ snprintfz(values[PAGES_UNSHARED].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_unshared");
+ snprintfz(values[PAGES_UNSHARED].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_unshared", values[PAGES_UNSHARED].filename));
ff_pages_unshared = procfile_open(values[PAGES_UNSHARED].filename, " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff_pages_volatile) {
- snprintf(values[PAGES_VOLATILE].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_volatile");
- snprintf(values[PAGES_VOLATILE].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_volatile", values[PAGES_VOLATILE].filename));
+ snprintfz(values[PAGES_VOLATILE].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_volatile");
+ snprintfz(values[PAGES_VOLATILE].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_volatile", values[PAGES_VOLATILE].filename));
ff_pages_volatile = procfile_open(values[PAGES_VOLATILE].filename, " \t:", PROCFILE_FLAG_DEFAULT);
}
if(!ff_pages_to_scan) {
- snprintf(values[PAGES_TO_SCAN].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_to_scan");
- snprintf(values[PAGES_TO_SCAN].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_to_scan", values[PAGES_TO_SCAN].filename));
+ snprintfz(values[PAGES_TO_SCAN].filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/kernel/mm/ksm/pages_to_scan");
+ snprintfz(values[PAGES_TO_SCAN].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_to_scan", values[PAGES_TO_SCAN].filename));
ff_pages_to_scan = procfile_open(values[PAGES_TO_SCAN].filename, " \t:", PROCFILE_FLAG_DEFAULT);
}
diff --git a/src/unit_test.c b/src/unit_test.c
index 47aa5396c..06b7afacb 100644
--- a/src/unit_test.c
+++ b/src/unit_test.c
@@ -128,7 +128,7 @@ void benchmark_storage_number(int loop, int multiplier) {
for(i = 0; i < loop ;i++) {
n *= multiplier;
if(n > STORAGE_NUMBER_POSITIVE_MAX) n = STORAGE_NUMBER_POSITIVE_MIN;
- snprintf(buffer, 100, CALCULATED_NUMBER_FORMAT, n);
+ snprintfz(buffer, 100, CALCULATED_NUMBER_FORMAT, n);
}
}
@@ -614,7 +614,7 @@ int run_test(struct test *test)
rrd_update_every = test->update_every;
char name[101];
- snprintf(name, 100, "unittest-%s", test->name);
+ snprintfz(name, 100, "unittest-%s", test->name);
// create the chart
RRDSET *st = rrdset_create("netdata", name, name, "netdata", NULL, "Unit Testing", "a value", 1, 1, RRDSET_TYPE_LINE);
@@ -703,7 +703,7 @@ int unit_test(long delay, long shift)
repeat++;
char name[101];
- snprintf(name, 100, "unittest-%d-%ld-%ld", repeat, delay, shift);
+ snprintfz(name, 100, "unittest-%d-%ld-%ld", repeat, delay, shift);
//debug_flags = 0xffffffff;
rrd_memory_mode = RRD_MEMORY_MODE_RAM;
diff --git a/src/url.c b/src/url.c
index edf52be7c..010b07ddd 100644
--- a/src/url.c
+++ b/src/url.c
@@ -27,28 +27,36 @@ char to_hex(char code) {
/* Returns a url-encoded version of str */
/* IMPORTANT: be sure to free() the returned string after use */
char *url_encode(char *str) {
- char *pstr = str,
- *buf = malloc(strlen(str) * 3 + 1),
- *pbuf = buf;
+ char *buf, *pbuf;
+
+ pbuf = buf = malloc(strlen(str) * 3 + 1);
if(!buf)
fatal("Cannot allocate memory.");
- while (*pstr) {
- if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
- *pbuf++ = *pstr;
+ while (*str) {
+ if (isalnum(*str) || *str == '-' || *str == '_' || *str == '.' || *str == '~')
+ *pbuf++ = *str;
- else if (*pstr == ' ')
+ else if (*str == ' ')
*pbuf++ = '+';
else
- *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
+ *pbuf++ = '%', *pbuf++ = to_hex(*str >> 4), *pbuf++ = to_hex(*str & 15);
- pstr++;
+ str++;
}
-
*pbuf = '\0';
+ // FIX: I think this is prudent. URLs can be as long as 2 KiB or more.
+ // We allocated 3 times more space to accomodate %NN encoding of
+ // non ASCII chars. If URL has none of these kind of chars we will
+ // end up with a big unused buffer.
+ //
+ // Try to shrink the buffer...
+ if (!!(pbuf = (char *)realloc(buf, strlen(buf)+1)))
+ buf = pbuf;
+
return buf;
}
diff --git a/src/web_buffer.c b/src/web_buffer.c
index 482eb3900..a0f153721 100644
--- a/src/web_buffer.c
+++ b/src/web_buffer.c
@@ -53,7 +53,7 @@ void buffer_reset(BUFFER *wb)
const char *buffer_tostring(BUFFER *wb)
{
- buffer_need_bytes(wb, (size_t)1);
+ buffer_need_bytes(wb, 1);
wb->buffer[wb->len] = '\0';
buffer_overflow_check(wb);
@@ -78,15 +78,16 @@ void buffer_strcat(BUFFER *wb, const char *txt)
{
if(unlikely(!txt || !*txt)) return;
- buffer_need_bytes(wb, (size_t)(1));
+ buffer_need_bytes(wb, 1);
- char *s = &wb->buffer[wb->len], *end = &wb->buffer[wb->size];
+ char *s = &wb->buffer[wb->len], *start, *end = &wb->buffer[wb->size];
long len = wb->len;
- while(*txt && s != end) {
+ start = s;
+ while(*txt && s != end)
*s++ = *txt++;
- len++;
- }
+
+ len += s - start;
wb->len = len;
buffer_overflow_check(wb);
@@ -110,44 +111,45 @@ void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...)
{
if(unlikely(!fmt || !*fmt)) return;
- buffer_need_bytes(wb, len+1);
+ buffer_need_bytes(wb, len + 1);
va_list args;
va_start(args, fmt);
- wb->len += vsnprintf(&wb->buffer[wb->len], len+1, fmt, args);
+ wb->len += vsnprintfz(&wb->buffer[wb->len], len, fmt, args);
va_end(args);
buffer_overflow_check(wb);
- // the buffer is \0 terminated by vsnprintf
+ // the buffer is \0 terminated by vsnprintfz
}
void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args)
{
if(unlikely(!fmt || !*fmt)) return;
- buffer_need_bytes(wb, 1);
+ buffer_need_bytes(wb, 2);
- size_t len = wb->size - wb->len;
+ size_t len = wb->size - wb->len - 1;
- wb->len += vsnprintf(&wb->buffer[wb->len], len, fmt, args);
+ wb->len += vsnprintfz(&wb->buffer[wb->len], len, fmt, args);
buffer_overflow_check(wb);
- // the buffer is \0 terminated by vsnprintf
+ // the buffer is \0 terminated by vsnprintfz
}
void buffer_sprintf(BUFFER *wb, const char *fmt, ...)
{
if(unlikely(!fmt || !*fmt)) return;
- buffer_need_bytes(wb, 1);
+ buffer_need_bytes(wb, 2);
- size_t len = wb->size - wb->len, wrote;
+ size_t len = wb->size - wb->len - 1;
+ size_t wrote;
va_list args;
va_start(args, fmt);
- wrote = (size_t) vsnprintf(&wb->buffer[wb->len], len, fmt, args);
+ wrote = (size_t) vsnprintfz(&wb->buffer[wb->len], len, fmt, args);
va_end(args);
if(unlikely(wrote >= len)) {
@@ -187,43 +189,52 @@ void buffer_rrd_value(BUFFER *wb, calculated_number value)
// generate a javascript date, the fastest possible way...
void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds)
{
- // 10 20 30 = 35
+ // 10 20 30 = 35
// 01234567890123456789012345678901234
// Date(2014,04,01,03,28,20)
buffer_need_bytes(wb, 30);
- char *b = &wb->buffer[wb->len];
-
- int i = 0;
- b[i++]='D';
- b[i++]='a';
- b[i++]='t';
- b[i++]='e';
- b[i++]='(';
- b[i++]= (char) (48 + year / 1000); year -= (year / 1000) * 1000;
- b[i++]= (char) (48 + year / 100); year -= (year / 100) * 100;
- b[i++]= (char) (48 + year / 10);
- b[i++]= (char) (48 + year % 10);
- b[i++]=',';
- b[i]= (char) (48 + month / 10); if(b[i] != '0') i++;
- b[i++]= (char) (48 + month % 10);
- b[i++]=',';
- b[i]= (char) (48 + day / 10); if(b[i] != '0') i++;
- b[i++]= (char) (48 + day % 10);
- b[i++]=',';
- b[i]= (char) (48 + hours / 10); if(b[i] != '0') i++;
- b[i++]= (char) (48 + hours % 10);
- b[i++]=',';
- b[i]= (char) (48 + minutes / 10); if(b[i] != '0') i++;
- b[i++]= (char) (48 + minutes % 10);
- b[i++]=',';
- b[i]= (char) (48 + seconds / 10); if(b[i] != '0') i++;
- b[i++]= (char) (48 + seconds % 10);
- b[i++]=')';
- b[i]='\0';
-
- wb->len += i;
+ char *b = &wb->buffer[wb->len], *p;
+ unsigned int *q = (unsigned int *)b;
+
+ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ *q++ = 0x65746144; // "Date" backwards.
+ #else
+ *q++ = 0x44617465; // "Date"
+ #endif
+ p = (char *)q;
+
+ *p++ = '(';
+ *p++ = '0' + year / 1000; year %= 1000;
+ *p++ = '0' + year / 100; year %= 100;
+ *p++ = '0' + year / 10;
+ *p++ = '0' + year % 10;
+ *p++ = ',';
+ *p = '0' + month / 10; if (*p != '0') p++;
+ *p++ = '0' + month % 10;
+ *p++ = ',';
+ *p = '0' + day / 10; if (*p != '0') p++;
+ *p++ = '0' + day % 10;
+ *p++ = ',';
+ *p = '0' + hours / 10; if (*p != '0') p++;
+ *p++ = '0' + hours % 10;
+ *p++ = ',';
+ *p = '0' + minutes / 10; if (*p != '0') p++;
+ *p++ = '0' + minutes % 10;
+ *p++ = ',';
+ *p = '0' + seconds / 10; if (*p != '0') p++;
+ *p++ = '0' + seconds % 10;
+
+ unsigned short *r = (unsigned short *)p;
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ *r++ = 0x0029; // ")\0" backwards.
+ #else
+ *r++ = 0x2900; // ")\0"
+ #endif
+
+ wb->len += (size_t)((char *)r - b - 1);
// terminate it
wb->buffer[wb->len] = '\0';
@@ -240,30 +251,30 @@ void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minute
buffer_need_bytes(wb, 36);
char *b = &wb->buffer[wb->len];
-
- int i = 0;
- b[i++]= (char) (48 + year / 1000); year -= (year / 1000) * 1000;
- b[i++]= (char) (48 + year / 100); year -= (year / 100) * 100;
- b[i++]= (char) (48 + year / 10);
- b[i++]= (char) (48 + year % 10);
- b[i++]='-';
- b[i++]= (char) (48 + month / 10);
- b[i++]= (char) (48 + month % 10);
- b[i++]='-';
- b[i++]= (char) (48 + day / 10);
- b[i++]= (char) (48 + day % 10);
- b[i++]=' ';
- b[i++]= (char) (48 + hours / 10);
- b[i++]= (char) (48 + hours % 10);
- b[i++]=':';
- b[i++]= (char) (48 + minutes / 10);
- b[i++]= (char) (48 + minutes % 10);
- b[i++]=':';
- b[i++]= (char) (48 + seconds / 10);
- b[i++]= (char) (48 + seconds % 10);
- b[i]='\0';
-
- wb->len += i;
+ char *p = b;
+
+ *p++ = '0' + year / 1000; year %= 1000;
+ *p++ = '0' + year / 100; year %= 100;
+ *p++ = '0' + year / 10;
+ *p++ = '0' + year % 10;
+ *p++ = '-';
+ *p++ = '0' + month / 10;
+ *p++ = '0' + month % 10;
+ *p++ = '-';
+ *p++ = '0' + day / 10;
+ *p++ = '0' + day % 10;
+ *p++ = ' ';
+ *p++ = '0' + hours / 10;
+ *p++ = '0' + hours % 10;
+ *p++ = ':';
+ *p++ = '0' + minutes / 10;
+ *p++ = '0' + minutes % 10;
+ *p++ = ':';
+ *p++ = '0' + seconds / 10;
+ *p++ = '0' + seconds % 10;
+ *p = '\0';
+
+ wb->len += (size_t)(p - b);
// terminate it
wb->buffer[wb->len] = '\0';
diff --git a/src/web_buffer.h b/src/web_buffer.h
index 58dd9c094..73533f499 100644
--- a/src/web_buffer.h
+++ b/src/web_buffer.h
@@ -48,7 +48,7 @@ extern const char *buffer_tostring(BUFFER *wb);
#define buffer_need_bytes(buffer, needed_free_size) do { if(unlikely((buffer)->size - (buffer)->len < (size_t)(needed_free_size))) buffer_increase((buffer), (size_t)(needed_free_size)); } while(0)
-#define buffer_flush(wb) wb->buffer[wb->len = 0] = '\0'
+#define buffer_flush(wb) wb->buffer[(wb)->len = 0] = '\0'
extern void buffer_reset(BUFFER *wb);
extern void buffer_strcat(BUFFER *wb, const char *txt);
diff --git a/src/web_client.c b/src/web_client.c
index 6500a59b2..601dda083 100644
--- a/src/web_client.c
+++ b/src/web_client.c
@@ -26,6 +26,7 @@
#include "global_statistics.h"
#include "rrd.h"
#include "rrd2json.h"
+#include "registry.h"
#include "web_client.h"
#include "../config.h"
@@ -72,8 +73,8 @@ struct web_client *web_client_create(int listener)
if(getnameinfo(sadr, addrlen, w->client_ip, NI_MAXHOST, w->client_port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
error("Cannot getnameinfo() on received client connection.");
- strncpy(w->client_ip, "UNKNOWN", NI_MAXHOST);
- strncpy(w->client_port, "UNKNOWN", NI_MAXSERV);
+ strncpyz(w->client_ip, "UNKNOWN", NI_MAXHOST);
+ strncpyz(w->client_port, "UNKNOWN", NI_MAXSERV);
}
w->client_ip[NI_MAXHOST] = '\0';
w->client_port[NI_MAXSERV] = '\0';
@@ -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,18 @@ void web_client_reset(struct web_client *w)
}
w->last_url[0] = '\0';
+ w->cookie1[0] = '\0';
+ w->cookie2[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);
@@ -316,7 +328,7 @@ int mysendfile(struct web_client *w, char *filename)
// access the file
char webfilename[FILENAME_MAX + 1];
- snprintf(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename);
+ snprintfz(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename);
// check if the file exists
struct stat stat;
@@ -341,7 +353,7 @@ int mysendfile(struct web_client *w, char *filename)
}
if((stat.st_mode & S_IFMT) == S_IFDIR) {
- snprintf(webfilename, FILENAME_MAX+1, "%s/index.html", filename);
+ snprintfz(webfilename, FILENAME_MAX, "%s/index.html", filename);
return mysendfile(w, webfilename);
}
@@ -644,7 +656,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 +796,149 @@ 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);
+
+ // FIXME
+ // The browser may send multiple cookies with our id
+
+ char *cookie = strstr(w->response.data->buffer, " " NETDATA_REGISTRY_COOKIE_NAME "=");
+ if(cookie)
+ strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME) + 1], 36);
+
+ 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,110 +1185,209 @@ 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';
+
+ // 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';
- // check if the client requested keep-alive HTTP
- if(strcasestr(w->response.data->buffer, "Connection: keep-alive")) w->keepalive = 1;
- else w->keepalive = 0;
+ // fprintf(stderr, "HEADER: '%s' = '%s'\n", s, v);
+ if(!strcasecmp(s, "Origin"))
+ strncpyz(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;
- 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);
+ // 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
+
+ *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);
- if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
- strncpy(w->last_url, url, URL_MAX);
- w->last_url[URL_MAX] = '\0';
+ 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();
+
+ // copy the URL - we are going to overwrite parts of it
+ // FIXME -- we should avoid it
+ strncpyz(w->last_url, w->decoded_url, URL_MAX);
+
+ 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
- strncpy(w->last_url, url, URL_MAX);
- w->last_url[URL_MAX] = '\0';
-
- tok = mystrsep(&url, "/?");
+ char *url = w->decoded_url;
+ char *tok = mystrsep(&url, "/?");
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, "/?&");
@@ -1176,7 +1417,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);
@@ -1196,7 +1470,7 @@ void web_client_process(struct web_client *w) {
else {
code = 200;
debug_flags |= D_RRD_STATS;
- st->debug = st->debug?0:1;
+ st->debug = !st->debug;
buffer_sprintf(w->response.data, "Chart %s has now debug %s.\r\n", tok, st->debug?"enabled":"disabled");
debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, st->debug?"enabled":"disabled");
}
@@ -1218,39 +1492,11 @@ 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;
- strncpy(filename, w->last_url, FILENAME_MAX);
- filename[FILENAME_MAX] = '\0';
+ strncpyz(filename, w->last_url, FILENAME_MAX);
tok = mystrsep(&url, "?");
buffer_flush(w->response.data);
code = mysendfile(w, (tok && *tok)?tok:"/");
@@ -1259,42 +1505,12 @@ void web_client_process(struct web_client *w) {
else {
char filename[FILENAME_MAX+1];
url = filename;
- strncpy(filename, w->last_url, FILENAME_MAX);
- filename[FILENAME_MAX] = '\0';
+ strncpyz(filename, w->last_url, FILENAME_MAX);
tok = mystrsep(&url, "?");
buffer_flush(w->response.data);
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);
@@ -1415,6 +1631,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;
@@ -1428,18 +1648,37 @@ 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: %s\r\n"
+ "Access-Control-Allow-Credentials: true\r\n"
"Content-Type: %s\r\n"
- "Access-Control-Allow-Origin: *\r\n"
- "Access-Control-Allow-Methods: GET, OPTIONS\r\n"
- "Access-Control-Allow-Headers: accept, x-requested-with\r\n"
- "Access-Control-Max-Age: 86400\r\n"
"Date: %s\r\n"
, code, code_msg
, w->keepalive?"keep-alive":"close"
+ , w->origin
, content_type_string
, date
);
+ if(w->cookie1[0]) {
+ buffer_sprintf(w->response.header_output,
+ "Set-Cookie: %s\r\n",
+ w->cookie1);
+ }
+
+ if(w->cookie2[0]) {
+ buffer_sprintf(w->response.header_output,
+ "Set-Cookie: %s\r\n",
+ w->cookie2);
+ }
+
+ 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, origin, content-type, cookie\r\n"
+ "Access-Control-Max-Age: 1209600\r\n" // 86400 * 14
+ );
+ }
+
if(buffer_strlen(w->response.header))
buffer_strcat(w->response.header_output, buffer_tostring(w->response.header));
@@ -1449,7 +1688,7 @@ void web_client_process(struct web_client *w) {
"Cache-Control: no-cache\r\n"
, date);
}
- else {
+ else if(w->mode != WEB_CLIENT_MODE_OPTIONS) {
char edate[100];
time_t et = w->response.data->date + (86400 * 14);
struct tm etmbuf, *etm = gmtime_r(&et, &etmbuf);
diff --git a/src/web_client.h b/src/web_client.h
index 3823dbc91..f663be4a1 100644
--- a/src/web_client.h
+++ b/src/web_client.h
@@ -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,14 @@ struct web_client {
struct timeval tv_in, tv_ready;
+ char cookie1[COOKIE_MAX+1];
+ char cookie2[COOKIE_MAX+1];
+ char origin[ORIGIN_MAX+1];
+
int mode;
int keepalive;
+ int enable_gzip;
+ char *decoded_url;
struct sockaddr_storage clientaddr;
diff --git a/src/web_server.c b/src/web_server.c
index 10bf39a78..0da72b5be 100644
--- a/src/web_server.c
+++ b/src/web_server.c
@@ -67,7 +67,6 @@ int create_listen_socket4(const char *ip, int port, int listen_backlog)
{
int sock;
int sockopt = 1;
- struct sockaddr_in name;
debug(D_LISTENER, "IPv4 creating new listening socket on port %d", port);
@@ -80,6 +79,7 @@ int create_listen_socket4(const char *ip, int port, int listen_backlog)
/* avoid "address already in use" */
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&sockopt, sizeof(sockopt));
+ struct sockaddr_in name;
memset(&name, 0, sizeof(struct sockaddr_in));
name.sin_family = AF_INET;
name.sin_port = htons (port);
@@ -118,7 +118,6 @@ int create_listen_socket6(const char *ip, int port, int listen_backlog)
{
int sock = -1;
int sockopt = 1;
- struct sockaddr_in6 name;
debug(D_LISTENER, "IPv6 creating new listening socket on port %d", port);
@@ -131,6 +130,7 @@ int create_listen_socket6(const char *ip, int port, int listen_backlog)
/* avoid "address already in use" */
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&sockopt, sizeof(sockopt));
+ struct sockaddr_in6 name;
memset(&name, 0, sizeof(struct sockaddr_in6));
name.sin6_family = AF_INET6;
name.sin6_port = htons ((uint16_t) port);