diff options
author | Lennart Weller <lhw@ring0.de> | 2016-09-05 08:27:26 +0000 |
---|---|---|
committer | Lennart Weller <lhw@ring0.de> | 2016-09-05 08:27:26 +0000 |
commit | 58d9525d7fcacffe52eff7282b7a888dd0dcc1d0 (patch) | |
tree | 251a805eb38d4d75b2a7f44c2cc22e7ea4849513 /src | |
parent | Fixes for service startup and extra config files (diff) | |
parent | Imported Upstream version 1.3.0+dfsg (diff) | |
download | netdata-58d9525d7fcacffe52eff7282b7a888dd0dcc1d0.tar.xz netdata-58d9525d7fcacffe52eff7282b7a888dd0dcc1d0.zip |
Merge tag 'upstream/1.3.0+dfsg'
Upstream version 1.3.0+dfsg
Diffstat (limited to '')
73 files changed, 27740 insertions, 20949 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index e9fc8f332..8fa6d5bdf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,6 +21,8 @@ AM_CFLAGS = \ sbin_PROGRAMS = netdata dist_cache_DATA = .keep +dist_varlib_DATA = .keep +dist_registry_DATA = .keep dist_log_DATA = .keep plugins_PROGRAMS = apps.plugin @@ -30,7 +32,9 @@ netdata_SOURCES = \ common.c common.h \ daemon.c daemon.h \ dictionary.c dictionary.h \ + eval.c eval.h \ global_statistics.c global_statistics.h \ + health.c health.h \ log.c log.h \ main.c main.h \ plugin_checks.c plugin_checks.h \ @@ -67,6 +71,7 @@ netdata_SOURCES = \ unit_test.c unit_test.h \ url.c url.h \ web_buffer.c web_buffer.h \ + web_buffer_svg.c web_buffer_svg.h \ web_client.c web_client.h \ web_server.c web_server.h \ $(NULL) @@ -83,6 +88,7 @@ apps_plugin_SOURCES = \ common.c common.h \ log.c log.h \ procfile.c procfile.h \ + web_buffer.c web_buffer.h \ $(NULL) install-data-hook: diff --git a/src/Makefile.in b/src/Makefile.in index 11b68946e..1a721a045 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -83,7 +83,8 @@ sbin_PROGRAMS = netdata$(EXEEXT) plugins_PROGRAMS = apps.plugin$(EXEEXT) subdir = src DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ - $(top_srcdir)/depcomp $(dist_cache_DATA) $(dist_log_DATA) + $(top_srcdir)/depcomp $(dist_cache_DATA) $(dist_log_DATA) \ + $(dist_registry_DATA) $(dist_varlib_DATA) ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_pthread.m4 \ $(top_srcdir)/configure.ac @@ -94,24 +95,27 @@ CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pluginsdir)" "$(DESTDIR)$(sbindir)" \ - "$(DESTDIR)$(cachedir)" "$(DESTDIR)$(logdir)" + "$(DESTDIR)$(cachedir)" "$(DESTDIR)$(logdir)" \ + "$(DESTDIR)$(registrydir)" "$(DESTDIR)$(varlibdir)" PROGRAMS = $(plugins_PROGRAMS) $(sbin_PROGRAMS) am_apps_plugin_OBJECTS = apps_plugin.$(OBJEXT) avl.$(OBJEXT) \ - common.$(OBJEXT) log.$(OBJEXT) procfile.$(OBJEXT) + common.$(OBJEXT) log.$(OBJEXT) procfile.$(OBJEXT) \ + web_buffer.$(OBJEXT) apps_plugin_OBJECTS = $(am_apps_plugin_OBJECTS) apps_plugin_LDADD = $(LDADD) am_netdata_OBJECTS = appconfig.$(OBJEXT) avl.$(OBJEXT) \ common.$(OBJEXT) daemon.$(OBJEXT) dictionary.$(OBJEXT) \ - global_statistics.$(OBJEXT) log.$(OBJEXT) main.$(OBJEXT) \ - plugin_checks.$(OBJEXT) plugin_idlejitter.$(OBJEXT) \ - plugin_nfacct.$(OBJEXT) plugin_proc.$(OBJEXT) \ - plugin_tc.$(OBJEXT) plugins_d.$(OBJEXT) popen.$(OBJEXT) \ - proc_diskstats.$(OBJEXT) proc_interrupts.$(OBJEXT) \ - proc_softirqs.$(OBJEXT) proc_loadavg.$(OBJEXT) \ - proc_meminfo.$(OBJEXT) proc_net_dev.$(OBJEXT) \ - proc_net_ip_vs_stats.$(OBJEXT) proc_net_netstat.$(OBJEXT) \ - proc_net_rpc_nfsd.$(OBJEXT) proc_net_snmp.$(OBJEXT) \ - proc_net_snmp6.$(OBJEXT) proc_net_stat_conntrack.$(OBJEXT) \ + eval.$(OBJEXT) global_statistics.$(OBJEXT) health.$(OBJEXT) \ + log.$(OBJEXT) main.$(OBJEXT) plugin_checks.$(OBJEXT) \ + plugin_idlejitter.$(OBJEXT) plugin_nfacct.$(OBJEXT) \ + plugin_proc.$(OBJEXT) plugin_tc.$(OBJEXT) plugins_d.$(OBJEXT) \ + popen.$(OBJEXT) proc_diskstats.$(OBJEXT) \ + proc_interrupts.$(OBJEXT) proc_softirqs.$(OBJEXT) \ + proc_loadavg.$(OBJEXT) proc_meminfo.$(OBJEXT) \ + proc_net_dev.$(OBJEXT) proc_net_ip_vs_stats.$(OBJEXT) \ + proc_net_netstat.$(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) \ @@ -119,7 +123,8 @@ am_netdata_OBJECTS = appconfig.$(OBJEXT) avl.$(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) + web_buffer_svg.$(OBJEXT) web_client.$(OBJEXT) \ + web_server.$(OBJEXT) netdata_OBJECTS = $(am_netdata_OBJECTS) am__DEPENDENCIES_1 = netdata_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ @@ -186,7 +191,8 @@ am__uninstall_files_from_dir = { \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } -DATA = $(dist_cache_DATA) $(dist_log_DATA) +DATA = $(dist_cache_DATA) $(dist_log_DATA) $(dist_registry_DATA) \ + $(dist_varlib_DATA) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is @@ -328,6 +334,8 @@ pluginsdir = @pluginsdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ +pythondir = @pythondir@ +registrydir = @registrydir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ @@ -361,6 +369,8 @@ AM_CFLAGS = \ $(NULL) dist_cache_DATA = .keep +dist_varlib_DATA = .keep +dist_registry_DATA = .keep dist_log_DATA = .keep netdata_SOURCES = \ appconfig.c appconfig.h \ @@ -368,7 +378,9 @@ netdata_SOURCES = \ common.c common.h \ daemon.c daemon.h \ dictionary.c dictionary.h \ + eval.c eval.h \ global_statistics.c global_statistics.h \ + health.c health.h \ log.c log.h \ main.c main.h \ plugin_checks.c plugin_checks.h \ @@ -405,6 +417,7 @@ netdata_SOURCES = \ unit_test.c unit_test.h \ url.c url.h \ web_buffer.c web_buffer.h \ + web_buffer_svg.c web_buffer_svg.h \ web_client.c web_client.h \ web_server.c web_server.h \ $(NULL) @@ -422,6 +435,7 @@ apps_plugin_SOURCES = \ common.c common.h \ log.c log.h \ procfile.c procfile.h \ + web_buffer.c web_buffer.h \ $(NULL) all: all-am @@ -563,7 +577,9 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/daemon.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dictionary.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eval.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/global_statistics.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/health.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plugin_checks.Po@am__quote@ @@ -600,6 +616,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unit_test.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/url.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_buffer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_buffer_svg.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_client.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_server.Po@am__quote@ @@ -658,6 +675,48 @@ uninstall-dist_logDATA: @list='$(dist_log_DATA)'; test -n "$(logdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(logdir)'; $(am__uninstall_files_from_dir) +install-dist_registryDATA: $(dist_registry_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_registry_DATA)'; test -n "$(registrydir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(registrydir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(registrydir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(registrydir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(registrydir)" || exit $$?; \ + done + +uninstall-dist_registryDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_registry_DATA)'; test -n "$(registrydir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(registrydir)'; $(am__uninstall_files_from_dir) +install-dist_varlibDATA: $(dist_varlib_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_varlib_DATA)'; test -n "$(varlibdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(varlibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(varlibdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(varlibdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(varlibdir)" || exit $$?; \ + done + +uninstall-dist_varlibDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_varlib_DATA)'; test -n "$(varlibdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(varlibdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique @@ -745,7 +804,7 @@ check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(DATA) installdirs: - for dir in "$(DESTDIR)$(pluginsdir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(cachedir)" "$(DESTDIR)$(logdir)"; do \ + for dir in "$(DESTDIR)$(pluginsdir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(cachedir)" "$(DESTDIR)$(logdir)" "$(DESTDIR)$(registrydir)" "$(DESTDIR)$(varlibdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am @@ -803,6 +862,7 @@ info: info-am info-am: install-data-am: install-dist_cacheDATA install-dist_logDATA \ + install-dist_registryDATA install-dist_varlibDATA \ install-pluginsPROGRAMS @$(NORMAL_INSTALL) $(MAKE) $(AM_MAKEFLAGS) install-data-hook @@ -850,6 +910,7 @@ ps: ps-am ps-am: uninstall-am: uninstall-dist_cacheDATA uninstall-dist_logDATA \ + uninstall-dist_registryDATA uninstall-dist_varlibDATA \ uninstall-pluginsPROGRAMS uninstall-sbinPROGRAMS .MAKE: install-am install-data-am install-strip @@ -860,14 +921,16 @@ uninstall-am: uninstall-dist_cacheDATA uninstall-dist_logDATA \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-data install-data-am \ install-data-hook install-dist_cacheDATA install-dist_logDATA \ - install-dvi install-dvi-am install-exec install-exec-am \ - install-html install-html-am install-info install-info-am \ - install-man install-pdf install-pdf-am install-pluginsPROGRAMS \ - install-ps install-ps-am install-sbinPROGRAMS install-strip \ - installcheck installcheck-am installdirs maintainer-clean \ + install-dist_registryDATA install-dist_varlibDATA install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-pluginsPROGRAMS install-ps \ + install-ps-am install-sbinPROGRAMS install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-dist_cacheDATA uninstall-dist_logDATA \ + uninstall-dist_registryDATA uninstall-dist_varlibDATA \ uninstall-pluginsPROGRAMS uninstall-sbinPROGRAMS diff --git a/src/appconfig.c b/src/appconfig.c index 0ec4cad32..34fb6d7d3 100644 --- a/src/appconfig.c +++ b/src/appconfig.c @@ -1,22 +1,4 @@ - -/* - * TODO - * - * 1. Re-write this using DICTIONARY - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <pthread.h> -#include <stdlib.h> -#include <string.h> - -#include "avl.h" #include "common.h" -#include "appconfig.h" -#include "log.h" #define CONFIG_FILE_LINE_MAX ((CONFIG_MAX_NAME + CONFIG_MAX_VALUE + 1024) * 2) @@ -31,33 +13,33 @@ pthread_mutex_t config_mutex = PTHREAD_MUTEX_INITIALIZER; #define CONFIG_VALUE_CHECKED 0x08 // has been checked if the value is different from the default struct config_value { - avl avl; // the index - this has to be first! + 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 + 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; + char *name; + char *value; - struct config_value *next; // config->mutex protects just this + struct config_value *next; // config->mutex protects just this }; struct config { - avl avl; + avl avl; - 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 + 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 *name; - struct config *next; // gloabl config_mutex protects just this + struct config *next; // gloabl config_mutex protects just this - struct config_value *values; - avl_tree_lock values_index; + struct config_value *values; + avl_tree_lock values_index; - pthread_mutex_t mutex; // this locks only the writers, to ensure atomic updates - // readers are protected using the rwlock in avl_tree_lock + 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; @@ -65,19 +47,19 @@ struct config { // locking static inline void config_global_write_lock(void) { - pthread_mutex_lock(&config_mutex); + pthread_mutex_lock(&config_mutex); } static inline void config_global_unlock(void) { - pthread_mutex_unlock(&config_mutex); + pthread_mutex_unlock(&config_mutex); } static inline void config_section_write_lock(struct config *co) { - pthread_mutex_lock(&co->mutex); + pthread_mutex_lock(&co->mutex); } static inline void config_section_unlock(struct config *co) { - pthread_mutex_unlock(&co->mutex); + pthread_mutex_unlock(&co->mutex); } @@ -85,20 +67,20 @@ static inline void config_section_unlock(struct config *co) { // 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; - else if(((struct config_value *)a)->hash > ((struct config_value *)b)->hash) return 1; - else return strcmp(((struct config_value *)a)->name, ((struct config_value *)b)->name); + if(((struct config_value *)a)->hash < ((struct config_value *)b)->hash) return -1; + else if(((struct config_value *)a)->hash > ((struct config_value *)b)->hash) return 1; + else return strcmp(((struct config_value *)a)->name, ((struct config_value *)b)->name); } #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 tmp; - tmp.hash = (hash)?hash:simple_hash(name); - tmp.name = (char *)name; + struct config_value tmp; + tmp.hash = (hash)?hash:simple_hash(name); + tmp.name = (char *)name; - return (struct config_value *)avl_search_lock(&(co->values_index), (avl *) &tmp); + return (struct config_value *)avl_search_lock(&(co->values_index), (avl *) &tmp); } @@ -106,25 +88,25 @@ static struct config_value *config_value_index_find(struct config *co, const cha // config sections index static int config_compare(void* a, void* b) { - if(((struct config *)a)->hash < ((struct config *)b)->hash) return -1; - else if(((struct config *)a)->hash > ((struct config *)b)->hash) return 1; - else return strcmp(((struct config *)a)->name, ((struct config *)b)->name); + if(((struct config *)a)->hash < ((struct config *)b)->hash) return -1; + else if(((struct config *)a)->hash > ((struct config *)b)->hash) return 1; + else return strcmp(((struct config *)a)->name, ((struct config *)b)->name); } avl_tree_lock config_root_index = { - { NULL, config_compare }, - AVL_LOCK_INITIALIZER + { NULL, config_compare }, + AVL_LOCK_INITIALIZER }; #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 tmp; - tmp.hash = (hash)?hash:simple_hash(name); - tmp.name = (char *)name; + struct config tmp; + tmp.hash = (hash)?hash:simple_hash(name); + tmp.name = (char *)name; - return (struct config *)avl_search_lock(&config_root_index, (avl *) &tmp); + return (struct config *)avl_search_lock(&config_root_index, (avl *) &tmp); } @@ -132,34 +114,31 @@ static struct config *config_index_find(const char *name, uint32_t hash) { // config section methods static inline struct config *config_section_find(const char *section) { - return config_index_find(section, 0); + return config_index_find(section, 0); } static inline struct config *config_section_create(const char *section) { - debug(D_CONFIG, "Creating section '%s'.", section); - - struct config *co = calloc(1, sizeof(struct config)); - if(!co) fatal("Cannot allocate config"); + debug(D_CONFIG, "Creating section '%s'.", section); - co->name = strdup(section); - if(!co->name) fatal("Cannot allocate config.name"); - co->hash = simple_hash(co->name); + struct config *co = callocz(1, sizeof(struct config)); + co->name = strdupz(section); + co->hash = simple_hash(co->name); - avl_init_lock(&co->values_index, config_value_compare); + avl_init_lock(&co->values_index, config_value_compare); - config_index_add(co); + config_index_add(co); - config_global_write_lock(); - struct config *co2 = config_root; - if(co2) { - while (co2->next) co2 = co2->next; - co2->next = co; - } - else config_root = co; - config_global_unlock(); + config_global_write_lock(); + struct config *co2 = config_root; + if(co2) { + while (co2->next) co2 = co2->next; + co2->next = co; + } + else config_root = co; + config_global_unlock(); - return co; + return co; } @@ -168,181 +147,221 @@ static inline struct config *config_section_create(const char *section) 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); + debug(D_CONFIG, "Creating config entry for name '%s', value '%s', in section '%s'.", name, value, co->name); + + struct config_value *cv = callocz(1, sizeof(struct config_value)); + cv->name = strdupz(name); + cv->hash = simple_hash(cv->name); + cv->value = strdupz(value); + + config_value_index_add(co, cv); + + 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); + + return cv; +} + +int config_exists(const char *section, const char *name) { + struct config_value *cv; + + debug(D_CONFIG, "request to get config in section '%s', name '%s'", section, name); + + struct config *co = config_section_find(section); + if(!co) return 0; + + cv = config_value_index_find(co, name, 0); + if(!cv) return 0; + + return 1; +} + +int config_rename(const char *section, const char *old, const char *new) { + struct config_value *cv, *cv2; + + debug(D_CONFIG, "request to rename config in section '%s', old name '%s', new name '%s'", section, old, new); + + struct config *co = config_section_find(section); + if(!co) return -1; + + config_section_write_lock(co); + + cv = config_value_index_find(co, old, 0); + if(!cv) goto cleanup; + + cv2 = config_value_index_find(co, new, 0); + if(cv2) goto cleanup; - struct config_value *cv = calloc(1, sizeof(struct config_value)); - if(!cv) fatal("Cannot allocate config_value"); + config_value_index_del(co, cv); - cv->name = strdup(name); - if(!cv->name) fatal("Cannot allocate config.name"); - cv->hash = simple_hash(cv->name); + freez(cv->name); + cv->name = strdupz(new); - cv->value = strdup(value); - if(!cv->value) fatal("Cannot allocate config.value"); + cv->hash = simple_hash(cv->name); - config_value_index_add(co, cv); + config_value_index_add(co, cv); + config_section_unlock(co); - 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); + return 0; - return cv; +cleanup: + config_section_unlock(co); + return -1; } char *config_get(const char *section, const char *name, const char *default_value) { - struct config_value *cv; - - debug(D_CONFIG, "request to get config in section '%s', name '%s', default_value '%s'", section, name, default_value); - - 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, default_value); - if(!cv) return NULL; - } - cv->flags |= CONFIG_VALUE_USED; - - if((cv->flags & CONFIG_VALUE_LOADED) || (cv->flags & CONFIG_VALUE_CHANGED)) { - // this is a loaded value from the config file - // if it is different that the default, mark it - if(!(cv->flags & CONFIG_VALUE_CHECKED)) { - if(strcmp(cv->value, default_value) != 0) cv->flags |= CONFIG_VALUE_CHANGED; - cv->flags |= CONFIG_VALUE_CHECKED; - } - } - - return(cv->value); + struct config_value *cv; + + debug(D_CONFIG, "request to get config in section '%s', name '%s', default_value '%s'", section, name, default_value); + + 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, default_value); + if(!cv) return NULL; + } + cv->flags |= CONFIG_VALUE_USED; + + if((cv->flags & CONFIG_VALUE_LOADED) || (cv->flags & CONFIG_VALUE_CHANGED)) { + // this is a loaded value from the config file + // if it is different that the default, mark it + if(!(cv->flags & CONFIG_VALUE_CHECKED)) { + if(strcmp(cv->value, default_value) != 0) cv->flags |= CONFIG_VALUE_CHANGED; + cv->flags |= CONFIG_VALUE_CHECKED; + } + } + + return(cv->value); } long long config_get_number(const char *section, const char *name, long long value) { - char buffer[100], *s; - sprintf(buffer, "%lld", value); + char buffer[100], *s; + sprintf(buffer, "%lld", value); - s = config_get(section, name, buffer); - if(!s) return value; + s = config_get(section, name, buffer); + if(!s) return value; - return strtoll(s, NULL, 0); + return strtoll(s, NULL, 0); } int config_get_boolean(const char *section, const char *name, int value) { - char *s; - if(value) s = "yes"; - else s = "no"; + char *s; + if(value) s = "yes"; + else s = "no"; - s = config_get(section, name, s); - if(!s) return value; + s = config_get(section, name, s); + if(!s) return value; - if(!strcmp(s, "yes")) return 1; - else return 0; + if(!strcmp(s, "yes") || !strcmp(s, "auto") || !strcmp(s, "on demand")) return 1; + return 0; } int config_get_boolean_ondemand(const char *section, const char *name, int value) { - char *s; + char *s; - if(value == CONFIG_ONDEMAND_ONDEMAND) - s = "on demand"; + if(value == CONFIG_ONDEMAND_ONDEMAND) + s = "auto"; - else if(value == CONFIG_ONDEMAND_NO) - s = "no"; + else if(value == CONFIG_ONDEMAND_NO) + s = "no"; - else - s = "yes"; + else + s = "yes"; - s = config_get(section, name, s); - if(!s) return value; + s = config_get(section, name, s); + if(!s) return value; - if(!strcmp(s, "yes")) - return CONFIG_ONDEMAND_YES; - else if(!strcmp(s, "no")) - return CONFIG_ONDEMAND_NO; - else if(!strcmp(s, "on demand")) - return CONFIG_ONDEMAND_ONDEMAND; + if(!strcmp(s, "yes")) + return CONFIG_ONDEMAND_YES; + else if(!strcmp(s, "no")) + return CONFIG_ONDEMAND_NO; + else if(!strcmp(s, "auto") || !strcmp(s, "on demand")) + return CONFIG_ONDEMAND_ONDEMAND; - return value; + return value; } const char *config_set_default(const char *section, const char *name, const char *value) { - struct config_value *cv; + struct config_value *cv; - debug(D_CONFIG, "request to set config in section '%s', name '%s', value '%s'", section, name, value); + debug(D_CONFIG, "request to set config in section '%s', name '%s', value '%s'", section, name, value); - struct config *co = config_section_find(section); - if(!co) return config_set(section, name, value); + struct config *co = config_section_find(section); + if(!co) return config_set(section, name, value); - cv = config_value_index_find(co, name, 0); - if(!cv) return config_set(section, name, value); + cv = config_value_index_find(co, name, 0); + if(!cv) return config_set(section, name, value); - cv->flags |= CONFIG_VALUE_USED; + cv->flags |= CONFIG_VALUE_USED; - if(cv->flags & CONFIG_VALUE_LOADED) - return cv->value; + if(cv->flags & CONFIG_VALUE_LOADED) + return cv->value; - if(strcmp(cv->value, value) != 0) { - cv->flags |= CONFIG_VALUE_CHANGED; + if(strcmp(cv->value, value) != 0) { + cv->flags |= CONFIG_VALUE_CHANGED; - free(cv->value); - cv->value = strdup(value); - if(!cv->value) fatal("Cannot allocate config.value"); - } + freez(cv->value); + cv->value = strdupz(value); + } - return cv->value; + return cv->value; } const char *config_set(const char *section, const char *name, const char *value) { - struct config_value *cv; + struct config_value *cv; - debug(D_CONFIG, "request to set config in section '%s', name '%s', value '%s'", section, name, value); + debug(D_CONFIG, "request to set config in section '%s', name '%s', value '%s'", section, name, value); - struct config *co = config_section_find(section); - if(!co) co = config_section_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); - cv->flags |= CONFIG_VALUE_USED; + cv = config_value_index_find(co, name, 0); + if(!cv) cv = config_value_create(co, name, value); + cv->flags |= CONFIG_VALUE_USED; - if(strcmp(cv->value, value) != 0) { - cv->flags |= CONFIG_VALUE_CHANGED; + if(strcmp(cv->value, value) != 0) { + cv->flags |= CONFIG_VALUE_CHANGED; - free(cv->value); - cv->value = strdup(value); - if(!cv->value) fatal("Cannot allocate config.value"); - } + freez(cv->value); + cv->value = strdupz(value); + } - return value; + return value; } long long config_set_number(const char *section, const char *name, long long value) { - char buffer[100]; - sprintf(buffer, "%lld", value); + char buffer[100]; + sprintf(buffer, "%lld", value); - config_set(section, name, buffer); + config_set(section, name, buffer); - return value; + return value; } int config_set_boolean(const char *section, const char *name, int value) { - char *s; - if(value) s = "yes"; - else s = "no"; + char *s; + if(value) s = "yes"; + else s = "no"; - config_set(section, name, s); + config_set(section, name, s); - return value; + return value; } @@ -351,152 +370,158 @@ int config_set_boolean(const char *section, const char *name, int value) 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; + int line = 0; + struct config *co = NULL; + + char buffer[CONFIG_FILE_LINE_MAX + 1], *s; + + if(!filename) filename = CONFIG_DIR "/" CONFIG_FILENAME; + + debug(D_CONFIG, "Opening config file '%s'", 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, "Line %d, overwriting '%s/%s'.", line, co->name, cv->name); + freez(cv->value); + cv->value = strdupz(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; - struct config *co; - struct config_value *cv; - - for(i = 0; i < 3 ;i++) { - switch(i) { - case 0: - buffer_strcat(wb, - "# NetData Configuration\n" - "# You can uncomment and change any of the options below.\n" - "# The value shown in the commented settings, is the default value.\n" - "\n# global netdata configuration\n"); - break; - - case 1: - buffer_strcat(wb, "\n\n# per plugin configuration\n"); - break; - - case 2: - buffer_strcat(wb, "\n\n# per chart configuration\n"); - break; - } - - config_global_write_lock(); - for(co = config_root; co ; co = co->next) { - 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; - - if(i == pri) { - int used = 0; - int changed = 0; - int count = 0; - - config_section_write_lock(co); - for(cv = co->values; cv ; cv = cv->next) { - used += (cv->flags & CONFIG_VALUE_USED)?1:0; - changed += (cv->flags & CONFIG_VALUE_CHANGED)?1:0; - count++; - } - config_section_unlock(co); - - if(!count) continue; - if(only_changed && !changed) continue; - - if(!used) { - buffer_sprintf(wb, "\n# node '%s' is not used.", co->name); - } - - buffer_sprintf(wb, "\n[%s]\n", co->name); - - config_section_write_lock(co); - for(cv = co->values; cv ; cv = cv->next) { - - if(used && !(cv->flags & CONFIG_VALUE_USED)) { - buffer_sprintf(wb, "\n\t# option '%s' is not used.\n", cv->name); - } - buffer_sprintf(wb, "\t%s%s = %s\n", ((!(cv->flags & CONFIG_VALUE_CHANGED)) && (cv->flags & CONFIG_VALUE_USED))?"# ":"", cv->name, cv->value); - } - config_section_unlock(co); - } - } - config_global_unlock(); - } + int i, pri; + struct config *co; + struct config_value *cv; + + for(i = 0; i < 3 ;i++) { + switch(i) { + case 0: + buffer_strcat(wb, + "# NetData Configuration\n" + "# You can uncomment and change any of the options below.\n" + "# The value shown in the commented settings, is the default value.\n" + "\n# global netdata configuration\n"); + break; + + case 1: + buffer_strcat(wb, "\n\n# per plugin configuration\n"); + break; + + case 2: + buffer_strcat(wb, "\n\n# per chart configuration\n"); + break; + } + + config_global_write_lock(); + for(co = config_root; co ; co = co->next) { + if(!strcmp(co->name, "global") || + !strcmp(co->name, "plugins") || + !strcmp(co->name, "registry") || + !strcmp(co->name, "health")) + pri = 0; + else if(!strncmp(co->name, "plugin:", 7)) pri = 1; + else pri = 2; + + if(i == pri) { + int used = 0; + int changed = 0; + int count = 0; + + config_section_write_lock(co); + for(cv = co->values; cv ; cv = cv->next) { + used += (cv->flags & CONFIG_VALUE_USED)?1:0; + changed += (cv->flags & CONFIG_VALUE_CHANGED)?1:0; + count++; + } + config_section_unlock(co); + + if(!count) continue; + if(only_changed && !changed) continue; + + if(!used) { + buffer_sprintf(wb, "\n# node '%s' is not used.", co->name); + } + + buffer_sprintf(wb, "\n[%s]\n", co->name); + + config_section_write_lock(co); + for(cv = co->values; cv ; cv = cv->next) { + + if(used && !(cv->flags & CONFIG_VALUE_USED)) { + buffer_sprintf(wb, "\n\t# option '%s' is not used.\n", cv->name); + } + buffer_sprintf(wb, "\t%s%s = %s\n", ((!(cv->flags & CONFIG_VALUE_CHANGED)) && (cv->flags & CONFIG_VALUE_USED))?"# ":"", cv->name, cv->value); + } + config_section_unlock(co); + } + } + config_global_unlock(); + } } diff --git a/src/appconfig.h b/src/appconfig.h index 41d1e19bb..08aae8348 100644 --- a/src/appconfig.h +++ b/src/appconfig.h @@ -1,5 +1,3 @@ -#include "web_buffer.h" - #ifndef NETDATA_CONFIG_H #define NETDATA_CONFIG_H 1 @@ -26,6 +24,9 @@ extern const char *config_set_default(const char *section, const char *name, con extern long long config_set_number(const char *section, const char *name, long long value); extern int config_set_boolean(const char *section, const char *name, int value); +extern int config_exists(const char *section, const char *name); +extern int config_rename(const char *section, const char *old, const char *new); + extern void generate_config(BUFFER *wb, int only_changed); #endif /* NETDATA_CONFIG_H */ diff --git a/src/apps_plugin.c b/src/apps_plugin.c index 0bcdfcf50..ee400b72e 100644 --- a/src/apps_plugin.c +++ b/src/apps_plugin.c @@ -1,272 +1,96 @@ -// TODO -// -// 1. disable RESET_OR_OVERFLOW check in charts - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/time.h> -#include <sys/wait.h> -#include <sys/stat.h> - -#include <sys/resource.h> -#include <sys/stat.h> - -#include <errno.h> -#include <stdarg.h> -#include <locale.h> -#include <ctype.h> -#include <fcntl.h> - -#include <malloc.h> -#include <dirent.h> -#include <arpa/inet.h> - -#include <sys/types.h> -#include <pwd.h> -#include <grp.h> - -#include "avl.h" - #include "common.h" -#include "log.h" -#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 -long processors = 1; -long pid_max = 32768; +// the rates we are going to send to netdata +// will have this detail +// a value of: +// 1 will send just integer parts to netdata +// 100 will send 2 decimal points +// 1000 will send 3 decimal points +// etc. +#define RATES_DETAIL 10000ULL + +int processors = 1; +pid_t pid_max = 32768; int debug = 0; int update_every = 1; +unsigned long long global_iterations_counter = 1; unsigned long long file_counter = 0; int proc_pid_cmdline_is_needed = 0; - +int include_exited_childs = 1; char *host_prefix = ""; char *config_dir = CONFIG_DIR; -#ifdef NETDATA_INTERNAL_CHECKS -// ---------------------------------------------------------------------------- -// memory debugger -// do not use in production systems - it mis-aligns allocated memory - -struct allocations { - size_t allocations; - size_t allocated; - size_t allocated_max; -} allocations = { 0, 0, 0 }; - -#define MALLOC_MARK (uint32_t)(0x0BADCAFE) -#define MALLOC_PREFIX (sizeof(uint32_t) * 2) -#define MALLOC_SUFFIX (sizeof(uint32_t)) -#define MALLOC_OVERHEAD (MALLOC_PREFIX + MALLOC_SUFFIX) - -void *mark_allocation(void *allocated_ptr, size_t size_without_overheads) { - uint32_t *real_ptr = (uint32_t *)allocated_ptr; - real_ptr[0] = MALLOC_MARK; - real_ptr[1] = (uint32_t) size_without_overheads; - - uint32_t *end_ptr = (uint32_t *)(allocated_ptr + MALLOC_PREFIX + size_without_overheads); - end_ptr[0] = MALLOC_MARK; - - // fprintf(stderr, "MEMORY_POINTER: Allocated at %p, returning %p.\n", allocated_ptr, (void *)(allocated_ptr + MALLOC_PREFIX)); - - return allocated_ptr + MALLOC_PREFIX; -} - -void *check_allocation(const char *file, int line, const char *function, void *marked_ptr, size_t *size_without_overheads_ptr) { - uint32_t *real_ptr = (uint32_t *)(marked_ptr - MALLOC_PREFIX); - - // fprintf(stderr, "MEMORY_POINTER: Checking pointer at %p, real %p for %s/%u@%s.\n", marked_ptr, (void *)(marked_ptr - MALLOC_PREFIX), function, line, file); - - if(real_ptr[0] != MALLOC_MARK) fatal("MEMORY: prefix MARK is not valid for %s/%u@%s.", function, line, file); - - size_t size = real_ptr[1]; - - uint32_t *end_ptr = (uint32_t *)(marked_ptr + size); - if(end_ptr[0] != MALLOC_MARK) fatal("MEMORY: suffix MARK of allocation with size %zu is not valid for %s/%u@%s.", size, function, line, file); - - if(size_without_overheads_ptr) *size_without_overheads_ptr = size; - - return real_ptr; -} - -void *malloc_debug(const char *file, int line, const char *function, size_t size) { - void *ptr = malloc(size + MALLOC_OVERHEAD); - if(!ptr) fatal("MEMORY: Cannot allocate %zu bytes for %s/%u@%s.", size, function, line, file); - - allocations.allocated += size; - allocations.allocations++; - - debug(D_MEMORY, "MEMORY: Allocated %zu bytes for %s/%u@%s." - " Status: allocated %zu in %zu allocs." - , size - , function, line, file - , allocations.allocated - , allocations.allocations - ); - - if(allocations.allocated > allocations.allocated_max) { - debug(D_MEMORY, "MEMORY: total allocation peak increased from %zu to %zu", allocations.allocated_max, allocations.allocated); - allocations.allocated_max = allocations.allocated; - } - - size_t csize; - check_allocation(file, line, function, mark_allocation(ptr, size), &csize); - if(size != csize) { - fatal("Invalid size."); - } - - return mark_allocation(ptr, size); -} - -void *calloc_debug(const char *file, int line, const char *function, size_t nmemb, size_t size) { - void *ptr = malloc_debug(file, line, function, (nmemb * size)); - bzero(ptr, nmemb * size); - return ptr; -} - -void free_debug(const char *file, int line, const char *function, void *ptr) { - size_t size; - void *real_ptr = check_allocation(file, line, function, ptr, &size); - - bzero(real_ptr, size + MALLOC_OVERHEAD); - - free(real_ptr); - allocations.allocated -= size; - allocations.allocations--; - - debug(D_MEMORY, "MEMORY: freed %zu bytes for %s/%u@%s." - " Status: allocated %zu in %zu allocs." - , size - , function, line, file - , allocations.allocated - , allocations.allocations - ); -} - -void *realloc_debug(const char *file, int line, const char *function, void *ptr, size_t size) { - if(!ptr) return malloc_debug(file, line, function, size); - if(!size) { free_debug(file, line, function, ptr); return NULL; } - - size_t old_size; - void *real_ptr = check_allocation(file, line, function, ptr, &old_size); - - void *new_ptr = realloc(real_ptr, size + MALLOC_OVERHEAD); - if(!new_ptr) fatal("MEMORY: Cannot allocate %zu bytes for %s/%u@%s.", size, function, line, file); +pid_t *all_pids_sortlist = NULL; - allocations.allocated += size; - allocations.allocated -= old_size; +// will be automatically set to 1, if guest values are collected +int show_guest_time = 0; +int show_guest_time_old = 0; - debug(D_MEMORY, "MEMORY: Re-allocated from %zu to %zu bytes for %s/%u@%s." - " Status: allocated %z in %zu allocs." - , old_size, size - , function, line, file - , allocations.allocated - , allocations.allocations - ); +int enable_guest_charts = 0; +int enable_file_charts = 1; - if(allocations.allocated > allocations.allocated_max) { - debug(D_MEMORY, "MEMORY: total allocation peak increased from %zu to %zu", allocations.allocated_max, allocations.allocated); - allocations.allocated_max = allocations.allocated; - } - - return mark_allocation(new_ptr, size); -} - -char *strdup_debug(const char *file, int line, const char *function, const char *ptr) { - size_t size = 0; - const char *s = ptr; - - while(*s++) size++; - size++; - - char *p = malloc_debug(file, line, function, size); - if(!p) fatal("Cannot allocate %zu bytes.", size); +// ---------------------------------------------------------------------------- - memcpy(p, ptr, size); - return p; +void netdata_cleanup_and_exit(int ret) { + exit(ret); } -#define malloc(size) malloc_debug(__FILE__, __LINE__, __FUNCTION__, (size)) -#define calloc(nmemb, size) calloc_debug(__FILE__, __LINE__, __FUNCTION__, (nmemb), (size)) -#define realloc(ptr, size) realloc_debug(__FILE__, __LINE__, __FUNCTION__, (ptr), (size)) -#define free(ptr) free_debug(__FILE__, __LINE__, __FUNCTION__, (ptr)) - -#ifdef strdup -#undef strdup -#endif -#define strdup(ptr) strdup_debug(__FILE__, __LINE__, __FUNCTION__, (ptr)) - -#endif /* NETDATA_INTERNAL_CHECKS */ // ---------------------------------------------------------------------------- // system functions // to retrieve settings of the system long get_system_cpus(void) { - procfile *ff = NULL; + procfile *ff = NULL; - int processors = 0; + int processors = 0; - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "%s/proc/stat", host_prefix); + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/stat", host_prefix); - ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT); - if(!ff) return 1; + ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT); + if(!ff) return 1; - ff = procfile_readall(ff); - if(!ff) { - procfile_close(ff); - return 1; - } + ff = procfile_readall(ff); + if(!ff) + return 1; - unsigned int i; - for(i = 0; i < procfile_lines(ff); i++) { - if(!procfile_linewords(ff, i)) continue; + unsigned int i; + for(i = 0; i < procfile_lines(ff); i++) { + if(!procfile_linewords(ff, i)) continue; - if(strncmp(procfile_lineword(ff, i, 0), "cpu", 3) == 0) processors++; - } - processors--; - if(processors < 1) processors = 1; + if(strncmp(procfile_lineword(ff, i, 0), "cpu", 3) == 0) processors++; + } + processors--; + if(processors < 1) processors = 1; - procfile_close(ff); - return processors; + procfile_close(ff); + return processors; } -long get_system_pid_max(void) { - procfile *ff = NULL; - long mpid = 32768; +pid_t get_system_pid_max(void) { + procfile *ff = NULL; + pid_t mpid = 32768; - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", host_prefix); - ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT); - if(!ff) return mpid; + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", host_prefix); + ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT); + if(!ff) return mpid; - ff = procfile_readall(ff); - if(!ff) { - procfile_close(ff); - return mpid; - } + ff = procfile_readall(ff); + if(!ff) + return mpid; - mpid = atol(procfile_lineword(ff, 0, 0)); - if(!mpid) mpid = 32768; + mpid = (pid_t)atoi(procfile_lineword(ff, 0, 0)); + if(!mpid) mpid = 32768; - procfile_close(ff); - return mpid; + procfile_close(ff); + return mpid; } // ---------------------------------------------------------------------------- @@ -274,83 +98,68 @@ long get_system_pid_max(void) { // target is the structure that process data are aggregated struct target { - char compare[MAX_COMPARE_NAME + 1]; - uint32_t comparehash; - size_t comparelen; - - char id[MAX_NAME + 1]; - uint32_t idhash; - - char name[MAX_NAME + 1]; - - uid_t uid; - gid_t gid; - - unsigned long long minflt; - unsigned long long cminflt; - unsigned long long majflt; - unsigned long long cmajflt; - unsigned long long utime; - unsigned long long stime; - unsigned long long cutime; - unsigned long long cstime; - unsigned long long num_threads; - unsigned long long rss; - - unsigned long long fix_minflt; - unsigned long long fix_cminflt; - unsigned long long fix_majflt; - unsigned long long fix_cmajflt; - unsigned long long fix_utime; - unsigned long long fix_stime; - unsigned long long fix_cutime; - unsigned long long fix_cstime; - - unsigned long long statm_size; - unsigned long long statm_resident; - unsigned long long statm_share; - unsigned long long statm_text; - unsigned long long statm_lib; - unsigned long long statm_data; - unsigned long long statm_dirty; - - unsigned long long io_logical_bytes_read; - unsigned long long io_logical_bytes_written; - unsigned long long io_read_calls; - unsigned long long io_write_calls; - unsigned long long io_storage_bytes_read; - unsigned long long io_storage_bytes_written; - unsigned long long io_cancelled_write_bytes; - - unsigned long long fix_io_logical_bytes_read; - unsigned long long fix_io_logical_bytes_written; - unsigned long long fix_io_read_calls; - unsigned long long fix_io_write_calls; - unsigned long long fix_io_storage_bytes_read; - unsigned long long fix_io_storage_bytes_written; - unsigned long long fix_io_cancelled_write_bytes; - - int *fds; - unsigned long long openfiles; - unsigned long long openpipes; - unsigned long long opensockets; - unsigned long long openinotifies; - unsigned long long openeventfds; - unsigned long long opentimerfds; - unsigned long long opensignalfds; - unsigned long long openeventpolls; - unsigned long long openother; - - unsigned long processes; // how many processes have been merged to this - int exposed; // if set, we have sent this to netdata - int hidden; // if set, we set the hidden flag on the dimension - int debug; - int ends_with; - int starts_with; // if set, the compare string matches only the - // beginning of the command - - struct target *target; // the one that will be reported to netdata - struct target *next; + char compare[MAX_COMPARE_NAME + 1]; + uint32_t comparehash; + size_t comparelen; + + char id[MAX_NAME + 1]; + uint32_t idhash; + + char name[MAX_NAME + 1]; + + uid_t uid; + gid_t gid; + + unsigned long long minflt; + unsigned long long cminflt; + unsigned long long majflt; + unsigned long long cmajflt; + unsigned long long utime; + unsigned long long stime; + unsigned long long gtime; + unsigned long long cutime; + unsigned long long cstime; + unsigned long long cgtime; + unsigned long long num_threads; + unsigned long long rss; + + unsigned long long statm_size; + unsigned long long statm_resident; + unsigned long long statm_share; + unsigned long long statm_text; + unsigned long long statm_lib; + unsigned long long statm_data; + unsigned long long statm_dirty; + + unsigned long long io_logical_bytes_read; + unsigned long long io_logical_bytes_written; + unsigned long long io_read_calls; + unsigned long long io_write_calls; + unsigned long long io_storage_bytes_read; + unsigned long long io_storage_bytes_written; + unsigned long long io_cancelled_write_bytes; + + int *fds; + unsigned long long openfiles; + unsigned long long openpipes; + unsigned long long opensockets; + unsigned long long openinotifies; + unsigned long long openeventfds; + unsigned long long opentimerfds; + unsigned long long opensignalfds; + unsigned long long openeventpolls; + unsigned long long openother; + + unsigned long processes; // how many processes have been merged to this + int exposed; // if set, we have sent this to netdata + int hidden; // if set, we set the hidden flag on the dimension + int debug; + int ends_with; + int starts_with; // if set, the compare string matches only the + // beginning of the command + + struct target *target; // the one that will be reported to netdata + struct target *next; }; @@ -367,227 +176,212 @@ struct target *groups_root_target = NULL; struct target *get_users_target(uid_t uid) { - struct target *w; - for(w = users_root_target ; w ; w = w->next) - if(w->uid == uid) return w; + struct target *w; + for(w = users_root_target ; w ; w = w->next) + if(w->uid == uid) return w; - w = calloc(sizeof(struct target), 1); - if(unlikely(!w)) { - error("Cannot allocate %lu bytes of memory", (unsigned long)sizeof(struct target)); - return NULL; - } + w = callocz(sizeof(struct target), 1); + snprintfz(w->compare, MAX_COMPARE_NAME, "%u", uid); + w->comparehash = simple_hash(w->compare); + w->comparelen = strlen(w->compare); - snprintfz(w->compare, MAX_COMPARE_NAME, "%d", uid); - w->comparehash = simple_hash(w->compare); - w->comparelen = strlen(w->compare); + snprintfz(w->id, MAX_NAME, "%u", uid); + w->idhash = simple_hash(w->id); - snprintfz(w->id, MAX_NAME, "%d", uid); - w->idhash = simple_hash(w->id); + struct passwd *pw = getpwuid(uid); + if(!pw) + snprintfz(w->name, MAX_NAME, "%u", uid); + else + snprintfz(w->name, MAX_NAME, "%s", pw->pw_name); - struct passwd *pw = getpwuid(uid); - if(!pw) - snprintfz(w->name, MAX_NAME, "%d", uid); - else - snprintfz(w->name, MAX_NAME, "%s", pw->pw_name); + netdata_fix_chart_name(w->name); - netdata_fix_chart_name(w->name); + w->uid = uid; - w->uid = uid; + w->next = users_root_target; + users_root_target = w; - w->next = users_root_target; - users_root_target = w; + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: added uid %u ('%s') target\n", w->uid, w->name); - if(unlikely(debug)) - fprintf(stderr, "apps.plugin: added uid %d ('%s') target\n", w->uid, w->name); - - return w; + return w; } struct target *get_groups_target(gid_t gid) { - struct target *w; - for(w = groups_root_target ; w ; w = w->next) - if(w->gid == gid) return w; - - w = calloc(sizeof(struct target), 1); - if(unlikely(!w)) { - error("Cannot allocate %lu bytes of memory", (unsigned long)sizeof(struct target)); - return NULL; - } + struct target *w; + for(w = groups_root_target ; w ; w = w->next) + if(w->gid == gid) return w; - snprintfz(w->compare, MAX_COMPARE_NAME, "%d", gid); - w->comparehash = simple_hash(w->compare); - w->comparelen = strlen(w->compare); + w = callocz(sizeof(struct target), 1); + snprintfz(w->compare, MAX_COMPARE_NAME, "%u", gid); + w->comparehash = simple_hash(w->compare); + w->comparelen = strlen(w->compare); - snprintfz(w->id, MAX_NAME, "%d", gid); - w->idhash = simple_hash(w->id); + snprintfz(w->id, MAX_NAME, "%u", gid); + w->idhash = simple_hash(w->id); - struct group *gr = getgrgid(gid); - if(!gr) - snprintfz(w->name, MAX_NAME, "%d", gid); - else - snprintfz(w->name, MAX_NAME, "%s", gr->gr_name); + struct group *gr = getgrgid(gid); + if(!gr) + snprintfz(w->name, MAX_NAME, "%u", gid); + else + snprintfz(w->name, MAX_NAME, "%s", gr->gr_name); - netdata_fix_chart_name(w->name); + netdata_fix_chart_name(w->name); - w->gid = gid; + w->gid = gid; - w->next = groups_root_target; - groups_root_target = w; + w->next = groups_root_target; + groups_root_target = w; - if(unlikely(debug)) - fprintf(stderr, "apps.plugin: added gid %d ('%s') target\n", w->gid, w->name); + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: added gid %u ('%s') target\n", w->gid, w->name); - return w; + return w; } // find or create a new target // there are targets that are just aggregated to other target (the second argument) -struct target *get_apps_groups_target(const char *id, struct target *target) -{ - int tdebug = 0, thidden = 0, ends_with = 0; - const char *nid = id; - - while(nid[0] == '-' || nid[0] == '+' || nid[0] == '*') { - if(nid[0] == '-') thidden = 1; - if(nid[0] == '+') tdebug = 1; - if(nid[0] == '*') ends_with = 1; - nid++; - } - uint32_t hash = simple_hash(id); - - struct target *w; - for(w = apps_groups_root_target ; w ; w = w->next) { - if(w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0) - return w; - } - - w = calloc(sizeof(struct target), 1); - if(unlikely(!w)) { - error("Cannot allocate %lu bytes of memory", (unsigned long)sizeof(struct target)); - return NULL; - } - - strncpyz(w->id, nid, MAX_NAME); - w->idhash = simple_hash(w->id); - - strncpyz(w->name, nid, MAX_NAME); - - strncpyz(w->compare, nid, MAX_COMPARE_NAME); - int len = strlen(w->compare); - if(w->compare[len - 1] == '*') { - w->compare[len - 1] = '\0'; - w->starts_with = 1; - } - w->ends_with = ends_with; - - if(w->starts_with && w->ends_with) - proc_pid_cmdline_is_needed = 1; - - w->comparehash = simple_hash(w->compare); - w->comparelen = strlen(w->compare); - - w->hidden = thidden; - w->debug = tdebug; - w->target = target; - - w->next = apps_groups_root_target; - apps_groups_root_target = w; - - if(unlikely(debug)) - fprintf(stderr, "apps.plugin: ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n" - , w->id - , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact")) - , w->target?w->target->id:w->id - , (w->hidden)?"hidden":"-" - , (w->debug)?"debug":"-" - ); - - return w; +struct target *get_apps_groups_target(const char *id, struct target *target) { + int tdebug = 0, thidden = 0, ends_with = 0; + const char *nid = id; + + while(nid[0] == '-' || nid[0] == '+' || nid[0] == '*') { + if(nid[0] == '-') thidden = 1; + if(nid[0] == '+') tdebug = 1; + if(nid[0] == '*') ends_with = 1; + nid++; + } + uint32_t hash = simple_hash(id); + + struct target *w, *last = apps_groups_root_target; + for(w = apps_groups_root_target ; w ; w = w->next) { + if(w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0) + return w; + + last = w; + } + + w = callocz(sizeof(struct target), 1); + strncpyz(w->id, nid, MAX_NAME); + w->idhash = simple_hash(w->id); + + strncpyz(w->name, nid, MAX_NAME); + + strncpyz(w->compare, nid, MAX_COMPARE_NAME); + size_t len = strlen(w->compare); + if(w->compare[len - 1] == '*') { + w->compare[len - 1] = '\0'; + w->starts_with = 1; + } + w->ends_with = ends_with; + + if(w->starts_with && w->ends_with) + proc_pid_cmdline_is_needed = 1; + + w->comparehash = simple_hash(w->compare); + w->comparelen = strlen(w->compare); + + w->hidden = thidden; + w->debug = tdebug; + w->target = target; + + // append it, to maintain the order in apps_groups.conf + if(last) last->next = w; + else apps_groups_root_target = w; + + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n" + , w->id + , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact")) + , w->target?w->target->id:w->id + , (w->hidden)?"hidden":"-" + , (w->debug)?"debug":"-" + ); + + return w; } // read the apps_groups.conf file int read_apps_groups_conf(const char *name) { - char filename[FILENAME_MAX + 1]; + char filename[FILENAME_MAX + 1]; - snprintfz(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); + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: process groups file: '%s'\n", filename); - // ---------------------------------------- + // ---------------------------------------- - procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT); - if(!ff) return 1; + procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT); + if(!ff) return 1; - procfile_set_quotes(ff, "'\""); + procfile_set_quotes(ff, "'\""); - ff = procfile_readall(ff); - if(!ff) { - procfile_close(ff); - return 1; - } + ff = procfile_readall(ff); + if(!ff) + return 1; - unsigned long line, lines = procfile_lines(ff); + unsigned long line, lines = procfile_lines(ff); - for(line = 0; line < lines ;line++) { - unsigned long word, words = procfile_linewords(ff, line); - struct target *w = NULL; + for(line = 0; line < lines ;line++) { + unsigned long word, words = procfile_linewords(ff, line); + struct target *w = NULL; - char *t = procfile_lineword(ff, line, 0); - if(!t || !*t) continue; + char *t = procfile_lineword(ff, line, 0); + if(!t || !*t) continue; - for(word = 0; word < words ;word++) { - char *s = procfile_lineword(ff, line, word); - if(!s || !*s) continue; - if(*s == '#') break; + for(word = 0; word < words ;word++) { + char *s = procfile_lineword(ff, line, word); + if(!s || !*s) continue; + if(*s == '#') break; - if(t == s) continue; + if(t == s) continue; - struct target *n = get_apps_groups_target(s, w); - if(!n) { - error("Cannot create target '%s' (line %d, word %d)", s, line, word); - continue; - } + struct target *n = get_apps_groups_target(s, w); + if(!n) { + error("Cannot create target '%s' (line %lu, word %lu)", s, line, word); + continue; + } - if(!w) w = n; - } + if(!w) w = n; + } - if(w) { - int tdebug = 0, thidden = 0; + if(w) { + int tdebug = 0, thidden = 0; - while(t[0] == '-' || t[0] == '+') { - if(t[0] == '-') thidden = 1; - if(t[0] == '+') tdebug = 1; - t++; - } + while(t[0] == '-' || t[0] == '+') { + if(t[0] == '-') thidden = 1; + if(t[0] == '+') tdebug = 1; + t++; + } - strncpyz(w->name, t, MAX_NAME); - w->hidden = thidden; - w->debug = tdebug; + strncpyz(w->name, t, MAX_NAME); + w->hidden = thidden; + w->debug = tdebug; - if(unlikely(debug)) - fprintf(stderr, "apps.plugin: AGGREGATION TARGET NAME '%s' on ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n" - , w->name - , w->id - , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact")) - , w->target?w->target->id:w->id - , (w->hidden)?"hidden":"-" - , (w->debug)?"debug":"-" - ); - } - } + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: AGGREGATION TARGET NAME '%s' on ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s\n" + , w->name + , w->id + , w->compare, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact")) + , w->target?w->target->id:w->id + , (w->hidden)?"hidden":"-" + , (w->debug)?"debug":"-" + ); + } + } - procfile_close(ff); + procfile_close(ff); - apps_groups_default_target = get_apps_groups_target("p+!o@w#e$i^r&7*5(-i)l-o_", NULL); // match nothing - if(!apps_groups_default_target) - error("Cannot create default target"); - else - strncpyz(apps_groups_default_target->name, "other", MAX_NAME); + apps_groups_default_target = get_apps_groups_target("p+!o@w#e$i^r&7*5(-i)l-o_", NULL); // match nothing + if(!apps_groups_default_target) + error("Cannot create default target"); + else + strncpyz(apps_groups_default_target->name, "other", MAX_NAME); - return 0; + return 0; } @@ -595,178 +389,183 @@ int read_apps_groups_conf(const char *name) // data to store for each pid // see: man proc -struct pid_stat { - int32_t pid; - char comm[MAX_COMPARE_NAME + 1]; - char cmdline[MAX_CMDLINE + 1]; - - // char state; - int32_t ppid; - // int32_t pgrp; - // int32_t session; - // int32_t tty_nr; - // int32_t tpgid; - // uint64_t flags; - unsigned long long minflt; - unsigned long long cminflt; - unsigned long long majflt; - unsigned long long cmajflt; - unsigned long long utime; - unsigned long long stime; - unsigned long long cutime; - unsigned long long cstime; - // int64_t priority; - // int64_t nice; - int32_t num_threads; - // int64_t itrealvalue; - // unsigned long long starttime; - // unsigned long long vsize; - unsigned long long rss; - // unsigned long long rsslim; - // unsigned long long starcode; - // unsigned long long endcode; - // unsigned long long startstack; - // unsigned long long kstkesp; - // unsigned long long kstkeip; - // uint64_t signal; - // uint64_t blocked; - // uint64_t sigignore; - // uint64_t sigcatch; - // uint64_t wchan; - // uint64_t nswap; - // uint64_t cnswap; - // int32_t exit_signal; - // int32_t processor; - // uint32_t rt_priority; - // uint32_t policy; - // unsigned long long delayacct_blkio_ticks; - // uint64_t guest_time; - // int64_t cguest_time; - - uid_t uid; - gid_t gid; - - unsigned long long statm_size; - unsigned long long statm_resident; - unsigned long long statm_share; - unsigned long long statm_text; - unsigned long long statm_lib; - unsigned long long statm_data; - unsigned long long statm_dirty; - - unsigned long long io_logical_bytes_read; - unsigned long long io_logical_bytes_written; - unsigned long long io_read_calls; - unsigned long long io_write_calls; - unsigned long long io_storage_bytes_read; - unsigned long long io_storage_bytes_written; - unsigned long long io_cancelled_write_bytes; - - // we need the last values - // for all incremental counters - // so that when a process switches users/groups - // we will subtract these values from the old - // target - unsigned long long last_minflt; - unsigned long long last_cminflt; - unsigned long long last_majflt; - unsigned long long last_cmajflt; - unsigned long long last_utime; - unsigned long long last_stime; - unsigned long long last_cutime; - unsigned long long last_cstime; - - unsigned long long last_io_logical_bytes_read; - unsigned long long last_io_logical_bytes_written; - unsigned long long last_io_read_calls; - unsigned long long last_io_write_calls; - unsigned long long last_io_storage_bytes_read; - unsigned long long last_io_storage_bytes_written; - unsigned long long last_io_cancelled_write_bytes; - -#ifdef AGGREGATE_CHILDREN_TO_PARENTS - unsigned long long old_utime; - unsigned long long old_stime; - unsigned long long old_minflt; - unsigned long long old_majflt; - - unsigned long long old_cutime; - unsigned long long old_cstime; - unsigned long long old_cminflt; - unsigned long long old_cmajflt; - - unsigned long long fix_cutime; - unsigned long long fix_cstime; - unsigned long long fix_cminflt; - unsigned long long fix_cmajflt; - - unsigned long long diff_cutime; - unsigned long long diff_cstime; - unsigned long long diff_cminflt; - unsigned long long diff_cmajflt; -#endif /* AGGREGATE_CHILDREN_TO_PARENTS */ - - int *fds; // array of fds it uses - int fds_size; // the size of the fds array - - int children_count; // number of processes directly referencing this - int updated; // 1 when update - int merged; // 1 when it has been merged to its parent - int new_entry; - - struct target *target; // app_groups.conf targets - struct target *user_target; // uid based targets - struct target *group_target; // gid based targets - - struct pid_stat *parent; - struct pid_stat *prev; - struct pid_stat *next; +#define PID_LOG_IO 0x00000001 +#define PID_LOG_STATM 0x00000002 +#define PID_LOG_CMDLINE 0x00000004 +#define PID_LOG_FDS 0x00000008 +#define PID_LOG_STAT 0x00000010 +struct pid_stat { + int32_t pid; + char comm[MAX_COMPARE_NAME + 1]; + char cmdline[MAX_CMDLINE + 1]; + + uint32_t log_thrown; + + // char state; + int32_t ppid; + // int32_t pgrp; + // int32_t session; + // int32_t tty_nr; + // int32_t tpgid; + // uint64_t flags; + + // these are raw values collected + unsigned long long minflt_raw; + unsigned long long cminflt_raw; + unsigned long long majflt_raw; + unsigned long long cmajflt_raw; + unsigned long long utime_raw; + unsigned long long stime_raw; + unsigned long long gtime_raw; // guest_time + unsigned long long cutime_raw; + unsigned long long cstime_raw; + unsigned long long cgtime_raw; // cguest_time + + // these are rates + unsigned long long minflt; + unsigned long long cminflt; + unsigned long long majflt; + unsigned long long cmajflt; + unsigned long long utime; + unsigned long long stime; + unsigned long long gtime; + unsigned long long cutime; + unsigned long long cstime; + unsigned long long cgtime; + + // int64_t priority; + // int64_t nice; + int32_t num_threads; + // int64_t itrealvalue; + // unsigned long long starttime; + // unsigned long long vsize; + unsigned long long rss; + // unsigned long long rsslim; + // unsigned long long starcode; + // unsigned long long endcode; + // unsigned long long startstack; + // unsigned long long kstkesp; + // unsigned long long kstkeip; + // uint64_t signal; + // uint64_t blocked; + // uint64_t sigignore; + // uint64_t sigcatch; + // uint64_t wchan; + // uint64_t nswap; + // uint64_t cnswap; + // int32_t exit_signal; + // int32_t processor; + // uint32_t rt_priority; + // uint32_t policy; + // unsigned long long delayacct_blkio_ticks; + + uid_t uid; + gid_t gid; + + unsigned long long statm_size; + unsigned long long statm_resident; + unsigned long long statm_share; + unsigned long long statm_text; + unsigned long long statm_lib; + unsigned long long statm_data; + unsigned long long statm_dirty; + + unsigned long long io_logical_bytes_read_raw; + unsigned long long io_logical_bytes_written_raw; + unsigned long long io_read_calls_raw; + unsigned long long io_write_calls_raw; + unsigned long long io_storage_bytes_read_raw; + unsigned long long io_storage_bytes_written_raw; + unsigned long long io_cancelled_write_bytes_raw; + + unsigned long long io_logical_bytes_read; + unsigned long long io_logical_bytes_written; + unsigned long long io_read_calls; + unsigned long long io_write_calls; + unsigned long long io_storage_bytes_read; + unsigned long long io_storage_bytes_written; + unsigned long long io_cancelled_write_bytes; + + int *fds; // array of fds it uses + int fds_size; // the size of the fds array + + int children_count; // number of processes directly referencing this + int keep; // 1 when we need to keep this process in memory even after it exited + int keeploops; // increases by 1 every time keep is 1 and updated 0 + int updated; // 1 when the process is currently running + int merged; // 1 when it has been merged to its parent + int new_entry; // 1 when this is a new process, just saw for the first time + int read; // 1 when we have already read this process for this iteration + int sortlist; // higher numbers = top on the process tree + // each process gets a unique number + + struct target *target; // app_groups.conf targets + struct target *user_target; // uid based targets + struct target *group_target; // gid based targets + + unsigned long long stat_collected_usec; + unsigned long long last_stat_collected_usec; + + unsigned long long io_collected_usec; + unsigned long long last_io_collected_usec; + + char *stat_filename; + char *statm_filename; + char *io_filename; + char *cmdline_filename; + + struct pid_stat *parent; + struct pid_stat *prev; + struct pid_stat *next; } *root_of_pids = NULL, **all_pids; long all_pids_count = 0; -struct pid_stat *get_pid_entry(pid_t pid) -{ - if(all_pids[pid]) { - all_pids[pid]->new_entry = 0; - return all_pids[pid]; - } - - all_pids[pid] = calloc(sizeof(struct pid_stat), 1); - if(!all_pids[pid]) { - error("Cannot allocate %lu bytes of memory", (unsigned long)sizeof(struct pid_stat)); - return NULL; - } - - all_pids[pid]->fds = calloc(sizeof(int), 100); - if(!all_pids[pid]->fds) - error("Cannot allocate %ld bytes of memory", (unsigned long)(sizeof(int) * 100)); - else all_pids[pid]->fds_size = 100; - - if(root_of_pids) root_of_pids->prev = all_pids[pid]; - all_pids[pid]->next = root_of_pids; - root_of_pids = all_pids[pid]; - - all_pids[pid]->pid = pid; - all_pids[pid]->new_entry = 1; - - return all_pids[pid]; +struct pid_stat *get_pid_entry(pid_t pid) { + if(all_pids[pid]) { + all_pids[pid]->new_entry = 0; + return all_pids[pid]; + } + + all_pids[pid] = callocz(sizeof(struct pid_stat), 1); + all_pids[pid]->fds = callocz(sizeof(int), 100); + all_pids[pid]->fds_size = 100; + + if(root_of_pids) root_of_pids->prev = all_pids[pid]; + all_pids[pid]->next = root_of_pids; + root_of_pids = all_pids[pid]; + + all_pids[pid]->pid = pid; + all_pids[pid]->new_entry = 1; + + all_pids_count++; + + return all_pids[pid]; } -void del_pid_entry(pid_t pid) -{ - if(!all_pids[pid]) return; +void del_pid_entry(pid_t pid) { + if(!all_pids[pid]) { + error("attempted to free pid %d that is not allocated.", pid); + return; + } + + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: process %d %s exited, deleting it.\n", pid, all_pids[pid]->comm); - if(debug) fprintf(stderr, "apps.plugin: process %d %s exited, deleting it.\n", pid, all_pids[pid]->comm); + if(root_of_pids == all_pids[pid]) root_of_pids = all_pids[pid]->next; + if(all_pids[pid]->next) all_pids[pid]->next->prev = all_pids[pid]->prev; + if(all_pids[pid]->prev) all_pids[pid]->prev->next = all_pids[pid]->next; - if(root_of_pids == all_pids[pid]) root_of_pids = all_pids[pid]->next; - if(all_pids[pid]->next) all_pids[pid]->next->prev = all_pids[pid]->prev; - if(all_pids[pid]->prev) all_pids[pid]->prev->next = all_pids[pid]->next; + if(all_pids[pid]->fds) freez(all_pids[pid]->fds); + if(all_pids[pid]->stat_filename) freez(all_pids[pid]->stat_filename); + if(all_pids[pid]->statm_filename) freez(all_pids[pid]->statm_filename); + if(all_pids[pid]->io_filename) freez(all_pids[pid]->io_filename); + if(all_pids[pid]->cmdline_filename) freez(all_pids[pid]->cmdline_filename); + freez(all_pids[pid]); - if(all_pids[pid]->fds) free(all_pids[pid]->fds); - free(all_pids[pid]); - all_pids[pid] = NULL; + all_pids[pid] = NULL; + all_pids_count--; } @@ -774,188 +573,380 @@ void del_pid_entry(pid_t pid) // update pids from proc int read_proc_pid_cmdline(struct pid_stat *p) { - char filename[FILENAME_MAX + 1]; - snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", host_prefix, p->pid); + + if(unlikely(!p->cmdline_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", host_prefix, p->pid); + p->cmdline_filename = strdupz(filename); + } - int fd = open(filename, O_RDONLY, 0666); - if(unlikely(fd == -1)) return 1; + int fd = open(p->cmdline_filename, O_RDONLY, 0666); + if(unlikely(fd == -1)) goto cleanup; - int i, bytes = read(fd, p->cmdline, MAX_CMDLINE); - close(fd); + ssize_t i, bytes = read(fd, p->cmdline, MAX_CMDLINE); + close(fd); - if(bytes <= 0) { - // copy the command to the command line - strncpyz(p->cmdline, p->comm, MAX_CMDLINE); - return 0; - } + if(unlikely(bytes < 0)) goto cleanup; - p->cmdline[bytes] = '\0'; - for(i = 0; i < bytes ; i++) - if(!p->cmdline[i]) p->cmdline[i] = ' '; + p->cmdline[bytes] = '\0'; + for(i = 0; i < bytes ; i++) + if(unlikely(!p->cmdline[i])) p->cmdline[i] = ' '; - if(unlikely(debug)) - fprintf(stderr, "Read file '%s' contents: %s\n", filename, p->cmdline); + if(unlikely(debug)) + fprintf(stderr, "Read file '%s' contents: %s\n", p->cmdline_filename, p->cmdline); - return 0; + return 1; + +cleanup: + // copy the command to the command line + strncpyz(p->cmdline, p->comm, MAX_CMDLINE); + return 0; } int read_proc_pid_ownership(struct pid_stat *p) { - char filename[FILENAME_MAX + 1]; - - snprintfz(filename, FILENAME_MAX, "%s/proc/%d", host_prefix, p->pid); + if(unlikely(!p->stat_filename)) { + error("pid %d does not have a stat_filename", p->pid); + return 0; + } - // ---------------------------------------- - // read uid and gid + // ---------------------------------------- + // read uid and gid - struct stat st; - if(stat(filename, &st) != 0) - return 1; + struct stat st; + if(stat(p->stat_filename, &st) != 0) { + error("Cannot stat file '%s'", p->stat_filename); + return 1; + } - p->uid = st.st_uid; - p->gid = st.st_gid; + p->uid = st.st_uid; + p->gid = st.st_gid; - return 0; + return 1; } int read_proc_pid_stat(struct pid_stat *p) { - static procfile *ff = NULL; - - char filename[FILENAME_MAX + 1]; - - snprintfz(filename, FILENAME_MAX, "%s/proc/%d/stat", host_prefix, p->pid); - - // ---------------------------------------- - - int set_quotes = (!ff)?1:0; - - ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); - if(!ff) return 1; - - // if(set_quotes) procfile_set_quotes(ff, "()"); - if(set_quotes) procfile_set_open_close(ff, "(", ")"); - - ff = procfile_readall(ff); - if(!ff) { - // procfile_close(ff); - return 1; - } - - file_counter++; - - // parse the process name - unsigned int i = 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 - // p->state = *(procfile_lineword(ff, 0, 2+i)); - p->ppid = (int32_t) atol(procfile_lineword(ff, 0, 3 + i)); - // p->pgrp = atol(procfile_lineword(ff, 0, 4+i)); - // p->session = atol(procfile_lineword(ff, 0, 5+i)); - // p->tty_nr = atol(procfile_lineword(ff, 0, 6+i)); - // p->tpgid = atol(procfile_lineword(ff, 0, 7+i)); - // p->flags = strtoull(procfile_lineword(ff, 0, 8+i), NULL, 10); - p->minflt = strtoull(procfile_lineword(ff, 0, 9+i), NULL, 10); - p->cminflt = strtoull(procfile_lineword(ff, 0, 10+i), NULL, 10); - p->majflt = strtoull(procfile_lineword(ff, 0, 11+i), NULL, 10); - p->cmajflt = strtoull(procfile_lineword(ff, 0, 12+i), NULL, 10); - p->utime = strtoull(procfile_lineword(ff, 0, 13+i), NULL, 10); - p->stime = strtoull(procfile_lineword(ff, 0, 14+i), NULL, 10); - p->cutime = strtoull(procfile_lineword(ff, 0, 15+i), NULL, 10); - p->cstime = strtoull(procfile_lineword(ff, 0, 16+i), NULL, 10); - // p->priority = strtoull(procfile_lineword(ff, 0, 17+i), NULL, 10); - // p->nice = strtoull(procfile_lineword(ff, 0, 18+i), NULL, 10); - p->num_threads = (int32_t) atol(procfile_lineword(ff, 0, 19 + i)); - // p->itrealvalue = strtoull(procfile_lineword(ff, 0, 20+i), NULL, 10); - // p->starttime = strtoull(procfile_lineword(ff, 0, 21+i), NULL, 10); - // p->vsize = strtoull(procfile_lineword(ff, 0, 22+i), NULL, 10); - p->rss = strtoull(procfile_lineword(ff, 0, 23+i), NULL, 10); - // p->rsslim = strtoull(procfile_lineword(ff, 0, 24+i), NULL, 10); - // p->starcode = strtoull(procfile_lineword(ff, 0, 25+i), NULL, 10); - // p->endcode = strtoull(procfile_lineword(ff, 0, 26+i), NULL, 10); - // p->startstack = strtoull(procfile_lineword(ff, 0, 27+i), NULL, 10); - // p->kstkesp = strtoull(procfile_lineword(ff, 0, 28+i), NULL, 10); - // p->kstkeip = strtoull(procfile_lineword(ff, 0, 29+i), NULL, 10); - // p->signal = strtoull(procfile_lineword(ff, 0, 30+i), NULL, 10); - // p->blocked = strtoull(procfile_lineword(ff, 0, 31+i), NULL, 10); - // p->sigignore = strtoull(procfile_lineword(ff, 0, 32+i), NULL, 10); - // p->sigcatch = strtoull(procfile_lineword(ff, 0, 33+i), NULL, 10); - // p->wchan = strtoull(procfile_lineword(ff, 0, 34+i), NULL, 10); - // p->nswap = strtoull(procfile_lineword(ff, 0, 35+i), NULL, 10); - // p->cnswap = strtoull(procfile_lineword(ff, 0, 36+i), NULL, 10); - // p->exit_signal = atol(procfile_lineword(ff, 0, 37+i)); - // p->processor = atol(procfile_lineword(ff, 0, 38+i)); - // p->rt_priority = strtoul(procfile_lineword(ff, 0, 39+i), NULL, 10); - // p->policy = strtoul(procfile_lineword(ff, 0, 40+i), NULL, 10); - // p->delayacct_blkio_ticks = strtoull(procfile_lineword(ff, 0, 41+i), NULL, 10); - // p->guest_time = strtoull(procfile_lineword(ff, 0, 42+i), NULL, 10); - // p->cguest_time = strtoull(procfile_lineword(ff, 0, 43), NULL, 10); - - if(debug || (p->target && p->target->debug)) - fprintf(stderr, "apps.plugin: READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' VALUES: utime=%llu, stime=%llu, cutime=%llu, cstime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu, threads=%d\n", host_prefix, p->pid, p->comm, p->utime, p->stime, p->cutime, p->cstime, p->minflt, p->majflt, p->cminflt, p->cmajflt, p->num_threads); - - // procfile_close(ff); - return 0; + static procfile *ff = NULL; + + if(unlikely(!p->stat_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/stat", host_prefix, p->pid); + p->stat_filename = strdupz(filename); + } + + int set_quotes = (!ff)?1:0; + + ff = procfile_reopen(ff, p->stat_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); + if(unlikely(!ff)) goto cleanup; + + // if(set_quotes) procfile_set_quotes(ff, "()"); + if(set_quotes) procfile_set_open_close(ff, "(", ")"); + + ff = procfile_readall(ff); + if(unlikely(!ff)) goto cleanup; + + p->last_stat_collected_usec = p->stat_collected_usec; + p->stat_collected_usec = time_usec(); + file_counter++; + + // p->pid = atol(procfile_lineword(ff, 0, 0+i)); + + strncpyz(p->comm, procfile_lineword(ff, 0, 1), MAX_COMPARE_NAME); + + // p->state = *(procfile_lineword(ff, 0, 2)); + p->ppid = (int32_t) atol(procfile_lineword(ff, 0, 3)); + // p->pgrp = atol(procfile_lineword(ff, 0, 4)); + // p->session = atol(procfile_lineword(ff, 0, 5)); + // p->tty_nr = atol(procfile_lineword(ff, 0, 6)); + // p->tpgid = atol(procfile_lineword(ff, 0, 7)); + // p->flags = strtoull(procfile_lineword(ff, 0, 8), NULL, 10); + + unsigned long long last; + + last = p->minflt_raw; + p->minflt_raw = strtoull(procfile_lineword(ff, 0, 9), NULL, 10); + p->minflt = (p->minflt_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->cminflt_raw; + p->cminflt_raw = strtoull(procfile_lineword(ff, 0, 10), NULL, 10); + p->cminflt = (p->cminflt_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->majflt_raw; + p->majflt_raw = strtoull(procfile_lineword(ff, 0, 11), NULL, 10); + p->majflt = (p->majflt_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->cmajflt_raw; + p->cmajflt_raw = strtoull(procfile_lineword(ff, 0, 12), NULL, 10); + p->cmajflt = (p->cmajflt_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->utime_raw; + p->utime_raw = strtoull(procfile_lineword(ff, 0, 13), NULL, 10); + p->utime = (p->utime_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->stime_raw; + p->stime_raw = strtoull(procfile_lineword(ff, 0, 14), NULL, 10); + p->stime = (p->stime_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->cutime_raw; + p->cutime_raw = strtoull(procfile_lineword(ff, 0, 15), NULL, 10); + p->cutime = (p->cutime_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->cstime_raw; + p->cstime_raw = strtoull(procfile_lineword(ff, 0, 16), NULL, 10); + p->cstime = (p->cstime_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + // p->priority = strtoull(procfile_lineword(ff, 0, 17), NULL, 10); + // p->nice = strtoull(procfile_lineword(ff, 0, 18), NULL, 10); + p->num_threads = (int32_t) atol(procfile_lineword(ff, 0, 19)); + // p->itrealvalue = strtoull(procfile_lineword(ff, 0, 20), NULL, 10); + // p->starttime = strtoull(procfile_lineword(ff, 0, 21), NULL, 10); + // p->vsize = strtoull(procfile_lineword(ff, 0, 22), NULL, 10); + p->rss = strtoull(procfile_lineword(ff, 0, 23), NULL, 10); + // p->rsslim = strtoull(procfile_lineword(ff, 0, 24), NULL, 10); + // p->starcode = strtoull(procfile_lineword(ff, 0, 25), NULL, 10); + // p->endcode = strtoull(procfile_lineword(ff, 0, 26), NULL, 10); + // p->startstack = strtoull(procfile_lineword(ff, 0, 27), NULL, 10); + // p->kstkesp = strtoull(procfile_lineword(ff, 0, 28), NULL, 10); + // p->kstkeip = strtoull(procfile_lineword(ff, 0, 29), NULL, 10); + // p->signal = strtoull(procfile_lineword(ff, 0, 30), NULL, 10); + // p->blocked = strtoull(procfile_lineword(ff, 0, 31), NULL, 10); + // p->sigignore = strtoull(procfile_lineword(ff, 0, 32), NULL, 10); + // p->sigcatch = strtoull(procfile_lineword(ff, 0, 33), NULL, 10); + // p->wchan = strtoull(procfile_lineword(ff, 0, 34), NULL, 10); + // p->nswap = strtoull(procfile_lineword(ff, 0, 35), NULL, 10); + // p->cnswap = strtoull(procfile_lineword(ff, 0, 36), NULL, 10); + // p->exit_signal = atol(procfile_lineword(ff, 0, 37)); + // p->processor = atol(procfile_lineword(ff, 0, 38)); + // p->rt_priority = strtoul(procfile_lineword(ff, 0, 39), NULL, 10); + // p->policy = strtoul(procfile_lineword(ff, 0, 40), NULL, 10); + // p->delayacct_blkio_ticks = strtoull(procfile_lineword(ff, 0, 41), NULL, 10); + + if(enable_guest_charts) { + last = p->gtime_raw; + p->gtime_raw = strtoull(procfile_lineword(ff, 0, 42), NULL, 10); + p->gtime = (p->gtime_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + last = p->cgtime_raw; + p->cgtime_raw = strtoull(procfile_lineword(ff, 0, 43), NULL, 10); + p->cgtime = (p->cgtime_raw - last) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + if (show_guest_time || p->gtime || p->cgtime) { + p->utime -= (p->utime >= p->gtime) ? p->gtime : p->utime; + p->cutime -= (p->cutime >= p->cgtime) ? p->cgtime : p->cutime; + show_guest_time = 1; + } + } + + if(unlikely(debug || (p->target && p->target->debug))) + fprintf(stderr, "apps.plugin: READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu) VALUES: utime=%llu, stime=%llu, cutime=%llu, cstime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu, threads=%d\n", host_prefix, p->pid, p->comm, (p->target)?p->target->name:"UNSET", p->stat_collected_usec - p->last_stat_collected_usec, p->utime, p->stime, p->cutime, p->cstime, p->minflt, p->majflt, p->cminflt, p->cmajflt, p->num_threads); + + if(unlikely(global_iterations_counter == 1)) { + p->minflt = 0; + p->cminflt = 0; + p->majflt = 0; + p->cmajflt = 0; + p->utime = 0; + p->stime = 0; + p->gtime = 0; + p->cutime = 0; + p->cstime = 0; + p->cgtime = 0; + } + + return 1; + +cleanup: + p->minflt = 0; + p->cminflt = 0; + p->majflt = 0; + p->cmajflt = 0; + p->utime = 0; + p->stime = 0; + p->gtime = 0; + p->cutime = 0; + p->cstime = 0; + p->cgtime = 0; + p->num_threads = 0; + p->rss = 0; + return 0; } int read_proc_pid_statm(struct pid_stat *p) { - static procfile *ff = NULL; + static procfile *ff = NULL; + + if(unlikely(!p->statm_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/statm", host_prefix, p->pid); + p->statm_filename = strdupz(filename); + } + + ff = procfile_reopen(ff, p->statm_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); + if(unlikely(!ff)) goto cleanup; + + ff = procfile_readall(ff); + if(unlikely(!ff)) goto cleanup; + + file_counter++; + + p->statm_size = strtoull(procfile_lineword(ff, 0, 0), NULL, 10); + p->statm_resident = strtoull(procfile_lineword(ff, 0, 1), NULL, 10); + p->statm_share = strtoull(procfile_lineword(ff, 0, 2), NULL, 10); + p->statm_text = strtoull(procfile_lineword(ff, 0, 3), NULL, 10); + p->statm_lib = strtoull(procfile_lineword(ff, 0, 4), NULL, 10); + p->statm_data = strtoull(procfile_lineword(ff, 0, 5), NULL, 10); + p->statm_dirty = strtoull(procfile_lineword(ff, 0, 6), NULL, 10); + + return 1; + +cleanup: + p->statm_size = 0; + p->statm_resident = 0; + p->statm_share = 0; + p->statm_text = 0; + p->statm_lib = 0; + p->statm_data = 0; + p->statm_dirty = 0; + return 0; +} - char filename[FILENAME_MAX + 1]; +int read_proc_pid_io(struct pid_stat *p) { + static procfile *ff = NULL; + + if(unlikely(!p->io_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/io", host_prefix, p->pid); + p->io_filename = strdupz(filename); + } + + // open the file + ff = procfile_reopen(ff, p->io_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); + if(unlikely(!ff)) goto cleanup; + + ff = procfile_readall(ff); + if(unlikely(!ff)) goto cleanup; + + file_counter++; + + p->last_io_collected_usec = p->io_collected_usec; + p->io_collected_usec = time_usec(); + + unsigned long long last; + + last = p->io_logical_bytes_read_raw; + p->io_logical_bytes_read_raw = strtoull(procfile_lineword(ff, 0, 1), NULL, 10); + p->io_logical_bytes_read = (p->io_logical_bytes_read_raw - last) * (1000000ULL * RATES_DETAIL) / (p->io_collected_usec - p->last_io_collected_usec); + + last = p->io_logical_bytes_written_raw; + p->io_logical_bytes_written_raw = strtoull(procfile_lineword(ff, 1, 1), NULL, 10); + p->io_logical_bytes_written = (p->io_logical_bytes_written_raw - last) * (1000000ULL * RATES_DETAIL) / (p->io_collected_usec - p->last_io_collected_usec); + + last = p->io_read_calls_raw; + p->io_read_calls_raw = strtoull(procfile_lineword(ff, 2, 1), NULL, 10); + p->io_read_calls = (p->io_read_calls_raw - last) * (1000000ULL * RATES_DETAIL) / (p->io_collected_usec - p->last_io_collected_usec); + + last = p->io_write_calls_raw; + p->io_write_calls_raw = strtoull(procfile_lineword(ff, 3, 1), NULL, 10); + p->io_write_calls = (p->io_write_calls_raw - last) * (1000000ULL * RATES_DETAIL) / (p->io_collected_usec - p->last_io_collected_usec); + + last = p->io_storage_bytes_read_raw; + p->io_storage_bytes_read_raw = strtoull(procfile_lineword(ff, 4, 1), NULL, 10); + p->io_storage_bytes_read = (p->io_storage_bytes_read_raw - last) * (1000000ULL * RATES_DETAIL) / (p->io_collected_usec - p->last_io_collected_usec); + + last = p->io_storage_bytes_written_raw; + p->io_storage_bytes_written_raw = strtoull(procfile_lineword(ff, 5, 1), NULL, 10); + p->io_storage_bytes_written = (p->io_storage_bytes_written_raw - last) * (1000000ULL * RATES_DETAIL) / (p->io_collected_usec - p->last_io_collected_usec); + + last = p->io_cancelled_write_bytes_raw; + p->io_cancelled_write_bytes_raw = strtoull(procfile_lineword(ff, 6, 1), NULL, 10); + p->io_cancelled_write_bytes = (p->io_cancelled_write_bytes_raw - last) * (1000000ULL * RATES_DETAIL) / (p->io_collected_usec - p->last_io_collected_usec); + + if(unlikely(global_iterations_counter == 1)) { + p->io_logical_bytes_read = 0; + p->io_logical_bytes_written = 0; + p->io_read_calls = 0; + p->io_write_calls = 0; + p->io_storage_bytes_read = 0; + p->io_storage_bytes_written = 0; + p->io_cancelled_write_bytes = 0; + } + + return 1; + +cleanup: + p->io_logical_bytes_read = 0; + p->io_logical_bytes_written = 0; + p->io_read_calls = 0; + p->io_write_calls = 0; + p->io_storage_bytes_read = 0; + p->io_storage_bytes_written = 0; + p->io_cancelled_write_bytes = 0; + return 0; +} - snprintfz(filename, FILENAME_MAX, "%s/proc/%d/statm", host_prefix, p->pid); +unsigned long long global_utime = 0; +unsigned long long global_stime = 0; +unsigned long long global_gtime = 0; - ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); - if(!ff) return 1; +int read_proc_stat() { + static char filename[FILENAME_MAX + 1] = ""; + static procfile *ff = NULL; + static unsigned long long utime_raw = 0, stime_raw = 0, gtime_raw = 0, gntime_raw = 0, ntime_raw = 0, collected_usec = 0, last_collected_usec = 0; - ff = procfile_readall(ff); - if(!ff) { - // procfile_close(ff); - return 1; - } + if(unlikely(!ff)) { + snprintfz(filename, FILENAME_MAX, "%s/proc/stat", host_prefix); + ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) goto cleanup; + } - file_counter++; + ff = procfile_readall(ff); + if(unlikely(!ff)) goto cleanup; - p->statm_size = strtoull(procfile_lineword(ff, 0, 0), NULL, 10); - p->statm_resident = strtoull(procfile_lineword(ff, 0, 1), NULL, 10); - p->statm_share = strtoull(procfile_lineword(ff, 0, 2), NULL, 10); - p->statm_text = strtoull(procfile_lineword(ff, 0, 3), NULL, 10); - p->statm_lib = strtoull(procfile_lineword(ff, 0, 4), NULL, 10); - p->statm_data = strtoull(procfile_lineword(ff, 0, 5), NULL, 10); - p->statm_dirty = strtoull(procfile_lineword(ff, 0, 6), NULL, 10); + last_collected_usec = collected_usec; + collected_usec = time_usec(); - // procfile_close(ff); - return 0; -} + file_counter++; -int read_proc_pid_io(struct pid_stat *p) { - static procfile *ff = NULL; + unsigned long long last; + + last = utime_raw; + utime_raw = strtoull(procfile_lineword(ff, 0, 1), NULL, 10); + global_utime = (utime_raw - last) * (1000000ULL * RATES_DETAIL) / (collected_usec - last_collected_usec); + + // nice time, on user time + last = ntime_raw; + ntime_raw = strtoull(procfile_lineword(ff, 0, 2), NULL, 10); + global_utime += (ntime_raw - last) * (1000000ULL * RATES_DETAIL) / (collected_usec - last_collected_usec); - char filename[FILENAME_MAX + 1]; + last = stime_raw; + stime_raw = strtoull(procfile_lineword(ff, 0, 3), NULL, 10); + global_stime = (stime_raw - last) * (1000000ULL * RATES_DETAIL) / (collected_usec - last_collected_usec); - snprintfz(filename, FILENAME_MAX, "%s/proc/%d/io", host_prefix, p->pid); + last = gtime_raw; + gtime_raw = strtoull(procfile_lineword(ff, 0, 10), NULL, 10); + global_gtime = (gtime_raw - last) * (1000000ULL * RATES_DETAIL) / (collected_usec - last_collected_usec); - ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); - if(!ff) return 1; + if(enable_guest_charts) { + // guest nice time, on guest time + last = gntime_raw; + gntime_raw = strtoull(procfile_lineword(ff, 0, 11), NULL, 10); + global_gtime += (gntime_raw - last) * (1000000ULL * RATES_DETAIL) / (collected_usec - last_collected_usec); - ff = procfile_readall(ff); - if(!ff) { - // procfile_close(ff); - return 1; - } + // remove guest time from user time + global_utime -= (global_utime > global_gtime) ? global_gtime : global_utime; + } - file_counter++; + if(unlikely(global_iterations_counter == 1)) { + global_utime = 0; + global_stime = 0; + global_gtime = 0; + } - p->io_logical_bytes_read = strtoull(procfile_lineword(ff, 0, 1), NULL, 10); - p->io_logical_bytes_written = strtoull(procfile_lineword(ff, 1, 1), NULL, 10); - p->io_read_calls = strtoull(procfile_lineword(ff, 2, 1), NULL, 10); - p->io_write_calls = strtoull(procfile_lineword(ff, 3, 1), NULL, 10); - p->io_storage_bytes_read = strtoull(procfile_lineword(ff, 4, 1), NULL, 10); - p->io_storage_bytes_written = strtoull(procfile_lineword(ff, 5, 1), NULL, 10); - p->io_cancelled_write_bytes = strtoull(procfile_lineword(ff, 6, 1), NULL, 10); + return 1; - // procfile_close(ff); - return 0; +cleanup: + global_utime = 0; + global_stime = 0; + global_gtime = 0; + return 0; } @@ -967,15 +958,15 @@ int read_proc_pid_io(struct pid_stat *p) { #define FILE_DESCRIPTORS_INCREASE_STEP 100 struct file_descriptor { - avl avl; + avl avl; #ifdef NETDATA_INTERNAL_CHECKS - uint32_t magic; + uint32_t magic; #endif /* NETDATA_INTERNAL_CHECKS */ - uint32_t hash; - const char *name; - int type; - int count; - int pos; + uint32_t hash; + const char *name; + int type; + int count; + int pos; } *all_files = NULL; int all_files_len = 0; @@ -983,38 +974,38 @@ int all_files_size = 0; int file_descriptor_compare(void* a, void* b) { #ifdef NETDATA_INTERNAL_CHECKS - if(((struct file_descriptor *)a)->magic != 0x0BADCAFE || ((struct file_descriptor *)b)->magic != 0x0BADCAFE) - error("Corrupted index data detected. Please report this."); + if(((struct file_descriptor *)a)->magic != 0x0BADCAFE || ((struct file_descriptor *)b)->magic != 0x0BADCAFE) + error("Corrupted index data detected. Please report this."); #endif /* NETDATA_INTERNAL_CHECKS */ - if(((struct file_descriptor *)a)->hash < ((struct file_descriptor *)b)->hash) - return -1; + if(((struct file_descriptor *)a)->hash < ((struct file_descriptor *)b)->hash) + return -1; - else if(((struct file_descriptor *)a)->hash > ((struct file_descriptor *)b)->hash) - return 1; + else if(((struct file_descriptor *)a)->hash > ((struct file_descriptor *)b)->hash) + return 1; - else - return strcmp(((struct file_descriptor *)a)->name, ((struct file_descriptor *)b)->name); + else + return strcmp(((struct file_descriptor *)a)->name, ((struct file_descriptor *)b)->name); } int file_descriptor_iterator(avl *a) { if(a) {}; return 0; } avl_tree all_files_index = { - NULL, - file_descriptor_compare + NULL, + file_descriptor_compare }; static struct file_descriptor *file_descriptor_find(const char *name, uint32_t hash) { - struct file_descriptor tmp; - tmp.hash = (hash)?hash:simple_hash(name); - tmp.name = name; - tmp.count = 0; - tmp.pos = 0; + struct file_descriptor tmp; + tmp.hash = (hash)?hash:simple_hash(name); + tmp.name = name; + tmp.count = 0; + tmp.pos = 0; #ifdef NETDATA_INTERNAL_CHECKS - tmp.magic = 0x0BADCAFE; + tmp.magic = 0x0BADCAFE; #endif /* NETDATA_INTERNAL_CHECKS */ - return (struct file_descriptor *)avl_search(&all_files_index, (avl *) &tmp); + return (struct file_descriptor *)avl_search(&all_files_index, (avl *) &tmp); } #define file_descriptor_add(fd) avl_insert(&all_files_index, (avl *)(fd)) @@ -1032,223 +1023,526 @@ static struct file_descriptor *file_descriptor_find(const char *name, uint32_t h void file_descriptor_not_used(int id) { - if(id > 0 && id < all_files_size) { + if(id > 0 && id < all_files_size) { #ifdef NETDATA_INTERNAL_CHECKS - if(all_files[id].magic != 0x0BADCAFE) { - error("Ignoring request to remove empty file id %d.", id); - return; - } + if(all_files[id].magic != 0x0BADCAFE) { + error("Ignoring request to remove empty file id %d.", id); + return; + } #endif /* NETDATA_INTERNAL_CHECKS */ - if(debug) fprintf(stderr, "apps.plugin: decreasing slot %d (count = %d).\n", id, all_files[id].count); + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: decreasing slot %d (count = %d).\n", id, all_files[id].count); - if(all_files[id].count > 0) { - all_files[id].count--; + if(all_files[id].count > 0) { + all_files[id].count--; - if(!all_files[id].count) { - if(debug) fprintf(stderr, "apps.plugin: >> slot %d is empty.\n", id); - file_descriptor_remove(&all_files[id]); + if(!all_files[id].count) { + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> slot %d is empty.\n", id); + + file_descriptor_remove(&all_files[id]); #ifdef NETDATA_INTERNAL_CHECKS - all_files[id].magic = 0x00000000; + all_files[id].magic = 0x00000000; #endif /* NETDATA_INTERNAL_CHECKS */ - all_files_len--; - } - } - else - error("Request to decrease counter of fd %d (%s), while the use counter is 0", id, all_files[id].name); - } - else error("Request to decrease counter of fd %d, which is outside the array size (1 to %d)", id, all_files_size); + all_files_len--; + } + } + else + error("Request to decrease counter of fd %d (%s), while the use counter is 0", id, all_files[id].name); + } + else error("Request to decrease counter of fd %d, which is outside the array size (1 to %d)", id, all_files_size); } int file_descriptor_find_or_add(const char *name) { - static int last_pos = 0; - uint32_t hash = simple_hash(name); - - if(debug) fprintf(stderr, "apps.plugin: adding or finding name '%s' with hash %u\n", name, hash); - - struct file_descriptor *fd = file_descriptor_find(name, hash); - if(fd) { - // found - if(debug) fprintf(stderr, "apps.plugin: >> found on slot %d\n", fd->pos); - fd->count++; - return fd->pos; - } - // not found - - // check we have enough memory to add it - if(!all_files || all_files_len == all_files_size) { - void *old = all_files; - int i; - - // there is no empty slot - if(debug) fprintf(stderr, "apps.plugin: extending fd array to %d entries\n", all_files_size + FILE_DESCRIPTORS_INCREASE_STEP); - all_files = realloc(all_files, (all_files_size + FILE_DESCRIPTORS_INCREASE_STEP) * sizeof(struct file_descriptor)); - - // if the address changed, we have to rebuild the index - // since all pointers are now invalid - if(old && old != (void *)all_files) { - if(debug) fprintf(stderr, "apps.plugin: >> re-indexing.\n"); - all_files_index.root = NULL; - for(i = 0; i < all_files_size; i++) { - if(!all_files[i].count) continue; - file_descriptor_add(&all_files[i]); - } - if(debug) fprintf(stderr, "apps.plugin: >> re-indexing done.\n"); - } - - for(i = all_files_size; i < (all_files_size + FILE_DESCRIPTORS_INCREASE_STEP); i++) { - all_files[i].count = 0; - all_files[i].name = NULL; + static int last_pos = 0; + uint32_t hash = simple_hash(name); + + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: adding or finding name '%s' with hash %u\n", name, hash); + + struct file_descriptor *fd = file_descriptor_find(name, hash); + if(fd) { + // found + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> found on slot %d\n", fd->pos); + + fd->count++; + return fd->pos; + } + // not found + + // check we have enough memory to add it + if(!all_files || all_files_len == all_files_size) { + void *old = all_files; + int i; + + // there is no empty slot + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: extending fd array to %d entries\n", all_files_size + FILE_DESCRIPTORS_INCREASE_STEP); + + all_files = reallocz(all_files, (all_files_size + FILE_DESCRIPTORS_INCREASE_STEP) * sizeof(struct file_descriptor)); + + // if the address changed, we have to rebuild the index + // since all pointers are now invalid + if(old && old != (void *)all_files) { + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> re-indexing.\n"); + + all_files_index.root = NULL; + for(i = 0; i < all_files_size; i++) { + if(!all_files[i].count) continue; + file_descriptor_add(&all_files[i]); + } + + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> re-indexing done.\n"); + } + + for(i = all_files_size; i < (all_files_size + FILE_DESCRIPTORS_INCREASE_STEP); i++) { + all_files[i].count = 0; + all_files[i].name = NULL; #ifdef NETDATA_INTERNAL_CHECKS - all_files[i].magic = 0x00000000; + all_files[i].magic = 0x00000000; #endif /* NETDATA_INTERNAL_CHECKS */ - all_files[i].pos = i; - } + all_files[i].pos = i; + } - if(!all_files_size) all_files_len = 1; - all_files_size += FILE_DESCRIPTORS_INCREASE_STEP; - } + if(!all_files_size) all_files_len = 1; + all_files_size += FILE_DESCRIPTORS_INCREASE_STEP; + } - if(debug) fprintf(stderr, "apps.plugin: >> searching for empty slot.\n"); + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> searching for empty slot.\n"); - // search for an empty slot - int i, c; - for(i = 0, c = last_pos ; i < all_files_size ; i++, c++) { - if(c >= all_files_size) c = 0; - if(c == 0) continue; + // search for an empty slot + int i, c; + for(i = 0, c = last_pos ; i < all_files_size ; i++, c++) { + if(c >= all_files_size) c = 0; + if(c == 0) continue; - if(!all_files[c].count) { - if(debug) fprintf(stderr, "apps.plugin: >> Examining slot %d.\n", c); + if(!all_files[c].count) { + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> Examining slot %d.\n", c); #ifdef NETDATA_INTERNAL_CHECKS - if(all_files[c].magic == 0x0BADCAFE && all_files[c].name && file_descriptor_find(all_files[c].name, all_files[c].hash)) - error("fd on position %d is not cleared properly. It still has %s in it.\n", c, all_files[c].name); + if(all_files[c].magic == 0x0BADCAFE && all_files[c].name && file_descriptor_find(all_files[c].name, all_files[c].hash)) + error("fd on position %d is not cleared properly. It still has %s in it.\n", c, all_files[c].name); #endif /* NETDATA_INTERNAL_CHECKS */ - if(debug) fprintf(stderr, "apps.plugin: >> %s fd position %d for %s (last name: %s)\n", all_files[c].name?"re-using":"using", c, name, all_files[c].name); - if(all_files[c].name) free((void *)all_files[c].name); - all_files[c].name = NULL; - last_pos = c; - break; - } - } - if(i == all_files_size) { - fatal("We should find an empty slot, but there isn't any"); - exit(1); - } - if(debug) fprintf(stderr, "apps.plugin: >> updating slot %d.\n", c); - - all_files_len++; - - // else we have an empty slot in 'c' - - int type; - if(name[0] == '/') type = FILETYPE_FILE; - else if(strncmp(name, "pipe:", 5) == 0) type = FILETYPE_PIPE; - else if(strncmp(name, "socket:", 7) == 0) type = FILETYPE_SOCKET; - else if(strcmp(name, "anon_inode:inotify") == 0 || strcmp(name, "inotify") == 0) type = FILETYPE_INOTIFY; - else if(strcmp(name, "anon_inode:[eventfd]") == 0) type = FILETYPE_EVENTFD; - else if(strcmp(name, "anon_inode:[eventpoll]") == 0) type = FILETYPE_EVENTPOLL; - else if(strcmp(name, "anon_inode:[timerfd]") == 0) type = FILETYPE_TIMERFD; - else if(strcmp(name, "anon_inode:[signalfd]") == 0) type = FILETYPE_SIGNALFD; - else if(strncmp(name, "anon_inode:", 11) == 0) { - if(debug) fprintf(stderr, "apps.plugin: FIXME: unknown anonymous inode: %s\n", name); - type = FILETYPE_OTHER; - } - else { - if(debug) fprintf(stderr, "apps.plugin: FIXME: cannot understand linkname: %s\n", name); - type = FILETYPE_OTHER; - } - - all_files[c].name = strdup(name); - all_files[c].hash = hash; - all_files[c].type = type; - all_files[c].pos = c; - all_files[c].count = 1; + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> %s fd position %d for %s (last name: %s)\n", all_files[c].name?"re-using":"using", c, name, all_files[c].name); + + if(all_files[c].name) freez((void *)all_files[c].name); + all_files[c].name = NULL; + last_pos = c; + break; + } + } + if(i == all_files_size) { + fatal("We should find an empty slot, but there isn't any"); + exit(1); + } + + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: >> updating slot %d.\n", c); + + all_files_len++; + + // else we have an empty slot in 'c' + + int type; + if(name[0] == '/') type = FILETYPE_FILE; + else if(strncmp(name, "pipe:", 5) == 0) type = FILETYPE_PIPE; + else if(strncmp(name, "socket:", 7) == 0) type = FILETYPE_SOCKET; + else if(strcmp(name, "anon_inode:inotify") == 0 || strcmp(name, "inotify") == 0) type = FILETYPE_INOTIFY; + else if(strcmp(name, "anon_inode:[eventfd]") == 0) type = FILETYPE_EVENTFD; + else if(strcmp(name, "anon_inode:[eventpoll]") == 0) type = FILETYPE_EVENTPOLL; + else if(strcmp(name, "anon_inode:[timerfd]") == 0) type = FILETYPE_TIMERFD; + else if(strcmp(name, "anon_inode:[signalfd]") == 0) type = FILETYPE_SIGNALFD; + else if(strncmp(name, "anon_inode:", 11) == 0) { + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: FIXME: unknown anonymous inode: %s\n", name); + + type = FILETYPE_OTHER; + } + else { + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: FIXME: cannot understand linkname: %s\n", name); + + type = FILETYPE_OTHER; + } + + all_files[c].name = strdupz(name); + all_files[c].hash = hash; + all_files[c].type = type; + all_files[c].pos = c; + all_files[c].count = 1; #ifdef NETDATA_INTERNAL_CHECKS - all_files[c].magic = 0x0BADCAFE; + all_files[c].magic = 0x0BADCAFE; #endif /* NETDATA_INTERNAL_CHECKS */ - file_descriptor_add(&all_files[c]); + file_descriptor_add(&all_files[c]); - if(debug) fprintf(stderr, "apps.plugin: using fd position %d (name: %s)\n", c, all_files[c].name); + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: using fd position %d (name: %s)\n", c, all_files[c].name); - return c; + return c; } int read_pid_file_descriptors(struct pid_stat *p) { - char dirname[FILENAME_MAX+1]; - - snprintfz(dirname, FILENAME_MAX, "%s/proc/%d/fd", host_prefix, p->pid); - DIR *fds = opendir(dirname); - if(fds) { - int c; - struct dirent *de; - char fdname[FILENAME_MAX + 1]; - char linkname[FILENAME_MAX + 1]; - - // make the array negative - for(c = 0 ; c < p->fds_size ; c++) - p->fds[c] = -p->fds[c]; - - while((de = readdir(fds))) { - if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) - continue; - - // check if the fds array is small - int fdid = atoi(de->d_name); - if(fdid < 0) continue; - if(fdid >= p->fds_size) { - // it is small, extend it - 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) { - fatal("Cannot re-allocate fds for %s", p->comm); - break; - } - - // and initialize it - for(c = p->fds_size ; c < (fdid + 100) ; c++) p->fds[c] = 0; - p->fds_size = fdid + 100; - } - - if(p->fds[fdid] == 0) { - // we don't know this fd, get it - - sprintf(fdname, "%s/proc/%d/fd/%s", host_prefix, p->pid, de->d_name); - ssize_t l = readlink(fdname, linkname, FILENAME_MAX); - if(l == -1) { - if(debug || (p->target && p->target->debug)) { - if(debug || (p->target && p->target->debug)) - error("Cannot read link %s", fdname); - } - continue; - } - linkname[l] = '\0'; - file_counter++; - - // if another process already has this, we will get - // the same id - p->fds[fdid] = file_descriptor_find_or_add(linkname); - } - - // else make it positive again, we need it - // of course, the actual file may have changed, but we don't care so much - // FIXME: we could compare the inode as returned by readdir direct structure - else p->fds[fdid] = -p->fds[fdid]; - } - closedir(fds); - - // remove all the negative file descriptors - for(c = 0 ; c < p->fds_size ; c++) if(p->fds[c] < 0) { - file_descriptor_not_used(-p->fds[c]); - p->fds[c] = 0; - } - } - else return 1; - - return 0; + char dirname[FILENAME_MAX+1]; + + snprintfz(dirname, FILENAME_MAX, "%s/proc/%d/fd", host_prefix, p->pid); + DIR *fds = opendir(dirname); + if(fds) { + int c; + struct dirent *de; + char fdname[FILENAME_MAX + 1]; + char linkname[FILENAME_MAX + 1]; + + // make the array negative + for(c = 0 ; c < p->fds_size ; c++) + p->fds[c] = -p->fds[c]; + + while((de = readdir(fds))) { + if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + + // check if the fds array is small + int fdid = atoi(de->d_name); + if(fdid < 0) continue; + if(fdid >= p->fds_size) { + // it is small, extend it + if(unlikely(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 = reallocz(p->fds, (fdid + 100) * sizeof(int)); + if(!p->fds) { + fatal("Cannot re-allocate fds for %s", p->comm); + break; + } + + // and initialize it + for(c = p->fds_size ; c < (fdid + 100) ; c++) p->fds[c] = 0; + p->fds_size = fdid + 100; + } + + if(p->fds[fdid] == 0) { + // we don't know this fd, get it + + sprintf(fdname, "%s/proc/%d/fd/%s", host_prefix, p->pid, de->d_name); + ssize_t l = readlink(fdname, linkname, FILENAME_MAX); + if(l == -1) { + if(debug || (p->target && p->target->debug)) { + if(debug || (p->target && p->target->debug)) + error("Cannot read link %s", fdname); + } + continue; + } + linkname[l] = '\0'; + file_counter++; + + // if another process already has this, we will get + // the same id + p->fds[fdid] = file_descriptor_find_or_add(linkname); + } + + // else make it positive again, we need it + // of course, the actual file may have changed, but we don't care so much + // FIXME: we could compare the inode as returned by readdir dirent structure + else p->fds[fdid] = -p->fds[fdid]; + } + closedir(fds); + + // remove all the negative file descriptors + for(c = 0 ; c < p->fds_size ; c++) if(p->fds[c] < 0) { + file_descriptor_not_used(-p->fds[c]); + p->fds[c] = 0; + } + } + else return 0; + + return 1; +} + +// ---------------------------------------------------------------------------- + +int print_process_and_parents(struct pid_stat *p, unsigned long long time) { + char *prefix = "\\_ "; + int indent = 0; + + if(p->parent) + indent = print_process_and_parents(p->parent, p->stat_collected_usec); + else + prefix = " > "; + + char buffer[indent + 1]; + int i; + + for(i = 0; i < indent ;i++) buffer[i] = ' '; + buffer[i] = '\0'; + + fprintf(stderr, " %s %s%s (%d %s %lld" + , buffer + , prefix + , p->comm + , p->pid + , p->updated?"running":"exited" + , (long long)p->stat_collected_usec - (long long)time + ); + + if(p->utime) fprintf(stderr, " utime=%llu", p->utime); + if(p->stime) fprintf(stderr, " stime=%llu", p->stime); + if(p->gtime) fprintf(stderr, " gtime=%llu", p->gtime); + if(p->cutime) fprintf(stderr, " cutime=%llu", p->cutime); + if(p->cstime) fprintf(stderr, " cstime=%llu", p->cstime); + if(p->cgtime) fprintf(stderr, " cgtime=%llu", p->cgtime); + if(p->minflt) fprintf(stderr, " minflt=%llu", p->minflt); + if(p->cminflt) fprintf(stderr, " cminflt=%llu", p->cminflt); + if(p->majflt) fprintf(stderr, " majflt=%llu", p->majflt); + if(p->cmajflt) fprintf(stderr, " cmajflt=%llu", p->cmajflt); + fprintf(stderr, ")\n"); + + return indent + 1; +} + +void print_process_tree(struct pid_stat *p, char *msg) { + log_date(stderr); + fprintf(stderr, "%s: process %s (%d, %s) with parents:\n", msg, p->comm, p->pid, p->updated?"running":"exited"); + print_process_and_parents(p, p->stat_collected_usec); +} + +void find_lost_child_debug(struct pid_stat *pe, unsigned long long lost, int type) { + int found = 0; + struct pid_stat *p = NULL; + + for(p = root_of_pids; p ; p = p->next) { + if(p == pe) continue; + + switch(type) { + case 1: + if(p->cminflt > lost) { + fprintf(stderr, " > process %d (%s) could use the lost exited child minflt %llu of process %d (%s)\n", p->pid, p->comm, lost, pe->pid, pe->comm); + found++; + } + break; + + case 2: + if(p->cmajflt > lost) { + fprintf(stderr, " > process %d (%s) could use the lost exited child majflt %llu of process %d (%s)\n", p->pid, p->comm, lost, pe->pid, pe->comm); + found++; + } + break; + + case 3: + if(p->cutime > lost) { + fprintf(stderr, " > process %d (%s) could use the lost exited child utime %llu of process %d (%s)\n", p->pid, p->comm, lost, pe->pid, pe->comm); + found++; + } + break; + + case 4: + if(p->cstime > lost) { + fprintf(stderr, " > process %d (%s) could use the lost exited child stime %llu of process %d (%s)\n", p->pid, p->comm, lost, pe->pid, pe->comm); + found++; + } + break; + + case 5: + if(p->cgtime > lost) { + fprintf(stderr, " > process %d (%s) could use the lost exited child gtime %llu of process %d (%s)\n", p->pid, p->comm, lost, pe->pid, pe->comm); + found++; + } + break; + } + } + + if(!found) { + switch(type) { + case 1: + fprintf(stderr, " > cannot find any process to use the lost exited child minflt %llu of process %d (%s)\n", lost, pe->pid, pe->comm); + break; + + case 2: + fprintf(stderr, " > cannot find any process to use the lost exited child majflt %llu of process %d (%s)\n", lost, pe->pid, pe->comm); + break; + + case 3: + fprintf(stderr, " > cannot find any process to use the lost exited child utime %llu of process %d (%s)\n", lost, pe->pid, pe->comm); + break; + + case 4: + fprintf(stderr, " > cannot find any process to use the lost exited child stime %llu of process %d (%s)\n", lost, pe->pid, pe->comm); + break; + + case 5: + fprintf(stderr, " > cannot find any process to use the lost exited child gtime %llu of process %d (%s)\n", lost, pe->pid, pe->comm); + break; + } + } +} + +unsigned long long remove_exited_child_from_parent(unsigned long long *field, unsigned long long *pfield) { + unsigned long long absorbed = 0; + + if(*field > *pfield) { + absorbed += *pfield; + *field -= *pfield; + *pfield = 0; + } + else { + absorbed += *field; + *pfield -= *field; + *field = 0; + } + + return absorbed; +} + +void process_exited_processes() { + struct pid_stat *p; + + for(p = root_of_pids; p ; p = p->next) { + if(p->updated || !p->stat_collected_usec) + continue; + + struct pid_stat *pp = p->parent; + + unsigned long long utime = (p->utime_raw + p->cutime_raw) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + unsigned long long stime = (p->stime_raw + p->cstime_raw) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + unsigned long long gtime = (p->gtime_raw + p->cgtime_raw) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + unsigned long long minflt = (p->minflt_raw + p->cminflt_raw) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + unsigned long long majflt = (p->majflt_raw + p->cmajflt_raw) * (1000000ULL * RATES_DETAIL) / (p->stat_collected_usec - p->last_stat_collected_usec); + + if(utime + stime + gtime + minflt + majflt == 0) + continue; + + if(unlikely(debug)) { + log_date(stderr); + fprintf(stderr, "Absorb %s (%d %s total resources: utime=%llu stime=%llu gtime=%llu minflt=%llu majflt=%llu)\n" + , p->comm + , p->pid + , p->updated?"running":"exited" + , utime + , stime + , gtime + , minflt + , majflt + ); + print_process_tree(p, "Searching parents"); + } + + for(pp = p->parent; pp ; pp = pp->parent) { + if(!pp->updated) continue; + + unsigned long long absorbed; + absorbed = remove_exited_child_from_parent(&utime, &pp->cutime); + if(unlikely(debug && absorbed)) + fprintf(stderr, " > process %s (%d %s) absorbed %llu utime (remaining: %llu)\n", pp->comm, pp->pid, pp->updated?"running":"exited", absorbed, utime); + + absorbed = remove_exited_child_from_parent(&stime, &pp->cstime); + if(unlikely(debug && absorbed)) + fprintf(stderr, " > process %s (%d %s) absorbed %llu stime (remaining: %llu)\n", pp->comm, pp->pid, pp->updated?"running":"exited", absorbed, stime); + + absorbed = remove_exited_child_from_parent(>ime, &pp->cgtime); + if(unlikely(debug && absorbed)) + fprintf(stderr, " > process %s (%d %s) absorbed %llu gtime (remaining: %llu)\n", pp->comm, pp->pid, pp->updated?"running":"exited", absorbed, gtime); + + absorbed = remove_exited_child_from_parent(&minflt, &pp->cminflt); + if(unlikely(debug && absorbed)) + fprintf(stderr, " > process %s (%d %s) absorbed %llu minflt (remaining: %llu)\n", pp->comm, pp->pid, pp->updated?"running":"exited", absorbed, minflt); + + absorbed = remove_exited_child_from_parent(&majflt, &pp->cmajflt); + if(unlikely(debug && absorbed)) + fprintf(stderr, " > process %s (%d %s) absorbed %llu majflt (remaining: %llu)\n", pp->comm, pp->pid, pp->updated?"running":"exited", absorbed, majflt); + } + + if(unlikely(utime + stime + gtime + minflt + majflt > 0)) { + if(unlikely(debug)) { + if(utime) find_lost_child_debug(p, utime, 3); + if(stime) find_lost_child_debug(p, stime, 4); + if(gtime) find_lost_child_debug(p, gtime, 5); + if(minflt) find_lost_child_debug(p, minflt, 1); + if(majflt) find_lost_child_debug(p, majflt, 2); + } + + p->keep = 1; + + if(unlikely(debug)) + fprintf(stderr, " > remaining resources - KEEP - for another loop: %s (%d %s total resources: utime=%llu stime=%llu gtime=%llu minflt=%llu majflt=%llu)\n" + , p->comm + , p->pid + , p->updated?"running":"exited" + , utime + , stime + , gtime + , minflt + , majflt + ); + + for(pp = p->parent; pp ; pp = pp->parent) { + if(pp->updated) break; + pp->keep = 1; + + if(unlikely(debug)) + fprintf(stderr, " > - KEEP - parent for another loop: %s (%d %s)\n" + , pp->comm + , pp->pid + , pp->updated?"running":"exited" + ); + } + + p->utime_raw = utime * (p->stat_collected_usec - p->last_stat_collected_usec) / (1000000ULL * RATES_DETAIL); + p->stime_raw = stime * (p->stat_collected_usec - p->last_stat_collected_usec) / (1000000ULL * RATES_DETAIL); + p->gtime_raw = gtime * (p->stat_collected_usec - p->last_stat_collected_usec) / (1000000ULL * RATES_DETAIL); + p->minflt_raw = minflt * (p->stat_collected_usec - p->last_stat_collected_usec) / (1000000ULL * RATES_DETAIL); + p->majflt_raw = majflt * (p->stat_collected_usec - p->last_stat_collected_usec) / (1000000ULL * RATES_DETAIL); + p->cutime_raw = p->cstime_raw = p->cgtime_raw = p->cminflt_raw = p->cmajflt_raw = 0; + + if(unlikely(debug)) + fprintf(stderr, "\n"); + } + else if(unlikely(debug)) { + fprintf(stderr, " > totally absorbed - DONE - %s (%d %s)\n" + , p->comm + , p->pid + , p->updated?"running":"exited" + ); + } + } +} + +void link_all_processes_to_their_parents(void) { + struct pid_stat *p, *pp; + + // link all children to their parents + // and update children count on parents + for(p = root_of_pids; p ; p = p->next) { + // for each process found + + p->sortlist = 0; + p->parent = NULL; + + if(unlikely(!p->ppid)) { + p->parent = NULL; + continue; + } + + pp = all_pids[p->ppid]; + if(likely(pp)) { + p->parent = pp; + pp->children_count++; + + if(unlikely(debug || (p->target && p->target->debug))) + fprintf(stderr, "apps.plugin: \tchild %d (%s, %s) on target '%s' has parent %d (%s, %s). Parent: utime=%llu, stime=%llu, gtime=%llu, minflt=%llu, majflt=%llu, cutime=%llu, cstime=%llu, cgtime=%llu, cminflt=%llu, cmajflt=%llu\n", p->pid, p->comm, p->updated?"running":"exited", (p->target)?p->target->name:"UNSET", pp->pid, pp->comm, pp->updated?"running":"exited", pp->utime, pp->stime, pp->gtime, pp->minflt, pp->majflt, pp->cutime, pp->cstime, pp->cgtime, pp->cminflt, pp->cmajflt); + } + else { + p->parent = NULL; + error("pid %d %s states parent %d, but the later does not exist.", p->pid, p->comm, p->ppid); + } + } } // ---------------------------------------------------------------------------- @@ -1269,206 +1563,224 @@ int read_pid_file_descriptors(struct pid_stat *p) { // to avoid filling up all disk space // if debug is enabled, all errors are printed -int collect_data_for_all_processes_from_proc(void) -{ - char dirname[FILENAME_MAX + 1]; +static int compar_pid(const void *pid1, const void *pid2) { + + struct pid_stat *p1 = all_pids[*((pid_t *)pid1)]; + struct pid_stat *p2 = all_pids[*((pid_t *)pid2)]; - snprintfz(dirname, FILENAME_MAX, "%s/proc", host_prefix); - DIR *dir = opendir(dirname); - if(!dir) return 0; + if(p1->sortlist > p2->sortlist) + return -1; + else + return 1; +} - struct dirent *file = NULL; - struct pid_stat *p = NULL; +static inline int managed_log(struct pid_stat *p, uint32_t log, int status) { + if(unlikely(!status)) { + // error("command failed log %u, errno %d", log, errno); + + if(unlikely(debug || errno != ENOENT)) { + if(unlikely(debug || !(p->log_thrown & log))) { + p->log_thrown |= log; + switch(log) { + case PID_LOG_IO: + error("Cannot process %s/proc/%d/io (command '%s')", host_prefix, p->pid, p->comm); + break; + + case PID_LOG_STATM: + error("Cannot process %s/proc/%d/statm (command '%s')", host_prefix, p->pid, p->comm); + break; + + case PID_LOG_CMDLINE: + error("Cannot process %s/proc/%d/cmdline (command '%s')", host_prefix, p->pid, p->comm); + break; + + case PID_LOG_FDS: + error("Cannot process entries in %s/proc/%d/fd (command '%s')", host_prefix, p->pid, p->comm); + break; + + case PID_LOG_STAT: + break; + + default: + error("unhandled error for pid %d, command '%s'", p->pid, p->comm); + break; + } + } + } + errno = 0; + } + else if(unlikely(p->log_thrown & log)) { + // error("unsetting log %u on pid %d", log, p->pid); + p->log_thrown &= ~log; + } + + return status; +} - // mark them all as un-updated - all_pids_count = 0; - for(p = root_of_pids; p ; p = p->next) { - all_pids_count++; - p->parent = NULL; - p->updated = 0; - p->children_count = 0; - p->merged = 0; - p->new_entry = 0; +void collect_data_for_pid(pid_t pid) { + if(unlikely(pid <= 0 || pid > pid_max)) { + error("Invalid pid %d read (expected 1 to %d). Ignoring process.", pid, pid_max); + return; + } - p->last_minflt = p->minflt; - p->last_cminflt = p->cminflt; - p->last_majflt = p->majflt; - p->last_cmajflt = p->cmajflt; - p->last_utime = p->utime; - p->last_stime = p->stime; - p->last_cutime = p->cutime; - p->last_cstime = p->cstime; - - p->last_io_logical_bytes_read = p->io_logical_bytes_read; - p->last_io_logical_bytes_written = p->io_logical_bytes_written; - p->last_io_read_calls = p->io_read_calls; - p->last_io_write_calls = p->io_write_calls; - p->last_io_storage_bytes_read = p->io_storage_bytes_read; - p->last_io_storage_bytes_written = p->io_storage_bytes_written; - p->last_io_cancelled_write_bytes = p->io_cancelled_write_bytes; - } - - while((file = readdir(dir))) { - char *endptr = file->d_name; - pid_t pid = (pid_t) strtoul(file->d_name, &endptr, 10); - - // make sure we read a valid number - if(unlikely(pid <= 0 || pid > pid_max || endptr == file->d_name || *endptr != '\0')) - continue; - - p = get_pid_entry(pid); - if(unlikely(!p)) continue; - - - // -------------------------------------------------------------------- - // /proc/<pid>/stat - - if(unlikely(read_proc_pid_stat(p))) { - error("Cannot process %s/proc/%d/stat", host_prefix, pid); - - // there is no reason to proceed if we cannot get its status - continue; - } - - // check its parent pid - if(unlikely(p->ppid < 0 || p->ppid > pid_max)) { - error("Pid %d states invalid parent pid %d. Using 0.", pid, p->ppid); - - p->ppid = 0; - } - - // -------------------------------------------------------------------- - // /proc/<pid>/cmdline - - if(proc_pid_cmdline_is_needed) { - if(unlikely(read_proc_pid_cmdline(p))) { - error("Cannot process %s/proc/%d/cmdline", host_prefix, pid); - } - } - - // -------------------------------------------------------------------- - // /proc/<pid>/statm - - if(unlikely(read_proc_pid_statm(p))) { - error("Cannot process %s/proc/%d/statm", host_prefix, pid); - - // there is no reason to proceed if we cannot get its memory status - continue; - } - - - // -------------------------------------------------------------------- - // /proc/<pid>/io - - if(unlikely(read_proc_pid_io(p))) { - error("Cannot process %s/proc/%d/io", host_prefix, pid); - - // on systems without /proc/X/io - // allow proceeding without I/O information - // continue; - } - - // -------------------------------------------------------------------- - // <pid> ownership - - if(unlikely(read_proc_pid_ownership(p))) { - error("Cannot stat %s/proc/%d", host_prefix, pid); - } - - // -------------------------------------------------------------------- - // link it - - // check if it is target - // we do this only once, the first time this pid is loaded - if(unlikely(p->new_entry)) { - if(debug) fprintf(stderr, "apps.plugin: \tJust added %s\n", p->comm); - uint32_t hash = simple_hash(p->comm); - size_t pclen = strlen(p->comm); - - struct target *w; - for(w = apps_groups_root_target; w ; w = w->next) { - // if(debug || (p->target && p->target->debug)) fprintf(stderr, "apps.plugin: \t\tcomparing '%s' with '%s'\n", w->compare, p->comm); - - // find it - 4 cases: - // 1. the target is not a pattern - // 2. the target has the prefix - // 3. the target has the suffix - // 4. the target is something inside cmdline - if( (!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm)) - || (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen)) - || (!w->starts_with && w->ends_with && pclen >= w->comparelen && !strcmp(w->compare, &p->comm[pclen - w->comparelen])) - || (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && strstr(p->cmdline, w->compare)) - ) { - if(w->target) p->target = w->target; - else p->target = w; + struct pid_stat *p = get_pid_entry(pid); + if(unlikely(!p || p->read)) return; + p->read = 1; - if(debug || (p->target && p->target->debug)) - fprintf(stderr, "apps.plugin: \t\t%s linked to target %s\n", p->comm, p->target->name); - } - } - } + // fprintf(stderr, "Reading process %d (%s), sortlist %d\n", p->pid, p->comm, p->sortlist); - // -------------------------------------------------------------------- - // /proc/<pid>/fd + // -------------------------------------------------------------------- + // /proc/<pid>/stat - if(unlikely(read_pid_file_descriptors(p))) { - error("Cannot process entries in %s/proc/%d/fd", host_prefix, pid); - } + if(unlikely(!managed_log(p, PID_LOG_STAT, read_proc_pid_stat(p)))) + // there is no reason to proceed if we cannot get its status + return; - // -------------------------------------------------------------------- - // done! + read_proc_pid_ownership(p); - // mark it as updated - p->updated = 1; - } + // check its parent pid + if(unlikely(p->ppid < 0 || p->ppid > pid_max)) { + error("Pid %d (command '%s') states invalid parent pid %d. Using 0.", pid, p->comm, p->ppid); + p->ppid = 0; + } - closedir(dir); + // -------------------------------------------------------------------- + // /proc/<pid>/io - return 1; -} + managed_log(p, PID_LOG_IO, read_proc_pid_io(p)); + // -------------------------------------------------------------------- + // /proc/<pid>/statm -// ---------------------------------------------------------------------------- + if(unlikely(!managed_log(p, PID_LOG_STATM, read_proc_pid_statm(p)))) + // there is no reason to proceed if we cannot get its memory status + return; -#ifdef AGGREGATE_CHILDREN_TO_PARENTS -// print a tree view of all processes -int debug_childrens_aggregations(pid_t pid, int level) { - struct pid_stat *p = NULL; - char b[level+3]; - int i, ret = 0; - - for(i = 0; i < level; i++) b[i] = '\t'; - b[level] = '|'; - b[level+1] = '-'; - b[level+2] = '\0'; - - for(p = root_of_pids; p ; p = p->next) { - if(p->ppid == pid) { - ret += debug_childrens_aggregations(p->pid, level+1); - } - } - - p = all_pids[pid]; - if(p) { - if(!p->updated) ret += 1; - if(ret) fprintf(stderr, "%s %s %d [%s, %s] c=%d u=%llu+%llu, s=%llu+%llu, cu=%llu+%llu, cs=%llu+%llu, n=%llu+%llu, j=%llu+%llu, cn=%llu+%llu, cj=%llu+%llu\n" - , b, p->comm, p->pid, p->updated?"OK":"KILLED", p->target->name, p->children_count - , p->utime, p->utime - p->old_utime - , p->stime, p->stime - p->old_stime - , p->cutime, p->cutime - p->old_cutime - , p->cstime, p->cstime - p->old_cstime - , p->minflt, p->minflt - p->old_minflt - , p->majflt, p->majflt - p->old_majflt - , p->cminflt, p->cminflt - p->old_cminflt - , p->cmajflt, p->cmajflt - p->old_cmajflt - ); - } - - return ret; -} -#endif /* AGGREGATE_CHILDREN_TO_PARENTS */ + // -------------------------------------------------------------------- + // link it + // check if it is target + // we do this only once, the first time this pid is loaded + if(unlikely(p->new_entry)) { + // /proc/<pid>/cmdline + if(likely(proc_pid_cmdline_is_needed)) + managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p)); + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: \tJust added %d (%s)\n", pid, p->comm); + + uint32_t hash = simple_hash(p->comm); + size_t pclen = strlen(p->comm); + + struct target *w; + for(w = apps_groups_root_target; w ; w = w->next) { + // if(debug || (p->target && p->target->debug)) fprintf(stderr, "apps.plugin: \t\tcomparing '%s' with '%s'\n", w->compare, p->comm); + + // find it - 4 cases: + // 1. the target is not a pattern + // 2. the target has the prefix + // 3. the target has the suffix + // 4. the target is something inside cmdline + if( (!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm)) + || (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen)) + || (!w->starts_with && w->ends_with && pclen >= w->comparelen && !strcmp(w->compare, &p->comm[pclen - w->comparelen])) + || (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && strstr(p->cmdline, w->compare)) + ) { + if(w->target) p->target = w->target; + else p->target = w; + + if(debug || (p->target && p->target->debug)) + fprintf(stderr, "apps.plugin: \t\t%s linked to target %s\n", p->comm, p->target->name); + + break; + } + } + } + + // -------------------------------------------------------------------- + // /proc/<pid>/fd + + if(enable_file_charts) + managed_log(p, PID_LOG_FDS, read_pid_file_descriptors(p)); + + // -------------------------------------------------------------------- + // done! + + if(unlikely(debug && include_exited_childs && all_pids_count && p->ppid && all_pids[p->ppid] && !all_pids[p->ppid]->read)) + fprintf(stderr, "Read process %d (%s) sortlisted %d, but its parent %d (%s) sortlisted %d, is not read\n", p->pid, p->comm, p->sortlist, all_pids[p->ppid]->pid, all_pids[p->ppid]->comm, all_pids[p->ppid]->sortlist); + + // mark it as updated + p->updated = 1; + p->keep = 0; + p->keeploops = 0; +} + +int collect_data_for_all_processes_from_proc(void) { + struct pid_stat *p = NULL; + + if(all_pids_count) { + // read parents before childs + // this is needed to prevent a situation where + // a child is found running, but until we read + // its parent, it has exited and its parent + // has accumulated its resources + + long slc = 0; + for(p = root_of_pids; p ; p = p->next) { + p->read = 0; + p->updated = 0; + p->new_entry = 0; + p->merged = 0; + p->children_count = 0; + p->parent = NULL; + + all_pids_sortlist[slc++] = p->pid; + } + + if(unlikely(slc != all_pids_count)) { + error("Internal error: I was thinking I had %ld processes in my arrays, but it seems there are more.", all_pids_count); + all_pids_count = slc; + } + + if(include_exited_childs) { + qsort((void *)all_pids_sortlist, all_pids_count, sizeof(pid_t), compar_pid); + for(slc = 0; slc < all_pids_count; slc++) + collect_data_for_pid(all_pids_sortlist[slc]); + } + } + + char dirname[FILENAME_MAX + 1]; + + snprintfz(dirname, FILENAME_MAX, "%s/proc", host_prefix); + DIR *dir = opendir(dirname); + if(!dir) return 0; + + struct dirent *file = NULL; + + while((file = readdir(dir))) { + char *endptr = file->d_name; + pid_t pid = (pid_t) strtoul(file->d_name, &endptr, 10); + + // make sure we read a valid number + if(unlikely(endptr == file->d_name || *endptr != '\0')) + continue; + + collect_data_for_pid(pid); + } + closedir(dir); + + // normally this is done + // however we may have processes exited while we collected values + // so let's find the exited ones + // we do this by collecting the ownership of process + // if we manage to get the ownership, the process still runs + + read_proc_stat(); + link_all_processes_to_their_parents(); + process_exited_processes(); + + return 1; +} // ---------------------------------------------------------------------------- // update statistics on the targets @@ -1486,729 +1798,760 @@ int debug_childrens_aggregations(pid_t pid, int level) { // 9. find the unique file count for each target // check: update_apps_groups_statistics() -void link_all_processes_to_their_parents(void) { - struct pid_stat *p = NULL; - - // link all children to their parents - // and update children count on parents - for(p = root_of_pids; p ; p = p->next) { - // for each process found running - - if(p->ppid > 0 - && p->ppid <= pid_max - && all_pids[p->ppid] - ) { - // for valid processes - - if(debug || (p->target && p->target->debug)) - fprintf(stderr, "apps.plugin: \tparent of %d (%s) is %d (%s)\n", p->pid, p->comm, p->ppid, all_pids[p->ppid]->comm); - - p->parent = all_pids[p->ppid]; - p->parent->children_count++; - } - else if(p->ppid != 0) - error("pid %d %s states parent %d, but the later does not exist.", p->pid, p->comm, p->ppid); - } -} - -#ifdef AGGREGATE_CHILDREN_TO_PARENTS -void aggregate_children_to_parents(void) { - struct pid_stat *p = NULL; - - // for each killed process, remove its values from the parents - // sums (we had already added them in a previous loop) - for(p = root_of_pids; p ; p = p->next) { - if(p->updated) continue; - - if(debug) fprintf(stderr, "apps.plugin: UNMERGING %d %s\n", p->pid, p->comm); - - unsigned long long diff_utime = p->utime + p->cutime + p->fix_cutime; - unsigned long long diff_stime = p->stime + p->cstime + p->fix_cstime; - unsigned long long diff_minflt = p->minflt + p->cminflt + p->fix_cminflt; - unsigned long long diff_majflt = p->majflt + p->cmajflt + p->fix_cmajflt; - - struct pid_stat *t = p; - while((t = t->parent)) { - if(!t->updated) continue; - - unsigned long long x; - if(diff_utime && t->diff_cutime) { - x = (t->diff_cutime < diff_utime)?t->diff_cutime:diff_utime; - diff_utime -= x; - t->diff_cutime -= x; - t->fix_cutime += x; - if(debug) fprintf(stderr, "apps.plugin: \t cutime %llu from %d %s %s\n", x, t->pid, t->comm, t->target->name); - } - if(diff_stime && t->diff_cstime) { - x = (t->diff_cstime < diff_stime)?t->diff_cstime:diff_stime; - diff_stime -= x; - t->diff_cstime -= x; - t->fix_cstime += x; - if(debug) fprintf(stderr, "apps.plugin: \t cstime %llu from %d %s %s\n", x, t->pid, t->comm, t->target->name); - } - if(diff_minflt && t->diff_cminflt) { - x = (t->diff_cminflt < diff_minflt)?t->diff_cminflt:diff_minflt; - diff_minflt -= x; - t->diff_cminflt -= x; - t->fix_cminflt += x; - if(debug) fprintf(stderr, "apps.plugin: \t cminflt %llu from %d %s %s\n", x, t->pid, t->comm, t->target->name); - } - if(diff_majflt && t->diff_cmajflt) { - x = (t->diff_cmajflt < diff_majflt)?t->diff_cmajflt:diff_majflt; - diff_majflt -= x; - t->diff_cmajflt -= x; - t->fix_cmajflt += x; - if(debug) fprintf(stderr, "apps.plugin: \t cmajflt %llu from %d %s %s\n", x, t->pid, t->comm, t->target->name); - } - } - - if(diff_utime) error("Cannot fix up utime %llu", diff_utime); - if(diff_stime) error("Cannot fix up stime %llu", diff_stime); - if(diff_minflt) error("Cannot fix up minflt %llu", diff_minflt); - if(diff_majflt) error("Cannot fix up majflt %llu", diff_majflt); - } -} -#endif /* AGGREGATE_CHILDREN_TO_PARENTS */ - -void cleanup_non_existing_pids(void) { - int c; - struct pid_stat *p = NULL; - - for(p = root_of_pids; p ;) { - if(!p->updated) { -// fprintf(stderr, "\tEXITED %d %s [parent %d %s, target %s] utime=%llu, stime=%llu, cutime=%llu, cstime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu\n", p->pid, p->comm, p->parent->pid, p->parent->comm, p->target->name, p->utime, p->stime, p->cutime, p->cstime, p->minflt, p->majflt, p->cminflt, p->cmajflt); - - for(c = 0 ; c < p->fds_size ; c++) if(p->fds[c] > 0) { - file_descriptor_not_used(p->fds[c]); - p->fds[c] = 0; - } - - pid_t r = p->pid; - p = p->next; - del_pid_entry(r); - } - else p = p->next; - } +void cleanup_exited_pids(void) { + int c; + struct pid_stat *p = NULL; + + for(p = root_of_pids; p ;) { + if(!p->updated && (!p->keep || p->keeploops > 0)) { +// fprintf(stderr, "\tEXITED %d %s [parent %d %s, target %s] utime=%llu, stime=%llu, gtime=%llu, cutime=%llu, cstime=%llu, cgtime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu\n", p->pid, p->comm, p->parent->pid, p->parent->comm, p->target->name, p->utime, p->stime, p->gtime, p->cutime, p->cstime, p->cgtime, p->minflt, p->majflt, p->cminflt, p->cmajflt); + + if(unlikely(debug && (p->keep || p->keeploops))) + fprintf(stderr, " > CLEANUP cannot keep exited process %d (%s) anymore - removing it.\n", p->pid, p->comm); + + for(c = 0 ; c < p->fds_size ; c++) if(p->fds[c] > 0) { + file_descriptor_not_used(p->fds[c]); + p->fds[c] = 0; + } + + pid_t r = p->pid; + p = p->next; + del_pid_entry(r); + } + else { + if(unlikely(p->keep)) p->keeploops++; + p->keep = 0; + p = p->next; + } + } } void apply_apps_groups_targets_inheritance(void) { - struct pid_stat *p = NULL; - - // children that do not have a target - // inherit their target from their parent - int found = 1; - while(found) { - found = 0; - for(p = root_of_pids; p ; p = p->next) { - // if this process does not have a target - // and it has a parent - // and its parent has a target - // then, set the parent's target to this process - if(unlikely(!p->target && p->parent && p->parent->target)) { - p->target = p->parent->target; - found++; - - if(debug || (p->target && p->target->debug)) - fprintf(stderr, "apps.plugin: \t\tTARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s).\n", p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm); - } - } - } - - - // find all the procs with 0 childs and merge them to their parents - // repeat, until nothing more can be done. - found = 1; - while(found) { - found = 0; - for(p = root_of_pids; p ; p = p->next) { - // if this process does not have any children - // and is not already merged - // and has a parent - // and its parent has children - // and the target of this process and its parent is the same, or the parent does not have a target - // and its parent is not init - // then, mark them as merged. - if(unlikely( - !p->children_count - && !p->merged - && p->parent - && p->parent->children_count - && (p->target == p->parent->target || !p->parent->target) - && p->ppid != 1 - )) { - p->parent->children_count--; - p->merged = 1; - - // the parent inherits the child's target, if it does not have a target itself - if(unlikely(p->target && !p->parent->target)) { - p->parent->target = p->target; - - if(debug || (p->target && p->target->debug)) - fprintf(stderr, "apps.plugin: \t\tTARGET INHERITANCE: %s is inherited by %d (%s) from its child %d (%s).\n", p->target->name, p->parent->pid, p->parent->comm, p->pid, p->comm); - } - - found++; - } - } - - if(debug) - fprintf(stderr, "apps.plugin: merged %d processes\n", found); - } - - // init goes always to default target - if(all_pids[1]) - all_pids[1]->target = apps_groups_default_target; - - // give a default target on all top level processes - for(p = root_of_pids; p ; p = p->next) { - // if the process is not merged itself - // then is is a top level process - if(!p->merged && !p->target) - p->target = apps_groups_default_target; - -#ifdef AGGREGATE_CHILDREN_TO_PARENTS - // by the way, update the diffs - // will be used later for subtracting killed process times - p->diff_cutime = p->utime - p->cutime; - p->diff_cstime = p->stime - p->cstime; - p->diff_cminflt = p->minflt - p->cminflt; - p->diff_cmajflt = p->majflt - p->cmajflt; -#endif /* AGGREGATE_CHILDREN_TO_PARENTS */ - } - - // give a target to all merged child processes - found = 1; - while(found) { - found = 0; - for(p = root_of_pids; p ; p = p->next) { - if(unlikely(!p->target && p->merged && p->parent && p->parent->target)) { - p->target = p->parent->target; - found++; - - if(debug || (p->target && p->target->debug)) - fprintf(stderr, "apps.plugin: \t\tTARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s) at phase 2.\n", p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm); - } - } - } + struct pid_stat *p = NULL; + + // children that do not have a target + // inherit their target from their parent + int found = 1, loops = 0; + while(found) { + if(unlikely(debug)) loops++; + found = 0; + for(p = root_of_pids; p ; p = p->next) { + // if this process does not have a target + // and it has a parent + // and its parent has a target + // then, set the parent's target to this process + if(unlikely(!p->target && p->parent && p->parent->target)) { + p->target = p->parent->target; + found++; + + if(debug || (p->target && p->target->debug)) + fprintf(stderr, "apps.plugin: \t\tTARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s).\n", p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm); + } + } + } + + // find all the procs with 0 childs and merge them to their parents + // repeat, until nothing more can be done. + int sortlist = 1; + found = 1; + while(found) { + if(unlikely(debug)) loops++; + found = 0; + + for(p = root_of_pids; p ; p = p->next) { + if(unlikely(!p->sortlist && !p->children_count)) + p->sortlist = sortlist++; + + // if this process does not have any children + // and is not already merged + // and has a parent + // and its parent has children + // and the target of this process and its parent is the same, or the parent does not have a target + // and its parent is not init + // then, mark them as merged. + if(unlikely( + !p->children_count + && !p->merged + && p->parent + && p->parent->children_count + && (p->target == p->parent->target || !p->parent->target) + && p->ppid != 1 + )) { + p->parent->children_count--; + p->merged = 1; + + // the parent inherits the child's target, if it does not have a target itself + if(unlikely(p->target && !p->parent->target)) { + p->parent->target = p->target; + + if(debug || (p->target && p->target->debug)) + fprintf(stderr, "apps.plugin: \t\tTARGET INHERITANCE: %s is inherited by %d (%s) from its child %d (%s).\n", p->target->name, p->parent->pid, p->parent->comm, p->pid, p->comm); + } + + found++; + } + } + + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: TARGET INHERITANCE: merged %d processes\n", found); + } + + // init goes always to default target + if(all_pids[1]) + all_pids[1]->target = apps_groups_default_target; + + // give a default target on all top level processes + if(unlikely(debug)) loops++; + for(p = root_of_pids; p ; p = p->next) { + // if the process is not merged itself + // then is is a top level process + if(unlikely(!p->merged && !p->target)) + p->target = apps_groups_default_target; + + // make sure all processes have a sortlist + if(unlikely(!p->sortlist)) + p->sortlist = sortlist++; + } + + if(all_pids[1]) + all_pids[1]->sortlist = sortlist++; + + // give a target to all merged child processes + found = 1; + while(found) { + if(unlikely(debug)) loops++; + found = 0; + for(p = root_of_pids; p ; p = p->next) { + if(unlikely(!p->target && p->merged && p->parent && p->parent->target)) { + p->target = p->parent->target; + found++; + + if(debug || (p->target && p->target->debug)) + fprintf(stderr, "apps.plugin: \t\tTARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s) at phase 2.\n", p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm); + } + } + } + + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: apply_apps_groups_targets_inheritance() made %d loops on the process tree\n", loops); } long zero_all_targets(struct target *root) { - struct target *w; - long count = 0; - - for (w = root; w ; w = w->next) { - count++; - - if(w->fds) free(w->fds); - w->fds = NULL; - - w->minflt = 0; - w->majflt = 0; - w->utime = 0; - w->stime = 0; - w->cminflt = 0; - w->cmajflt = 0; - w->cutime = 0; - w->cstime = 0; - w->num_threads = 0; - w->rss = 0; - w->processes = 0; - - w->statm_size = 0; - w->statm_resident = 0; - w->statm_share = 0; - w->statm_text = 0; - w->statm_lib = 0; - w->statm_data = 0; - w->statm_dirty = 0; - - w->io_logical_bytes_read = 0; - w->io_logical_bytes_written = 0; - w->io_read_calls = 0; - w->io_write_calls = 0; - w->io_storage_bytes_read = 0; - w->io_storage_bytes_written = 0; - w->io_cancelled_write_bytes = 0; - } - - return count; + struct target *w; + long count = 0; + + for (w = root; w ; w = w->next) { + count++; + + if(w->fds) freez(w->fds); + w->fds = NULL; + + w->minflt = 0; + w->majflt = 0; + w->utime = 0; + w->stime = 0; + w->gtime = 0; + w->cminflt = 0; + w->cmajflt = 0; + w->cutime = 0; + w->cstime = 0; + w->cgtime = 0; + w->num_threads = 0; + w->rss = 0; + w->processes = 0; + + w->statm_size = 0; + w->statm_resident = 0; + w->statm_share = 0; + w->statm_text = 0; + w->statm_lib = 0; + w->statm_data = 0; + w->statm_dirty = 0; + + w->io_logical_bytes_read = 0; + w->io_logical_bytes_written = 0; + w->io_read_calls = 0; + w->io_write_calls = 0; + w->io_storage_bytes_read = 0; + w->io_storage_bytes_written = 0; + w->io_cancelled_write_bytes = 0; + } + + return count; } void aggregate_pid_on_target(struct target *w, struct pid_stat *p, struct target *o) { - if(unlikely(!w->fds)) { - w->fds = calloc(sizeof(int), (size_t) all_files_size); - if(unlikely(!w->fds)) - error("Cannot allocate memory for fds in %s", w->name); - } - - if(likely(p->updated)) { - w->cutime += p->cutime; // - p->fix_cutime; - w->cstime += p->cstime; // - p->fix_cstime; - w->cminflt += p->cminflt; // - p->fix_cminflt; - w->cmajflt += p->cmajflt; // - p->fix_cmajflt; - - w->utime += p->utime; //+ (p->pid != 1)?(p->cutime - p->fix_cutime):0; - w->stime += p->stime; //+ (p->pid != 1)?(p->cstime - p->fix_cstime):0; - w->minflt += p->minflt; //+ (p->pid != 1)?(p->cminflt - p->fix_cminflt):0; - w->majflt += p->majflt; //+ (p->pid != 1)?(p->cmajflt - p->fix_cmajflt):0; - - //if(p->num_threads < 0) - // error("Negative threads number for pid '%s' (%d): %d", p->comm, p->pid, p->num_threads); - - //if(p->num_threads > 10000) - // error("Excessive threads number for pid '%s' (%d): %d", p->comm, p->pid, p->num_threads); - - w->num_threads += p->num_threads; - w->rss += p->rss; - - w->statm_size += p->statm_size; - w->statm_resident += p->statm_resident; - w->statm_share += p->statm_share; - w->statm_text += p->statm_text; - w->statm_lib += p->statm_lib; - w->statm_data += p->statm_data; - w->statm_dirty += p->statm_dirty; - - w->io_logical_bytes_read += p->io_logical_bytes_read; - w->io_logical_bytes_written += p->io_logical_bytes_written; - w->io_read_calls += p->io_read_calls; - w->io_write_calls += p->io_write_calls; - w->io_storage_bytes_read += p->io_storage_bytes_read; - w->io_storage_bytes_written += p->io_storage_bytes_written; - w->io_cancelled_write_bytes += p->io_cancelled_write_bytes; - - w->processes++; - - if(likely(w->fds)) { - int c; - for(c = 0; c < p->fds_size ;c++) { - if(p->fds[c] == 0) continue; - - if(likely(p->fds[c] < all_files_size)) { - if(w->fds) w->fds[p->fds[c]]++; - } - else - error("Invalid fd number %d", p->fds[c]); - } - } - - if(unlikely(debug || w->debug)) - fprintf(stderr, "apps.plugin: \tAgregating %s pid %d on %s utime=%llu, stime=%llu, cutime=%llu, cstime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu\n", p->comm, p->pid, w->name, p->utime, p->stime, p->cutime, p->cstime, p->minflt, p->majflt, p->cminflt, p->cmajflt); - -/* if(p->utime - p->old_utime > 100) fprintf(stderr, "BIG CHANGE: %d %s utime increased by %llu from %llu to %llu\n", p->pid, p->comm, p->utime - p->old_utime, p->old_utime, p->utime); - if(p->cutime - p->old_cutime > 100) fprintf(stderr, "BIG CHANGE: %d %s cutime increased by %llu from %llu to %llu\n", p->pid, p->comm, p->cutime - p->old_cutime, p->old_cutime, p->cutime); - if(p->stime - p->old_stime > 100) fprintf(stderr, "BIG CHANGE: %d %s stime increased by %llu from %llu to %llu\n", p->pid, p->comm, p->stime - p->old_stime, p->old_stime, p->stime); - if(p->cstime - p->old_cstime > 100) fprintf(stderr, "BIG CHANGE: %d %s cstime increased by %llu from %llu to %llu\n", p->pid, p->comm, p->cstime - p->old_cstime, p->old_cstime, p->cstime); - if(p->minflt - p->old_minflt > 5000) fprintf(stderr, "BIG CHANGE: %d %s minflt increased by %llu from %llu to %llu\n", p->pid, p->comm, p->minflt - p->old_minflt, p->old_minflt, p->minflt); - if(p->majflt - p->old_majflt > 5000) fprintf(stderr, "BIG CHANGE: %d %s majflt increased by %llu from %llu to %llu\n", p->pid, p->comm, p->majflt - p->old_majflt, p->old_majflt, p->majflt); - if(p->cminflt - p->old_cminflt > 15000) fprintf(stderr, "BIG CHANGE: %d %s cminflt increased by %llu from %llu to %llu\n", p->pid, p->comm, p->cminflt - p->old_cminflt, p->old_cminflt, p->cminflt); - if(p->cmajflt - p->old_cmajflt > 15000) fprintf(stderr, "BIG CHANGE: %d %s cmajflt increased by %llu from %llu to %llu\n", p->pid, p->comm, p->cmajflt - p->old_cmajflt, p->old_cmajflt, p->cmajflt); -*/ -#ifdef AGGREGATE_CHILDREN_TO_PARENTS - p->old_utime = p->utime; - p->old_cutime = p->cutime; - p->old_stime = p->stime; - p->old_cstime = p->cstime; - p->old_minflt = p->minflt; - p->old_majflt = p->majflt; - p->old_cminflt = p->cminflt; - p->old_cmajflt = p->cmajflt; -#endif /* AGGREGATE_CHILDREN_TO_PARENTS */ - - if(o) { - // since the process switched target - // for all incremental values - // we have to subtract its OLD values from the new target - // and add its OLD values to the old target - - // IMPORTANT - // We add/subtract the last/OLD values we added to the target - - w->fix_cutime -= p->last_cutime; - w->fix_cstime -= p->last_cstime; - w->fix_cminflt -= p->last_cminflt; - w->fix_cmajflt -= p->last_cmajflt; - - w->fix_utime -= p->last_utime; - w->fix_stime -= p->last_stime; - w->fix_minflt -= p->last_minflt; - w->fix_majflt -= p->last_majflt; - - - w->fix_io_logical_bytes_read -= p->last_io_logical_bytes_read; - w->fix_io_logical_bytes_written -= p->last_io_logical_bytes_written; - w->fix_io_read_calls -= p->last_io_read_calls; - w->fix_io_write_calls -= p->last_io_write_calls; - w->fix_io_storage_bytes_read -= p->last_io_storage_bytes_read; - w->fix_io_storage_bytes_written -= p->last_io_storage_bytes_written; - w->fix_io_cancelled_write_bytes -= p->last_io_cancelled_write_bytes; - - // --- - - o->fix_cutime += p->last_cutime; - o->fix_cstime += p->last_cstime; - o->fix_cminflt += p->last_cminflt; - o->fix_cmajflt += p->last_cmajflt; - - o->fix_utime += p->last_utime; - o->fix_stime += p->last_stime; - o->fix_minflt += p->last_minflt; - o->fix_majflt += p->last_majflt; - - o->fix_io_logical_bytes_read += p->last_io_logical_bytes_read; - o->fix_io_logical_bytes_written += p->last_io_logical_bytes_written; - o->fix_io_read_calls += p->last_io_read_calls; - o->fix_io_write_calls += p->last_io_write_calls; - o->fix_io_storage_bytes_read += p->last_io_storage_bytes_read; - o->fix_io_storage_bytes_written += p->last_io_storage_bytes_written; - o->fix_io_cancelled_write_bytes += p->last_io_cancelled_write_bytes; - } - } - else { - // if(o) fprintf(stderr, "apps.plugin: \t\tpid %d (%s) is not updated by OLD target %s (%s) is present.\n", p->pid, p->comm, o->id, o->name); - - // since the process has exited, the user - // will see a drop in our charts, because the incremental - // values of this process will not be there - - // add them to the fix_* values and they will be added to - // the reported values, so that the report goes steady - w->fix_minflt += p->minflt; - w->fix_majflt += p->majflt; - w->fix_utime += p->utime; - w->fix_stime += p->stime; - w->fix_cminflt += p->cminflt; - w->fix_cmajflt += p->cmajflt; - w->fix_cutime += p->cutime; - w->fix_cstime += p->cstime; - - w->fix_io_logical_bytes_read += p->io_logical_bytes_read; - w->fix_io_logical_bytes_written += p->io_logical_bytes_written; - w->fix_io_read_calls += p->io_read_calls; - w->fix_io_write_calls += p->io_write_calls; - w->fix_io_storage_bytes_read += p->io_storage_bytes_read; - w->fix_io_storage_bytes_written += p->io_storage_bytes_written; - w->fix_io_cancelled_write_bytes += p->io_cancelled_write_bytes; - } - + (void)o; + + if(unlikely(!w->fds)) + w->fds = callocz(sizeof(int), (size_t) all_files_size); + + if(likely(p->updated)) { + w->cutime += p->cutime; + w->cstime += p->cstime; + w->cgtime += p->cgtime; + w->cminflt += p->cminflt; + w->cmajflt += p->cmajflt; + + w->utime += p->utime; + w->stime += p->stime; + w->gtime += p->gtime; + w->minflt += p->minflt; + w->majflt += p->majflt; + + w->rss += p->rss; + + w->statm_size += p->statm_size; + w->statm_resident += p->statm_resident; + w->statm_share += p->statm_share; + w->statm_text += p->statm_text; + w->statm_lib += p->statm_lib; + w->statm_data += p->statm_data; + w->statm_dirty += p->statm_dirty; + + w->io_logical_bytes_read += p->io_logical_bytes_read; + w->io_logical_bytes_written += p->io_logical_bytes_written; + w->io_read_calls += p->io_read_calls; + w->io_write_calls += p->io_write_calls; + w->io_storage_bytes_read += p->io_storage_bytes_read; + w->io_storage_bytes_written += p->io_storage_bytes_written; + w->io_cancelled_write_bytes += p->io_cancelled_write_bytes; + + w->processes++; + w->num_threads += p->num_threads; + + if(likely(w->fds)) { + int c; + for(c = 0; c < p->fds_size ;c++) { + if(p->fds[c] == 0) continue; + + if(likely(p->fds[c] < all_files_size)) { + if(w->fds) w->fds[p->fds[c]]++; + } + else + error("Invalid fd number %d", p->fds[c]); + } + } + + if(unlikely(debug || w->debug)) + fprintf(stderr, "apps.plugin: \taggregating '%s' pid %d on target '%s' utime=%llu, stime=%llu, gtime=%llu, cutime=%llu, cstime=%llu, cgtime=%llu, minflt=%llu, majflt=%llu, cminflt=%llu, cmajflt=%llu\n", p->comm, p->pid, w->name, p->utime, p->stime, p->gtime, p->cutime, p->cstime, p->cgtime, p->minflt, p->majflt, p->cminflt, p->cmajflt); + } } void count_targets_fds(struct target *root) { - int c; - struct target *w; - - for (w = root; w ; w = w->next) { - if(!w->fds) continue; - - w->openfiles = 0; - w->openpipes = 0; - w->opensockets = 0; - w->openinotifies = 0; - w->openeventfds = 0; - w->opentimerfds = 0; - w->opensignalfds = 0; - w->openeventpolls = 0; - w->openother = 0; - - for(c = 1; c < all_files_size ;c++) { - if(w->fds[c] > 0) - switch(all_files[c].type) { - case FILETYPE_FILE: - w->openfiles++; - break; - - case FILETYPE_PIPE: - w->openpipes++; - break; - - case FILETYPE_SOCKET: - w->opensockets++; - break; - - case FILETYPE_INOTIFY: - w->openinotifies++; - break; - - case FILETYPE_EVENTFD: - w->openeventfds++; - break; - - case FILETYPE_TIMERFD: - w->opentimerfds++; - break; - - case FILETYPE_SIGNALFD: - w->opensignalfds++; - break; - - case FILETYPE_EVENTPOLL: - w->openeventpolls++; - break; - - default: - w->openother++; - } - } - - free(w->fds); - w->fds = NULL; - } + int c; + struct target *w; + + for (w = root; w ; w = w->next) { + if(!w->fds) continue; + + w->openfiles = 0; + w->openpipes = 0; + w->opensockets = 0; + w->openinotifies = 0; + w->openeventfds = 0; + w->opentimerfds = 0; + w->opensignalfds = 0; + w->openeventpolls = 0; + w->openother = 0; + + for(c = 1; c < all_files_size ;c++) { + if(w->fds[c] > 0) + switch(all_files[c].type) { + case FILETYPE_FILE: + w->openfiles++; + break; + + case FILETYPE_PIPE: + w->openpipes++; + break; + + case FILETYPE_SOCKET: + w->opensockets++; + break; + + case FILETYPE_INOTIFY: + w->openinotifies++; + break; + + case FILETYPE_EVENTFD: + w->openeventfds++; + break; + + case FILETYPE_TIMERFD: + w->opentimerfds++; + break; + + case FILETYPE_SIGNALFD: + w->opensignalfds++; + break; + + case FILETYPE_EVENTPOLL: + w->openeventpolls++; + break; + + default: + w->openother++; + } + } + + freez(w->fds); + w->fds = NULL; + } } -void calculate_netdata_statistics(void) -{ - link_all_processes_to_their_parents(); - apply_apps_groups_targets_inheritance(); - -#ifdef AGGREGATE_CHILDREN_TO_PARENTS - aggregate_children_to_parents(); -#endif /* AGGREGATE_CHILDREN_TO_PARENTS */ - - zero_all_targets(users_root_target); - zero_all_targets(groups_root_target); - apps_groups_targets = zero_all_targets(apps_groups_root_target); - -#ifdef AGGREGATE_CHILDREN_TO_PARENTS - if(debug) - debug_childrens_aggregations(0, 1); -#endif /* AGGREGATE_CHILDREN_TO_PARENTS */ - - // this has to be done, before the cleanup - struct pid_stat *p = NULL; - struct target *w = NULL, *o = NULL; - - // concentrate everything on the apps_groups_targets - for(p = root_of_pids; p ; p = p->next) { - - // -------------------------------------------------------------------- - // apps_groups targets - if(likely(p->target)) - aggregate_pid_on_target(p->target, p, NULL); - else - error("pid %d %s was left without a target!", p->pid, p->comm); - - - // -------------------------------------------------------------------- - // user targets - o = p->user_target; - if(likely(p->user_target && p->user_target->uid == p->uid)) - w = p->user_target; - else { - if(unlikely(debug && p->user_target)) - fprintf(stderr, "apps.plugin: \t\tpid %d (%s) switched user from %d (%s) to %d.\n", p->pid, p->comm, p->user_target->uid, p->user_target->name, p->uid); - - w = p->user_target = get_users_target(p->uid); - } - - if(likely(w)) - aggregate_pid_on_target(w, p, o); - else - error("pid %d %s was left without a user target!", p->pid, p->comm); - - - // -------------------------------------------------------------------- - // group targets - o = p->group_target; - if(likely(p->group_target && p->group_target->gid == p->gid)) - w = p->group_target; - else { - if(unlikely(debug && p->group_target)) - fprintf(stderr, "apps.plugin: \t\tpid %d (%s) switched group from %d (%s) to %d.\n", p->pid, p->comm, p->group_target->gid, p->group_target->name, p->gid); - - w = p->group_target = get_groups_target(p->gid); - } - - if(likely(w)) - aggregate_pid_on_target(w, p, o); - else - error("pid %d %s was left without a group target!", p->pid, p->comm); - - } - - count_targets_fds(apps_groups_root_target); - count_targets_fds(users_root_target); - count_targets_fds(groups_root_target); - - cleanup_non_existing_pids(); -} +void calculate_netdata_statistics(void) { + apply_apps_groups_targets_inheritance(); -// ---------------------------------------------------------------------------- -// update chart dimensions + zero_all_targets(users_root_target); + zero_all_targets(groups_root_target); + apps_groups_targets = zero_all_targets(apps_groups_root_target); -unsigned long long send_resource_usage_to_netdata() { - static struct timeval last = { 0, 0 }; - static struct rusage me_last; - - struct timeval now; - struct rusage me; - - unsigned long long usec; - unsigned long long cpuuser; - unsigned long long cpusyst; - - if(!last.tv_sec) { - gettimeofday(&last, NULL); - getrusage(RUSAGE_SELF, &me_last); - - // the first time, give a zero to allow - // netdata calibrate to the current time - // usec = update_every * 1000000ULL; - usec = 0ULL; - cpuuser = 0; - cpusyst = 0; - } - else { - gettimeofday(&now, NULL); - getrusage(RUSAGE_SELF, &me); - - usec = usecdiff(&now, &last); - cpuuser = me.ru_utime.tv_sec * 1000000ULL + me.ru_utime.tv_usec; - cpusyst = me.ru_stime.tv_sec * 1000000ULL + me.ru_stime.tv_usec; - - bcopy(&now, &last, sizeof(struct timeval)); - bcopy(&me, &me_last, sizeof(struct rusage)); - } - - fprintf(stdout, "BEGIN netdata.apps_cpu %llu\n", usec); - fprintf(stdout, "SET user = %llu\n", cpuuser); - fprintf(stdout, "SET system = %llu\n", cpusyst); - fprintf(stdout, "END\n"); - - fprintf(stdout, "BEGIN netdata.apps_files %llu\n", usec); - fprintf(stdout, "SET files = %llu\n", file_counter); - fprintf(stdout, "SET pids = %ld\n", all_pids_count); - fprintf(stdout, "SET fds = %d\n", all_files_len); - fprintf(stdout, "SET targets = %ld\n", apps_groups_targets); - fprintf(stdout, "END\n"); - - return usec; -} - -void send_collected_data_to_netdata(struct target *root, const char *type, unsigned long long usec) -{ - struct target *w; - - fprintf(stdout, "BEGIN %s.cpu %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "SET %s = %llu\n", w->name, w->utime + w->stime + w->fix_utime + w->fix_stime); - } - fprintf(stdout, "END\n"); - - fprintf(stdout, "BEGIN %s.cpu_user %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "SET %s = %llu\n", w->name, w->utime + w->fix_utime); - } - fprintf(stdout, "END\n"); - - fprintf(stdout, "BEGIN %s.cpu_system %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "SET %s = %llu\n", w->name, w->stime + w->fix_stime); - } - fprintf(stdout, "END\n"); - - fprintf(stdout, "BEGIN %s.threads %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + // this has to be done, before the cleanup + struct pid_stat *p = NULL; + struct target *w = NULL, *o = NULL; - fprintf(stdout, "SET %s = %llu\n", w->name, w->num_threads); - } - fprintf(stdout, "END\n"); + // concentrate everything on the apps_groups_targets + for(p = root_of_pids; p ; p = p->next) { - fprintf(stdout, "BEGIN %s.processes %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + // -------------------------------------------------------------------- + // apps_groups targets + if(likely(p->target)) + aggregate_pid_on_target(p->target, p, NULL); + else + error("pid %d %s was left without a target!", p->pid, p->comm); - fprintf(stdout, "SET %s = %lu\n", w->name, w->processes); - } - fprintf(stdout, "END\n"); - fprintf(stdout, "BEGIN %s.mem %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + // -------------------------------------------------------------------- + // user targets + o = p->user_target; + if(likely(p->user_target && p->user_target->uid == p->uid)) + w = p->user_target; + else { + if(unlikely(debug && p->user_target)) + fprintf(stderr, "apps.plugin: \t\tpid %d (%s) switched user from %u (%s) to %u.\n", p->pid, p->comm, p->user_target->uid, p->user_target->name, p->uid); - fprintf(stdout, "SET %s = %lld\n", w->name, (long long)w->statm_resident - (long long)w->statm_share); - } - fprintf(stdout, "END\n"); + w = p->user_target = get_users_target(p->uid); + } - fprintf(stdout, "BEGIN %s.minor_faults %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + if(likely(w)) + aggregate_pid_on_target(w, p, o); + else + error("pid %d %s was left without a user target!", p->pid, p->comm); - fprintf(stdout, "SET %s = %llu\n", w->name, w->minflt + w->fix_minflt); - } - fprintf(stdout, "END\n"); - fprintf(stdout, "BEGIN %s.major_faults %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + // -------------------------------------------------------------------- + // group targets + o = p->group_target; + if(likely(p->group_target && p->group_target->gid == p->gid)) + w = p->group_target; + else { + if(unlikely(debug && p->group_target)) + fprintf(stderr, "apps.plugin: \t\tpid %d (%s) switched group from %u (%s) to %u.\n", p->pid, p->comm, p->group_target->gid, p->group_target->name, p->gid); - fprintf(stdout, "SET %s = %llu\n", w->name, w->majflt + w->fix_majflt); - } - fprintf(stdout, "END\n"); + w = p->group_target = get_groups_target(p->gid); + } - fprintf(stdout, "BEGIN %s.lreads %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + if(likely(w)) + aggregate_pid_on_target(w, p, o); + else + error("pid %d %s was left without a group target!", p->pid, p->comm); - fprintf(stdout, "SET %s = %llu\n", w->name, w->io_logical_bytes_read + w->fix_io_logical_bytes_read); - } - fprintf(stdout, "END\n"); + } - fprintf(stdout, "BEGIN %s.lwrites %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + count_targets_fds(apps_groups_root_target); + count_targets_fds(users_root_target); + count_targets_fds(groups_root_target); - fprintf(stdout, "SET %s = %llu\n", w->name, w->io_logical_bytes_written + w->fix_io_logical_bytes_written); - } - fprintf(stdout, "END\n"); - - fprintf(stdout, "BEGIN %s.preads %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "SET %s = %llu\n", w->name, w->io_storage_bytes_read + w->fix_io_storage_bytes_read); - } - fprintf(stdout, "END\n"); - - fprintf(stdout, "BEGIN %s.pwrites %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; + cleanup_exited_pids(); +} - fprintf(stdout, "SET %s = %llu\n", w->name, w->io_storage_bytes_written + w->fix_io_storage_bytes_written); - } - fprintf(stdout, "END\n"); +// ---------------------------------------------------------------------------- +// update chart dimensions - fprintf(stdout, "BEGIN %s.files %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; +BUFFER *output = NULL; +int print_calculated_number(char *str, calculated_number value) { (void)str; (void)value; return 0; } + +static inline void send_BEGIN(const char *type, const char *id, unsigned long long usec) { + // fprintf(stdout, "BEGIN %s.%s %llu\n", type, id, usec); + buffer_strcat(output, "BEGIN "); + buffer_strcat(output, type); + buffer_strcat(output, "."); + buffer_strcat(output, id); + buffer_strcat(output, " "); + buffer_print_llu(output, usec); + buffer_strcat(output, "\n"); +} - fprintf(stdout, "SET %s = %llu\n", w->name, w->openfiles); - } - fprintf(stdout, "END\n"); +static inline void send_SET(const char *name, unsigned long long value) { + // fprintf(stdout, "SET %s = %llu\n", name, value); + buffer_strcat(output, "SET "); + buffer_strcat(output, name); + buffer_strcat(output, " = "); + buffer_print_llu(output, value); + buffer_strcat(output, "\n"); +} - fprintf(stdout, "BEGIN %s.sockets %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; +static inline void send_END(void) { + // fprintf(stdout, "END\n"); + buffer_strcat(output, "END\n"); +} - fprintf(stdout, "SET %s = %llu\n", w->name, w->opensockets); - } - fprintf(stdout, "END\n"); +double utime_fix_ratio = 1.0, stime_fix_ratio = 1.0, gtime_fix_ratio = 1.0, cutime_fix_ratio = 1.0, cstime_fix_ratio = 1.0, cgtime_fix_ratio = 1.0; +double minflt_fix_ratio = 1.0, majflt_fix_ratio = 1.0, cminflt_fix_ratio = 1.0, cmajflt_fix_ratio = 1.0; - fprintf(stdout, "BEGIN %s.pipes %llu\n", type, usec); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; +unsigned long long send_resource_usage_to_netdata() { + static struct timeval last = { 0, 0 }; + static struct rusage me_last; + + struct timeval now; + struct rusage me; + + unsigned long long usec; + unsigned long long cpuuser; + unsigned long long cpusyst; + + if(!last.tv_sec) { + gettimeofday(&last, NULL); + getrusage(RUSAGE_SELF, &me_last); + + // the first time, give a zero to allow + // netdata calibrate to the current time + // usec = update_every * 1000000ULL; + usec = 0ULL; + cpuuser = 0; + cpusyst = 0; + } + else { + gettimeofday(&now, NULL); + getrusage(RUSAGE_SELF, &me); + + usec = usec_dt(&now, &last); + cpuuser = me.ru_utime.tv_sec * 1000000ULL + me.ru_utime.tv_usec; + cpusyst = me.ru_stime.tv_sec * 1000000ULL + me.ru_stime.tv_usec; + + bcopy(&now, &last, sizeof(struct timeval)); + bcopy(&me, &me_last, sizeof(struct rusage)); + } + + buffer_sprintf(output, + "BEGIN netdata.apps_cpu %llu\n" + "SET user = %llu\n" + "SET system = %llu\n" + "END\n" + "BEGIN netdata.apps_files %llu\n" + "SET files = %llu\n" + "SET pids = %ld\n" + "SET fds = %d\n" + "SET targets = %ld\n" + "END\n" + "BEGIN netdata.apps_fix %llu\n" + "SET utime = %llu\n" + "SET stime = %llu\n" + "SET gtime = %llu\n" + "SET minflt = %llu\n" + "SET majflt = %llu\n" + "END\n" + , usec + , cpuuser + , cpusyst + , usec + , file_counter + , all_pids_count + , all_files_len + , apps_groups_targets + , usec + , (unsigned long long)(utime_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(stime_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(gtime_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(minflt_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(majflt_fix_ratio * 100 * RATES_DETAIL) + ); + + if(include_exited_childs) + buffer_sprintf(output, + "BEGIN netdata.apps_children_fix %llu\n" + "SET cutime = %llu\n" + "SET cstime = %llu\n" + "SET cgtime = %llu\n" + "SET cminflt = %llu\n" + "SET cmajflt = %llu\n" + "END\n" + , usec + , (unsigned long long)(cutime_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(cstime_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(cgtime_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(cminflt_fix_ratio * 100 * RATES_DETAIL) + , (unsigned long long)(cmajflt_fix_ratio * 100 * RATES_DETAIL) + ); + + return usec; +} - fprintf(stdout, "SET %s = %llu\n", w->name, w->openpipes); - } - fprintf(stdout, "END\n"); +void normalize_data(struct target *root) { + struct target *w; + + // childs processing introduces spikes + // here we try to eliminate them by disabling childs processing either for specific dimensions + // or entirely. Of course, either way, we disable it just a single iteration. + + unsigned long long max = processors * hz * RATES_DETAIL; + unsigned long long utime = 0, cutime = 0, stime = 0, cstime = 0, gtime = 0, cgtime = 0, minflt = 0, cminflt = 0, majflt = 0, cmajflt = 0; + + if(global_utime > max) global_utime = max; + if(global_stime > max) global_stime = max; + if(global_gtime > max) global_gtime = max; + + for(w = root; w ; w = w->next) { + if(w->target || (!w->processes && !w->exposed)) continue; + + utime += w->utime; + stime += w->stime; + gtime += w->gtime; + cutime += w->cutime; + cstime += w->cstime; + cgtime += w->cgtime; + + minflt += w->minflt; + majflt += w->majflt; + cminflt += w->cminflt; + cmajflt += w->cmajflt; + } + + if((global_utime || global_stime || global_gtime) && (utime || stime || gtime)) { + if(global_utime + global_stime + global_gtime > utime + cutime + stime + cstime + gtime + cgtime) { + // everything we collected fits + utime_fix_ratio = + stime_fix_ratio = + gtime_fix_ratio = + cutime_fix_ratio = + cstime_fix_ratio = + cgtime_fix_ratio = 1.0; //(double)(global_utime + global_stime) / (double)(utime + cutime + stime + cstime); + } + else if(global_utime + global_stime > utime + stime) { + // childrens resources are too high + // lower only the children resources + utime_fix_ratio = + stime_fix_ratio = + gtime_fix_ratio = 1.0; + cutime_fix_ratio = + cstime_fix_ratio = + cgtime_fix_ratio = (double)((global_utime + global_stime) - (utime + stime)) / (double)(cutime + cstime); + } + else { + // even running processes are unrealistic + // zero the children resources + // lower the running processes resources + utime_fix_ratio = + stime_fix_ratio = + gtime_fix_ratio = (double)(global_utime + global_stime) / (double)(utime + stime); + cutime_fix_ratio = + cstime_fix_ratio = + cgtime_fix_ratio = 0.0; + } + } + else { + utime_fix_ratio = + stime_fix_ratio = + gtime_fix_ratio = + cutime_fix_ratio = + cstime_fix_ratio = + cgtime_fix_ratio = 0.0; + } + + if(utime_fix_ratio > 1.0) utime_fix_ratio = 1.0; + if(cutime_fix_ratio > 1.0) cutime_fix_ratio = 1.0; + if(stime_fix_ratio > 1.0) stime_fix_ratio = 1.0; + if(cstime_fix_ratio > 1.0) cstime_fix_ratio = 1.0; + if(gtime_fix_ratio > 1.0) gtime_fix_ratio = 1.0; + if(cgtime_fix_ratio > 1.0) cgtime_fix_ratio = 1.0; + + // if(utime_fix_ratio < 0.0) utime_fix_ratio = 0.0; + // if(cutime_fix_ratio < 0.0) cutime_fix_ratio = 0.0; + // if(stime_fix_ratio < 0.0) stime_fix_ratio = 0.0; + // if(cstime_fix_ratio < 0.0) cstime_fix_ratio = 0.0; + // if(gtime_fix_ratio < 0.0) gtime_fix_ratio = 0.0; + // if(cgtime_fix_ratio < 0.0) cgtime_fix_ratio = 0.0; + + // FIXME + // we use cpu time to normalize page faults + // the problem is that to find the proper max values + // for page faults we have to parse /proc/vmstat + // which is quite big to do it again (netdata does it already) + // + // a better solution could be to somehow have netdata + // do this normalization for us + + if(utime || stime || gtime) + majflt_fix_ratio = + minflt_fix_ratio = (double)(utime * utime_fix_ratio + stime * stime_fix_ratio + gtime * gtime_fix_ratio) / (double)(utime + stime + gtime); + else + minflt_fix_ratio = + majflt_fix_ratio = 1.0; + + if(cutime || cstime || cgtime) + cmajflt_fix_ratio = + cminflt_fix_ratio = (double)(cutime * cutime_fix_ratio + cstime * cstime_fix_ratio + cgtime * cgtime_fix_ratio) / (double)(cutime + cstime + cgtime); + else + cminflt_fix_ratio = + cmajflt_fix_ratio = 1.0; + + // the report + + if(unlikely(debug)) { + fprintf(stderr, + "SYSTEM: u=%llu s=%llu g=%llu " + "COLLECTED: u=%llu s=%llu g=%llu cu=%llu cs=%llu cg=%llu " + "DELTA: u=%lld s=%lld g=%lld " + "FIX: u=%0.2f s=%0.2f g=%0.2f cu=%0.2f cs=%0.2f cg=%0.2f " + "FINALLY: u=%llu s=%llu g=%llu cu=%llu cs=%llu cg=%llu " + "\n" + , global_utime + , global_stime + , global_gtime + , utime + , stime + , gtime + , cutime + , cstime + , cgtime + , (long long)utime + (long long)cutime - (long long)global_utime + , (long long)stime + (long long)cstime - (long long)global_stime + , (long long)gtime + (long long)cgtime - (long long)global_gtime + , utime_fix_ratio + , stime_fix_ratio + , gtime_fix_ratio + , cutime_fix_ratio + , cstime_fix_ratio + , cgtime_fix_ratio + , (unsigned long long)(utime * utime_fix_ratio) + , (unsigned long long)(stime * stime_fix_ratio) + , (unsigned long long)(gtime * gtime_fix_ratio) + , (unsigned long long)(cutime * cutime_fix_ratio) + , (unsigned long long)(cstime * cstime_fix_ratio) + , (unsigned long long)(cgtime * cgtime_fix_ratio) + ); + } +} - fflush(stdout); +void send_collected_data_to_netdata(struct target *root, const char *type, unsigned long long usec) { + struct target *w; + + send_BEGIN(type, "cpu", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, (unsigned long long)(w->utime * utime_fix_ratio) + (unsigned long long)(w->stime * stime_fix_ratio) + (unsigned long long)(w->gtime * gtime_fix_ratio) + (include_exited_childs?((unsigned long long)(w->cutime * cutime_fix_ratio) + (unsigned long long)(w->cstime * cstime_fix_ratio) + (unsigned long long)(w->cgtime * cgtime_fix_ratio)):0ULL)); + } + send_END(); + + send_BEGIN(type, "cpu_user", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, (unsigned long long)(w->utime * utime_fix_ratio) + (include_exited_childs?((unsigned long long)(w->cutime * cutime_fix_ratio)):0ULL)); + } + send_END(); + + send_BEGIN(type, "cpu_system", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, (unsigned long long)(w->stime * stime_fix_ratio) + (include_exited_childs?((unsigned long long)(w->cstime * cstime_fix_ratio)):0ULL)); + } + send_END(); + + if(show_guest_time) { + send_BEGIN(type, "cpu_guest", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, (unsigned long long)(w->gtime * gtime_fix_ratio) + (include_exited_childs?((unsigned long long)(w->cgtime * cgtime_fix_ratio)):0ULL)); + } + send_END(); + } + + send_BEGIN(type, "threads", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, w->num_threads); + } + send_END(); + + send_BEGIN(type, "processes", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, w->processes); + } + send_END(); + + send_BEGIN(type, "mem", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, (w->statm_resident > w->statm_share)?(w->statm_resident - w->statm_share):0ULL); + } + send_END(); + + send_BEGIN(type, "minor_faults", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, (unsigned long long)(w->minflt * minflt_fix_ratio) + (include_exited_childs?((unsigned long long)(w->cminflt * cminflt_fix_ratio)):0ULL)); + } + send_END(); + + send_BEGIN(type, "major_faults", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, (unsigned long long)(w->majflt * majflt_fix_ratio) + (include_exited_childs?((unsigned long long)(w->cmajflt * cmajflt_fix_ratio)):0ULL)); + } + send_END(); + + send_BEGIN(type, "lreads", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, w->io_logical_bytes_read); + } + send_END(); + + send_BEGIN(type, "lwrites", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, w->io_logical_bytes_written); + } + send_END(); + + send_BEGIN(type, "preads", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, w->io_storage_bytes_read); + } + send_END(); + + send_BEGIN(type, "pwrites", usec); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + send_SET(w->name, w->io_storage_bytes_written); + } + send_END(); + + if(enable_file_charts) { + send_BEGIN(type, "files", usec); + for (w = root; w; w = w->next) { + if (unlikely(w->exposed)) + send_SET(w->name, w->openfiles); + } + send_END(); + + send_BEGIN(type, "sockets", usec); + for (w = root; w; w = w->next) { + if (unlikely(w->exposed)) + send_SET(w->name, w->opensockets); + } + send_END(); + + send_BEGIN(type, "pipes", usec); + for (w = root; w; w = w->next) { + if (unlikely(w->exposed)) + send_SET(w->name, w->openpipes); + } + send_END(); + } } @@ -2217,125 +2560,126 @@ void send_collected_data_to_netdata(struct target *root, const char *type, unsig void send_charts_updates_to_netdata(struct target *root, const char *type, const char *title) { - struct target *w; - int newly_added = 0; - - for(w = root ; w ; w = w->next) - if(!w->exposed && w->processes) { - newly_added++; - w->exposed = 1; - if(debug || w->debug) fprintf(stderr, "apps.plugin: %s just added - regenerating charts.\n", w->name); - } - - // nothing more to show - if(!newly_added) return; - - // we have something new to show - // update the charts - fprintf(stdout, "CHART %s.cpu '' '%s CPU Time (%ld%% = %ld core%s)' 'cpu time %%' cpu %s.cpu stacked 20001 %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 %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); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' absolute %ld %ld noreset\n", w->name, sysconf(_SC_PAGESIZE), 1024L*1024L); - } - - fprintf(stdout, "CHART %s.threads '' '%s Threads' 'threads' processes %s.threads stacked 20005 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' absolute 1 1 noreset\n", w->name); - } - - fprintf(stdout, "CHART %s.processes '' '%s Processes' 'processes' processes %s.processes stacked 20004 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' absolute 1 1 noreset\n", w->name); - } - - fprintf(stdout, "CHART %s.cpu_user '' '%s CPU User Time (%ld%% = %ld core%s)' 'cpu time %%' cpu %s.cpu_user stacked 20020 %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 %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 %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); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' incremental 1 1 noreset\n", w->name); - } - - fprintf(stdout, "CHART %s.minor_faults '' '%s Minor Page Faults' 'page faults/s' mem %s.minor_faults stacked 20011 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' incremental 1 1 noreset\n", w->name); - } - - fprintf(stdout, "CHART %s.lreads '' '%s Disk Logical Reads' 'kilobytes/s' disk %s.lreads stacked 20042 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' incremental 1 %d noreset\n", w->name, 1024); - } - - fprintf(stdout, "CHART %s.lwrites '' '%s I/O Logical Writes' 'kilobytes/s' disk %s.lwrites stacked 20042 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' incremental 1 %d noreset\n", w->name, 1024); - } - - fprintf(stdout, "CHART %s.preads '' '%s Disk Reads' 'kilobytes/s' disk %s.preads stacked 20002 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' incremental 1 %d noreset\n", w->name, 1024); - } - - fprintf(stdout, "CHART %s.pwrites '' '%s Disk Writes' 'kilobytes/s' disk %s.pwrites stacked 20002 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' incremental 1 %d noreset\n", w->name, 1024); - } - - fprintf(stdout, "CHART %s.files '' '%s Open Files' 'open files' disk %s.files stacked 20050 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' absolute 1 1 noreset\n", w->name); - } - - fprintf(stdout, "CHART %s.sockets '' '%s Open Sockets' 'open sockets' net %s.sockets stacked 20051 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' absolute 1 1 noreset\n", w->name); - } - - fprintf(stdout, "CHART %s.pipes '' '%s Pipes' 'open pipes' processes %s.pipes stacked 20053 %d\n", type, title, type, update_every); - for (w = root; w ; w = w->next) { - if(w->target || (!w->processes && !w->exposed)) continue; - - fprintf(stdout, "DIMENSION %s '' absolute 1 1 noreset\n", w->name); - } + struct target *w; + int newly_added = 0; + + for(w = root ; w ; w = w->next) { + if (w->target) continue; + + if (!w->exposed && w->processes) { + newly_added++; + w->exposed = 1; + if (debug || w->debug) fprintf(stderr, "apps.plugin: %s just added - regenerating charts.\n", w->name); + } + } + + // nothing more to show + if(!newly_added && show_guest_time == show_guest_time_old) return; + + // we have something new to show + // update the charts + buffer_sprintf(output, "CHART %s.cpu '' '%s CPU Time (%d%% = %d core%s)' 'cpu time %%' cpu %s.cpu stacked 20001 %d\n", type, title, (processors * 100), processors, (processors>1)?"s":"", type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu %s\n", w->name, hz * RATES_DETAIL / 100, w->hidden ? "hidden" : ""); + } + + buffer_sprintf(output, "CHART %s.mem '' '%s Dedicated Memory (w/o shared)' 'MB' mem %s.mem stacked 20003 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute %ld %ld\n", w->name, sysconf(_SC_PAGESIZE), 1024L*1024L); + } + + buffer_sprintf(output, "CHART %s.threads '' '%s Threads' 'threads' processes %s.threads stacked 20005 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 1\n", w->name); + } + + buffer_sprintf(output, "CHART %s.processes '' '%s Processes' 'processes' processes %s.processes stacked 20004 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 1\n", w->name); + } + + buffer_sprintf(output, "CHART %s.cpu_user '' '%s CPU User Time (%d%% = %d core%s)' 'cpu time %%' cpu %s.cpu_user stacked 20020 %d\n", type, title, (processors * 100), processors, (processors>1)?"s":"", type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, hz * RATES_DETAIL / 100LLU); + } + + buffer_sprintf(output, "CHART %s.cpu_system '' '%s CPU System Time (%d%% = %d 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(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, hz * RATES_DETAIL / 100LLU); + } + + if(show_guest_time) { + buffer_sprintf(output, "CHART %s.cpu_guest '' '%s CPU Guest Time (%d%% = %d core%s)' 'cpu time %%' cpu %s.cpu_system stacked 20022 %d\n", type, title, (processors * 100), processors, (processors > 1) ? "s" : "", type, update_every); + for (w = root; w; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, hz * RATES_DETAIL / 100LLU); + } + } + + buffer_sprintf(output, "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); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); + } + + buffer_sprintf(output, "CHART %s.minor_faults '' '%s Minor Page Faults' 'page faults/s' mem %s.minor_faults stacked 20011 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); + } + + buffer_sprintf(output, "CHART %s.lreads '' '%s Disk Logical Reads' 'kilobytes/s' disk %s.lreads stacked 20042 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); + } + + buffer_sprintf(output, "CHART %s.lwrites '' '%s I/O Logical Writes' 'kilobytes/s' disk %s.lwrites stacked 20042 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); + } + + buffer_sprintf(output, "CHART %s.preads '' '%s Disk Reads' 'kilobytes/s' disk %s.preads stacked 20002 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); + } + + buffer_sprintf(output, "CHART %s.pwrites '' '%s Disk Writes' 'kilobytes/s' disk %s.pwrites stacked 20002 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); + } + + if(enable_file_charts) { + buffer_sprintf(output, "CHART %s.files '' '%s Open Files' 'open files' disk %s.files stacked 20050 %d\n", type, + title, type, update_every); + for (w = root; w; w = w->next) { + if (unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 1\n", w->name); + } + + buffer_sprintf(output, "CHART %s.sockets '' '%s Open Sockets' 'open sockets' net %s.sockets stacked 20051 %d\n", + type, title, type, update_every); + for (w = root; w; w = w->next) { + if (unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 1\n", w->name); + } + + buffer_sprintf(output, "CHART %s.pipes '' '%s Pipes' 'open pipes' processes %s.pipes stacked 20053 %d\n", type, + title, type, update_every); + for (w = root; w; w = w->next) { + if (unlikely(w->exposed)) + buffer_sprintf(output, "DIMENSION %s '' absolute 1 1\n", w->name); + } + } } @@ -2344,152 +2688,245 @@ void send_charts_updates_to_netdata(struct target *root, const char *type, const void parse_args(int argc, char **argv) { - int i, freq = 0; - char *name = NULL; - - for(i = 1; i < argc; i++) { - if(!freq) { - int n = atoi(argv[i]); - if(n > 0) { - freq = n; - continue; - } - } - - if(strcmp("debug", argv[i]) == 0) { - debug = 1; - debug_flags = 0xffffffff; - continue; - } - - if(!name) { - name = argv[i]; - continue; - } - - error("Cannot understand option %s", argv[i]); - exit(1); - } - - if(freq > 0) update_every = freq; - if(!name) name = "groups"; - - if(read_apps_groups_conf(name)) { - error("Cannot read process groups %s", name); - exit(1); - } + int i, freq = 0; + char *name = NULL; + + for(i = 1; i < argc; i++) { + if(!freq) { + int n = atoi(argv[i]); + if(n > 0) { + freq = n; + continue; + } + } + + if(strcmp("debug", argv[i]) == 0) { + debug = 1; + // debug_flags = 0xffffffff; + continue; + } + + if(strcmp("no-childs", argv[i]) == 0 || strcmp("without-childs", argv[i]) == 0) { + include_exited_childs = 0; + continue; + } + + if(strcmp("with-childs", argv[i]) == 0) { + include_exited_childs = 1; + continue; + } + + if(strcmp("with-guest", argv[i]) == 0) { + enable_guest_charts = 1; + continue; + } + + if(strcmp("no-guest", argv[i]) == 0 || strcmp("without-guest", argv[i]) == 0) { + enable_guest_charts = 0; + continue; + } + + if(strcmp("with-files", argv[i]) == 0) { + enable_file_charts = 1; + continue; + } + + if(strcmp("no-files", argv[i]) == 0 || strcmp("without-files", argv[i]) == 0) { + enable_file_charts = 0; + continue; + } + + if(strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) { + fprintf(stderr, + "apps.plugin\n" + "(C) 2016 Costa Tsaousis" + "GPL v3+\n" + "This program is a data collector plugin for netdata.\n" + "\n" + "Valid command line options:\n" + "\n" + "SECONDS set the data collection frequency\n" + "\n" + "debug enable debugging (lot of output)\n" + "\n" + "with-childs\n" + "without-childs enable / disable aggregating exited\n" + " children resources into parents\n" + " (default is enabled)\n" + "\n" + "with-guest\n" + "without-guest enable / disable reporting guest charts\n" + " (default is disabled)\n" + "\n" + "with-files\n" + "without-files enable / disable reporting files, sockets, pipes\n" + " (default is enabled)\n" + "\n" + "NAME read apps_NAME.conf instead of\n" + " apps_groups.conf\n" + " (default NAME=groups)\n" + ); + exit(1); + } + + if(!name) { + name = argv[i]; + continue; + } + + error("Cannot understand option %s", argv[i]); + exit(1); + } + + if(freq > 0) update_every = freq; + if(!name) name = "groups"; + + if(read_apps_groups_conf(name)) { + error("Cannot read process groups %s", name); + exit(1); + } } int main(int argc, char **argv) { - // debug_flags = D_PROCFILE; + // debug_flags = D_PROCFILE; + + // set the name for logging + program_name = "apps.plugin"; - // set the name for logging - program_name = "apps.plugin"; + info("started on pid %d", getpid()); - // disable syslog for apps.plugin - error_log_syslog = 0; + // 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; + // 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"); - host_prefix = ""; - } - else info("Found NETDATA_HOST_PREFIX='%s'", host_prefix); + host_prefix = getenv("NETDATA_HOST_PREFIX"); + if(host_prefix == NULL) { + // info("NETDATA_HOST_PREFIX is not passed from netdata"); + host_prefix = ""; + } + // else info("Found NETDATA_HOST_PREFIX='%s'", host_prefix); - config_dir = getenv("NETDATA_CONFIG_DIR"); - if(config_dir == NULL) { - info("NETDATA_CONFIG_DIR is not passed from netdata"); - config_dir = CONFIG_DIR; - } - else info("Found NETDATA_CONFIG_DIR='%s'", config_dir); + config_dir = getenv("NETDATA_CONFIG_DIR"); + if(config_dir == NULL) { + // info("NETDATA_CONFIG_DIR is not passed from netdata"); + config_dir = CONFIG_DIR; + } + // 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); - } + 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; - get_HZ(); - pid_max = get_system_pid_max(); - processors = get_system_cpus(); - - parse_args(argc, argv); - - all_pids = calloc(sizeof(struct pid_stat *), (size_t) pid_max); - if(!all_pids) { - error("Cannot allocate %lu bytes of memory.", sizeof(struct pid_stat *) * pid_max); - printf("DISABLE\n"); - exit(1); - } - - 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); + procfile_adaptive_initial_allocation = 1; + + time_t started_t = time(NULL); + time_t current_t; + get_HZ(); + pid_max = get_system_pid_max(); + processors = get_system_cpus(); + + parse_args(argc, argv); + + all_pids_sortlist = callocz(sizeof(pid_t), (size_t)pid_max); + all_pids = callocz(sizeof(struct pid_stat *), (size_t) pid_max); + + output = buffer_create(1024); + buffer_sprintf(output, + "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" + "CHART netdata.apps_fix '' 'Apps Plugin Normalization Ratios' 'percentage' apps.plugin netdata.apps_fix line 140002 %1$d\n" + "DIMENSION utime '' absolute 1 %2$llu\n" + "DIMENSION stime '' absolute 1 %2$llu\n" + "DIMENSION gtime '' absolute 1 %2$llu\n" + "DIMENSION minflt '' absolute 1 %2$llu\n" + "DIMENSION majflt '' absolute 1 %2$llu\n" + , update_every + , RATES_DETAIL + ); + + if(include_exited_childs) + buffer_sprintf(output, + "CHART netdata.apps_children_fix '' 'Apps Plugin Exited Children Normalization Ratios' 'percentage' apps.plugin netdata.apps_children_fix line 140003 %1$d\n" + "DIMENSION cutime '' absolute 1 %2$llu\n" + "DIMENSION cstime '' absolute 1 %2$llu\n" + "DIMENSION cgtime '' absolute 1 %2$llu\n" + "DIMENSION cminflt '' absolute 1 %2$llu\n" + "DIMENSION cmajflt '' absolute 1 %2$llu\n" + , update_every + , RATES_DETAIL + ); #ifndef PROFILING_MODE - unsigned long long sunext = (time(NULL) - (time(NULL) % update_every) + update_every) * 1000000ULL; - unsigned long long sunow; + unsigned long long sunext = (time(NULL) - (time(NULL) % update_every) + update_every) * 1000000ULL; + unsigned long long sunow; #endif /* PROFILING_MODE */ - unsigned long long counter = 1; - for(;1; counter++) { + global_iterations_counter = 1; + for(;1; global_iterations_counter++) { #ifndef PROFILING_MODE - // delay until it is our time to run - while((sunow = timems()) < sunext) - usleep((useconds_t)(sunext - sunow)); + // delay until it is our time to run + while((sunow = time_usec()) < sunext) + sleep_usec(sunext - sunow); - // find the next time we need to run - while(timems() > sunext) - sunext += update_every * 1000000ULL; + // find the next time we need to run + while(time_usec() > sunext) + sunext += update_every * 1000000ULL; #endif /* PROFILING_MODE */ - if(!collect_data_for_all_processes_from_proc()) { - error("Cannot collect /proc data for running processes. Disabling apps.plugin..."); - printf("DISABLE\n"); - exit(1); - } + if(!collect_data_for_all_processes_from_proc()) { + error("Cannot collect /proc data for running processes. Disabling apps.plugin..."); + printf("DISABLE\n"); + exit(1); + } + + calculate_netdata_statistics(); + normalize_data(apps_groups_root_target); + + unsigned long long dt = send_resource_usage_to_netdata(); + + // this is smart enough to show only newly added apps, when needed + send_charts_updates_to_netdata(apps_groups_root_target, "apps", "Apps"); + send_charts_updates_to_netdata(users_root_target, "users", "Users"); + send_charts_updates_to_netdata(groups_root_target, "groups", "User Groups"); - calculate_netdata_statistics(); + send_collected_data_to_netdata(apps_groups_root_target, "apps", dt); + send_collected_data_to_netdata(users_root_target, "users", dt); + send_collected_data_to_netdata(groups_root_target, "groups", dt); - unsigned long long dt = send_resource_usage_to_netdata(); + show_guest_time_old = show_guest_time; - // this is smart enough to show only newly added apps, when needed - send_charts_updates_to_netdata(apps_groups_root_target, "apps", "Apps"); - send_charts_updates_to_netdata(users_root_target, "users", "Users"); - send_charts_updates_to_netdata(groups_root_target, "groups", "User Groups"); + //if(puts(buffer_tostring(output)) == EOF) + if(write(STDOUT_FILENO, buffer_tostring(output), buffer_strlen(output)) == -1) + fatal("Cannot send chart values to netdata."); - send_collected_data_to_netdata(apps_groups_root_target, "apps", dt); - send_collected_data_to_netdata(users_root_target, "users", dt); - send_collected_data_to_netdata(groups_root_target, "groups", dt); + // fflush(stdout); + buffer_flush(output); - if(debug) fprintf(stderr, "apps.plugin: done Loop No %llu\n", counter); + if(unlikely(debug)) + fprintf(stderr, "apps.plugin: done Loop No %llu\n", global_iterations_counter); - current_t = time(NULL); + current_t = time(NULL); #ifndef PROFILING_MODE - // restart check (14400 seconds) - if(current_t - started_t > 14400) exit(0); + // restart check (14400 seconds) + if(current_t - started_t > 14400) exit(0); #else - if(current_t - started_t > 10) exit(0); + if(current_t - started_t > 10) exit(0); #endif /* PROFILING_MODE */ - } + } } @@ -1,350 +1,311 @@ +#include "common.h" + +/* ------------------------------------------------------------------------- */ /* - * ANSI C Library for maintainance of AVL Balanced Trees - * - * ref.: - * G. M. Adelson-Velskij & E. M. Landis - * Doklady Akad. Nauk SSSR 146 (1962), 263-266 - * - * see also: - * D. E. Knuth: The Art of Computer Programming Vol.3 (Sorting and Searching) + * avl_insert(), avl_remove() and avl_search() + * are adaptations (by Costa Tsaousis) of the AVL algorithm found in libavl + * v2.0.3, so that they do not use any memory allocations and their memory + * footprint is optimized (by eliminating non-necessary data members). * - * (C) 2000 Daniel Nagy, Budapest University of Technology and Economics - * Released under GNU General Public License (GPL) version 2 - * - */ + * libavl - library for manipulation of binary trees. + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004 Free Software + * Foundation, Inc. + * GNU Lesser General Public License +*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include "avl.h" -#include "log.h" -/* Swing to the left - * Warning: no balance maintainance - */ -void avl_swl(avl** root) { - avl* a = *root; - avl* b = a->right; - *root = b; - a->right = b->left; - b->left = a; -} +/* Search |tree| for an item matching |item|, and return it if found. + Otherwise return |NULL|. */ +avl *avl_search(avl_tree *tree, avl *item) { + avl *p; -/* Swing to the right - * Warning: no balance maintainance - */ -void avl_swr(avl** root) { - avl* a = *root; - avl* b = a->left; - *root = b; - a->left = b->right; - b->right = a; -} + // assert (tree != NULL && item != NULL); -/* Balance maintainance after especially nasty swings - */ -void avl_nasty(avl* root) { - switch (root->balance) { - case -1: - root->left->balance = 0; - root->right->balance = 1; - break; - case 1: - root->left->balance = -1; - root->right->balance = 0; - break; - case 0: - root->left->balance = 0; - root->right->balance = 0; - } - root->balance = 0; -} + for (p = tree->root; p != NULL; ) { + int cmp = tree->compar(item, p); -/* Public methods */ + if (cmp < 0) + p = p->avl_link[0]; + else if (cmp > 0) + p = p->avl_link[1]; + else /* |cmp == 0| */ + return p; + } -/* Insert element a into the AVL tree t - * 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) { - /* initialize */ - a->left = 0; - a->right = 0; - a->balance = 0; - /* insert into an empty tree */ - if (!t->root) { - t->root = a; - return 1; - } - - if (t->compar(t->root, a) > 0) { - /* insert into the left subtree */ - if (t->root->left) { - avl_tree left_subtree; - left_subtree.root = t->root->left; - left_subtree.compar = t->compar; - if (avl_insert(&left_subtree, a)) { - switch (t->root->balance--) { - case 1: - return 0; - case 0: - return 1; - } - if (t->root->left->balance < 0) { - avl_swr(&(t->root)); - t->root->balance = 0; - t->root->right->balance = 0; - } else { - avl_swl(&(t->root->left)); - avl_swr(&(t->root)); - avl_nasty(t->root); - } - } else - t->root->left = left_subtree.root; - return 0; - } else { - t->root->left = a; - if (t->root->balance--) - return 0; - return 1; - } - } else { - /* insert into the right subtree */ - if (t->root->right) { - avl_tree right_subtree; - right_subtree.root = t->root->right; - right_subtree.compar = t->compar; - if (avl_insert(&right_subtree, a)) { - switch (t->root->balance++) { - case -1: - return 0; - case 0: - return 1; - } - if (t->root->right->balance > 0) { - avl_swl(&(t->root)); - t->root->balance = 0; - t->root->left->balance = 0; - } else { - avl_swr(&(t->root->right)); - avl_swl(&(t->root)); - avl_nasty(t->root); - } - } else - t->root->right = right_subtree.root; - return 0; - } else { - t->root->right = a; - if (t->root->balance++) - return 0; - return 1; - } - } -} - -/* 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 b; - if (t->root == a) - return avl_removeroot(t); - b = t->compar(t->root, a); - if (b >= 0) { - /* remove from the left subtree */ - int ch; - avl_tree left_subtree; - if ((left_subtree.root = t->root->left)) { - left_subtree.compar = t->compar; - ch = avl_remove(&left_subtree, a); - t->root->left = left_subtree.root; - if (ch) { - switch (t->root->balance++) { - case -1: - return -1; - case 0: - return 0; - } - switch (t->root->right->balance) { - case 0: - avl_swl(&(t->root)); - t->root->balance = -1; - t->root->left->balance = 1; - return 0; - case 1: - avl_swl(&(t->root)); - t->root->balance = 0; - t->root->left->balance = 0; - return -1; - } - avl_swr(&(t->root->right)); - avl_swl(&(t->root)); - avl_nasty(t->root); - return -1; - } - } - } - if (b <= 0) { - /* remove from the right subtree */ - int ch; - avl_tree right_subtree; - if ((right_subtree.root = t->root->right)) { - right_subtree.compar = t->compar; - ch = avl_remove(&right_subtree, a); - t->root->right = right_subtree.root; - if (ch) { - switch (t->root->balance--) { - case 1: - return -1; - case 0: - return 0; - } - switch (t->root->left->balance) { - case 0: - avl_swr(&(t->root)); - t->root->balance = 1; - t->root->right->balance = -1; - return 0; - case -1: - avl_swr(&(t->root)); - t->root->balance = 0; - t->root->right->balance = 0; - return -1; - } - avl_swl(&(t->root->left)); - avl_swr(&(t->root)); - avl_nasty(t->root); - return -1; - } - } - } - return 0; + return NULL; } -/* Remove the root of the AVL tree t - * Warning: dumps core if t is empty +/* Inserts |item| into |tree| and returns a pointer to |item|'s address. + If a duplicate item is found in the tree, + returns a pointer to the duplicate without inserting |item|. */ -int avl_removeroot(avl_tree* t) { - int ch; - avl* a; - if (!t->root->left) { - if (!t->root->right) { - t->root = 0; - return -1; - } - t->root = t->root->right; - return -1; - } - if (!t->root->right) { - t->root = t->root->left; - return -1; - } - if (t->root->balance < 0) { - /* remove from the left subtree */ - a = t->root->left; - while (a->right) - a = a->right; - } else { - /* remove from the right subtree */ - a = t->root->right; - while (a->left) - a = a->left; - } - ch = avl_remove(t, a); - a->left = t->root->left; - a->right = t->root->right; - a->balance = t->root->balance; - t->root = a; - if (a->balance == 0) - return ch; - return 0; +avl *avl_insert(avl_tree *tree, avl *item) { + avl *y, *z; /* Top node to update balance factor, and parent. */ + avl *p, *q; /* Iterator, and parent. */ + avl *n; /* Newly inserted node. */ + avl *w; /* New root of rebalanced subtree. */ + int dir; /* Direction to descend. */ + + unsigned char da[AVL_MAX_HEIGHT]; /* Cached comparison results. */ + int k = 0; /* Number of cached results. */ + + // assert(tree != NULL && item != NULL); + + z = (avl *) &tree->root; + y = tree->root; + dir = 0; + for (q = z, p = y; p != NULL; q = p, p = p->avl_link[dir]) { + int cmp = tree->compar(item, p); + if (cmp == 0) + return p; + + if (p->avl_balance != 0) + z = q, y = p, k = 0; + da[k++] = dir = (cmp > 0); + } + + n = q->avl_link[dir] = item; + + // tree->avl_count++; + n->avl_link[0] = n->avl_link[1] = NULL; + n->avl_balance = 0; + if (y == NULL) return n; + + for (p = y, k = 0; p != n; p = p->avl_link[da[k]], k++) + if (da[k] == 0) + p->avl_balance--; + else + p->avl_balance++; + + if (y->avl_balance == -2) { + avl *x = y->avl_link[0]; + if (x->avl_balance == -1) { + w = x; + y->avl_link[0] = x->avl_link[1]; + x->avl_link[1] = y; + x->avl_balance = y->avl_balance = 0; + } + else { + // assert (x->avl_balance == +1); + w = x->avl_link[1]; + x->avl_link[1] = w->avl_link[0]; + w->avl_link[0] = x; + y->avl_link[0] = w->avl_link[1]; + w->avl_link[1] = y; + if (w->avl_balance == -1) + x->avl_balance = 0, y->avl_balance = +1; + else if (w->avl_balance == 0) + x->avl_balance = y->avl_balance = 0; + else /* |w->avl_balance == +1| */ + x->avl_balance = -1, y->avl_balance = 0; + w->avl_balance = 0; + } + } + else if (y->avl_balance == +2) { + avl *x = y->avl_link[1]; + if (x->avl_balance == +1) { + w = x; + y->avl_link[1] = x->avl_link[0]; + x->avl_link[0] = y; + x->avl_balance = y->avl_balance = 0; + } + else { + // assert (x->avl_balance == -1); + w = x->avl_link[0]; + x->avl_link[0] = w->avl_link[1]; + w->avl_link[1] = x; + y->avl_link[1] = w->avl_link[0]; + w->avl_link[0] = y; + if (w->avl_balance == +1) + x->avl_balance = 0, y->avl_balance = -1; + else if (w->avl_balance == 0) + x->avl_balance = y->avl_balance = 0; + else /* |w->avl_balance == -1| */ + x->avl_balance = +1, y->avl_balance = 0; + w->avl_balance = 0; + } + } + else return n; + + z->avl_link[y != z->avl_link[0]] = w; + + // tree->avl_generation++; + return n; } -/* 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 x, c = 0; - if (!t->root) - return 0; - x = t->compar(t->root, a); - if (a != b) { - if (x < 0) { - x = t->compar(t->root, b); - if (x > 0) - x = 0; - } - } - if (x >= 0) { - /* search in the left subtree */ - 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 (x > 0) - return 0; - } - } - if (x == 0) { - if (!(c = iter(t->root))) { - if (ret) - *ret = t->root; - return 0; - } - } - if (x <= 0) { - /* search in the right subtree */ - 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 (x < 0) - return 0; - } - } - return c; +/* Deletes from |tree| and returns an item matching |item|. + Returns a null pointer if no matching item found. */ +avl *avl_remove(avl_tree *tree, avl *item) { + /* Stack of nodes. */ + avl *pa[AVL_MAX_HEIGHT]; /* Nodes. */ + unsigned char da[AVL_MAX_HEIGHT]; /* |avl_link[]| indexes. */ + int k; /* Stack pointer. */ + + avl *p; /* Traverses tree to find node to delete. */ + int cmp; /* Result of comparison between |item| and |p|. */ + + // assert (tree != NULL && item != NULL); + + k = 0; + p = (avl *) &tree->root; + for(cmp = -1; cmp != 0; cmp = tree->compar(item, p)) { + int dir = (cmp > 0); + + pa[k] = p; + da[k++] = dir; + + p = p->avl_link[dir]; + if(p == NULL) return NULL; + } + + item = p; + + if (p->avl_link[1] == NULL) + pa[k - 1]->avl_link[da[k - 1]] = p->avl_link[0]; + else { + avl *r = p->avl_link[1]; + if (r->avl_link[0] == NULL) { + r->avl_link[0] = p->avl_link[0]; + r->avl_balance = p->avl_balance; + pa[k - 1]->avl_link[da[k - 1]] = r; + da[k] = 1; + pa[k++] = r; + } + else { + avl *s; + int j = k++; + + for (;;) { + da[k] = 0; + pa[k++] = r; + s = r->avl_link[0]; + if (s->avl_link[0] == NULL) break; + + r = s; + } + + s->avl_link[0] = p->avl_link[0]; + r->avl_link[0] = s->avl_link[1]; + s->avl_link[1] = p->avl_link[1]; + s->avl_balance = p->avl_balance; + + pa[j - 1]->avl_link[da[j - 1]] = s; + da[j] = 1; + pa[j] = s; + } + } + + // assert (k > 0); + while (--k > 0) { + avl *y = pa[k]; + + if (da[k] == 0) { + y->avl_balance++; + if (y->avl_balance == +1) break; + else if (y->avl_balance == +2) { + avl *x = y->avl_link[1]; + if (x->avl_balance == -1) { + avl *w; + // assert (x->avl_balance == -1); + w = x->avl_link[0]; + x->avl_link[0] = w->avl_link[1]; + w->avl_link[1] = x; + y->avl_link[1] = w->avl_link[0]; + w->avl_link[0] = y; + if (w->avl_balance == +1) + x->avl_balance = 0, y->avl_balance = -1; + else if (w->avl_balance == 0) + x->avl_balance = y->avl_balance = 0; + else /* |w->avl_balance == -1| */ + x->avl_balance = +1, y->avl_balance = 0; + w->avl_balance = 0; + pa[k - 1]->avl_link[da[k - 1]] = w; + } + else { + y->avl_link[1] = x->avl_link[0]; + x->avl_link[0] = y; + pa[k - 1]->avl_link[da[k - 1]] = x; + if (x->avl_balance == 0) { + x->avl_balance = -1; + y->avl_balance = +1; + break; + } + else x->avl_balance = y->avl_balance = 0; + } + } + } + else + { + y->avl_balance--; + if (y->avl_balance == -1) break; + else if (y->avl_balance == -2) { + avl *x = y->avl_link[0]; + if (x->avl_balance == +1) { + avl *w; + // assert (x->avl_balance == +1); + w = x->avl_link[1]; + x->avl_link[1] = w->avl_link[0]; + w->avl_link[0] = x; + y->avl_link[0] = w->avl_link[1]; + w->avl_link[1] = y; + if (w->avl_balance == -1) + x->avl_balance = 0, y->avl_balance = +1; + else if (w->avl_balance == 0) + x->avl_balance = y->avl_balance = 0; + else /* |w->avl_balance == +1| */ + x->avl_balance = -1, y->avl_balance = 0; + w->avl_balance = 0; + pa[k - 1]->avl_link[da[k - 1]] = w; + } + else { + y->avl_link[0] = x->avl_link[1]; + x->avl_link[1] = y; + pa[k - 1]->avl_link[da[k - 1]] = x; + if (x->avl_balance == 0) { + x->avl_balance = +1; + y->avl_balance = -1; + break; + } + else x->avl_balance = y->avl_balance = 0; + } + } + } + } + + // tree->avl_count--; + // tree->avl_generation++; + return item; } -/* 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); +/* ------------------------------------------------------------------------- */ +// below are functions by (C) Costa Tsaousis - if(x > 0) { - root = root->left; - continue; - } +// --------------------------- +// traversing - if(x < 0) { - root = root->right; - continue; - } +void avl_walker(avl *node, void (*callback)(void *)) { + if(node->avl_link[0]) + avl_walker(node->avl_link[0], callback); - return root; - } + callback(node); - return NULL; + if(node->avl_link[1]) + avl_walker(node->avl_link[1], callback); } -void avl_init(avl_tree *t, int (*compar)(void *a, void *b)) { - t->root = NULL; - t->compar = compar; +void avl_traverse(avl_tree *t, void (*callback)(void *)) { + avl_walker(t->root, callback); } -/* ------------------------------------------------------------------------- */ +// --------------------------- +// locks void avl_read_lock(avl_tree_lock *t) { #ifndef AVL_WITHOUT_PTHREADS #ifdef AVL_LOCK_WITH_MUTEX - pthread_mutex_lock(&t->mutex); + pthread_mutex_lock(&t->mutex); #else - pthread_rwlock_rdlock(&t->rwlock); + pthread_rwlock_rdlock(&t->rwlock); #endif #endif /* AVL_WITHOUT_PTHREADS */ } @@ -352,9 +313,9 @@ void avl_read_lock(avl_tree_lock *t) { void avl_write_lock(avl_tree_lock *t) { #ifndef AVL_WITHOUT_PTHREADS #ifdef AVL_LOCK_WITH_MUTEX - pthread_mutex_lock(&t->mutex); + pthread_mutex_lock(&t->mutex); #else - pthread_rwlock_wrlock(&t->rwlock); + pthread_rwlock_wrlock(&t->rwlock); #endif #endif /* AVL_WITHOUT_PTHREADS */ } @@ -362,64 +323,64 @@ void avl_write_lock(avl_tree_lock *t) { void avl_unlock(avl_tree_lock *t) { #ifndef AVL_WITHOUT_PTHREADS #ifdef AVL_LOCK_WITH_MUTEX - pthread_mutex_unlock(&t->mutex); + pthread_mutex_unlock(&t->mutex); #else - pthread_rwlock_unlock(&t->rwlock); + pthread_rwlock_unlock(&t->rwlock); #endif #endif /* AVL_WITHOUT_PTHREADS */ } -/* ------------------------------------------------------------------------- */ +// --------------------------- +// operations with locking void avl_init_lock(avl_tree_lock *t, int (*compar)(void *a, void *b)) { - avl_init(&t->avl_tree, compar); + avl_init(&t->avl_tree, compar); #ifndef AVL_WITHOUT_PTHREADS - int lock; + int lock; #ifdef AVL_LOCK_WITH_MUTEX - lock = pthread_mutex_init(&t->mutex, NULL); + lock = pthread_mutex_init(&t->mutex, NULL); #else - lock = pthread_rwlock_init(&t->rwlock, NULL); + lock = pthread_rwlock_init(&t->rwlock, NULL); #endif - if(lock != 0) - fatal("Failed to initialize AVL mutex/rwlock, error: %d", lock); + if(lock != 0) + 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; + 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; +avl * avl_remove_lock(avl_tree_lock *t, avl *a) { + avl_write_lock(t); + avl *ret = avl_remove(&t->avl_tree, a); + avl_unlock(t); + return ret; } -int avl_removeroot_lock(avl_tree_lock *t) { - avl_write_lock(t); - int ret = avl_removeroot(&t->avl_tree); - avl_unlock(t); - return ret; +avl *avl_insert_lock(avl_tree_lock *t, avl *a) { + avl_write_lock(t); + avl * ret = avl_insert(&t->avl_tree, a); + 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; +void avl_traverse_lock(avl_tree_lock *t, void (*callback)(void *)) { + avl_read_lock(t); + avl_traverse(&t->avl_tree, callback); + avl_unlock(t); } -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; +void avl_init(avl_tree *t, int (*compar)(void *a, void *b)) { + t->root = NULL; + t->compar = compar; } + +// ------------------
\ No newline at end of file @@ -1,20 +1,12 @@ -/* - * ANSI C Library for maintainance of AVL Balanced Trees - * - * ref.: - * G. M. Adelson-Velskij & E. M. Landis - * Doklady Akad. Nauk SSSR 146 (1962), 263-266 - * - * see also: - * D. E. Knuth: The Art of Computer Programming Vol.3 (Sorting and Searching) - * - * (C) 2000 Daniel Nagy, Budapest University of Technology and Economics - * Released under GNU General Public License (GPL) version 2 - * - */ + #ifndef _AVL_H #define _AVL_H 1 +/* Maximum AVL tree height. */ +#ifndef AVL_MAX_HEIGHT +#define AVL_MAX_HEIGHT 92 +#endif + #ifndef AVL_WITHOUT_PTHREADS #include <pthread.h> @@ -34,26 +26,24 @@ /* One element of the AVL tree */ typedef struct avl { - struct avl* left; - struct avl* right; - signed char balance; + struct avl *avl_link[2]; /* Subtrees. */ + signed char avl_balance; /* Balance factor. */ } 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; + avl_tree avl_tree; #ifndef AVL_WITHOUT_PTHREADS #ifdef AVL_LOCK_WITH_MUTEX - pthread_mutex_t mutex; + pthread_mutex_t mutex; #else /* AVL_LOCK_WITH_MUTEX */ - pthread_rwlock_t rwlock; + pthread_rwlock_t rwlock; #endif /* AVL_LOCK_WITH_MUTEX */ #endif /* AVL_WITHOUT_PTHREADS */ } avl_tree_lock; @@ -61,37 +51,25 @@ typedef struct avl_tree_lock { /* Public methods */ /* Insert element a into the AVL tree t - * returns 1 if the depth of the tree has grown - * Warning: do not insert elements already present + * returns the added element a, or a pointer the + * element that is equal to a (as returned by t->compar()) + * a is linked directly to the tree, so it has to + * be properly allocated by the caller. */ -int avl_insert_lock(avl_tree_lock *t, avl *a); -int avl_insert(avl_tree *t, avl *a); +avl *avl_insert_lock(avl_tree_lock *t, avl *a); +avl *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_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_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 + * returns a pointer to the removed element + * or NULL if an element equal to a is not found + * (equal as returned by t->compar()) */ -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); +avl *avl_remove_lock(avl_tree_lock *t, avl *a); +avl *avl_remove(avl_tree *t, avl *a); -/* 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 +/* Find the element into the tree that equal to a + * (equal as returned by t->compar()) + * returns NULL is no element is equal to a */ avl *avl_search_lock(avl_tree_lock *t, avl *a); avl *avl_search(avl_tree *t, avl *a); @@ -101,4 +79,8 @@ avl *avl_search(avl_tree *t, avl *a); 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)); + +void avl_traverse_lock(avl_tree_lock *t, void (*callback)(void *)); +void avl_traverse(avl_tree *t, void (*callback)(void *)); + #endif /* avl.h */ diff --git a/src/common.c b/src/common.c index a2b0d940f..7d0fac9aa 100644 --- a/src/common.c +++ b/src/common.c @@ -1,752 +1,859 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <sys/syscall.h> -#include <string.h> -#include <ctype.h> -#include <errno.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <sys/mman.h> - - -#include "log.h" #include "common.h" -#include "appconfig.h" -#include "../config.h" char *global_host_prefix = ""; int enable_ksm = 1; -// time(NULL) in milliseconds -unsigned long long timems(void) { - struct timeval now; - gettimeofday(&now, NULL); - return now.tv_sec * 1000000ULL + now.tv_usec; +volatile sig_atomic_t netdata_exit = 0; + +// ---------------------------------------------------------------------------- +// memory allocation functions that handle failures + +// although netdata does not use memory allocations too often (netdata tries to +// maintain its memory footprint stable during runtime, i.e. all buffers are +// allocated during initialization and are adapted to current use throughout +// its lifetime), these can be used to override the default system allocation +// routines. + +char *strdupz(const char *s) { + char *t = strdup(s); + if (unlikely(!t)) fatal("Cannot strdup() string '%s'", s); + return t; +} + +void *mallocz(size_t size) { + void *p = malloc(size); + if (unlikely(!p)) fatal("Cannot allocate %zu bytes of memory.", size); + return p; +} + +void *callocz(size_t nmemb, size_t size) { + void *p = calloc(nmemb, size); + if (unlikely(!p)) fatal("Cannot allocate %zu bytes of memory.", nmemb * size); + return p; +} + +void *reallocz(void *ptr, size_t size) { + void *p = realloc(ptr, size); + if (unlikely(!p)) fatal("Cannot re-allocate memory to %zu bytes.", size); + return p; +} + +void freez(void *ptr) { + free(ptr); +} + +// ---------------------------------------------------------------------------- +// time functions + +inline unsigned long long timeval_usec(struct timeval *tv) { + return tv->tv_sec * 1000000ULL + tv->tv_usec; +} + +// time(NULL) in nanoseconds +inline unsigned long long time_usec(void) { + struct timeval now; + gettimeofday(&now, NULL); + return timeval_usec(&now); +} + +inline unsigned long long usec_dt(struct timeval *now, struct timeval *old) { + unsigned long long tv1 = timeval_usec(now); + unsigned long long tv2 = timeval_usec(old); + return (tv1 > tv2) ? (tv1 - tv2) : (tv2 - tv1); +} + +int sleep_usec(unsigned long long usec) { + +#ifndef NETDATA_WITH_USLEEP + // we expect microseconds (1.000.000 per second) + // but timespec is nanoseconds (1.000.000.000 per second) + struct timespec rem, req = { + .tv_sec = (time_t) (usec / 1000000), + .tv_nsec = (suseconds_t) ((usec % 1000000) * 1000) + }; + + while (nanosleep(&req, &rem) == -1) { + if (likely(errno == EINTR)) { + info("nanosleep() interrupted (while sleeping for %llu microseconds).", usec); + req.tv_sec = rem.tv_sec; + req.tv_nsec = rem.tv_nsec; + } else { + error("Cannot nanosleep() for %llu microseconds.", usec); + break; + } + } + + return 0; +#else + int ret = usleep(usec); + if(unlikely(ret == -1 && errno == EINVAL)) { + // on certain systems, usec has to be up to 999999 + if(usec > 999999) { + int counter = usec / 999999; + while(counter--) + usleep(999999); + + usleep(usec % 999999); + } + else { + error("Cannot usleep() for %llu microseconds.", usec); + return ret; + } + } + + if(ret != 0) + error("usleep() failed for %llu microseconds.", usec); + + return ret; +#endif } unsigned char netdata_map_chart_names[256] = { - [0] = '\0', // - [1] = '_', // - [2] = '_', // - [3] = '_', // - [4] = '_', // - [5] = '_', // - [6] = '_', // - [7] = '_', // - [8] = '_', // - [9] = '_', // - [10] = '_', // - [11] = '_', // - [12] = '_', // - [13] = '_', // - [14] = '_', // - [15] = '_', // - [16] = '_', // - [17] = '_', // - [18] = '_', // - [19] = '_', // - [20] = '_', // - [21] = '_', // - [22] = '_', // - [23] = '_', // - [24] = '_', // - [25] = '_', // - [26] = '_', // - [27] = '_', // - [28] = '_', // - [29] = '_', // - [30] = '_', // - [31] = '_', // - [32] = '_', // - [33] = '_', // ! - [34] = '_', // " - [35] = '_', // # - [36] = '_', // $ - [37] = '_', // % - [38] = '_', // & - [39] = '_', // ' - [40] = '_', // ( - [41] = '_', // ) - [42] = '_', // * - [43] = '_', // + - [44] = '.', // , - [45] = '-', // - - [46] = '.', // . - [47] = '/', // / - [48] = '0', // 0 - [49] = '1', // 1 - [50] = '2', // 2 - [51] = '3', // 3 - [52] = '4', // 4 - [53] = '5', // 5 - [54] = '6', // 6 - [55] = '7', // 7 - [56] = '8', // 8 - [57] = '9', // 9 - [58] = '_', // : - [59] = '_', // ; - [60] = '_', // < - [61] = '_', // = - [62] = '_', // > - [63] = '_', // ? - [64] = '_', // @ - [65] = 'a', // A - [66] = 'b', // B - [67] = 'c', // C - [68] = 'd', // D - [69] = 'e', // E - [70] = 'f', // F - [71] = 'g', // G - [72] = 'h', // H - [73] = 'i', // I - [74] = 'j', // J - [75] = 'k', // K - [76] = 'l', // L - [77] = 'm', // M - [78] = 'n', // N - [79] = 'o', // O - [80] = 'p', // P - [81] = 'q', // Q - [82] = 'r', // R - [83] = 's', // S - [84] = 't', // T - [85] = 'u', // U - [86] = 'v', // V - [87] = 'w', // W - [88] = 'x', // X - [89] = 'y', // Y - [90] = 'z', // Z - [91] = '_', // [ - [92] = '/', // backslash - [93] = '_', // ] - [94] = '_', // ^ - [95] = '_', // _ - [96] = '_', // ` - [97] = 'a', // a - [98] = 'b', // b - [99] = 'c', // c - [100] = 'd', // d - [101] = 'e', // e - [102] = 'f', // f - [103] = 'g', // g - [104] = 'h', // h - [105] = 'i', // i - [106] = 'j', // j - [107] = 'k', // k - [108] = 'l', // l - [109] = 'm', // m - [110] = 'n', // n - [111] = 'o', // o - [112] = 'p', // p - [113] = 'q', // q - [114] = 'r', // r - [115] = 's', // s - [116] = 't', // t - [117] = 'u', // u - [118] = 'v', // v - [119] = 'w', // w - [120] = 'x', // x - [121] = 'y', // y - [122] = 'z', // z - [123] = '_', // { - [124] = '_', // | - [125] = '_', // } - [126] = '_', // ~ - [127] = '_', // - [128] = '_', // - [129] = '_', // - [130] = '_', // - [131] = '_', // - [132] = '_', // - [133] = '_', // - [134] = '_', // - [135] = '_', // - [136] = '_', // - [137] = '_', // - [138] = '_', // - [139] = '_', // - [140] = '_', // - [141] = '_', // - [142] = '_', // - [143] = '_', // - [144] = '_', // - [145] = '_', // - [146] = '_', // - [147] = '_', // - [148] = '_', // - [149] = '_', // - [150] = '_', // - [151] = '_', // - [152] = '_', // - [153] = '_', // - [154] = '_', // - [155] = '_', // - [156] = '_', // - [157] = '_', // - [158] = '_', // - [159] = '_', // - [160] = '_', // - [161] = '_', // - [162] = '_', // - [163] = '_', // - [164] = '_', // - [165] = '_', // - [166] = '_', // - [167] = '_', // - [168] = '_', // - [169] = '_', // - [170] = '_', // - [171] = '_', // - [172] = '_', // - [173] = '_', // - [174] = '_', // - [175] = '_', // - [176] = '_', // - [177] = '_', // - [178] = '_', // - [179] = '_', // - [180] = '_', // - [181] = '_', // - [182] = '_', // - [183] = '_', // - [184] = '_', // - [185] = '_', // - [186] = '_', // - [187] = '_', // - [188] = '_', // - [189] = '_', // - [190] = '_', // - [191] = '_', // - [192] = '_', // - [193] = '_', // - [194] = '_', // - [195] = '_', // - [196] = '_', // - [197] = '_', // - [198] = '_', // - [199] = '_', // - [200] = '_', // - [201] = '_', // - [202] = '_', // - [203] = '_', // - [204] = '_', // - [205] = '_', // - [206] = '_', // - [207] = '_', // - [208] = '_', // - [209] = '_', // - [210] = '_', // - [211] = '_', // - [212] = '_', // - [213] = '_', // - [214] = '_', // - [215] = '_', // - [216] = '_', // - [217] = '_', // - [218] = '_', // - [219] = '_', // - [220] = '_', // - [221] = '_', // - [222] = '_', // - [223] = '_', // - [224] = '_', // - [225] = '_', // - [226] = '_', // - [227] = '_', // - [228] = '_', // - [229] = '_', // - [230] = '_', // - [231] = '_', // - [232] = '_', // - [233] = '_', // - [234] = '_', // - [235] = '_', // - [236] = '_', // - [237] = '_', // - [238] = '_', // - [239] = '_', // - [240] = '_', // - [241] = '_', // - [242] = '_', // - [243] = '_', // - [244] = '_', // - [245] = '_', // - [246] = '_', // - [247] = '_', // - [248] = '_', // - [249] = '_', // - [250] = '_', // - [251] = '_', // - [252] = '_', // - [253] = '_', // - [254] = '_', // - [255] = '_' // + [0] = '\0', // + [1] = '_', // + [2] = '_', // + [3] = '_', // + [4] = '_', // + [5] = '_', // + [6] = '_', // + [7] = '_', // + [8] = '_', // + [9] = '_', // + [10] = '_', // + [11] = '_', // + [12] = '_', // + [13] = '_', // + [14] = '_', // + [15] = '_', // + [16] = '_', // + [17] = '_', // + [18] = '_', // + [19] = '_', // + [20] = '_', // + [21] = '_', // + [22] = '_', // + [23] = '_', // + [24] = '_', // + [25] = '_', // + [26] = '_', // + [27] = '_', // + [28] = '_', // + [29] = '_', // + [30] = '_', // + [31] = '_', // + [32] = '_', // + [33] = '_', // ! + [34] = '_', // " + [35] = '_', // # + [36] = '_', // $ + [37] = '_', // % + [38] = '_', // & + [39] = '_', // ' + [40] = '_', // ( + [41] = '_', // ) + [42] = '_', // * + [43] = '_', // + + [44] = '.', // , + [45] = '-', // - + [46] = '.', // . + [47] = '/', // / + [48] = '0', // 0 + [49] = '1', // 1 + [50] = '2', // 2 + [51] = '3', // 3 + [52] = '4', // 4 + [53] = '5', // 5 + [54] = '6', // 6 + [55] = '7', // 7 + [56] = '8', // 8 + [57] = '9', // 9 + [58] = '_', // : + [59] = '_', // ; + [60] = '_', // < + [61] = '_', // = + [62] = '_', // > + [63] = '_', // ? + [64] = '_', // @ + [65] = 'a', // A + [66] = 'b', // B + [67] = 'c', // C + [68] = 'd', // D + [69] = 'e', // E + [70] = 'f', // F + [71] = 'g', // G + [72] = 'h', // H + [73] = 'i', // I + [74] = 'j', // J + [75] = 'k', // K + [76] = 'l', // L + [77] = 'm', // M + [78] = 'n', // N + [79] = 'o', // O + [80] = 'p', // P + [81] = 'q', // Q + [82] = 'r', // R + [83] = 's', // S + [84] = 't', // T + [85] = 'u', // U + [86] = 'v', // V + [87] = 'w', // W + [88] = 'x', // X + [89] = 'y', // Y + [90] = 'z', // Z + [91] = '_', // [ + [92] = '/', // backslash + [93] = '_', // ] + [94] = '_', // ^ + [95] = '_', // _ + [96] = '_', // ` + [97] = 'a', // a + [98] = 'b', // b + [99] = 'c', // c + [100] = 'd', // d + [101] = 'e', // e + [102] = 'f', // f + [103] = 'g', // g + [104] = 'h', // h + [105] = 'i', // i + [106] = 'j', // j + [107] = 'k', // k + [108] = 'l', // l + [109] = 'm', // m + [110] = 'n', // n + [111] = 'o', // o + [112] = 'p', // p + [113] = 'q', // q + [114] = 'r', // r + [115] = 's', // s + [116] = 't', // t + [117] = 'u', // u + [118] = 'v', // v + [119] = 'w', // w + [120] = 'x', // x + [121] = 'y', // y + [122] = 'z', // z + [123] = '_', // { + [124] = '_', // | + [125] = '_', // } + [126] = '_', // ~ + [127] = '_', // + [128] = '_', // + [129] = '_', // + [130] = '_', // + [131] = '_', // + [132] = '_', // + [133] = '_', // + [134] = '_', // + [135] = '_', // + [136] = '_', // + [137] = '_', // + [138] = '_', // + [139] = '_', // + [140] = '_', // + [141] = '_', // + [142] = '_', // + [143] = '_', // + [144] = '_', // + [145] = '_', // + [146] = '_', // + [147] = '_', // + [148] = '_', // + [149] = '_', // + [150] = '_', // + [151] = '_', // + [152] = '_', // + [153] = '_', // + [154] = '_', // + [155] = '_', // + [156] = '_', // + [157] = '_', // + [158] = '_', // + [159] = '_', // + [160] = '_', // + [161] = '_', // + [162] = '_', // + [163] = '_', // + [164] = '_', // + [165] = '_', // + [166] = '_', // + [167] = '_', // + [168] = '_', // + [169] = '_', // + [170] = '_', // + [171] = '_', // + [172] = '_', // + [173] = '_', // + [174] = '_', // + [175] = '_', // + [176] = '_', // + [177] = '_', // + [178] = '_', // + [179] = '_', // + [180] = '_', // + [181] = '_', // + [182] = '_', // + [183] = '_', // + [184] = '_', // + [185] = '_', // + [186] = '_', // + [187] = '_', // + [188] = '_', // + [189] = '_', // + [190] = '_', // + [191] = '_', // + [192] = '_', // + [193] = '_', // + [194] = '_', // + [195] = '_', // + [196] = '_', // + [197] = '_', // + [198] = '_', // + [199] = '_', // + [200] = '_', // + [201] = '_', // + [202] = '_', // + [203] = '_', // + [204] = '_', // + [205] = '_', // + [206] = '_', // + [207] = '_', // + [208] = '_', // + [209] = '_', // + [210] = '_', // + [211] = '_', // + [212] = '_', // + [213] = '_', // + [214] = '_', // + [215] = '_', // + [216] = '_', // + [217] = '_', // + [218] = '_', // + [219] = '_', // + [220] = '_', // + [221] = '_', // + [222] = '_', // + [223] = '_', // + [224] = '_', // + [225] = '_', // + [226] = '_', // + [227] = '_', // + [228] = '_', // + [229] = '_', // + [230] = '_', // + [231] = '_', // + [232] = '_', // + [233] = '_', // + [234] = '_', // + [235] = '_', // + [236] = '_', // + [237] = '_', // + [238] = '_', // + [239] = '_', // + [240] = '_', // + [241] = '_', // + [242] = '_', // + [243] = '_', // + [244] = '_', // + [245] = '_', // + [246] = '_', // + [247] = '_', // + [248] = '_', // + [249] = '_', // + [250] = '_', // + [251] = '_', // + [252] = '_', // + [253] = '_', // + [254] = '_', // + [255] = '_' // }; // make sure the supplied string // is good for a netdata chart/dimension ID/NAME void netdata_fix_chart_name(char *s) { - while((*s = netdata_map_chart_names[(unsigned char)*s])) s++; + 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] = '_' // + [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++; + while ((*s = netdata_map_chart_ids[(unsigned char) *s])) s++; } /* // http://stackoverflow.com/questions/7666509/hash-function-for-string uint32_t simple_hash(const char *name) { - const char *s = name; - uint32_t hash = 5381; - int i; + const char *s = name; + uint32_t hash = 5381; + int i; - while((i = *s++)) hash = ((hash << 5) + hash) + i; + while((i = *s++)) hash = ((hash << 5) + hash) + i; - // fprintf(stderr, "HASH: %lu %s\n", hash, name); + // fprintf(stderr, "HASH: %lu %s\n", hash, name); - return hash; + return hash; } */ // http://isthe.com/chongo/tech/comp/fnv/#FNV-1a uint32_t simple_hash(const char *name) { - unsigned char *s = (unsigned char *)name; - uint32_t hval = 0x811c9dc5; - - // FNV-1a algorithm - while (*s) { - // multiply by the 32 bit FNV magic prime mod 2^32 - // 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++; - } - - // fprintf(stderr, "HASH: %u = %s\n", hval, name); - return hval; + unsigned char *s = (unsigned char *) name; + uint32_t hval = 0x811c9dc5; + + // FNV-1a algorithm + while (*s) { + // multiply by the 32 bit FNV magic prime mod 2^32 + // 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++; + } + + // fprintf(stderr, "HASH: %u = %s\n", hval, name); + return hval; +} + +uint32_t simple_uhash(const char *name) { + unsigned char *s = (unsigned char *) name; + uint32_t hval = 0x811c9dc5, c; + + // FNV-1a algorithm + while ((c = *s++)) { + if (unlikely(c >= 'A' && c <= 'Z')) c += 'a' - 'A'; + hval *= 16777619; + hval ^= c; + } + return hval; } /* // http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx // one at a time hash uint32_t simple_hash(const char *name) { - unsigned char *s = (unsigned char *)name; - uint32_t h = 0; + unsigned char *s = (unsigned char *)name; + uint32_t h = 0; - while(*s) { - h += *s++; - h += (h << 10); - h ^= (h >> 6); - } + while(*s) { + h += *s++; + h += (h << 10); + h ^= (h >> 6); + } - h += (h << 3); - h ^= (h >> 11); - h += (h << 15); + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); - // fprintf(stderr, "HASH: %u = %s\n", h, name); + // fprintf(stderr, "HASH: %u = %s\n", h, name); - return h; + return h; } */ -void strreverse(char* begin, char* end) -{ - char aux; - - while (end > begin) - { - // clearer code. - aux = *end; - *end-- = *begin; - *begin++ = aux; - } +void strreverse(char *begin, char *end) { + char aux; + + while (end > begin) { + // clearer code. + aux = *end; + *end-- = *begin; + *begin++ = aux; + } } -char *mystrsep(char **ptr, char *s) -{ - char *p = ""; - while ( p && !p[0] && *ptr ) p = strsep(ptr, s); - return(p); +char *mystrsep(char **ptr, char *s) { + char *p = ""; + while (p && !p[0] && *ptr) p = strsep(ptr, s); + return (p); } -char *trim(char *s) -{ - // skip leading spaces - // and 'comments' as well!? - while(*s && isspace(*s)) s++; - if(!*s || *s == '#') return NULL; - - // skip tailing spaces - // 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(!*s) return NULL; - - return 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 + // 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 (!*s) return NULL; + + return s; } -void *mymmap(const char *filename, size_t size, int flags, int ksm) -{ - int fd; - void *mem = NULL; +void *mymmap(const char *filename, size_t size, int flags, int ksm) { + static int log_madvise_1 = 1; +#ifdef MADV_MERGEABLE + static int log_madvise_2 = 1, log_madvise_3 = 1; +#endif + int fd; + void *mem = NULL; - errno = 0; - fd = open(filename, O_RDWR|O_CREAT|O_NOATIME, 0664); - if(fd != -1) { - if(lseek(fd, size, SEEK_SET) == (long)size) { - if(write(fd, "", 1) == 1) { - if(ftruncate(fd, size)) - error("Cannot truncate file '%s' to size %ld. Will use the larger file.", filename, size); + errno = 0; + fd = open(filename, O_RDWR | O_CREAT | O_NOATIME, 0664); + if (fd != -1) { + if (lseek(fd, size, SEEK_SET) == (off_t) size) { + if (write(fd, "", 1) == 1) { + if (ftruncate(fd, size)) + error("Cannot truncate file '%s' to size %zu. Will use the larger file.", filename, size); #ifdef MADV_MERGEABLE - if(flags & MAP_SHARED || !enable_ksm || !ksm) { + if (flags & MAP_SHARED || !enable_ksm || !ksm) { #endif - mem = mmap(NULL, size, PROT_READ|PROT_WRITE, flags, fd, 0); - if(mem) { - int advise = MADV_SEQUENTIAL|MADV_DONTFORK; - if(flags & MAP_SHARED) advise |= MADV_WILLNEED; - - if(madvise(mem, size, advise) != 0) - error("Cannot advise the kernel about the memory usage of file '%s'.", filename); - } + mem = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, fd, 0); + if (mem != MAP_FAILED) { + int advise = MADV_SEQUENTIAL | MADV_DONTFORK; + if (flags & MAP_SHARED) advise |= MADV_WILLNEED; + + if (madvise(mem, size, advise) != 0 && log_madvise_1) { + error("Cannot advise the kernel about the memory usage of file '%s'.", filename); + log_madvise_1--; + } + } #ifdef MADV_MERGEABLE - } - else { - mem = mmap(NULL, size, PROT_READ|PROT_WRITE, flags|MAP_ANONYMOUS, -1, 0); - if(mem) { - if(lseek(fd, 0, SEEK_SET) == 0) { - if(read(fd, mem, size) != (ssize_t)size) - error("Cannot read from file '%s'", filename); - } - else - error("Cannot seek to beginning of file '%s'.", filename); - - // don't use MADV_SEQUENTIAL|MADV_DONTFORK, they disable MADV_MERGEABLE - if(madvise(mem, size, MADV_SEQUENTIAL|MADV_DONTFORK) != 0) - error("Cannot advise the kernel about the memory usage (MADV_SEQUENTIAL|MADV_DONTFORK) of file '%s'.", filename); - - if(madvise(mem, size, MADV_MERGEABLE) != 0) - error("Cannot advise the kernel about the memory usage (MADV_MERGEABLE) of file '%s'.", filename); - } - else - error("Cannot allocate PRIVATE ANONYMOUS memory for KSM for file '%s'.", filename); - } + } else { +/* + // test - load the file into memory + mem = calloc(1, size); + if(mem) { + if(lseek(fd, 0, SEEK_SET) == 0) { + if(read(fd, mem, size) != (ssize_t)size) + error("Cannot read from file '%s'", filename); + } + else + error("Cannot seek to beginning of file '%s'.", filename); + } +*/ + mem = mmap(NULL, size, PROT_READ | PROT_WRITE, flags | MAP_ANONYMOUS, -1, 0); + if (mem != MAP_FAILED) { + if (lseek(fd, 0, SEEK_SET) == 0) { + if (read(fd, mem, size) != (ssize_t) size) + error("Cannot read from file '%s'", filename); + } else + error("Cannot seek to beginning of file '%s'.", filename); + + // don't use MADV_SEQUENTIAL|MADV_DONTFORK, they disable MADV_MERGEABLE + if (madvise(mem, size, MADV_SEQUENTIAL | MADV_DONTFORK) != 0 && log_madvise_2) { + error("Cannot advise the kernel about the memory usage (MADV_SEQUENTIAL|MADV_DONTFORK) of file '%s'.", + filename); + log_madvise_2--; + } + + if (madvise(mem, size, MADV_MERGEABLE) != 0 && log_madvise_3) { + error("Cannot advise the kernel about the memory usage (MADV_MERGEABLE) of file '%s'.", + filename); + log_madvise_3--; + } + } else + error("Cannot allocate PRIVATE ANONYMOUS memory for KSM for file '%s'.", filename); + } #endif - } - else error("Cannot write to file '%s' at position %ld.", filename, size); - } - else error("Cannot seek file '%s' to size %ld.", filename, size); + } else + error("Cannot write to file '%s' at position %zu.", filename, size); + } else + error("Cannot seek file '%s' to size %zu.", filename, size); - close(fd); - } - else error("Cannot create/open file '%s'.", filename); + close(fd); + } else + error("Cannot create/open file '%s'.", filename); - return mem; + return mem; } -int savememory(const char *filename, void *mem, unsigned long size) -{ - char tmpfilename[FILENAME_MAX + 1]; +int savememory(const char *filename, void *mem, size_t size) { + char tmpfilename[FILENAME_MAX + 1]; - snprintfz(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) { - error("Cannot create/open file '%s'.", filename); - return -1; - } + int fd = open(tmpfilename, O_RDWR | O_CREAT | O_NOATIME, 0664); + if (fd < 0) { + error("Cannot create/open file '%s'.", filename); + return -1; + } - if(write(fd, mem, size) != (long)size) { - error("Cannot write to file '%s' %ld bytes.", filename, (long)size); - close(fd); - return -1; - } + if (write(fd, mem, size) != (ssize_t) size) { + error("Cannot write to file '%s' %ld bytes.", filename, (long) size); + close(fd); + return -1; + } - close(fd); + close(fd); - int ret = 0; - if(rename(tmpfilename, filename)) { - error("Cannot rename '%s' to '%s'", tmpfilename, filename); - ret = -1; - } + if (rename(tmpfilename, filename)) { + error("Cannot rename '%s' to '%s'", tmpfilename, filename); + return -1; + } - return ret; + return 0; } int fd_is_valid(int fd) { @@ -760,73 +867,69 @@ int fd_is_valid(int fd) { */ unsigned int hz; -void get_HZ(void) -{ - long ticks; +void get_HZ(void) { + long ticks; - if ((ticks = sysconf(_SC_CLK_TCK)) == -1) { - perror("sysconf"); - } + if ((ticks = sysconf(_SC_CLK_TCK)) == -1) { + perror("sysconf"); + } - hz = (unsigned int) ticks; + hz = (unsigned int) ticks; } -pid_t gettid(void) -{ - return syscall(SYS_gettid); +pid_t gettid(void) { + return (pid_t)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 *s = fgets(buf, (int)buf_size, fp); + if (!s) return NULL; - char *t = s; - if(*t != '\0') { - // find the string end - while (*++t != '\0'); + 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'; - } + // trim trailing spaces/newlines/tabs + while (--t > s && *t == '\n') + *t = '\0'; + } - if(len) - *len = t - s + 1; + if (len) + *len = t - s + 1; - return s; + return s; } char *strncpyz(char *dst, const char *src, size_t n) { - char *p = dst; + char *p = dst; - while(*src && n--) - *dst++ = *src++; + while (*src && n--) + *dst++ = *src++; - *dst = '\0'; + *dst = '\0'; - return p; + return p; } int vsnprintfz(char *dst, size_t n, const char *fmt, va_list args) { - int size; + int size = vsnprintf(dst, n, fmt, args); - size = vsnprintf(dst, n, fmt, args); + if (unlikely((size_t) size > n)) { + // truncated + size = (int)n; + } - 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; + dst[size] = '\0'; + return size; } int snprintfz(char *dst, size_t n, const char *fmt, ...) { - va_list args; + va_list args; + + va_start(args, fmt); + int ret = vsnprintfz(dst, n, fmt, args); + va_end(args); - va_start(args, fmt); - return vsnprintfz(dst, n, fmt, args); - va_end(args); + return ret; } diff --git a/src/common.h b/src/common.h index c94f1cde5..a6e85034b 100644 --- a/src/common.h +++ b/src/common.h @@ -1,36 +1,152 @@ +#ifndef NETDATA_COMMON_H +#define NETDATA_COMMON_H 1 + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pthread.h> +#include <errno.h> + +#include <stdio.h> +#include <stdlib.h> #include <stdarg.h> -#include <sys/time.h> +#include <stddef.h> + +#include <ctype.h> +#include <string.h> +#include <strings.h> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <dirent.h> +#include <fcntl.h> +#include <getopt.h> +#include <grp.h> +#include <pwd.h> +#include <locale.h> +#include <malloc.h> +#include <netdb.h> +#include <poll.h> +#include <signal.h> +#include <syslog.h> +#include <sys/mman.h> +#include <sys/prctl.h> #include <sys/resource.h> -#include <stdio.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <uuid/uuid.h> -#ifndef NETDATA_COMMON_H -#define NETDATA_COMMON_H 1 +#ifdef STORAGE_WITH_MATH +#include <math.h> +#endif #if defined(HAVE_INTTYPES_H) #include <inttypes.h> #elif defined(HAVE_STDINT_H) #include <stdint.h> #endif -#include <sys/types.h> -#include <unistd.h> +#ifdef NETDATA_WITH_ZLIB +#include <zlib.h> +#endif + +#ifndef __ATOMIC_SEQ_CST +#define NETDATA_NO_ATOMIC_INSTRUCTIONS 1 +#endif + +#ifdef __GNUC__ +#define GCC_VERSION (__GNUC__ * 10000 \ + + __GNUC_MINOR__ * 100 \ + + __GNUC_PATCHLEVEL__) + +#if __x86_64__ || __ppc64__ +#define ENVIRONMENT64 +#else +#define ENVIRONMENT32 +#endif + +#else // !__GNUC__ +#define NETDATA_NO_ATOMIC_INSTRUCTIONS 1 +#define ENVIRONMENT32 +#endif // __GNUC__ + +#include "avl.h" +#include "log.h" +#include "global_statistics.h" +#include "storage_number.h" +#include "web_buffer.h" +#include "web_buffer_svg.h" +#include "url.h" +#include "popen.h" + +#include "procfile.h" +#include "appconfig.h" +#include "dictionary.h" +#include "proc_self_mountinfo.h" +#include "plugin_checks.h" +#include "plugin_idlejitter.h" +#include "plugin_nfacct.h" +#include "plugin_proc.h" +#include "plugin_tc.h" +#include "plugins_d.h" + +#include "eval.h" +#include "health.h" + +#include "rrd.h" +#include "rrd2json.h" + +#include "web_client.h" +#include "web_server.h" + +#include "registry.h" +#include "daemon.h" +#include "main.h" +#include "unit_test.h" + +#ifdef abs +#undef abs +#endif #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 unsigned long long usec_dt(struct timeval *now, struct timeval *old); +extern unsigned long long timeval_usec(struct timeval *tv); + +// #define usec_dt(now, last) (((((now)->tv_sec * 1000000ULL) + (now)->tv_usec) - (((last)->tv_sec * 1000000ULL) + (last)->tv_usec))) 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 uint32_t simple_uhash(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 int snprintfz(char *dst, size_t n, const char *fmt, ...) __attribute__ (( format (printf, 3, 4))); + +// memory allocation functions that handle failures +extern char *strdupz(const char *s); +extern void *callocz(size_t nmemb, size_t size); +extern void *mallocz(size_t size); +extern void freez(void *ptr); +extern void *reallocz(void *ptr, size_t size); extern void *mymmap(const char *filename, size_t size, int flags, int ksm); -extern int savememory(const char *filename, void *mem, unsigned long size); +extern int savememory(const char *filename, void *mem, size_t size); extern int fd_is_valid(int fd); @@ -43,7 +159,8 @@ extern void get_HZ(void); extern pid_t gettid(void); -extern unsigned long long timems(void); +extern unsigned long long time_usec(void); +extern int sleep_usec(unsigned long long usec); extern char *fgets_trim_len(char *buf, size_t buf_size, FILE *fp, size_t *len); diff --git a/src/daemon.c b/src/daemon.c index 2a56ae0cc..bc4614f07 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -1,315 +1,230 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> -#include <signal.h> -#include <string.h> -#include <sys/types.h> -#include <pwd.h> -#include <grp.h> -#include <pthread.h> -#include <sys/wait.h> -#include <sys/stat.h> - #include "common.h" -#include "appconfig.h" -#include "log.h" -#include "web_client.h" -#include "plugins_d.h" -#include "rrd.h" -#include "popen.h" -#include "main.h" -#include "daemon.h" +#include <sched.h> char pidfile[FILENAME_MAX + 1] = ""; -int pidfd = -1; -void sig_handler(int signo) +void sig_handler_exit(int signo) +{ + if(signo) { + error_log_limit_unlimited(); + error("Received signal %d. Exiting...", signo); + netdata_exit = 1; + } +} + +void sig_handler_logrotate(int signo) { - if(signo) - netdata_exit = 1; + if(signo) { + error_log_limit_reset(); + info("Received signal %d to re-open the log files", signo); + reopen_all_log_files(); + } } -int become_user(const char *username) +void sig_handler_save(int signo) { - struct passwd *pw = getpwnam(username); - if(!pw) { - error("User %s is not present.", username); - return -1; - } - - 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) - error("Cannot chown pidfile '%s' to user '%s'", pidfile, username); - - else if(pidfd != -1) { - // not need to keep it open - close(pidfd); - pidfd = -1; - } - } - else if(pidfd != -1) { - // not need to keep it open - close(pidfd); - 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; - } - - if(setresuid(uid, uid, uid) != 0) { - error("Cannot switch to user %s (uid: %d).", username, uid); - return -1; - } - - if(setgid(gid) != 0) { - error("Cannot switch to user's %s group (gid: %d).", username, gid); - return -1; - } - if(setegid(gid) != 0) { - error("Cannot effectively switch to user's %s group (gid: %d).", username, gid); - return -1; - } - if(setuid(uid) != 0) { - error("Cannot switch to user %s (uid: %d).", username, uid); - return -1; - } - if(seteuid(uid) != 0) { - error("Cannot effectively switch to user %s (uid: %d).", username, uid); - return -1; - } - - return(0); + if(signo) { + error_log_limit_reset(); + info("Received signal %d to save the database...", signo); + rrdset_save_all(); + } +} + +void sig_handler_reload_health(int signo) +{ + if(signo) { + error_log_limit_reset(); + info("Received signal %d to reload health configuration...", signo); + health_reload(); + } +} + +static void chown_open_file(int fd, uid_t uid, gid_t gid) { + if(fd == -1) return; + + struct stat buf; + + if(fstat(fd, &buf) == -1) { + error("Cannot fstat() fd %d", fd); + return; + } + + if((buf.st_uid != uid || buf.st_gid != gid) && S_ISREG(buf.st_mode)) { + if(fchown(fd, uid, gid) == -1) + error("Cannot fchown() fd %d.", fd); + } +} + +int become_user(const char *username, int pid_fd) +{ + struct passwd *pw = getpwnam(username); + if(!pw) { + error("User %s is not present.", username); + return -1; + } + + uid_t uid = pw->pw_uid; + gid_t gid = pw->pw_gid; + + int ngroups = (int)sysconf(_SC_NGROUPS_MAX); + gid_t *supplementary_groups = NULL; + if(ngroups) { + supplementary_groups = mallocz(sizeof(gid_t) * ngroups); + if(getgrouplist(username, gid, supplementary_groups, &ngroups) == -1) { + error("Cannot get supplementary groups of user '%s'.", username); + freez(supplementary_groups); + supplementary_groups = NULL; + ngroups = 0; + } + } + + chown_open_file(STDOUT_FILENO, uid, gid); + chown_open_file(STDERR_FILENO, uid, gid); + chown_open_file(stdaccess_fd, uid, gid); + chown_open_file(pid_fd, uid, gid); + + if(supplementary_groups && ngroups) { + if(setgroups(ngroups, supplementary_groups) == -1) + error("Cannot set supplementary groups for user '%s'", username); + + freez(supplementary_groups); + supplementary_groups = NULL; + ngroups = 0; + } + + if(setresgid(gid, gid, gid) != 0) { + error("Cannot switch to user's %s group (gid: %u).", username, gid); + return -1; + } + + if(setresuid(uid, uid, uid) != 0) { + error("Cannot switch to user %s (uid: %u).", username, uid); + return -1; + } + + if(setgid(gid) != 0) { + error("Cannot switch to user's %s group (gid: %u).", username, gid); + return -1; + } + if(setegid(gid) != 0) { + error("Cannot effectively switch to user's %s group (gid: %u).", username, gid); + return -1; + } + if(setuid(uid) != 0) { + error("Cannot switch to user %s (uid: %u).", username, uid); + return -1; + } + if(seteuid(uid) != 0) { + error("Cannot effectively switch to user %s (uid: %u).", username, uid); + return -1; + } + + return(0); +} + +void oom_score_adj(int score) { + int done = 0; + int fd = open("/proc/self/oom_score_adj", O_WRONLY); + if(fd != -1) { + char buf[10 + 1]; + ssize_t len = snprintfz(buf, 10, "%d", score); + if(write(fd, buf, len) == len) done = 1; + close(fd); + } + + if(!done) + error("Cannot adjust my Out-Of-Memory score to %d.", score); + else + info("Adjusted my Out-Of-Memory score to %d.", score); +} + +int sched_setscheduler_idle(void) { +#ifdef SCHED_IDLE + const struct sched_param param = { + .sched_priority = 0 + }; + + int i = sched_setscheduler(0, SCHED_IDLE, ¶m); + if(i != 0) + error("Cannot adjust my scheduling priority to IDLE."); + else + info("Adjusted my scheduling priority to IDLE."); + + return i; +#else + return -1; +#endif } -int become_daemon(int dont_fork, int close_all_files, const char *user, const char *input, const char *output, const char *error, const char *access, int *access_fd, FILE **access_fp) +int become_daemon(int dont_fork, const char *user) { - fflush(NULL); - - // open the files before forking - int input_fd = -1, output_fd = -1, error_fd = -1, dev_null; - - if(input && *input) { - if((input_fd = open(input, O_RDONLY, 0666)) == -1) { - error("Cannot open input file '%s'.", input); - return -1; - } - } - - if(output && *output) { - if((output_fd = open(output, O_RDWR | O_APPEND | O_CREAT, 0666)) == -1) { - error("Cannot open output log file '%s'", output); - if(input_fd != -1) close(input_fd); - return -1; - } - } - - if(error && *error) { - if((error_fd = open(error, O_RDWR | O_APPEND | O_CREAT, 0666)) == -1) { - error("Cannot open error log file '%s'.", error); - if(input_fd != -1) close(input_fd); - if(output_fd != -1) close(output_fd); - return -1; - } - } - - if(access && *access && access_fd) { - if((*access_fd = open(access, O_RDWR | O_APPEND | O_CREAT, 0666)) == -1) { - error("Cannot open access log file '%s'", access); - if(input_fd != -1) close(input_fd); - if(output_fd != -1) close(output_fd); - if(error_fd != -1) close(error_fd); - return -1; - } - - if(access_fp) { - *access_fp = fdopen(*access_fd, "w"); - if(!*access_fp) { - error("Cannot migrate file's '%s' fd %d.", access, *access_fd); - if(input_fd != -1) close(input_fd); - if(output_fd != -1) close(output_fd); - if(error_fd != -1) close(error_fd); - close(*access_fd); - *access_fd = -1; - return -1; - } - if(setvbuf(*access_fp, NULL, _IOLBF, 0) != 0) - error("Cannot set line buffering on access.log"); - } - } - - if((dev_null = open("/dev/null", O_RDWR, 0666)) == -1) { - perror("Cannot open /dev/null"); - if(input_fd != -1) close(input_fd); - if(output_fd != -1) close(output_fd); - if(error_fd != -1) close(error_fd); - if(access && access_fd && *access_fd != -1) { - close(*access_fd); - *access_fd = -1; - if(access_fp) { - fclose(*access_fp); - *access_fp = NULL; - } - } - return -1; - } - - // all files opened - // lets do it - - if(!dont_fork) { - int i = fork(); - if(i == -1) { - perror("cannot fork"); - exit(1); - } - if(i != 0) { - exit(0); // the parent - } - - // become session leader - if (setsid() < 0) { - perror("Cannot become session leader."); - exit(2); - } - } - - // fork() again - if(!dont_fork) { - int i = fork(); - if(i == -1) { - perror("cannot fork"); - exit(1); - } - if(i != 0) { - exit(0); // the parent - } - } - - // Set new file permissions - umask(0); - - // close all files - if(close_all_files) { - int i; - for(i = (int) (sysconf(_SC_OPEN_MAX) - 1); i > 0; i--) - if( - ((access_fd && i != *access_fd) || !access_fd) - && i != dev_null - && i != input_fd - && i != output_fd - && i != error_fd - && fd_is_valid(i) - ) close(i); - } - else { - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - } - - // put the opened files - // to our standard file descriptors - if(input_fd != -1) { - if(input_fd != STDIN_FILENO) { - dup2(input_fd, STDIN_FILENO); - close(input_fd); - } - input_fd = -1; - } - else dup2(dev_null, STDIN_FILENO); - - if(output_fd != -1) { - if(output_fd != STDOUT_FILENO) { - 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); - - if(error_fd != -1) { - if(error_fd != STDERR_FILENO) { - dup2(error_fd, STDERR_FILENO); - close(error_fd); - } - - if(setvbuf(stderr, NULL, _IOLBF, 0) != 0) - error("Cannot set line buffering on error.log"); - - error_fd = -1; - } - else dup2(dev_null, STDERR_FILENO); - - // close /dev/null - if(dev_null != STDIN_FILENO && dev_null != STDOUT_FILENO && dev_null != STDERR_FILENO) - close(dev_null); - - // generate our pid file - if(pidfile[0]) { - pidfd = open(pidfile, O_RDWR | O_CREAT, 0644); - if(pidfd >= 0) { - if(ftruncate(pidfd, 0) != 0) - error("Cannot truncate pidfile '%s'.", pidfile); - - char b[100]; - sprintf(b, "%d\n", getpid()); - ssize_t i = write(pidfd, b, strlen(b)); - if(i <= 0) - error("Cannot write pidfile '%s'.", pidfile); - - // don't close it, we might need it at exit - // close(pidfd); - } - else error("Failed to open pidfile '%s'.", pidfile); - } - - if(user && *user) { - if(become_user(user) != 0) { - error("Cannot become user '%s'. Continuing as we are.", user); - } - else info("Successfully became user '%s'.", user); - } - else if(pidfd != -1) - close(pidfd); - - return(0); + if(!dont_fork) { + int i = fork(); + if(i == -1) { + perror("cannot fork"); + exit(1); + } + if(i != 0) { + exit(0); // the parent + } + + // become session leader + if (setsid() < 0) { + perror("Cannot become session leader."); + exit(2); + } + + // fork() again + i = fork(); + if(i == -1) { + perror("cannot fork"); + exit(1); + } + if(i != 0) { + exit(0); // the parent + } + } + + // generate our pid file + int pidfd = -1; + if(pidfile[0]) { + pidfd = open(pidfile, O_WRONLY | O_CREAT, 0644); + if(pidfd >= 0) { + if(ftruncate(pidfd, 0) != 0) + error("Cannot truncate pidfile '%s'.", pidfile); + + char b[100]; + sprintf(b, "%d\n", getpid()); + ssize_t i = write(pidfd, b, strlen(b)); + if(i <= 0) + error("Cannot write pidfile '%s'.", pidfile); + } + else error("Failed to open pidfile '%s'.", pidfile); + } + + // Set new file permissions + umask(0002); + + // adjust my Out-Of-Memory score + oom_score_adj(1000); + + // never become a problem + if(sched_setscheduler_idle() != 0) { + if(nice(19) == -1) error("Cannot lower my CPU priority."); + else info("Set my nice value to 19."); + } + + if(user && *user) { + if(become_user(user, pidfd) != 0) { + error("Cannot become user '%s'. Continuing as we are.", user); + } + else info("Successfully became user '%s'.", user); + } + + if(pidfd != -1) { + close(pidfd); + pidfd = -1; + } + + return(0); } diff --git a/src/daemon.h b/src/daemon.h index 0642be3c0..b193602d6 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -1,15 +1,17 @@ #ifndef NETDATA_DAEMON_H #define NETDATA_DAEMON_H 1 -extern void sig_handler(int signo); +extern void sig_handler_exit(int signo); +extern void sig_handler_save(int signo); +extern void sig_handler_logrotate(int signo); +extern void sig_handler_reload_health(int signo); -extern int become_user(const char *username); +extern int become_user(const char *username, int pid_fd); -extern int become_daemon(int dont_fork, int close_all_files, const char *user, const char *input, const char *output, const char *error, const char *access, int *access_fd, FILE **access_fp); +extern int become_daemon(int dont_fork, const char *user); extern void netdata_cleanup_and_exit(int i); extern char pidfile[]; -extern int pidfd; #endif /* NETDATA_DAEMON_H */ diff --git a/src/dictionary.c b/src/dictionary.c index 1543f4d0e..91d3b45f1 100644 --- a/src/dictionary.c +++ b/src/dictionary.c @@ -1,39 +1,52 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif +#include "common.h" -#include <pthread.h> -#include <stdlib.h> -#include <string.h> +// ---------------------------------------------------------------------------- +// dictionary statistics -#include "avl.h" -#include "common.h" -#include "log.h" +static inline void NETDATA_DICTIONARY_STATS_INSERTS_PLUS1(DICTIONARY *dict) { + if(likely(dict->stats)) + dict->stats->inserts++; +} +static inline void NETDATA_DICTIONARY_STATS_DELETES_PLUS1(DICTIONARY *dict) { + if(likely(dict->stats)) + dict->stats->deletes++; +} +static inline void NETDATA_DICTIONARY_STATS_SEARCHES_PLUS1(DICTIONARY *dict) { + if(likely(dict->stats)) + dict->stats->searches++; +} +static inline void NETDATA_DICTIONARY_STATS_ENTRIES_PLUS1(DICTIONARY *dict) { + if(likely(dict->stats)) + dict->stats->entries++; +} +static inline void NETDATA_DICTIONARY_STATS_ENTRIES_MINUS1(DICTIONARY *dict) { + if(likely(dict->stats)) + dict->stats->entries--; +} -#include "dictionary.h" // ---------------------------------------------------------------------------- // dictionary locks static inline void dictionary_read_lock(DICTIONARY *dict) { - if(likely(!(dict->flags & DICTIONARY_FLAG_SINGLE_THREADED))) { - // debug(D_DICTIONARY, "Dictionary READ lock"); - pthread_rwlock_rdlock(&dict->rwlock); - } + if(likely(dict->rwlock)) { + // 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); - } + if(likely(dict->rwlock)) { + // 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); - } + if(likely(dict->rwlock)) { + // debug(D_DICTIONARY, "Dictionary UNLOCK lock"); + pthread_rwlock_unlock(dict->rwlock); + } } @@ -41,192 +54,196 @@ static inline void dictionary_unlock(DICTIONARY *dict) { // avl index static int name_value_compare(void* a, void* b) { - if(((NAME_VALUE *)a)->hash < ((NAME_VALUE *)b)->hash) return -1; - else if(((NAME_VALUE *)a)->hash > ((NAME_VALUE *)b)->hash) return 1; - else return strcmp(((NAME_VALUE *)a)->name, ((NAME_VALUE *)b)->name); + if(((NAME_VALUE *)a)->hash < ((NAME_VALUE *)b)->hash) return -1; + else if(((NAME_VALUE *)a)->hash > ((NAME_VALUE *)b)->hash) return 1; + else return strcmp(((NAME_VALUE *)a)->name, ((NAME_VALUE *)b)->name); } -#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) +#define dictionary_name_value_index_add_nolock(dict, nv) do { NETDATA_DICTIONARY_STATS_INSERTS_PLUS1(dict); avl_insert(&((dict)->values_index), (avl *)(nv)); } while(0) +#define dictionary_name_value_index_del_nolock(dict, nv) do { NETDATA_DICTIONARY_STATS_DELETES_PLUS1(dict); avl_remove(&(dict->values_index), (avl *)(nv)); } while(0) 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; + NAME_VALUE tmp; + tmp.hash = (hash)?hash:simple_hash(name); + tmp.name = (char *)name; - dict->searches++; - return (NAME_VALUE *)avl_search(&(dict->values_index), (avl *) &tmp); + NETDATA_DICTIONARY_STATS_SEARCHES_PLUS1(dict); + return (NAME_VALUE *)avl_search(&(dict->values_index), (avl *) &tmp); } // ---------------------------------------------------------------------------- // internal methods static NAME_VALUE *dictionary_name_value_create_nolock(DICTIONARY *dict, const char *name, void *value, size_t value_len, uint32_t hash) { - debug(D_DICTIONARY, "Creating name value entry for name '%s'.", name); - - NAME_VALUE *nv = calloc(1, sizeof(NAME_VALUE)); - if(unlikely(!nv)) fatal("Cannot allocate name_value of size %z", sizeof(NAME_VALUE)); + debug(D_DICTIONARY, "Creating name value entry for name '%s'.", name); - if(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE) - nv->name = (char *)name; - else { - nv->name = strdup(name); - if (unlikely(!nv->name)) - fatal("Cannot allocate name_value.name of size %z", strlen(name)); - } + NAME_VALUE *nv = callocz(1, sizeof(NAME_VALUE)); - nv->hash = (hash)?hash:simple_hash(nv->name); + if(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE) + nv->name = (char *)name; + else { + nv->name = strdupz(name); + } - 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); + nv->hash = (hash)?hash:simple_hash(nv->name); - memcpy(nv->value, value, value_len); - } + if(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE) + nv->value = value; + else { + nv->value = mallocz(value_len); + memcpy(nv->value, value, value_len); + } - // index it - dictionary_name_value_index_add_nolock(dict, nv); - dict->entries++; + // index it + dictionary_name_value_index_add_nolock(dict, nv); + NETDATA_DICTIONARY_STATS_ENTRIES_PLUS1(dict); - return nv; + return 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); + debug(D_DICTIONARY, "Destroying name value entry for name '%s'.", nv->name); - dictionary_name_value_index_del_nolock(dict, nv); + dictionary_name_value_index_del_nolock(dict, nv); - dict->entries--; + NETDATA_DICTIONARY_STATS_ENTRIES_MINUS1(dict); - 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_VALUE_LINK_DONT_CLONE)) { + debug(D_REGISTRY, "Dictionary freeing value of '%s'", nv->name); + freez(nv->value); + } - if(!(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE)) { - debug(D_REGISTRY, "Dictionary freeing name '%s'", nv->name); - free(nv->name); - } + if(!(dict->flags & DICTIONARY_FLAG_NAME_LINK_DONT_CLONE)) { + debug(D_REGISTRY, "Dictionary freeing name '%s'", nv->name); + freez(nv->name); + } - free(nv); + freez(nv); } // ---------------------------------------------------------------------------- // API - basic methods -DICTIONARY *dictionary_create(uint32_t flags) { - debug(D_DICTIONARY, "Creating dictionary."); +DICTIONARY *dictionary_create(uint8_t flags) { + debug(D_DICTIONARY, "Creating dictionary."); + + DICTIONARY *dict = callocz(1, sizeof(DICTIONARY)); - DICTIONARY *dict = calloc(1, sizeof(DICTIONARY)); - if(unlikely(!dict)) fatal("Cannot allocate DICTIONARY"); + if(flags & DICTIONARY_FLAG_WITH_STATISTICS) + dict->stats = callocz(1, sizeof(struct dictionary_stats)); - avl_init(&dict->values_index, name_value_compare); - pthread_rwlock_init(&dict->rwlock, NULL); + if(!(flags & DICTIONARY_FLAG_SINGLE_THREADED)) { + dict->rwlock = callocz(1, sizeof(pthread_rwlock_t)); + pthread_rwlock_init(dict->rwlock, NULL); + } - dict->flags = flags; + avl_init(&dict->values_index, name_value_compare); + dict->flags = flags; - return dict; + return dict; } void dictionary_destroy(DICTIONARY *dict) { - debug(D_DICTIONARY, "Destroying dictionary."); + debug(D_DICTIONARY, "Destroying dictionary."); - dictionary_write_lock(dict); + dictionary_write_lock(dict); - while(dict->values_index.root) - dictionary_name_value_destroy_nolock(dict, (NAME_VALUE *)dict->values_index.root); + while(dict->values_index.root) + dictionary_name_value_destroy_nolock(dict, (NAME_VALUE *)dict->values_index.root); - dictionary_unlock(dict); + dictionary_unlock(dict); - free(dict); + if(dict->stats) + freez(dict->stats); + + if(dict->rwlock) + freez(dict->rwlock); + + freez(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); - - uint32_t hash = simple_hash(name); + debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name); - dictionary_write_lock(dict); + uint32_t hash = simple_hash(name); - 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); + dictionary_write_lock(dict); - nv = dictionary_name_value_create_nolock(dict, name, value, value_len, hash); - if(unlikely(!nv)) - fatal("Cannot create name_value."); - } - else { - debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", name); + 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); - 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); + nv = dictionary_name_value_create_nolock(dict, name, value, value_len, hash); + if(unlikely(!nv)) + fatal("Cannot create name_value."); + } + else { + debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", name); - void *value = malloc(value_len), - *old = nv->value; + 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); - if(unlikely(!nv->value)) - fatal("Cannot allocate value of size %z", value_len); + // copy the new value without breaking + // any other thread accessing the same entry + void *new = mallocz(value_len), + *old = nv->value; - memcpy(value, value, value_len); - nv->value = value; + memcpy(new, value, value_len); + nv->value = new; - debug(D_REGISTRY, "Dictionary: freeing old value of '%s'", name); - free(old); - } - } + debug(D_REGISTRY, "Dictionary: freeing old value of '%s'", name); + freez(old); + } + } - dictionary_unlock(dict); + dictionary_unlock(dict); - return nv->value; + return nv->value; } void *dictionary_get(DICTIONARY *dict, const char *name) { - debug(D_DICTIONARY, "GET dictionary entry with name '%s'.", name); + debug(D_DICTIONARY, "GET dictionary entry with name '%s'.", name); - dictionary_read_lock(dict); - NAME_VALUE *nv = dictionary_name_value_index_find_nolock(dict, name, 0); - dictionary_unlock(dict); + dictionary_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; - } + if(unlikely(!nv)) { + debug(D_DICTIONARY, "Not found dictionary entry with name '%s'.", name); + return NULL; + } - debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name); - return nv->value; + debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name); + return nv->value; } int dictionary_del(DICTIONARY *dict, const char *name) { - int ret; + int ret; - debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name); + debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name); - dictionary_write_lock(dict); + dictionary_write_lock(dict); - NAME_VALUE *nv = dictionary_name_value_index_find_nolock(dict, name, 0); - if(unlikely(!nv)) { - debug(D_DICTIONARY, "Not found dictionary entry with name '%s'.", name); - ret = -1; - } - else { - debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name); - dictionary_name_value_destroy_nolock(dict, nv); - ret = 0; - } + 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); + dictionary_unlock(dict); - return ret; + return ret; } @@ -236,36 +253,36 @@ int dictionary_del(DICTIONARY *dict, const char *name) { // 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; + int total = 0, ret = 0; - if(a->right) { - ret = dictionary_walker(a->right, callback, data); - if(ret < 0) return ret; - total += ret; - } + if(a->avl_link[0]) { + ret = dictionary_walker(a->avl_link[0], callback, data); + if(ret < 0) return ret; + total += ret; + } - ret = callback(((NAME_VALUE *)a)->value, 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; - } + if(a->avl_link[1]) { + ret = dictionary_walker(a->avl_link[1], callback, data); + if (ret < 0) return ret; + total += ret; + } - return total; + return total; } int dictionary_get_all(DICTIONARY *dict, int (*callback)(void *entry, void *data), void *data) { - int ret = 0; + int ret = 0; - dictionary_read_lock(dict); + dictionary_read_lock(dict); - if(likely(dict->values_index.root)) - ret = dictionary_walker(dict->values_index.root, callback, data); + if(likely(dict->values_index.root)) + ret = dictionary_walker(dict->values_index.root, callback, data); - dictionary_unlock(dict); + dictionary_unlock(dict); - return ret; + return ret; } diff --git a/src/dictionary.h b/src/dictionary.h index 575f28271..6bebbfa85 100644 --- a/src/dictionary.h +++ b/src/dictionary.h @@ -1,45 +1,44 @@ -#include <pthread.h> - -#include "web_buffer.h" -#include "avl.h" - #ifndef NETDATA_DICTIONARY_H #define NETDATA_DICTIONARY_H 1 +struct dictionary_stats { + unsigned long long inserts; + unsigned long long deletes; + unsigned long long searches; + unsigned long long entries; +}; + typedef struct name_value { - avl avl; // the index - this has to be first! + avl avl; // the index - this has to be first! - 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 + uint32_t hash; // a simple hash to speed up searching + // we first compare hashes, and only if the hashes are equal we do string comparisons - char *name; - void *value; + char *name; + void *value; } NAME_VALUE; typedef struct dictionary { - avl_tree values_index; - - uint8_t flags; + avl_tree values_index; - unsigned long long inserts; - unsigned long long deletes; - unsigned long long searches; - unsigned long long entries; + uint8_t flags; - pthread_rwlock_t rwlock; + struct dictionary_stats *stats; + pthread_rwlock_t *rwlock; } DICTIONARY; -#define DICTIONARY_FLAG_DEFAULT 0x00000000 -#define DICTIONARY_FLAG_SINGLE_THREADED 0x00000001 -#define DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE 0x00000002 -#define DICTIONARY_FLAG_NAME_LINK_DONT_CLONE 0x00000004 +#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 +#define DICTIONARY_FLAG_WITH_STATISTICS 0x00000008 -extern DICTIONARY *dictionary_create(uint32_t flags); +extern DICTIONARY *dictionary_create(uint8_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); +extern int dictionary_get_all(DICTIONARY *dict, int (*callback)(void *entry, void *d), void *data); #endif /* NETDATA_DICTIONARY_H */ diff --git a/src/eval.c b/src/eval.c new file mode 100644 index 000000000..8866ee95d --- /dev/null +++ b/src/eval.c @@ -0,0 +1,1119 @@ +#include "common.h" + +// ---------------------------------------------------------------------------- +// data structures for storing the parsed expression in memory + +typedef struct eval_value { + int type; + + union { + calculated_number number; + EVAL_VARIABLE *variable; + struct eval_node *expression; + }; +} EVAL_VALUE; + +typedef struct eval_node { + int id; + unsigned char operator; + int precedence; + + int count; + EVAL_VALUE ops[]; +} EVAL_NODE; + +// these are used for EVAL_NODE.operator +// they are used as internal IDs to identify an operator +// THEY ARE NOT USED FOR PARSING OPERATORS LIKE THAT +#define EVAL_OPERATOR_NOP '\0' +#define EVAL_OPERATOR_EXPRESSION_OPEN '(' +#define EVAL_OPERATOR_EXPRESSION_CLOSE ')' +#define EVAL_OPERATOR_NOT '!' +#define EVAL_OPERATOR_PLUS '+' +#define EVAL_OPERATOR_MINUS '-' +#define EVAL_OPERATOR_AND '&' +#define EVAL_OPERATOR_OR '|' +#define EVAL_OPERATOR_GREATER_THAN_OR_EQUAL 'G' +#define EVAL_OPERATOR_LESS_THAN_OR_EQUAL 'L' +#define EVAL_OPERATOR_NOT_EQUAL '~' +#define EVAL_OPERATOR_EQUAL '=' +#define EVAL_OPERATOR_LESS '<' +#define EVAL_OPERATOR_GREATER '>' +#define EVAL_OPERATOR_MULTIPLY '*' +#define EVAL_OPERATOR_DIVIDE '/' +#define EVAL_OPERATOR_SIGN_PLUS 'P' +#define EVAL_OPERATOR_SIGN_MINUS 'M' +#define EVAL_OPERATOR_ABS 'A' +#define EVAL_OPERATOR_IF_THEN_ELSE '?' + +// ---------------------------------------------------------------------------- +// forward function definitions + +static inline void eval_node_free(EVAL_NODE *op); +static inline EVAL_NODE *parse_full_expression(const char **string, int *error); +static inline EVAL_NODE *parse_one_full_operand(const char **string, int *error); +static inline calculated_number eval_node(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error); +static inline void print_parsed_as_node(BUFFER *out, EVAL_NODE *op, int *error); +static inline void print_parsed_as_constant(BUFFER *out, calculated_number n); + +// ---------------------------------------------------------------------------- +// evaluation of expressions + +static inline calculated_number eval_check_number(calculated_number n, int *error) { + if(unlikely(isnan(n))) { + *error = EVAL_ERROR_VALUE_IS_NAN; + return 0; + } + + if(unlikely(isinf(n))) { + *error = EVAL_ERROR_VALUE_IS_INFINITE; + return 0; + } + + return n; +} + +static inline calculated_number eval_variable(EVAL_EXPRESSION *exp, EVAL_VARIABLE *v, int *error) { + static uint32_t this_hash = 0, now_hash = 0, after_hash = 0, before_hash = 0; + calculated_number n; + + if(unlikely(this_hash == 0)) { + this_hash = simple_hash("this"); + now_hash = simple_hash("now"); + after_hash = simple_hash("after"); + before_hash = simple_hash("before"); + } + + if(v->hash == this_hash && !strcmp(v->name, "this")) { + n = (exp->this)?*exp->this:NAN; + buffer_strcat(exp->error_msg, "[ $this = "); + print_parsed_as_constant(exp->error_msg, n); + buffer_strcat(exp->error_msg, " ] "); + return n; + } + + if(v->hash == after_hash && !strcmp(v->name, "after")) { + n = (exp->after && *exp->after)?*exp->after:NAN; + buffer_strcat(exp->error_msg, "[ $after = "); + print_parsed_as_constant(exp->error_msg, n); + buffer_strcat(exp->error_msg, " ] "); + return n; + } + + if(v->hash == before_hash && !strcmp(v->name, "before")) { + n = (exp->before && *exp->before)?*exp->before:NAN; + buffer_strcat(exp->error_msg, "[ $before = "); + print_parsed_as_constant(exp->error_msg, n); + buffer_strcat(exp->error_msg, " ] "); + return n; + } + + if(v->hash == now_hash && !strcmp(v->name, "now")) { + n = time(NULL); + buffer_strcat(exp->error_msg, "[ $now = "); + print_parsed_as_constant(exp->error_msg, n); + buffer_strcat(exp->error_msg, " ] "); + return n; + } + + if(exp->rrdcalc && health_variable_lookup(v->name, v->hash, exp->rrdcalc, &n)) { + buffer_sprintf(exp->error_msg, "[ $%s = ", v->name); + print_parsed_as_constant(exp->error_msg, n); + buffer_strcat(exp->error_msg, " ] "); + return n; + } + + *error = EVAL_ERROR_UNKNOWN_VARIABLE; + buffer_sprintf(exp->error_msg, "unknown variable '%s'", v->name); + return 0; +} + +static inline calculated_number eval_value(EVAL_EXPRESSION *exp, EVAL_VALUE *v, int *error) { + calculated_number n; + + switch(v->type) { + case EVAL_VALUE_EXPRESSION: + n = eval_node(exp, v->expression, error); + break; + + case EVAL_VALUE_NUMBER: + n = v->number; + break; + + case EVAL_VALUE_VARIABLE: + n = eval_variable(exp, v->variable, error); + break; + + default: + *error = EVAL_ERROR_INVALID_VALUE; + n = 0; + break; + } + + // return eval_check_number(n, error); + return n; +} + +static inline int is_true(calculated_number n) { + if(isnan(n)) return 0; + if(isinf(n)) return 1; + if(n == 0) return 0; + return 1; +} + +calculated_number eval_and(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + return is_true(eval_value(exp, &op->ops[0], error)) && is_true(eval_value(exp, &op->ops[1], error)); +} +calculated_number eval_or(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + return is_true(eval_value(exp, &op->ops[0], error)) || is_true(eval_value(exp, &op->ops[1], error)); +} +calculated_number eval_greater_than_or_equal(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + return isgreaterequal(n1, n2); +} +calculated_number eval_less_than_or_equal(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + return islessequal(n1, n2); +} +calculated_number eval_equal(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + if(isnan(n1) && isnan(n2)) return 1; + if(isinf(n1) && isinf(n2)) return 1; + if(isnan(n1) || isnan(n2)) return 0; + if(isinf(n1) || isinf(n2)) return 0; + return n1 == n2; +} +calculated_number eval_not_equal(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + return !eval_equal(exp, op, error); +} +calculated_number eval_less(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + return isless(n1, n2); +} +calculated_number eval_greater(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + return isgreater(n1, n2); +} +calculated_number eval_plus(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + if(isnan(n1) || isnan(n2)) return NAN; + if(isinf(n1) || isinf(n2)) return INFINITY; + return n1 + n2; +} +calculated_number eval_minus(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + if(isnan(n1) || isnan(n2)) return NAN; + if(isinf(n1) || isinf(n2)) return INFINITY; + return n1 - n2; +} +calculated_number eval_multiply(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + if(isnan(n1) || isnan(n2)) return NAN; + if(isinf(n1) || isinf(n2)) return INFINITY; + return n1 * n2; +} +calculated_number eval_divide(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + calculated_number n2 = eval_value(exp, &op->ops[1], error); + if(isnan(n1) || isnan(n2)) return NAN; + if(isinf(n1) || isinf(n2)) return INFINITY; + return n1 / n2; +} +calculated_number eval_nop(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + return eval_value(exp, &op->ops[0], error); +} +calculated_number eval_not(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + return !is_true(eval_value(exp, &op->ops[0], error)); +} +calculated_number eval_sign_plus(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + return eval_value(exp, &op->ops[0], error); +} +calculated_number eval_sign_minus(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + if(isnan(n1)) return NAN; + if(isinf(n1)) return INFINITY; + return -n1; +} +calculated_number eval_abs(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + calculated_number n1 = eval_value(exp, &op->ops[0], error); + if(isnan(n1)) return NAN; + if(isinf(n1)) return INFINITY; + return abs(n1); +} +calculated_number eval_if_then_else(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + if(is_true(eval_value(exp, &op->ops[0], error))) + return eval_value(exp, &op->ops[1], error); + else + return eval_value(exp, &op->ops[2], error); +} + +static struct operator { + const char *print_as; + char precedence; + char parameters; + char isfunction; + calculated_number (*eval)(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error); +} operators[256] = { + // this is a random access array + // we always access it with a known EVAL_OPERATOR_X + + [EVAL_OPERATOR_IF_THEN_ELSE] = { "?", 1, 3, 0, eval_if_then_else }, + [EVAL_OPERATOR_AND] = { "&&", 2, 2, 0, eval_and }, + [EVAL_OPERATOR_OR] = { "||", 2, 2, 0, eval_or }, + [EVAL_OPERATOR_GREATER_THAN_OR_EQUAL] = { ">=", 3, 2, 0, eval_greater_than_or_equal }, + [EVAL_OPERATOR_LESS_THAN_OR_EQUAL] = { "<=", 3, 2, 0, eval_less_than_or_equal }, + [EVAL_OPERATOR_NOT_EQUAL] = { "!=", 3, 2, 0, eval_not_equal }, + [EVAL_OPERATOR_EQUAL] = { "==", 3, 2, 0, eval_equal }, + [EVAL_OPERATOR_LESS] = { "<", 3, 2, 0, eval_less }, + [EVAL_OPERATOR_GREATER] = { ">", 3, 2, 0, eval_greater }, + [EVAL_OPERATOR_PLUS] = { "+", 4, 2, 0, eval_plus }, + [EVAL_OPERATOR_MINUS] = { "-", 4, 2, 0, eval_minus }, + [EVAL_OPERATOR_MULTIPLY] = { "*", 5, 2, 0, eval_multiply }, + [EVAL_OPERATOR_DIVIDE] = { "/", 5, 2, 0, eval_divide }, + [EVAL_OPERATOR_NOT] = { "!", 6, 1, 0, eval_not }, + [EVAL_OPERATOR_SIGN_PLUS] = { "+", 6, 1, 0, eval_sign_plus }, + [EVAL_OPERATOR_SIGN_MINUS] = { "-", 6, 1, 0, eval_sign_minus }, + [EVAL_OPERATOR_ABS] = { "abs(",6,1, 1, eval_abs }, + [EVAL_OPERATOR_NOP] = { NULL, 7, 1, 0, eval_nop }, + [EVAL_OPERATOR_EXPRESSION_OPEN] = { NULL, 7, 1, 0, eval_nop }, + + // this should exist in our evaluation list + [EVAL_OPERATOR_EXPRESSION_CLOSE] = { NULL, 99, 1, 0, eval_nop } +}; + +#define eval_precedence(operator) (operators[(unsigned char)(operator)].precedence) + +static inline calculated_number eval_node(EVAL_EXPRESSION *exp, EVAL_NODE *op, int *error) { + if(unlikely(op->count != operators[op->operator].parameters)) { + *error = EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS; + return 0; + } + + calculated_number n = operators[op->operator].eval(exp, op, error); + + // return eval_check_number(n, error); + return n; +} + +// ---------------------------------------------------------------------------- +// parsed-as generation + +static inline void print_parsed_as_variable(BUFFER *out, EVAL_VARIABLE *v, int *error) { + (void)error; + buffer_sprintf(out, "$%s", v->name); +} + +static inline void print_parsed_as_constant(BUFFER *out, calculated_number n) { + if(unlikely(isnan(n))) { + buffer_strcat(out, "nan"); + return; + } + + if(unlikely(isinf(n))) { + buffer_strcat(out, "inf"); + return; + } + + char b[100+1], *s; + snprintfz(b, 100, CALCULATED_NUMBER_FORMAT, n); + + s = &b[strlen(b) - 1]; + while(s > b && *s == '0') { + *s ='\0'; + s--; + } + + if(s > b && *s == '.') + *s = '\0'; + + buffer_strcat(out, b); +} + +static inline void print_parsed_as_value(BUFFER *out, EVAL_VALUE *v, int *error) { + switch(v->type) { + case EVAL_VALUE_EXPRESSION: + print_parsed_as_node(out, v->expression, error); + break; + + case EVAL_VALUE_NUMBER: + print_parsed_as_constant(out, v->number); + break; + + case EVAL_VALUE_VARIABLE: + print_parsed_as_variable(out, v->variable, error); + break; + + default: + *error = EVAL_ERROR_INVALID_VALUE; + break; + } +} + +static inline void print_parsed_as_node(BUFFER *out, EVAL_NODE *op, int *error) { + if(unlikely(op->count != operators[op->operator].parameters)) { + *error = EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS; + return; + } + + if(operators[op->operator].parameters == 1) { + + if(operators[op->operator].print_as) + buffer_sprintf(out, "%s", operators[op->operator].print_as); + + //if(op->operator == EVAL_OPERATOR_EXPRESSION_OPEN) + // buffer_strcat(out, "("); + + print_parsed_as_value(out, &op->ops[0], error); + + //if(op->operator == EVAL_OPERATOR_EXPRESSION_OPEN) + // buffer_strcat(out, ")"); + } + + else if(operators[op->operator].parameters == 2) { + buffer_strcat(out, "("); + print_parsed_as_value(out, &op->ops[0], error); + + if(operators[op->operator].print_as) + buffer_sprintf(out, " %s ", operators[op->operator].print_as); + + print_parsed_as_value(out, &op->ops[1], error); + buffer_strcat(out, ")"); + } + else if(op->operator == EVAL_OPERATOR_IF_THEN_ELSE && operators[op->operator].parameters == 3) { + buffer_strcat(out, "("); + print_parsed_as_value(out, &op->ops[0], error); + + if(operators[op->operator].print_as) + buffer_sprintf(out, " %s ", operators[op->operator].print_as); + + print_parsed_as_value(out, &op->ops[1], error); + buffer_strcat(out, " : "); + print_parsed_as_value(out, &op->ops[2], error); + buffer_strcat(out, ")"); + } + + if(operators[op->operator].isfunction) + buffer_strcat(out, ")"); +} + +// ---------------------------------------------------------------------------- +// parsing expressions + +// skip spaces +static inline void skip_spaces(const char **string) { + const char *s = *string; + while(isspace(*s)) s++; + *string = s; +} + +// what character can appear just after an operator keyword +// like NOT AND OR ? +static inline int isoperatorterm_word(const char s) { + if(isspace(s) || s == '(' || s == '$' || s == '!' || s == '-' || s == '+' || isdigit(s) || !s) + return 1; + + return 0; +} + +// what character can appear just after an operator symbol? +static inline int isoperatorterm_symbol(const char s) { + if(isoperatorterm_word(s) || isalpha(s)) + return 1; + + return 0; +} + +// return 1 if the character should never appear in a variable +static inline int isvariableterm(const char s) { + if(isalnum(s) || s == '.' || s == '_') + return 0; + + return 1; +} + +// ---------------------------------------------------------------------------- +// parse operators + +static inline int parse_and(const char **string) { + const char *s = *string; + + // AND + if((s[0] == 'A' || s[0] == 'a') && (s[1] == 'N' || s[1] == 'n') && (s[2] == 'D' || s[2] == 'd') && isoperatorterm_word(s[3])) { + *string = &s[4]; + return 1; + } + + // && + if(s[0] == '&' && s[1] == '&' && isoperatorterm_symbol(s[2])) { + *string = &s[2]; + return 1; + } + + return 0; +} + +static inline int parse_or(const char **string) { + const char *s = *string; + + // OR + if((s[0] == 'O' || s[0] == 'o') && (s[1] == 'R' || s[1] == 'r') && isoperatorterm_word(s[2])) { + *string = &s[3]; + return 1; + } + + // || + if(s[0] == '|' && s[1] == '|' && isoperatorterm_symbol(s[2])) { + *string = &s[2]; + return 1; + } + + return 0; +} + +static inline int parse_greater_than_or_equal(const char **string) { + const char *s = *string; + + // >= + if(s[0] == '>' && s[1] == '=' && isoperatorterm_symbol(s[2])) { + *string = &s[2]; + return 1; + } + + return 0; +} + +static inline int parse_less_than_or_equal(const char **string) { + const char *s = *string; + + // <= + if (s[0] == '<' && s[1] == '=' && isoperatorterm_symbol(s[2])) { + *string = &s[2]; + return 1; + } + + return 0; +} + +static inline int parse_greater(const char **string) { + const char *s = *string; + + // > + if(s[0] == '>' && isoperatorterm_symbol(s[1])) { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_less(const char **string) { + const char *s = *string; + + // < + if(s[0] == '<' && isoperatorterm_symbol(s[1])) { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_equal(const char **string) { + const char *s = *string; + + // == + if(s[0] == '=' && s[1] == '=' && isoperatorterm_symbol(s[2])) { + *string = &s[2]; + return 1; + } + + // = + if(s[0] == '=' && isoperatorterm_symbol(s[1])) { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_not_equal(const char **string) { + const char *s = *string; + + // != + if(s[0] == '!' && s[1] == '=' && isoperatorterm_symbol(s[2])) { + *string = &s[2]; + return 1; + } + + // <> + if(s[0] == '<' && s[1] == '>' && isoperatorterm_symbol(s[2])) { + *string = &s[2]; + } + + return 0; +} + +static inline int parse_not(const char **string) { + const char *s = *string; + + // NOT + if((s[0] == 'N' || s[0] == 'n') && (s[1] == 'O' || s[1] == 'o') && (s[2] == 'T' || s[2] == 't') && isoperatorterm_word(s[3])) { + *string = &s[3]; + return 1; + } + + if(s[0] == '!') { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_multiply(const char **string) { + const char *s = *string; + + // * + if(s[0] == '*' && isoperatorterm_symbol(s[1])) { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_divide(const char **string) { + const char *s = *string; + + // / + if(s[0] == '/' && isoperatorterm_symbol(s[1])) { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_minus(const char **string) { + const char *s = *string; + + // - + if(s[0] == '-' && isoperatorterm_symbol(s[1])) { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_plus(const char **string) { + const char *s = *string; + + // + + if(s[0] == '+' && isoperatorterm_symbol(s[1])) { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_open_subexpression(const char **string) { + const char *s = *string; + + // ( + if(s[0] == '(') { + *string = &s[1]; + return 1; + } + + return 0; +} + +#define parse_close_function(x) parse_close_subexpression(x) + +static inline int parse_close_subexpression(const char **string) { + const char *s = *string; + + // ) + if(s[0] == ')') { + *string = &s[1]; + return 1; + } + + return 0; +} + +static inline int parse_variable(const char **string, char *buffer, size_t len) { + const char *s = *string; + + // $ + if(s[0] == '$') { + size_t i = 0; + s++; + + while(*s && !isvariableterm(*s) && i < len) + buffer[i++] = *s++; + + buffer[i] = '\0'; + + if(buffer[0]) { + *string = &s[0]; + return 1; + } + } + + return 0; +} + +static inline int parse_constant(const char **string, calculated_number *number) { + char *end = NULL; + calculated_number n = strtold(*string, &end); + if(unlikely(!end || *string == end)) { + *number = 0; + return 0; + } + *number = n; + *string = end; + return 1; +} + +static inline int parse_abs(const char **string) { + const char *s = *string; + + // ABS + if((s[0] == 'A' || s[0] == 'a') && (s[1] == 'B' || s[1] == 'b') && (s[2] == 'S' || s[2] == 's') && s[3] == '(') { + *string = &s[3]; + return 1; + } + + return 0; +} + +static inline int parse_if_then_else(const char **string) { + const char *s = *string; + + // ? + if(s[0] == '?') { + *string = &s[1]; + return 1; + } + + return 0; +} + +static struct operator_parser { + unsigned char id; + int (*parse)(const char **); +} operator_parsers[] = { + // the order in this list is important! + // the first matching will be used + // so place the longer of overlapping ones + // at the top + + { EVAL_OPERATOR_AND, parse_and }, + { EVAL_OPERATOR_OR, parse_or }, + { EVAL_OPERATOR_GREATER_THAN_OR_EQUAL, parse_greater_than_or_equal }, + { EVAL_OPERATOR_LESS_THAN_OR_EQUAL, parse_less_than_or_equal }, + { EVAL_OPERATOR_NOT_EQUAL, parse_not_equal }, + { EVAL_OPERATOR_EQUAL, parse_equal }, + { EVAL_OPERATOR_LESS, parse_less }, + { EVAL_OPERATOR_GREATER, parse_greater }, + { EVAL_OPERATOR_PLUS, parse_plus }, + { EVAL_OPERATOR_MINUS, parse_minus }, + { EVAL_OPERATOR_MULTIPLY, parse_multiply }, + { EVAL_OPERATOR_DIVIDE, parse_divide }, + { EVAL_OPERATOR_IF_THEN_ELSE, parse_if_then_else }, + + /* we should not put in this list the following: + * + * - NOT + * - ( + * - ) + * + * these are handled in code + */ + + // termination + { EVAL_OPERATOR_NOP, NULL } +}; + +static inline unsigned char parse_operator(const char **string, int *precedence) { + skip_spaces(string); + + int i; + for(i = 0 ; operator_parsers[i].parse != NULL ; i++) + if(operator_parsers[i].parse(string)) { + if(precedence) *precedence = eval_precedence(operator_parsers[i].id); + return operator_parsers[i].id; + } + + return EVAL_OPERATOR_NOP; +} + +// ---------------------------------------------------------------------------- +// memory management + +static inline EVAL_NODE *eval_node_alloc(int count) { + static int id = 1; + + EVAL_NODE *op = callocz(1, sizeof(EVAL_NODE) + (sizeof(EVAL_VALUE) * count)); + + op->id = id++; + op->operator = EVAL_OPERATOR_NOP; + op->precedence = eval_precedence(EVAL_OPERATOR_NOP); + op->count = count; + return op; +} + +static inline void eval_node_set_value_to_node(EVAL_NODE *op, int pos, EVAL_NODE *value) { + if(pos >= op->count) + fatal("Invalid request to set position %d of OPERAND that has only %d values", pos + 1, op->count + 1); + + op->ops[pos].type = EVAL_VALUE_EXPRESSION; + op->ops[pos].expression = value; +} + +static inline void eval_node_set_value_to_constant(EVAL_NODE *op, int pos, calculated_number value) { + if(pos >= op->count) + fatal("Invalid request to set position %d of OPERAND that has only %d values", pos + 1, op->count + 1); + + op->ops[pos].type = EVAL_VALUE_NUMBER; + op->ops[pos].number = value; +} + +static inline void eval_node_set_value_to_variable(EVAL_NODE *op, int pos, const char *variable) { + if(pos >= op->count) + fatal("Invalid request to set position %d of OPERAND that has only %d values", pos + 1, op->count + 1); + + op->ops[pos].type = EVAL_VALUE_VARIABLE; + op->ops[pos].variable = callocz(1, sizeof(EVAL_VARIABLE)); + op->ops[pos].variable->name = strdupz(variable); + op->ops[pos].variable->hash = simple_hash(op->ops[pos].variable->name); +} + +static inline void eval_variable_free(EVAL_VARIABLE *v) { + freez(v->name); + freez(v); +} + +static inline void eval_value_free(EVAL_VALUE *v) { + switch(v->type) { + case EVAL_VALUE_EXPRESSION: + eval_node_free(v->expression); + break; + + case EVAL_VALUE_VARIABLE: + eval_variable_free(v->variable); + break; + + default: + break; + } +} + +static inline void eval_node_free(EVAL_NODE *op) { + if(op->count) { + int i; + for(i = op->count - 1; i >= 0 ;i--) + eval_value_free(&op->ops[i]); + } + + freez(op); +} + +// ---------------------------------------------------------------------------- +// the parsing logic + +// helper function to avoid allocations all over the place +static inline EVAL_NODE *parse_next_operand_given_its_operator(const char **string, unsigned char operator_type, int *error) { + EVAL_NODE *sub = parse_one_full_operand(string, error); + if(!sub) return NULL; + + EVAL_NODE *op = eval_node_alloc(1); + op->operator = operator_type; + eval_node_set_value_to_node(op, 0, sub); + return op; +} + +// parse a full operand, including its sign or other associative operator (e.g. NOT) +static inline EVAL_NODE *parse_one_full_operand(const char **string, int *error) { + char variable_buffer[EVAL_MAX_VARIABLE_NAME_LENGTH + 1]; + EVAL_NODE *op1 = NULL; + calculated_number number; + + *error = EVAL_ERROR_OK; + + skip_spaces(string); + if(!(**string)) { + *error = EVAL_ERROR_MISSING_OPERAND; + return NULL; + } + + if(parse_not(string)) { + op1 = parse_next_operand_given_its_operator(string, EVAL_OPERATOR_NOT, error); + op1->precedence = eval_precedence(EVAL_OPERATOR_NOT); + } + else if(parse_plus(string)) { + op1 = parse_next_operand_given_its_operator(string, EVAL_OPERATOR_SIGN_PLUS, error); + op1->precedence = eval_precedence(EVAL_OPERATOR_SIGN_PLUS); + } + else if(parse_minus(string)) { + op1 = parse_next_operand_given_its_operator(string, EVAL_OPERATOR_SIGN_MINUS, error); + op1->precedence = eval_precedence(EVAL_OPERATOR_SIGN_MINUS); + } + else if(parse_abs(string)) { + op1 = parse_next_operand_given_its_operator(string, EVAL_OPERATOR_ABS, error); + op1->precedence = eval_precedence(EVAL_OPERATOR_ABS); + } + else if(parse_open_subexpression(string)) { + EVAL_NODE *sub = parse_full_expression(string, error); + if(sub) { + op1 = eval_node_alloc(1); + op1->operator = EVAL_OPERATOR_EXPRESSION_OPEN; + op1->precedence = eval_precedence(EVAL_OPERATOR_EXPRESSION_OPEN); + eval_node_set_value_to_node(op1, 0, sub); + if(!parse_close_subexpression(string)) { + *error = EVAL_ERROR_MISSING_CLOSE_SUBEXPRESSION; + eval_node_free(op1); + return NULL; + } + } + } + else if(parse_variable(string, variable_buffer, EVAL_MAX_VARIABLE_NAME_LENGTH)) { + op1 = eval_node_alloc(1); + op1->operator = EVAL_OPERATOR_NOP; + eval_node_set_value_to_variable(op1, 0, variable_buffer); + } + else if(parse_constant(string, &number)) { + op1 = eval_node_alloc(1); + op1->operator = EVAL_OPERATOR_NOP; + eval_node_set_value_to_constant(op1, 0, number); + } + else if(**string) + *error = EVAL_ERROR_UNKNOWN_OPERAND; + else + *error = EVAL_ERROR_MISSING_OPERAND; + + return op1; +} + +// parse an operator and the rest of the expression +// precedence processing is handled here +static inline EVAL_NODE *parse_rest_of_expression(const char **string, int *error, EVAL_NODE *op1) { + EVAL_NODE *op2 = NULL; + unsigned char operator; + int precedence; + + operator = parse_operator(string, &precedence); + skip_spaces(string); + + if(operator != EVAL_OPERATOR_NOP) { + op2 = parse_one_full_operand(string, error); + if(!op2) { + // error is already reported + eval_node_free(op1); + return NULL; + } + + EVAL_NODE *op = eval_node_alloc(operators[operator].parameters); + op->operator = operator; + op->precedence = precedence; + + if(operator == EVAL_OPERATOR_IF_THEN_ELSE && op->count == 3) { + skip_spaces(string); + + if(**string != ':') { + eval_node_free(op); + eval_node_free(op1); + eval_node_free(op2); + *error = EVAL_ERROR_IF_THEN_ELSE_MISSING_ELSE; + return NULL; + } + (*string)++; + + skip_spaces(string); + + EVAL_NODE *op3 = parse_one_full_operand(string, error); + if(!op3) { + eval_node_free(op); + eval_node_free(op1); + eval_node_free(op2); + // error is already reported + return NULL; + } + + eval_node_set_value_to_node(op, 2, op3); + } + + eval_node_set_value_to_node(op, 1, op2); + + // precedence processing + // if this operator has a higher precedence compared to its next + // put the next operator on top of us (top = evaluated later) + // function recursion does the rest... + if(op->precedence > op1->precedence && op1->count == 2 && op1->operator != '(' && op1->ops[1].type == EVAL_VALUE_EXPRESSION) { + eval_node_set_value_to_node(op, 0, op1->ops[1].expression); + op1->ops[1].expression = op; + op = op1; + } + else + eval_node_set_value_to_node(op, 0, op1); + + return parse_rest_of_expression(string, error, op); + } + else if(**string == ')') { + ; + } + else if(**string) { + eval_node_free(op1); + op1 = NULL; + *error = EVAL_ERROR_MISSING_OPERATOR; + } + + return op1; +} + +// high level function to parse an expression or a sub-expression +static inline EVAL_NODE *parse_full_expression(const char **string, int *error) { + EVAL_NODE *op1 = NULL; + + op1 = parse_one_full_operand(string, error); + if(!op1) { + *error = EVAL_ERROR_MISSING_OPERAND; + return NULL; + } + + return parse_rest_of_expression(string, error, op1); +} + +// ---------------------------------------------------------------------------- +// public API + +int expression_evaluate(EVAL_EXPRESSION *exp) { + exp->error = EVAL_ERROR_OK; + + buffer_reset(exp->error_msg); + exp->result = eval_node(exp, (EVAL_NODE *)exp->nodes, &exp->error); + + if(exp->error == EVAL_ERROR_OK) + exp->result = eval_check_number(exp->result, &exp->error); + + if(exp->error != EVAL_ERROR_OK) { + exp->result = NAN; + + if(buffer_strlen(exp->error_msg)) + buffer_strcat(exp->error_msg, "; "); + + buffer_sprintf(exp->error_msg, "failed to evaluate expression with error %d (%s)", exp->error, expression_strerror(exp->error)); + return 0; + } + + return 1; +} + +EVAL_EXPRESSION *expression_parse(const char *string, const char **failed_at, int *error) { + const char *s = string; + int err = EVAL_ERROR_OK; + unsigned long pos = 0; + + EVAL_NODE *op = parse_full_expression(&s, &err); + + if(*s) { + if(op) { + eval_node_free(op); + op = NULL; + } + err = EVAL_ERROR_REMAINING_GARBAGE; + } + + if (failed_at) *failed_at = s; + if (error) *error = err; + + if(!op) { + pos = s - string + 1; + error("failed to parse expression '%s': %s at character %lu (i.e.: '%s').", string, expression_strerror(err), pos, s); + return NULL; + } + + BUFFER *out = buffer_create(1024); + print_parsed_as_node(out, op, &err); + if(err != EVAL_ERROR_OK) { + error("failed to re-generate expression '%s' with reason: %s", string, expression_strerror(err)); + eval_node_free(op); + buffer_free(out); + return NULL; + } + + EVAL_EXPRESSION *exp = callocz(1, sizeof(EVAL_EXPRESSION)); + + exp->source = strdupz(string); + exp->parsed_as = strdupz(buffer_tostring(out)); + buffer_free(out); + + exp->error_msg = buffer_create(100); + exp->nodes = (void *)op; + + return exp; +} + +void expression_free(EVAL_EXPRESSION *exp) { + if(!exp) return; + + if(exp->nodes) eval_node_free((EVAL_NODE *)exp->nodes); + freez((void *)exp->source); + freez((void *)exp->parsed_as); + buffer_free(exp->error_msg); + freez(exp); +} + +const char *expression_strerror(int error) { + switch(error) { + case EVAL_ERROR_OK: + return "success"; + + case EVAL_ERROR_MISSING_CLOSE_SUBEXPRESSION: + return "missing closing parenthesis"; + + case EVAL_ERROR_UNKNOWN_OPERAND: + return "unknown operand"; + + case EVAL_ERROR_MISSING_OPERAND: + return "expected operand"; + + case EVAL_ERROR_MISSING_OPERATOR: + return "expected operator"; + + case EVAL_ERROR_REMAINING_GARBAGE: + return "remaining characters after expression"; + + case EVAL_ERROR_INVALID_VALUE: + return "invalid value structure - internal error"; + + case EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS: + return "wrong number of operands for operation - internal error"; + + case EVAL_ERROR_VALUE_IS_NAN: + return "value is unset"; + + case EVAL_ERROR_VALUE_IS_INFINITE: + return "computed value is infinite"; + + case EVAL_ERROR_UNKNOWN_VARIABLE: + return "undefined variable"; + + case EVAL_ERROR_IF_THEN_ELSE_MISSING_ELSE: + return "missing second sub-expression of inline conditional"; + + default: + return "unknown error"; + } +} diff --git a/src/eval.h b/src/eval.h new file mode 100644 index 000000000..b509a9fa8 --- /dev/null +++ b/src/eval.h @@ -0,0 +1,72 @@ +#ifndef NETDATA_EVAL_H +#define NETDATA_EVAL_H + +#define EVAL_MAX_VARIABLE_NAME_LENGTH 300 + +typedef struct eval_variable { + char *name; + uint32_t hash; + struct rrdvar *rrdvar; + struct eval_variable *next; +} EVAL_VARIABLE; + +typedef struct eval_expression { + const char *source; + const char *parsed_as; + + calculated_number *this; + time_t *after; + time_t *before; + + calculated_number result; + + int error; + BUFFER *error_msg; + + // hidden EVAL_NODE * + void *nodes; + + // custom data to be used for looking up variables + struct rrdcalc *rrdcalc; +} EVAL_EXPRESSION; + +#define EVAL_VALUE_INVALID 0 +#define EVAL_VALUE_NUMBER 1 +#define EVAL_VALUE_VARIABLE 2 +#define EVAL_VALUE_EXPRESSION 3 + +// parsing and evaluation +#define EVAL_ERROR_OK 0 + +// parsing errors +#define EVAL_ERROR_MISSING_CLOSE_SUBEXPRESSION 1 +#define EVAL_ERROR_UNKNOWN_OPERAND 2 +#define EVAL_ERROR_MISSING_OPERAND 3 +#define EVAL_ERROR_MISSING_OPERATOR 4 +#define EVAL_ERROR_REMAINING_GARBAGE 5 +#define EVAL_ERROR_IF_THEN_ELSE_MISSING_ELSE 6 + +// evaluation errors +#define EVAL_ERROR_INVALID_VALUE 101 +#define EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS 102 +#define EVAL_ERROR_VALUE_IS_NAN 103 +#define EVAL_ERROR_VALUE_IS_INFINITE 104 +#define EVAL_ERROR_UNKNOWN_VARIABLE 105 + +// parse the given string as an expression and return: +// a pointer to an expression if it parsed OK +// NULL in which case the pointer to error has the error code +extern EVAL_EXPRESSION *expression_parse(const char *string, const char **failed_at, int *error); + +// free all resources allocated for an expression +extern void expression_free(EVAL_EXPRESSION *op); + +// convert an error code to a message +extern const char *expression_strerror(int error); + +// evaluate an expression and return +// 1 = OK, the result is in: expression->result +// 2 = FAILED, the error message is in: buffer_tostring(expression->error_msg) +extern int expression_evaluate(EVAL_EXPRESSION *expression); + +#endif //NETDATA_EVAL_H diff --git a/src/global_statistics.c b/src/global_statistics.c index d4a04efd2..f39a4cf25 100644 --- a/src/global_statistics.c +++ b/src/global_statistics.c @@ -1,18 +1,262 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> +#include "common.h" + +volatile struct global_statistics global_statistics = { + .connected_clients = 0, + .web_requests = 0, + .web_usec = 0, + .bytes_received = 0, + .bytes_sent = 0, + .content_size = 0, + .compressed_content_size = 0 +}; + +pthread_mutex_t global_statistics_mutex = PTHREAD_MUTEX_INITIALIZER; + +inline void global_statistics_lock(void) { + pthread_mutex_lock(&global_statistics_mutex); +} + +inline void global_statistics_unlock(void) { + pthread_mutex_unlock(&global_statistics_mutex); +} + +void finished_web_request_statistics(uint64_t dt, + uint64_t bytes_received, + uint64_t bytes_sent, + uint64_t content_size, + uint64_t compressed_content_size) { +#ifndef NETDATA_NO_ATOMIC_INSTRUCTIONS + uint64_t old_web_usec_max = global_statistics.web_usec_max; + while(dt > old_web_usec_max) + __atomic_compare_exchange(&global_statistics.web_usec_max, &old_web_usec_max, &dt, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + + __atomic_fetch_add(&global_statistics.web_requests, 1, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&global_statistics.web_usec, dt, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&global_statistics.bytes_received, bytes_received, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&global_statistics.bytes_sent, bytes_sent, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&global_statistics.content_size, content_size, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&global_statistics.compressed_content_size, compressed_content_size, __ATOMIC_SEQ_CST); +#else +#warning NOT using atomic operations - using locks for global statistics + if (web_server_mode == WEB_SERVER_MODE_MULTI_THREADED) + global_statistics_lock(); + + if (dt > global_statistics.web_usec_max) + global_statistics.web_usec_max = dt; + + global_statistics.web_requests++; + global_statistics.web_usec += dt; + global_statistics.bytes_received += bytes_received; + global_statistics.bytes_sent += bytes_sent; + global_statistics.content_size += content_size; + global_statistics.compressed_content_size += compressed_content_size; + + if (web_server_mode == WEB_SERVER_MODE_MULTI_THREADED) + global_statistics_unlock(); #endif -#include <pthread.h> +} -#include "global_statistics.h" +void web_client_connected(void) { +#ifndef NETDATA_NO_ATOMIC_INSTRUCTIONS + __atomic_fetch_add(&global_statistics.connected_clients, 1, __ATOMIC_SEQ_CST); +#else + if (web_server_mode == WEB_SERVER_MODE_MULTI_THREADED) + global_statistics_lock(); -struct global_statistics global_statistics = { 0ULL, 0ULL, 0ULL, 0ULL }; + global_statistics.connected_clients++; -pthread_mutex_t global_statistics_mutex = PTHREAD_MUTEX_INITIALIZER; + if (web_server_mode == WEB_SERVER_MODE_MULTI_THREADED) + global_statistics_unlock(); +#endif +} + +void web_client_disconnected(void) { +#ifndef NETDATA_NO_ATOMIC_INSTRUCTIONS + __atomic_fetch_sub(&global_statistics.connected_clients, 1, __ATOMIC_SEQ_CST); +#else + if (web_server_mode == WEB_SERVER_MODE_MULTI_THREADED) + global_statistics_lock(); -void global_statistics_lock(void) { - pthread_mutex_lock(&global_statistics_mutex); + global_statistics.connected_clients--; + + if (web_server_mode == WEB_SERVER_MODE_MULTI_THREADED) + global_statistics_unlock(); +#endif } -void global_statistics_unlock(void) { - pthread_mutex_unlock(&global_statistics_mutex); + +inline void global_statistics_copy(struct global_statistics *gs, uint8_t options) { +#ifndef NETDATA_NO_ATOMIC_INSTRUCTIONS + gs->connected_clients = __atomic_fetch_add(&global_statistics.connected_clients, 0, __ATOMIC_SEQ_CST); + gs->web_requests = __atomic_fetch_add(&global_statistics.web_requests, 0, __ATOMIC_SEQ_CST); + gs->web_usec = __atomic_fetch_add(&global_statistics.web_usec, 0, __ATOMIC_SEQ_CST); + gs->web_usec_max = __atomic_fetch_add(&global_statistics.web_usec_max, 0, __ATOMIC_SEQ_CST); + gs->bytes_received = __atomic_fetch_add(&global_statistics.bytes_received, 0, __ATOMIC_SEQ_CST); + gs->bytes_sent = __atomic_fetch_add(&global_statistics.bytes_sent, 0, __ATOMIC_SEQ_CST); + gs->content_size = __atomic_fetch_add(&global_statistics.content_size, 0, __ATOMIC_SEQ_CST); + gs->compressed_content_size = __atomic_fetch_add(&global_statistics.compressed_content_size, 0, __ATOMIC_SEQ_CST); + + if(options & GLOBAL_STATS_RESET_WEB_USEC_MAX) { + uint64_t n = 0; + __atomic_compare_exchange(&global_statistics.web_usec_max, &gs->web_usec_max, &n, 1, __ATOMIC_SEQ_CST, + __ATOMIC_SEQ_CST); + } +#else + global_statistics_lock(); + + memcpy(gs, (const void *)&global_statistics, sizeof(struct global_statistics)); + + if (options & GLOBAL_STATS_RESET_WEB_USEC_MAX) + global_statistics.web_usec_max = 0; + + global_statistics_unlock(); +#endif } + +void global_statistics_charts(void) { + static unsigned long long old_web_requests = 0, old_web_usec = 0, + old_content_size = 0, old_compressed_content_size = 0; + + static collected_number compression_ratio = -1, average_response_time = -1; + + static RRDSET *stcpu = NULL, *stcpu_thread = NULL, *stclients = NULL, *streqs = NULL, *stbytes = NULL, *stduration = NULL, + *stcompression = NULL; + + struct global_statistics gs; + struct rusage me, thread; + + global_statistics_copy(&gs, GLOBAL_STATS_RESET_WEB_USEC_MAX); + getrusage(RUSAGE_THREAD, &thread); + getrusage(RUSAGE_SELF, &me); + + 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", 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); + + // ---------------------------------------------------------------- + + if (!stcpu) stcpu = rrdset_find("netdata.server_cpu"); + if (!stcpu) { + stcpu = rrdset_create("netdata", "server_cpu", NULL, "netdata", NULL, "NetData CPU usage", "milliseconds/s", + 130000, rrd_update_every, RRDSET_TYPE_STACKED); + + rrddim_add(stcpu, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL); + rrddim_add(stcpu, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL); + } else rrdset_next(stcpu); + + rrddim_set(stcpu, "user", me.ru_utime.tv_sec * 1000000ULL + me.ru_utime.tv_usec); + rrddim_set(stcpu, "system", me.ru_stime.tv_sec * 1000000ULL + me.ru_stime.tv_usec); + rrdset_done(stcpu); + + // ---------------------------------------------------------------- + + if (!stclients) stclients = rrdset_find("netdata.clients"); + if (!stclients) { + stclients = rrdset_create("netdata", "clients", NULL, "netdata", NULL, "NetData Web Clients", + "connected clients", 130200, rrd_update_every, RRDSET_TYPE_LINE); + + rrddim_add(stclients, "clients", NULL, 1, 1, RRDDIM_ABSOLUTE); + } else rrdset_next(stclients); + + rrddim_set(stclients, "clients", gs.connected_clients); + rrdset_done(stclients); + + // ---------------------------------------------------------------- + + if (!streqs) streqs = rrdset_find("netdata.requests"); + if (!streqs) { + streqs = rrdset_create("netdata", "requests", NULL, "netdata", NULL, "NetData Web Requests", "requests/s", + 130300, rrd_update_every, RRDSET_TYPE_LINE); + + rrddim_add(streqs, "requests", NULL, 1, 1, RRDDIM_INCREMENTAL); + } else rrdset_next(streqs); + + rrddim_set(streqs, "requests", (collected_number) gs.web_requests); + rrdset_done(streqs); + + // ---------------------------------------------------------------- + + if (!stbytes) stbytes = rrdset_find("netdata.net"); + if (!stbytes) { + stbytes = rrdset_create("netdata", "net", NULL, "netdata", NULL, "NetData Network Traffic", "kilobits/s", + 130000, rrd_update_every, RRDSET_TYPE_AREA); + + rrddim_add(stbytes, "in", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(stbytes, "out", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } else rrdset_next(stbytes); + + rrddim_set(stbytes, "in", (collected_number) gs.bytes_received); + rrddim_set(stbytes, "out", (collected_number) gs.bytes_sent); + rrdset_done(stbytes); + + // ---------------------------------------------------------------- + + if (!stduration) stduration = rrdset_find("netdata.response_time"); + if (!stduration) { + stduration = rrdset_create("netdata", "response_time", NULL, "netdata", NULL, "NetData API Response Time", + "ms/request", 130400, rrd_update_every, RRDSET_TYPE_LINE); + + rrddim_add(stduration, "average", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(stduration, "max", NULL, 1, 1000, RRDDIM_ABSOLUTE); + } else rrdset_next(stduration); + + uint64_t gweb_usec = gs.web_usec; + uint64_t gweb_requests = gs.web_requests; + + uint64_t web_usec = (gweb_usec >= old_web_usec) ? gweb_usec - old_web_usec : 0; + uint64_t web_requests = (gweb_requests >= old_web_requests) ? gweb_requests - old_web_requests : 0; + + old_web_usec = gweb_usec; + old_web_requests = gweb_requests; + + if (web_requests) + average_response_time = (collected_number) (web_usec / web_requests); + + if (unlikely(average_response_time != -1)) + rrddim_set(stduration, "average", average_response_time); + else + rrddim_set(stduration, "average", 0); + + rrddim_set(stduration, "max", ((gs.web_usec_max)?(collected_number)gs.web_usec_max:average_response_time)); + rrdset_done(stduration); + + // ---------------------------------------------------------------- + + if (!stcompression) stcompression = rrdset_find("netdata.compression_ratio"); + if (!stcompression) { + stcompression = rrdset_create("netdata", "compression_ratio", NULL, "netdata", NULL, + "NetData API Responses Compression Savings Ratio", "percentage", 130500, + rrd_update_every, RRDSET_TYPE_LINE); + + rrddim_add(stcompression, "savings", NULL, 1, 1000, RRDDIM_ABSOLUTE); + } else rrdset_next(stcompression); + + // since we don't lock here to read the global statistics + // read the smaller value first + unsigned long long gcompressed_content_size = gs.compressed_content_size; + unsigned long long gcontent_size = gs.content_size; + + unsigned long long compressed_content_size = gcompressed_content_size - old_compressed_content_size; + unsigned long long content_size = gcontent_size - old_content_size; + + old_compressed_content_size = gcompressed_content_size; + old_content_size = gcontent_size; + + if (content_size && content_size >= compressed_content_size) + compression_ratio = ((content_size - compressed_content_size) * 100 * 1000) / content_size; + + if (compression_ratio != -1) + rrddim_set(stcompression, "savings", compression_ratio); + + rrdset_done(stcompression); +}
\ No newline at end of file diff --git a/src/global_statistics.h b/src/global_statistics.h index ce3c3490e..d28aa4401 100644 --- a/src/global_statistics.h +++ b/src/global_statistics.h @@ -5,16 +5,32 @@ // global statistics struct global_statistics { - unsigned long long connected_clients; - unsigned long long web_requests; - unsigned long long bytes_received; - unsigned long long bytes_sent; + volatile uint16_t connected_clients; + volatile uint64_t web_requests; + volatile uint64_t web_usec; + volatile uint64_t web_usec_max; + volatile uint64_t bytes_received; + volatile uint64_t bytes_sent; + volatile uint64_t content_size; + volatile uint64_t compressed_content_size; }; -extern struct global_statistics global_statistics; +extern volatile struct global_statistics global_statistics; extern void global_statistics_lock(void); extern void global_statistics_unlock(void); +extern void finished_web_request_statistics(uint64_t dt, + uint64_t bytes_received, + uint64_t bytes_sent, + uint64_t content_size, + uint64_t compressed_content_size); + +extern void web_client_connected(void); +extern void web_client_disconnected(void); + +#define GLOBAL_STATS_RESET_WEB_USEC_MAX 0x01 +extern void global_statistics_copy(struct global_statistics *gs, uint8_t options); +extern void global_statistics_charts(void); #endif /* NETDATA_GLOBAL_STATISTICS_H */ diff --git a/src/health.c b/src/health.c new file mode 100644 index 000000000..3156cd080 --- /dev/null +++ b/src/health.c @@ -0,0 +1,2194 @@ +#include "common.h" + +#define RRDVAR_MAX_LENGTH 1024 + +static const char *health_default_exec = PLUGINS_DIR "/alarm-email.sh"; +int health_enabled = 1; + +// ---------------------------------------------------------------------------- +// RRDVAR management + +static inline int rrdvar_fix_name(char *variable) { + int fixed = 0; + while(*variable) { + if (!isalnum(*variable) && *variable != '.' && *variable != '_') { + *variable++ = '_'; + fixed++; + } + else + variable++; + } + + return fixed; +} + +int rrdvar_compare(void* a, void* b) { + if(((RRDVAR *)a)->hash < ((RRDVAR *)b)->hash) return -1; + else if(((RRDVAR *)a)->hash > ((RRDVAR *)b)->hash) return 1; + else return strcmp(((RRDVAR *)a)->name, ((RRDVAR *)b)->name); +} + +static inline RRDVAR *rrdvar_index_add(avl_tree_lock *tree, RRDVAR *rv) { + RRDVAR *ret = (RRDVAR *)avl_insert_lock(tree, (avl *)(rv)); + if(ret != rv) + debug(D_VARIABLES, "Request to insert RRDVAR '%s' into index failed. Already exists.", rv->name); + + return ret; +} + +static inline RRDVAR *rrdvar_index_del(avl_tree_lock *tree, RRDVAR *rv) { + RRDVAR *ret = (RRDVAR *)avl_remove_lock(tree, (avl *)(rv)); + if(!ret) + error("Request to remove RRDVAR '%s' from index failed. Not Found.", rv->name); + + return ret; +} + +static inline RRDVAR *rrdvar_index_find(avl_tree_lock *tree, const char *name, uint32_t hash) { + RRDVAR tmp; + tmp.name = (char *)name; + tmp.hash = (hash)?hash:simple_hash(tmp.name); + + return (RRDVAR *)avl_search_lock(tree, (avl *)&tmp); +} + +static inline void rrdvar_free(RRDHOST *host, avl_tree_lock *tree, RRDVAR *rv) { + (void)host; + + if(!rv) return; + + if(tree) + rrdvar_index_del(tree, rv); + + freez(rv->name); + freez(rv); +} + +static inline RRDVAR *rrdvar_create_and_index(const char *scope, avl_tree_lock *tree, const char *name, int type, calculated_number *value) { + char *variable = strdupz(name); + rrdvar_fix_name(variable); + uint32_t hash = simple_hash(variable); + + RRDVAR *rv = rrdvar_index_find(tree, variable, hash); + if(unlikely(!rv)) { + debug(D_VARIABLES, "Variable '%s' not found in scope '%s'. Creating a new one.", variable, scope); + + rv = callocz(1, sizeof(RRDVAR)); + rv->name = variable; + rv->hash = hash; + rv->type = type; + rv->value = value; + + RRDVAR *ret = rrdvar_index_add(tree, rv); + if(unlikely(ret != rv)) { + debug(D_VARIABLES, "Variable '%s' in scope '%s' already exists", variable, scope); + rrdvar_free(NULL, NULL, rv); + rv = NULL; + } + else + debug(D_VARIABLES, "Variable '%s' created in scope '%s'", variable, scope); + } + else { + // already exists + freez(variable); + rv = NULL; + } + + return rv; +} + +// ---------------------------------------------------------------------------- +// RRDVAR lookup + +calculated_number rrdvar2number(RRDVAR *rv) { + switch(rv->type) { + case RRDVAR_TYPE_CALCULATED: { + calculated_number *n = (calculated_number *)rv->value; + return *n; + } + + case RRDVAR_TYPE_TIME_T: { + time_t *n = (time_t *)rv->value; + return *n; + } + + case RRDVAR_TYPE_COLLECTED: { + collected_number *n = (collected_number *)rv->value; + return *n; + } + + case RRDVAR_TYPE_TOTAL: { + total_number *n = (total_number *)rv->value; + return *n; + } + + case RRDVAR_TYPE_INT: { + int *n = (int *)rv->value; + return *n; + } + + default: + error("I don't know how to convert RRDVAR type %d to calculated_number", rv->type); + return NAN; + } +} + +void dump_variable(void *data) { + RRDVAR *rv = (RRDVAR *)data; + debug(D_HEALTH, "%50s : %20.5Lf", rv->name, rrdvar2number(rv)); +} + +int health_variable_lookup(const char *variable, uint32_t hash, RRDCALC *rc, calculated_number *result) { + RRDSET *st = rc->rrdset; + RRDVAR *rv; + + if(!st) return 0; + + rv = rrdvar_index_find(&st->variables_root_index, variable, hash); + if(rv) { + *result = rrdvar2number(rv); + return 1; + } + + rv = rrdvar_index_find(&st->rrdfamily->variables_root_index, variable, hash); + if(rv) { + *result = rrdvar2number(rv); + return 1; + } + + rv = rrdvar_index_find(&st->rrdhost->variables_root_index, variable, hash); + if(rv) { + *result = rrdvar2number(rv); + return 1; + } + + debug(D_HEALTH, "Available local chart '%s' variables:", st->id); + avl_traverse_lock(&st->variables_root_index, dump_variable); + + debug(D_HEALTH, "Available family '%s' variables:", st->rrdfamily->family); + avl_traverse_lock(&st->rrdfamily->variables_root_index, dump_variable); + + debug(D_HEALTH, "Available host '%s' variables:", st->rrdhost->hostname); + avl_traverse_lock(&st->rrdhost->variables_root_index, dump_variable); + + return 0; +} + +// ---------------------------------------------------------------------------- +// RRDSETVAR management + +RRDSETVAR *rrdsetvar_create(RRDSET *st, const char *variable, int type, void *value, uint32_t options) { + debug(D_VARIABLES, "RRDVARSET create for chart id '%s' name '%s' with variable name '%s'", st->id, st->name, variable); + RRDSETVAR *rs = (RRDSETVAR *)callocz(1, sizeof(RRDSETVAR)); + + char buffer[RRDVAR_MAX_LENGTH + 1]; + snprintfz(buffer, RRDVAR_MAX_LENGTH, "%s.%s", st->id, variable); + rs->fullid = strdupz(buffer); + + snprintfz(buffer, RRDVAR_MAX_LENGTH, "%s.%s", st->name, variable); + rs->fullname = strdupz(buffer); + + rs->variable = strdupz(variable); + + rs->type = type; + rs->value = value; + rs->options = options; + rs->rrdset = st; + + rs->local = rrdvar_create_and_index("local", &st->variables_root_index, rs->variable, rs->type, rs->value); + rs->family = rrdvar_create_and_index("family", &st->rrdfamily->variables_root_index, rs->fullid, rs->type, rs->value); + rs->host = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullid, rs->type, rs->value); + rs->family_name = rrdvar_create_and_index("family", &st->rrdfamily->variables_root_index, rs->fullname, rs->type, rs->value); + rs->host_name = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullname, rs->type, rs->value); + + rs->next = st->variables; + st->variables = rs; + + return rs; +} + +void rrdsetvar_rename_all(RRDSET *st) { + debug(D_VARIABLES, "RRDSETVAR rename for chart id '%s' name '%s'", st->id, st->name); + + // only these 2 can change name + // rs->family_name + // rs->host_name + + char buffer[RRDVAR_MAX_LENGTH + 1]; + RRDSETVAR *rs, *next = st->variables; + while((rs = next)) { + next = rs->next; + + snprintfz(buffer, RRDVAR_MAX_LENGTH, "%s.%s", st->name, rs->variable); + + if (strcmp(buffer, rs->fullname)) { + // name changed + rrdvar_free(st->rrdhost, &st->rrdfamily->variables_root_index, rs->family_name); + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_name); + + freez(rs->fullname); + rs->fullname = strdupz(st->name); + rs->family_name = rrdvar_create_and_index("family", &st->rrdfamily->variables_root_index, rs->fullname, rs->type, rs->value); + rs->host_name = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullname, rs->type, rs->value); + } + } + + rrdsetcalc_link_matching(st); +} + +void rrdsetvar_free(RRDSETVAR *rs) { + RRDSET *st = rs->rrdset; + debug(D_VARIABLES, "RRDSETVAR free for chart id '%s' name '%s', variable '%s'", st->id, st->name, rs->variable); + + rrdvar_free(st->rrdhost, &st->variables_root_index, rs->local); + rrdvar_free(st->rrdhost, &st->rrdfamily->variables_root_index, rs->family); + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host); + rrdvar_free(st->rrdhost, &st->rrdfamily->variables_root_index, rs->family_name); + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_name); + + if(st->variables == rs) { + st->variables = rs->next; + } + else { + RRDSETVAR *t; + for (t = st->variables; t && t->next != rs; t = t->next); + if(!t) error("RRDSETVAR '%s' not found in chart '%s' variables linked list", rs->fullname, st->id); + else t->next = rs->next; + } + + freez(rs->fullid); + freez(rs->fullname); + freez(rs->variable); + freez(rs); +} + +// ---------------------------------------------------------------------------- +// RRDDIMVAR management + +#define RRDDIMVAR_ID_MAX 1024 + +RRDDIMVAR *rrddimvar_create(RRDDIM *rd, int type, const char *prefix, const char *suffix, void *value, uint32_t options) { + RRDSET *st = rd->rrdset; + + debug(D_VARIABLES, "RRDDIMSET create for chart id '%s' name '%s', dimension id '%s', name '%s%s%s'", st->id, st->name, rd->id, (prefix)?prefix:"", rd->name, (suffix)?suffix:""); + + if(!prefix) prefix = ""; + if(!suffix) suffix = ""; + + char buffer[RRDDIMVAR_ID_MAX + 1]; + RRDDIMVAR *rs = (RRDDIMVAR *)callocz(1, sizeof(RRDDIMVAR)); + + rs->prefix = strdupz(prefix); + rs->suffix = strdupz(suffix); + + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s%s%s", rs->prefix, rd->id, rs->suffix); + rs->id = strdupz(buffer); + + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s%s%s", rs->prefix, rd->name, rs->suffix); + rs->name = strdupz(buffer); + + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->id, rs->id); + rs->fullidid = strdupz(buffer); + + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->id, rs->name); + rs->fullidname = strdupz(buffer); + + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->name, rs->id); + rs->fullnameid = strdupz(buffer); + + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->name, rs->name); + rs->fullnamename = strdupz(buffer); + + rs->type = type; + rs->value = value; + rs->options = options; + rs->rrddim = rd; + + rs->local_id = rrdvar_create_and_index("local", &st->variables_root_index, rs->id, rs->type, rs->value); + rs->local_name = rrdvar_create_and_index("local", &st->variables_root_index, rs->name, rs->type, rs->value); + + rs->family_id = rrdvar_create_and_index("family", &st->rrdfamily->variables_root_index, rs->id, rs->type, rs->value); + rs->family_name = rrdvar_create_and_index("family", &st->rrdfamily->variables_root_index, rs->name, rs->type, rs->value); + + rs->host_fullidid = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullidid, rs->type, rs->value); + rs->host_fullidname = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullidname, rs->type, rs->value); + rs->host_fullnameid = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullnameid, rs->type, rs->value); + rs->host_fullnamename = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullnamename, rs->type, rs->value); + + rs->next = rd->variables; + rd->variables = rs; + + return rs; +} + +void rrddimvar_rename_all(RRDDIM *rd) { + RRDSET *st = rd->rrdset; + debug(D_VARIABLES, "RRDDIMSET rename for chart id '%s' name '%s', dimension id '%s', name '%s'", st->id, st->name, rd->id, rd->name); + + RRDDIMVAR *rs, *next = rd->variables; + while((rs = next)) { + next = rs->next; + + if (strcmp(rd->name, rs->name)) { + char buffer[RRDDIMVAR_ID_MAX + 1]; + // name changed + + // name + rrdvar_free(st->rrdhost, &st->variables_root_index, rs->local_name); + freez(rs->name); + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s%s%s", rs->prefix, rd->name, rs->suffix); + rs->name = strdupz(buffer); + rs->local_name = rrdvar_create_and_index("local", &st->variables_root_index, rs->name, rs->type, rs->value); + + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_fullidname); + freez(rs->fullidname); + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", st->id, rs->name); + rs->fullidname = strdupz(buffer); + rs->host_fullidname = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, + rs->fullidname, rs->type, rs->value); + + // fullnameid + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_fullnameid); + freez(rs->fullnameid); + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", st->name, rs->id); + rs->fullnameid = strdupz(buffer); + rs->host_fullnameid = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, + rs->fullnameid, rs->type, rs->value); + + // fullnamename + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_fullnamename); + freez(rs->fullnamename); + snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", st->name, rs->name); + rs->fullnamename = strdupz(buffer); + rs->host_fullnamename = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, + rs->fullnamename, rs->type, rs->value); + } + } +} + +void rrddimvar_free(RRDDIMVAR *rs) { + RRDDIM *rd = rs->rrddim; + RRDSET *st = rd->rrdset; + debug(D_VARIABLES, "RRDDIMSET free for chart id '%s' name '%s', dimension id '%s', name '%s', prefix='%s', suffix='%s'", st->id, st->name, rd->id, rd->name, rs->prefix, rs->suffix); + + rrdvar_free(st->rrdhost, &st->variables_root_index, rs->local_id); + rrdvar_free(st->rrdhost, &st->variables_root_index, rs->local_name); + + rrdvar_free(st->rrdhost, &st->rrdfamily->variables_root_index, rs->family_id); + rrdvar_free(st->rrdhost, &st->rrdfamily->variables_root_index, rs->family_name); + + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_fullidid); + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_fullidname); + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_fullnameid); + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rs->host_fullnamename); + + if(rd->variables == rs) { + debug(D_VARIABLES, "RRDDIMSET removing first entry for chart id '%s' name '%s', dimension id '%s', name '%s'", st->id, st->name, rd->id, rd->name); + rd->variables = rs->next; + } + else { + debug(D_VARIABLES, "RRDDIMSET removing non-first entry for chart id '%s' name '%s', dimension id '%s', name '%s'", st->id, st->name, rd->id, rd->name); + RRDDIMVAR *t; + for (t = rd->variables; t && t->next != rs; t = t->next) ; + if(!t) error("RRDDIMVAR '%s' not found in dimension '%s/%s' variables linked list", rs->name, st->id, rd->id); + else t->next = rs->next; + } + + freez(rs->prefix); + freez(rs->suffix); + freez(rs->id); + freez(rs->name); + freez(rs->fullidid); + freez(rs->fullidname); + freez(rs->fullnameid); + freez(rs->fullnamename); + freez(rs); +} + +// ---------------------------------------------------------------------------- +// RRDCALC management + +static inline const char *rrdcalc_status2string(int status) { + switch(status) { + case RRDCALC_STATUS_UNINITIALIZED: + return "UNINITIALIZED"; + + case RRDCALC_STATUS_UNDEFINED: + return "UNDEFINED"; + + case RRDCALC_STATUS_CLEAR: + return "CLEAR"; + + case RRDCALC_STATUS_RAISED: + return "RAISED"; + + case RRDCALC_STATUS_WARNING: + return "WARNING"; + + case RRDCALC_STATUS_CRITICAL: + return "CRITICAL"; + + default: + return "UNKNOWN"; + } +} + +static void rrdsetcalc_link(RRDSET *st, RRDCALC *rc) { + debug(D_HEALTH, "Health linking alarm '%s.%s' to chart '%s' of host '%s'", rc->chart?rc->chart:"NOCHART", rc->name, st->id, st->rrdhost->hostname); + + rc->last_status_change = time(NULL); + rc->rrdset = st; + + rc->rrdset_next = st->alarms; + rc->rrdset_prev = NULL; + st->alarms = rc; + + if(rc->update_every < rc->rrdset->update_every) { + error("Health alarm '%s.%s' has update every %d, less than chart update every %d. Setting alarm update frequency to %d.", rc->rrdset->id, rc->name, rc->update_every, rc->rrdset->update_every, rc->rrdset->update_every); + rc->update_every = rc->rrdset->update_every; + } + + if(!isnan(rc->green) && isnan(st->green)) { + debug(D_HEALTH, "Health alarm '%s.%s' green threshold set from %Lf to %Lf.", rc->rrdset->id, rc->name, rc->rrdset->green, rc->green); + st->green = rc->green; + } + + if(!isnan(rc->red) && isnan(st->red)) { + debug(D_HEALTH, "Health alarm '%s.%s' red threshold set from %Lf to %Lf.", rc->rrdset->id, rc->name, rc->rrdset->red, rc->red); + st->red = rc->red; + } + + rc->local = rrdvar_create_and_index("local", &st->variables_root_index, rc->name, RRDVAR_TYPE_CALCULATED, &rc->value); + rc->family = rrdvar_create_and_index("family", &st->rrdfamily->variables_root_index, rc->name, RRDVAR_TYPE_CALCULATED, &rc->value); + + char fullname[RRDVAR_MAX_LENGTH + 1]; + snprintfz(fullname, RRDVAR_MAX_LENGTH, "%s.%s", st->id, rc->name); + rc->hostid = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, fullname, RRDVAR_TYPE_CALCULATED, &rc->value); + + snprintfz(fullname, RRDVAR_MAX_LENGTH, "%s.%s", st->name, rc->name); + rc->hostname = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, fullname, RRDVAR_TYPE_CALCULATED, &rc->value); + + if(!rc->units) rc->units = strdupz(st->units); +} + +static inline int rrdcalc_is_matching_this_rrdset(RRDCALC *rc, RRDSET *st) { + if( (rc->hash_chart == st->hash && !strcmp(rc->chart, st->id)) || + (rc->hash_chart == st->hash_name && !strcmp(rc->chart, st->name))) + return 1; + + return 0; +} + +// this has to be called while the RRDHOST is locked +inline void rrdsetcalc_link_matching(RRDSET *st) { + // debug(D_HEALTH, "find matching alarms for chart '%s'", st->id); + + RRDCALC *rc; + for(rc = st->rrdhost->alarms; rc ; rc = rc->next) { + if(rc->rrdset) continue; + + if(rrdcalc_is_matching_this_rrdset(rc, st)) + rrdsetcalc_link(st, rc); + } +} + +// this has to be called while the RRDHOST is locked +inline void rrdsetcalc_unlink(RRDCALC *rc) { + RRDSET *st = rc->rrdset; + + if(!st) { + error("Requested to unlink RRDCALC '%s.%s' which is not linked to any RRDSET", rc->chart?rc->chart:"NOCHART", rc->name); + return; + } + + RRDHOST *host = st->rrdhost; + + debug(D_HEALTH, "Health unlinking alarm '%s.%s' from chart '%s' of host '%s'", rc->chart?rc->chart:"NOCHART", rc->name, st->id, host->hostname); + + // unlink it + if(rc->rrdset_prev) + rc->rrdset_prev->rrdset_next = rc->rrdset_next; + + if(rc->rrdset_next) + rc->rrdset_next->rrdset_prev = rc->rrdset_prev; + + if(st->alarms == rc) + st->alarms = rc->rrdset_next; + + rc->rrdset_prev = rc->rrdset_next = NULL; + + rrdvar_free(st->rrdhost, &st->variables_root_index, rc->local); + rc->local = NULL; + + rrdvar_free(st->rrdhost, &st->rrdfamily->variables_root_index, rc->family); + rc->family = NULL; + + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rc->hostid); + rc->hostid = NULL; + + rrdvar_free(st->rrdhost, &st->rrdhost->variables_root_index, rc->hostname); + rc->hostname = NULL; + + rc->rrdset = NULL; + + // RRDCALC will remain in RRDHOST + // so that if the matching chart is found in the future + // it will be applied automatically +} + +RRDCALC *rrdcalc_find(RRDSET *st, const char *name) { + RRDCALC *rc; + uint32_t hash = simple_hash(name); + + for( rc = st->alarms; rc ; rc = rc->rrdset_next ) { + if(rc->hash == hash && !strcmp(rc->name, name)) + return rc; + } + + return NULL; +} + +static inline int rrdcalc_exists(RRDHOST *host, const char *name, uint32_t hash) { + RRDCALC *rc; + + // make sure it does not already exist + for(rc = host->alarms; rc ; rc = rc->next) { + if (rc->hash == hash && !strcmp(name, rc->name)) { + error("Health alarm '%s' already exists in host '%s'.", name, host->hostname); + return 1; + } + } + + return 0; +} + +static inline void rrdcalc_create_part2(RRDHOST *host, RRDCALC *rc) { + rrdhost_check_rdlock(host); + + if(rc->calculation) { + rc->calculation->this = &rc->value; + rc->calculation->after = &rc->db_after; + rc->calculation->before = &rc->db_before; + rc->calculation->rrdcalc = rc; + } + + if(rc->warning) { + rc->warning->this = &rc->value; + rc->warning->after = &rc->db_after; + rc->warning->before = &rc->db_before; + rc->warning->rrdcalc = rc; + } + + if(rc->critical) { + rc->critical->this = &rc->value; + rc->critical->after = &rc->db_after; + rc->critical->before = &rc->db_before; + rc->critical->rrdcalc = rc; + } + + // link it to the host + rc->next = host->alarms; + host->alarms = rc; + + // link it to its chart + RRDSET *st; + for(st = host->rrdset_root; st ; st = st->next) { + if(rrdcalc_is_matching_this_rrdset(rc, st)) { + rrdsetcalc_link(st, rc); + break; + } + } +} + +static inline uint32_t rrdcalc_fullname(char *fullname, size_t len, const char *chart, const char *name) { + snprintfz(fullname, len - 1, "%s%s%s", chart?chart:"", chart?".":"", name); + rrdvar_fix_name(fullname); + return simple_hash(fullname); +} + +static inline RRDCALC *rrdcalc_create(RRDHOST *host, const char *name, const char *chart, const char *dimensions, + const char *units, const char *info, + int group_method, int after, int before, int update_every, uint32_t options, + calculated_number green, calculated_number red, + const char *exec, const char *source, + const char *calc, const char *warn, const char *crit) { + + char fullname[RRDVAR_MAX_LENGTH + 1]; + uint32_t hash = rrdcalc_fullname(fullname, RRDVAR_MAX_LENGTH + 1, chart, name); + + if(rrdcalc_exists(host, fullname, hash)) + return NULL; + + RRDCALC *rc = callocz(1, sizeof(RRDCALC)); + + rc->name = strdupz(name); + rc->hash = simple_hash(rc->name); + + rc->chart = strdupz(chart); + rc->hash_chart = simple_hash(rc->chart); + + if(dimensions) rc->dimensions = strdupz(dimensions); + + rc->green = green; + rc->red = red; + rc->value = NAN; + rc->old_value = NAN; + + rc->group = group_method; + rc->after = after; + rc->before = before; + rc->update_every = update_every; + rc->options = options; + + if(exec) rc->exec = strdupz(exec); + if(source) rc->source = strdupz(source); + if(units) rc->units = strdupz(units); + if(info) rc->info = strdupz(info); + + if(calc) { + rc->calculation = expression_parse(calc, NULL, NULL); + if(!rc->calculation) + error("Health alarm '%s.%s': failed to parse calculation expression '%s'", chart, name, calc); + } + if(warn) { + rc->warning = expression_parse(warn, NULL, NULL); + if(!rc->warning) + error("Health alarm '%s.%s': failed to re-parse warning expression '%s'", chart, name, warn); + } + if(crit) { + rc->critical = expression_parse(crit, NULL, NULL); + if(!rc->critical) + error("Health alarm '%s.%s': failed to re-parse critical expression '%s'", chart, name, crit); + } + + debug(D_HEALTH, "Health runtime added alarm '%s.%s': exec '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s", + (rc->chart)?rc->chart:"NOCHART", + rc->name, + (rc->exec)?rc->exec:"DEFAULT", + rc->green, + rc->red, + rc->group, + rc->after, + rc->before, + rc->options, + (rc->dimensions)?rc->dimensions:"NONE", + rc->update_every, + (rc->calculation)?rc->calculation->parsed_as:"NONE", + (rc->warning)?rc->warning->parsed_as:"NONE", + (rc->critical)?rc->critical->parsed_as:"NONE", + rc->source + ); + + rrdcalc_create_part2(host, rc); + return rc; +} + +void rrdcalc_free(RRDHOST *host, RRDCALC *rc) { + if(!rc) return; + + debug(D_HEALTH, "Health removing alarm '%s.%s' of host '%s'", rc->chart?rc->chart:"NOCHART", rc->name, host->hostname); + + // unlink it from RRDSET + if(rc->rrdset) rrdsetcalc_unlink(rc); + + // unlink it from RRDHOST + if(rc == host->alarms) + host->alarms = rc->next; + + else if(host->alarms) { + RRDCALC *t, *last = host->alarms; + + for(t = last->next; t && t != rc; last = t, t = t->next) ; + if(last && last->next == rc) + last->next = rc->next; + else + error("Cannot unlink alarm '%s.%s' from host '%s': not found", rc->chart?rc->chart:"NOCHART", rc->name, host->hostname); + } + else + error("Cannot unlink unlink '%s.%s' from host '%s': This host does not have any calculations", rc->chart?rc->chart:"NOCHART", rc->name, host->hostname); + + expression_free(rc->calculation); + expression_free(rc->warning); + expression_free(rc->critical); + + freez(rc->name); + freez(rc->chart); + freez(rc->family); + freez(rc->dimensions); + freez(rc->exec); + freez(rc->source); + freez(rc->units); + freez(rc->info); + freez(rc); +} + +// ---------------------------------------------------------------------------- +// RRDCALCTEMPLATE management + +void rrdcalctemplate_link_matching(RRDSET *st) { + RRDCALCTEMPLATE *rt; + + for(rt = st->rrdhost->templates; rt ; rt = rt->next) { + if(rt->hash_context == st->hash_context && !strcmp(rt->context, st->context)) { + + RRDCALC *rc = rrdcalc_create(st->rrdhost, rt->name, st->id, + rt->dimensions, rt->units, rt->info, rt->group, rt->after, rt->before, rt->update_every, rt->options, + rt->green, rt->red, rt->exec, rt->source, + (rt->calculation)?rt->calculation->source:NULL, + (rt->warning)?rt->warning->source:NULL, + (rt->critical)?rt->critical->source:NULL); + + if(!rc) + error("Health tried to create alarm from template '%s', but it failed", rt->name); + +#ifdef NETDATA_INTERNAL_CHECKS + else if(rc->rrdset != st) + error("Health alarm '%s.%s' should be linked to chart '%s', but it is not", rc->chart?rc->chart:"NOCHART", rc->name, st->id); +#else + (void)rc; +#endif + } + } +} + +static inline void rrdcalctemplate_free(RRDHOST *host, RRDCALCTEMPLATE *rt) { + debug(D_HEALTH, "Health removing template '%s' of host '%s'", rt->name, host->hostname); + + if(host->templates) { + if(host->templates == rt) { + host->templates = rt->next; + } + else { + RRDCALCTEMPLATE *t, *last = host->templates; + for (t = last->next; t && t != rt; last = t, t = t->next ) ; + if(last && last->next == rt) { + last->next = rt->next; + rt->next = NULL; + } + else + error("Cannot find RRDCALCTEMPLATE '%s' linked in host '%s'", rt->name, host->hostname); + } + } + + expression_free(rt->calculation); + expression_free(rt->warning); + expression_free(rt->critical); + + freez(rt->name); + freez(rt->exec); + freez(rt->context); + freez(rt->source); + freez(rt->units); + freez(rt->info); + freez(rt->dimensions); + freez(rt); +} + +// ---------------------------------------------------------------------------- +// load health configuration + +#define HEALTH_CONF_MAX_LINE 4096 + +#define HEALTH_ALARM_KEY "alarm" +#define HEALTH_TEMPLATE_KEY "template" +#define HEALTH_ON_KEY "on" +#define HEALTH_LOOKUP_KEY "lookup" +#define HEALTH_CALC_KEY "calc" +#define HEALTH_EVERY_KEY "every" +#define HEALTH_GREEN_KEY "green" +#define HEALTH_RED_KEY "red" +#define HEALTH_WARN_KEY "warn" +#define HEALTH_CRIT_KEY "crit" +#define HEALTH_EXEC_KEY "exec" +#define HEALTH_UNITS_KEY "units" +#define HEALTH_INFO_KEY "info" + +static inline int rrdcalc_add_alarm_from_config(RRDHOST *host, RRDCALC *rc) { + { + char fullname[RRDVAR_MAX_LENGTH + 1]; + uint32_t hash = rrdcalc_fullname(fullname, RRDVAR_MAX_LENGTH + 1, rc->chart, rc->name); + + if (rrdcalc_exists(host, fullname, hash)) + return 0; + } + + if(!rc->chart) { + error("Health configuration for alarm '%s' does not have a chart", rc->name); + return 0; + } + + if(!rc->update_every) { + error("Health configuration for alarm '%s.%s' has no frequency (parameter 'every'). Ignoring it.", rc->chart?rc->chart:"NOCHART", rc->name); + return 0; + } + + if(!RRDCALC_HAS_DB_LOOKUP(rc) && !rc->warning && !rc->critical) { + error("Health configuration for alarm '%s.%s' is useless (no calculation, no warning and no critical evaluation)", rc->chart?rc->chart:"NOCHART", rc->name); + return 0; + } + + debug(D_HEALTH, "Health configuration adding alarm '%s.%s': exec '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s", + rc->chart?rc->chart:"NOCHART", + rc->name, + (rc->exec)?rc->exec:"DEFAULT", + rc->green, + rc->red, + rc->group, + rc->after, + rc->before, + rc->options, + (rc->dimensions)?rc->dimensions:"NONE", + rc->update_every, + (rc->calculation)?rc->calculation->parsed_as:"NONE", + (rc->warning)?rc->warning->parsed_as:"NONE", + (rc->critical)?rc->critical->parsed_as:"NONE", + rc->source + ); + + rrdcalc_create_part2(host, rc); + return 1; +} + +static inline int rrdcalctemplate_add_template_from_config(RRDHOST *host, RRDCALCTEMPLATE *rt) { + if(!rt->context) { + error("Health configuration for template '%s' does not have a context", rt->name); + return 0; + } + + if(!rt->update_every) { + error("Health configuration for template '%s' has no frequency (parameter 'every'). Ignoring it.", rt->name); + return 0; + } + + if(!RRDCALCTEMPLATE_HAS_CALCULATION(rt) && !rt->warning && !rt->critical) { + error("Health configuration for template '%s' is useless (no calculation, no warning and no critical evaluation)", rt->name); + return 0; + } + + RRDCALCTEMPLATE *t; + for (t = host->templates; t ; t = t->next) { + if(t->hash_name == rt->hash_name && !strcmp(t->name, rt->name)) { + error("Health configuration template '%s' already exists for host '%s'.", rt->name, host->hostname); + return 0; + } + } + + debug(D_HEALTH, "Health configuration adding template '%s': context '%s', exec '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s'", + rt->name, + (rt->context)?rt->context:"NONE", + (rt->exec)?rt->exec:"DEFAULT", + rt->green, + rt->red, + rt->group, + rt->after, + rt->before, + rt->options, + (rt->dimensions)?rt->dimensions:"NONE", + rt->update_every, + (rt->calculation)?rt->calculation->parsed_as:"NONE", + (rt->warning)?rt->warning->parsed_as:"NONE", + (rt->critical)?rt->critical->parsed_as:"NONE", + rt->source + ); + + rt->next = host->templates; + host->templates = rt; + return 1; +} + +static inline int health_parse_duration(char *string, int *result) { + // make sure it is a number + if(!*string || !(isdigit(*string) || *string == '+' || *string == '-')) { + *result = 0; + return 0; + } + + char *e = NULL; + calculated_number n = strtold(string, &e); + if(e && *e) { + switch (*e) { + case 'Y': + *result = (int) (n * 86400 * 365); + break; + case 'M': + *result = (int) (n * 86400 * 30); + break; + case 'w': + *result = (int) (n * 86400 * 7); + break; + case 'd': + *result = (int) (n * 86400); + break; + case 'h': + *result = (int) (n * 3600); + break; + case 'm': + *result = (int) (n * 60); + break; + + default: + case 's': + *result = (int) (n); + break; + } + } + else + *result = (int)(n); + + return 1; +} + +static inline int health_parse_db_lookup( + size_t line, const char *path, const char *file, char *string, + int *group_method, int *after, int *before, int *every, + uint32_t *options, char **dimensions +) { + debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s/%s: %s", line, path, file, string); + + if(*dimensions) freez(*dimensions); + *dimensions = NULL; + *after = 0; + *before = 0; + *every = 0; + *options = 0; + + char *s = string, *key; + + // first is the group method + key = s; + while(*s && !isspace(*s)) s++; + while(*s && isspace(*s)) *s++ = '\0'; + if(!*s) { + error("Health configuration invalid chart calculation at line %zu of file '%s/%s': expected group method followed by the 'after' time, but got '%s'", + line, path, file, key); + return 0; + } + + if((*group_method = web_client_api_request_v1_data_group(key, -1)) == -1) { + error("Health configuration at line %zu of file '%s/%s': invalid group method '%s'", + line, path, file, key); + return 0; + } + + // then is the 'after' time + key = s; + while(*s && !isspace(*s)) s++; + while(*s && isspace(*s)) *s++ = '\0'; + + if(!health_parse_duration(key, after)) { + error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' after group method", + line, path, file, key); + return 0; + } + + // sane defaults + *every = abs(*after); + + // now we may have optional parameters + while(*s) { + key = s; + while(*s && !isspace(*s)) s++; + while(*s && isspace(*s)) *s++ = '\0'; + if(!*key) break; + + if(!strcasecmp(key, "at")) { + char *value = s; + while(*s && !isspace(*s)) s++; + while(*s && isspace(*s)) *s++ = '\0'; + + if (!health_parse_duration(value, before)) { + error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword", + line, path, file, value, key); + } + } + else if(!strcasecmp(key, HEALTH_EVERY_KEY)) { + char *value = s; + while(*s && !isspace(*s)) s++; + while(*s && isspace(*s)) *s++ = '\0'; + + if (!health_parse_duration(value, every)) { + error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword", + line, path, file, value, key); + } + } + else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) { + *options |= RRDR_OPTION_ABSOLUTE; + } + else if(!strcasecmp(key, "min2max")) { + *options |= RRDR_OPTION_MIN2MAX; + } + else if(!strcasecmp(key, "null2zero")) { + *options |= RRDR_OPTION_NULL2ZERO; + } + else if(!strcasecmp(key, "percentage")) { + *options |= RRDR_OPTION_PERCENTAGE; + } + else if(!strcasecmp(key, "unaligned")) { + *options |= RRDR_OPTION_NOT_ALIGNED; + } + else if(!strcasecmp(key, "of")) { + if(*s && strcasecmp(s, "all")) + *dimensions = strdupz(s); + break; + } + else { + error("Health configuration at line %zu of file '%s/%s': unknown keyword '%s'", + line, path, file, key); + } + } + + return 1; +} + +static inline char *health_source_file(size_t line, const char *path, const char *filename) { + char buffer[FILENAME_MAX + 1]; + snprintfz(buffer, FILENAME_MAX, "%zu@%s/%s", line, path, filename); + return strdupz(buffer); +} + +static inline void strip_quotes(char *s) { + while(*s) { + if(*s == '\'' || *s == '"') *s = ' '; + s++; + } +} + +int health_readfile(const char *path, const char *filename) { + debug(D_HEALTH, "Health configuration reading file '%s/%s'", path, filename); + + static uint32_t hash_alarm = 0, hash_template = 0, hash_on = 0, hash_calc = 0, hash_green = 0, hash_red = 0, hash_warn = 0, hash_crit = 0, hash_exec = 0, hash_every = 0, hash_lookup = 0, hash_units = 0, hash_info = 0; + char buffer[HEALTH_CONF_MAX_LINE + 1]; + + if(unlikely(!hash_alarm)) { + hash_alarm = simple_uhash(HEALTH_ALARM_KEY); + hash_template = simple_uhash(HEALTH_TEMPLATE_KEY); + hash_on = simple_uhash(HEALTH_ON_KEY); + hash_calc = simple_uhash(HEALTH_CALC_KEY); + hash_lookup = simple_uhash(HEALTH_LOOKUP_KEY); + hash_green = simple_uhash(HEALTH_GREEN_KEY); + hash_red = simple_uhash(HEALTH_RED_KEY); + hash_warn = simple_uhash(HEALTH_WARN_KEY); + hash_crit = simple_uhash(HEALTH_CRIT_KEY); + hash_exec = simple_uhash(HEALTH_EXEC_KEY); + hash_every = simple_uhash(HEALTH_EVERY_KEY); + hash_units = simple_hash(HEALTH_UNITS_KEY); + hash_info = simple_hash(HEALTH_INFO_KEY); + } + + snprintfz(buffer, HEALTH_CONF_MAX_LINE, "%s/%s", path, filename); + FILE *fp = fopen(buffer, "r"); + if(!fp) { + error("Health configuration cannot read file '%s'.", buffer); + return 0; + } + + RRDCALC *rc = NULL; + RRDCALCTEMPLATE *rt = NULL; + + size_t line = 0, append = 0; + char *s; + while((s = fgets(&buffer[append], (int)(HEALTH_CONF_MAX_LINE - append), fp)) || append) { + int stop_appending = !s; + line++; + // info("Line %zu of file '%s/%s': '%s'", line, path, filename, s); + s = trim(buffer); + if(!s) continue; + // info("Trimmed line %zu of file '%s/%s': '%s'", line, path, filename, s); + + append = strlen(s); + if(!stop_appending && s[append - 1] == '\\') { + s[append - 1] = ' '; + append = &s[append] - buffer; + if(append < HEALTH_CONF_MAX_LINE) + continue; + else { + error("Health configuration has too long muli-line at line %zu of file '%s/%s'.", line, path, filename); + } + } + append = 0; + + char *key = s; + while(*s && *s != ':') s++; + if(!*s) { + error("Health configuration has invalid line %zu of file '%s/%s'. It does not contain a ':'. Ignoring it.", line, path, filename); + continue; + } + *s = '\0'; + s++; + + char *value = s; + key = trim(key); + value = trim(value); + + if(!key) { + error("Health configuration has invalid line %zu of file '%s/%s'. Keyword is empty. Ignoring it.", line, path, filename); + continue; + } + + if(!value) { + error("Health configuration has invalid line %zu of file '%s/%s'. value is empty. Ignoring it.", line, path, filename); + continue; + } + + // info("Health file '%s/%s', key '%s', value '%s'", path, filename, key, value); + uint32_t hash = simple_uhash(key); + + if(hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) { + if(rc && !rrdcalc_add_alarm_from_config(&localhost, rc)) + rrdcalc_free(&localhost, rc); + + if(rt) { + if (!rrdcalctemplate_add_template_from_config(&localhost, rt)) + rrdcalctemplate_free(&localhost, rt); + rt = NULL; + } + + rc = callocz(1, sizeof(RRDCALC)); + rc->name = strdupz(value); + rc->hash = simple_hash(rc->name); + rc->source = health_source_file(line, path, filename); + rc->green = NAN; + rc->red = NAN; + rc->value = NAN; + rc->old_value = NAN; + + if(rrdvar_fix_name(rc->name)) + error("Health configuration renamed alarm '%s' to '%s'", value, rc->name); + } + else if(hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) { + if(rc) { + if(!rrdcalc_add_alarm_from_config(&localhost, rc)) + rrdcalc_free(&localhost, rc); + rc = NULL; + } + + if(rt && !rrdcalctemplate_add_template_from_config(&localhost, rt)) + rrdcalctemplate_free(&localhost, rt); + + rt = callocz(1, sizeof(RRDCALCTEMPLATE)); + rt->name = strdupz(value); + rt->hash_name = simple_hash(rt->name); + rt->source = health_source_file(line, path, filename); + rt->green = NAN; + rt->red = NAN; + + if(rrdvar_fix_name(rt->name)) + error("Health configuration renamed template '%s' to '%s'", value, rt->name); + } + else if(rc) { + if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) { + if(rc->chart) { + if(strcmp(rc->chart, value)) + info("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rc->name, key, rc->chart, value, value); + + freez(rc->chart); + } + rc->chart = strdupz(value); + rc->hash_chart = simple_hash(rc->chart); + } + else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) { + health_parse_db_lookup(line, path, filename, value, &rc->group, &rc->after, &rc->before, + &rc->update_every, + &rc->options, &rc->dimensions); + } + else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) { + if(!health_parse_duration(value, &rc->update_every)) + info("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' cannot parse duration: '%s'.", + line, path, filename, rc->name, key, value); + } + else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) { + char *e; + rc->green = strtold(value, &e); + if(e && *e) { + info("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.", + line, path, filename, rc->name, key, e); + } + } + else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) { + char *e; + rc->red = strtold(value, &e); + if(e && *e) { + info("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.", + line, path, filename, rc->name, key, e); + } + } + else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) { + const char *failed_at = NULL; + int error = 0; + rc->calculation = expression_parse(value, &failed_at, &error); + if(!rc->calculation) { + error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", + line, path, filename, rc->name, key, value, expression_strerror(error), failed_at); + } + } + else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) { + const char *failed_at = NULL; + int error = 0; + rc->warning = expression_parse(value, &failed_at, &error); + if(!rc->warning) { + error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", + line, path, filename, rc->name, key, value, expression_strerror(error), failed_at); + } + } + else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) { + const char *failed_at = NULL; + int error = 0; + rc->critical = expression_parse(value, &failed_at, &error); + if(!rc->critical) { + error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", + line, path, filename, rc->name, key, value, expression_strerror(error), failed_at); + } + } + else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) { + if(rc->exec) { + if(strcmp(rc->exec, value)) + info("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rc->name, key, rc->exec, value, value); + + freez(rc->exec); + } + rc->exec = strdupz(value); + } + else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) { + if(rc->units) { + if(strcmp(rc->units, value)) + info("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rc->name, key, rc->units, value, value); + + freez(rc->units); + } + rc->units = strdupz(value); + strip_quotes(rc->units); + } + else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) { + if(rc->info) { + if(strcmp(rc->info, value)) + info("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rc->name, key, rc->info, value, value); + + freez(rc->info); + } + rc->info = strdupz(value); + strip_quotes(rc->info); + } + else { + error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has unknown key '%s'.", + line, path, filename, rc->name, key); + } + } + else if(rt) { + if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) { + if(rt->context) { + if(strcmp(rt->context, value)) + info("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rt->name, key, rt->context, value, value); + + freez(rt->context); + } + rt->context = strdupz(value); + rt->hash_context = simple_hash(rt->context); + } + else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) { + health_parse_db_lookup(line, path, filename, value, &rt->group, &rt->after, &rt->before, + &rt->update_every, + &rt->options, &rt->dimensions); + } + else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) { + if(!health_parse_duration(value, &rt->update_every)) + info("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' cannot parse duration: '%s'.", + line, path, filename, rt->name, key, value); + } + else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) { + char *e; + rt->green = strtold(value, &e); + if(e && *e) { + info("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.", + line, path, filename, rt->name, key, e); + } + } + else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) { + char *e; + rt->red = strtold(value, &e); + if(e && *e) { + info("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.", + line, path, filename, rt->name, key, e); + } + } + else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) { + const char *failed_at = NULL; + int error = 0; + rt->calculation = expression_parse(value, &failed_at, &error); + if(!rt->calculation) { + error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", + line, path, filename, rt->name, key, value, expression_strerror(error), failed_at); + } + } + else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) { + const char *failed_at = NULL; + int error = 0; + rt->warning = expression_parse(value, &failed_at, &error); + if(!rt->warning) { + error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", + line, path, filename, rt->name, key, value, expression_strerror(error), failed_at); + } + } + else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) { + const char *failed_at = NULL; + int error = 0; + rt->critical = expression_parse(value, &failed_at, &error); + if(!rt->critical) { + error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", + line, path, filename, rt->name, key, value, expression_strerror(error), failed_at); + } + } + else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) { + if(rt->exec) { + if(strcmp(rt->exec, value)) + info("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rt->name, key, rt->exec, value, value); + + freez(rt->exec); + } + rt->exec = strdupz(value); + } + else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) { + if(rt->units) { + if(strcmp(rt->units, value)) + info("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rt->name, key, rt->units, value, value); + + freez(rt->units); + } + rt->units = strdupz(value); + strip_quotes(rt->units); + } + else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) { + if(rt->info) { + if(strcmp(rt->info, value)) + info("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').", + line, path, filename, rt->name, key, rt->info, value, value); + + freez(rt->info); + } + rt->info = strdupz(value); + strip_quotes(rt->info); + } + else { + error("Health configuration at line %zu of file '%s/%s' for template '%s' has unknown key '%s'.", + line, path, filename, rt->name, key); + } + } + else { + error("Health configuration at line %zu of file '%s/%s' has unknown key '%s'. Expected either '" HEALTH_ALARM_KEY "' or '" HEALTH_TEMPLATE_KEY "'.", + line, path, filename, key); + } + } + + if(rc && !rrdcalc_add_alarm_from_config(&localhost, rc)) + rrdcalc_free(&localhost, rc); + + if(rt && !rrdcalctemplate_add_template_from_config(&localhost, rt)) + rrdcalctemplate_free(&localhost, rt); + + fclose(fp); + return 1; +} + +void health_readdir(const char *path) { + size_t pathlen = strlen(path); + + debug(D_HEALTH, "Health configuration reading directory '%s'", path); + + DIR *dir = opendir(path); + if (!dir) { + error("Health configuration cannot open directory '%s'.", path); + return; + } + + struct dirent *de = NULL; + while ((de = readdir(dir))) { + size_t len = strlen(de->d_name); + + 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; + + else if(de->d_type == DT_DIR) { + char *s = mallocz(pathlen + strlen(de->d_name) + 2); + strcpy(s, path); + strcat(s, "/"); + strcat(s, de->d_name); + health_readdir(s); + freez(s); + continue; + } + + else if((de->d_type == DT_LNK || de->d_type == DT_REG) && + len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) { + health_readfile(path, de->d_name); + } + } + + closedir(dir); +} + +static inline char *health_config_dir(void) { + char buffer[FILENAME_MAX + 1]; + snprintfz(buffer, FILENAME_MAX, "%s/health.d", config_get("global", "config directory", CONFIG_DIR)); + return config_get("health", "health configuration directory", buffer); +} + +void health_init(void) { + debug(D_HEALTH, "Health configuration initializing"); + + if(!(health_enabled = config_get_boolean("health", "enabled", 1))) { + debug(D_HEALTH, "Health is disabled."); + return; + } + + char *path = health_config_dir(); + + { + char buffer[FILENAME_MAX + 1]; + snprintfz(buffer, FILENAME_MAX, "%s/alarm-email.sh", config_get("global", "plugins directory", PLUGINS_DIR)); + health_default_exec = config_get("health", "script to execute on alarm", buffer); + } + + long n = config_get_number("health", "in memory max health log entries", (long)localhost.health_log.max); + if(n < 2) { + error("Health configuration has invalid max log entries %ld. Using default %u", n, localhost.health_log.max); + config_set_number("health", "in memory max health log entries", (long)localhost.health_log.max); + } + else localhost.health_log.max = (unsigned int)n; + + rrdhost_rwlock(&localhost); + health_readdir(path); + rrdhost_unlock(&localhost); +} + +// ---------------------------------------------------------------------------- +// JSON generation + +static inline void health_string2json(BUFFER *wb, const char *prefix, const char *label, const char *value, const char *suffix) { + if(value && *value) + buffer_sprintf(wb, "%s\"%s\":\"%s\"%s", prefix, label, value, suffix); + else + buffer_sprintf(wb, "%s\"%s\":null%s", prefix, label, suffix); +} + +static inline void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae) { + buffer_sprintf(wb, "\n\t{\n" + "\t\t\"id\":%u,\n" + "\t\t\"name\":\"%s\",\n" + "\t\t\"chart\":\"%s\",\n" + "\t\t\"family\":\"%s\",\n" + "\t\t\"processed\":%s,\n" + "\t\t\"updated\":%s,\n" + "\t\t\"exec_run\":%s,\n" + "\t\t\"exec_failed\":%s,\n" + "\t\t\"exec\":\"%s\",\n" + "\t\t\"exec_code\":%d,\n" + "\t\t\"source\":\"%s\",\n" + "\t\t\"units\":\"%s\",\n" + "\t\t\"info\":\"%s\",\n" + "\t\t\"when\":%lu,\n" + "\t\t\"duration\":%lu,\n" + "\t\t\"non_clear_duration\":%lu,\n" + "\t\t\"status\":\"%s\",\n" + "\t\t\"old_status\":\"%s\",\n", + ae->id, + ae->name, + ae->chart, + ae->family, + (ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_PROCESSED)?"true":"false", + (ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_UPDATED)?"true":"false", + (ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_EXEC_RUN)?"true":"false", + (ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_EXEC_FAILED)?"true":"false", + ae->exec?ae->exec:health_default_exec, + ae->exec_code, + ae->source, + ae->units?ae->units:"", + ae->info?ae->info:"", + (unsigned long)ae->when, + (unsigned long)ae->duration, + (unsigned long)ae->non_clear_duration, + rrdcalc_status2string(ae->new_status), + rrdcalc_status2string(ae->old_status) + ); + + buffer_strcat(wb, "\t\t\"value\":"); + buffer_rrd_value(wb, ae->new_value); + buffer_strcat(wb, ",\n"); + + buffer_strcat(wb, "\t\t\"old_value\":"); + buffer_rrd_value(wb, ae->old_value); + buffer_strcat(wb, "\n"); + + buffer_strcat(wb, "\t}"); +} + +void health_alarm_log2json(RRDHOST *host, BUFFER *wb) { + pthread_rwlock_rdlock(&host->health_log.alarm_log_rwlock); + + buffer_strcat(wb, "["); + + unsigned int max = host->health_log.max; + unsigned int count = 0; + ALARM_ENTRY *ae; + for(ae = host->health_log.alarms; ae && count < max ; count++, ae = ae->next) { + if(likely(count)) buffer_strcat(wb, ","); + health_alarm_entry2json_nolock(wb, ae); + } + + buffer_strcat(wb, "\n]\n"); + + pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock); +} + +static inline void health_rrdcalc2json_nolock(BUFFER *wb, RRDCALC *rc) { + buffer_sprintf(wb, + "\t\t\"%s.%s\": {\n" + "\t\t\t\"name\": \"%s\",\n" + "\t\t\t\"chart\": \"%s\",\n" + "\t\t\t\"family\": \"%s\",\n" + "\t\t\t\"active\": %s,\n" + "\t\t\t\"exec\": \"%s\",\n" + "\t\t\t\"source\": \"%s\",\n" + "\t\t\t\"units\": \"%s\",\n" + "\t\t\t\"info\": \"%s\",\n" + "\t\t\t\"status\": \"%s\",\n" + "\t\t\t\"last_status_change\": %lu,\n" + "\t\t\t\"last_updated\": %lu,\n" + "\t\t\t\"next_update\": %lu,\n" + "\t\t\t\"update_every\": %d,\n" + , rc->chart, rc->name + , rc->name + , rc->chart + , (rc->rrdset && rc->rrdset->family)?rc->rrdset->family:"" + , (rc->rrdset)?"true":"false" + , rc->exec?rc->exec:health_default_exec + , rc->source + , rc->units?rc->units:"" + , rc->info?rc->info:"" + , rrdcalc_status2string(rc->status) + , (unsigned long)rc->last_status_change + , (unsigned long)rc->last_updated + , (unsigned long)rc->next_update + , rc->update_every + ); + + if(RRDCALC_HAS_DB_LOOKUP(rc)) { + if(rc->dimensions && *rc->dimensions) + health_string2json(wb, "\t\t\t", "lookup_dimensions", rc->dimensions, ",\n"); + + buffer_sprintf(wb, + "\t\t\t\"db_after\": %lu,\n" + "\t\t\t\"db_before\": %lu,\n" + "\t\t\t\"lookup_method\": \"%s\",\n" + "\t\t\t\"lookup_after\": %d,\n" + "\t\t\t\"lookup_before\": %d,\n" + "\t\t\t\"lookup_options\": \"", + (unsigned long) rc->db_after, + (unsigned long) rc->db_before, + group_method2string(rc->group), + rc->after, + rc->before + ); + buffer_data_options2string(wb, rc->options); + buffer_strcat(wb, "\",\n"); + } + + if(rc->calculation) { + health_string2json(wb, "\t\t\t", "calc", rc->calculation->source, ",\n"); + health_string2json(wb, "\t\t\t", "calc_parsed", rc->calculation->parsed_as, ",\n"); + } + + if(rc->warning) { + health_string2json(wb, "\t\t\t", "warn", rc->warning->source, ",\n"); + health_string2json(wb, "\t\t\t", "warn_parsed", rc->warning->parsed_as, ",\n"); + } + + if(rc->critical) { + health_string2json(wb, "\t\t\t", "crit", rc->critical->source, ",\n"); + health_string2json(wb, "\t\t\t", "crit_parsed", rc->critical->parsed_as, ",\n"); + } + + buffer_strcat(wb, "\t\t\t\"green\":"); + buffer_rrd_value(wb, rc->green); + buffer_strcat(wb, ",\n"); + + buffer_strcat(wb, "\t\t\t\"red\":"); + buffer_rrd_value(wb, rc->red); + buffer_strcat(wb, ",\n"); + + buffer_strcat(wb, "\t\t\t\"value\":"); + buffer_rrd_value(wb, rc->value); + buffer_strcat(wb, "\n"); + + buffer_strcat(wb, "\t\t}"); +} + +//void health_rrdcalctemplate2json_nolock(BUFFER *wb, RRDCALCTEMPLATE *rt) { +// +//} + +void health_alarms2json(RRDHOST *host, BUFFER *wb, int all) { + int i; + rrdhost_rdlock(&localhost); + + buffer_strcat(wb, "{\n\t\"alarms\": {\n"); + RRDCALC *rc; + for(i = 0, rc = host->alarms; rc ; rc = rc->next) { + if(!rc->rrdset) + continue; + + if(!all && !(rc->status == RRDCALC_STATUS_WARNING || rc->status == RRDCALC_STATUS_CRITICAL)) + continue; + + if(likely(i)) buffer_strcat(wb, ",\n"); + health_rrdcalc2json_nolock(wb, rc); + i++; + } + +// buffer_strcat(wb, "\n\t},\n\t\"templates\": {"); + +// RRDCALCTEMPLATE *rt; +// for(rt = host->templates; rt ; rt = rt->next) +// health_rrdcalctemplate2json_nolock(wb, rt); + + buffer_sprintf(wb, "\n\t},\n\t\"now\": %lu\n}\n", (unsigned long)time(NULL)); + rrdhost_unlock(&localhost); +} + + +// ---------------------------------------------------------------------------- +// re-load health configuration + +static inline void health_free_all_nolock(RRDHOST *host) { + while(host->templates) + rrdcalctemplate_free(host, host->templates); + + while(host->alarms) + rrdcalc_free(host, host->alarms); +} + +void health_reload(void) { + if(!health_enabled) { + error("Health reload is requested, but health is not enabled."); + return; + } + + char *path = health_config_dir(); + + rrdhost_rwlock(&localhost); + health_free_all_nolock(&localhost); + rrdhost_unlock(&localhost); + + RRDSET *st; + for(st = localhost.rrdset_root; st ; st = st->next) { + st->green = NAN; + st->red = NAN; + } + + rrdhost_rwlock(&localhost); + health_readdir(path); + rrdhost_unlock(&localhost); + + for(st = localhost.rrdset_root; st ; st = st->next) { + rrdhost_rwlock(&localhost); + + rrdsetcalc_link_matching(st); + rrdcalctemplate_link_matching(st); + + rrdhost_unlock(&localhost); + } +} + + +// ---------------------------------------------------------------------------- +// health main thread and friends + +static inline int rrdcalc_isrunnable(RRDCALC *rc, time_t now, time_t *next_run) { + if (unlikely(!rc->rrdset)) { + debug(D_HEALTH, "Health not running alarm '%s.%s'. It is not linked to a chart.", rc->chart?rc->chart:"NOCHART", rc->name); + return 0; + } + + if (unlikely(!rc->update_every)) { + debug(D_HEALTH, "Health not running alarm '%s.%s'. It does not have an update frequency", rc->chart?rc->chart:"NOCHART", rc->name); + return 0; + } + + if (unlikely(rc->next_update > now)) { + if (*next_run > rc->next_update) + *next_run = rc->next_update; + + debug(D_HEALTH, "Health not examining alarm '%s.%s' yet (will do in %d secs).", rc->chart?rc->chart:"NOCHART", rc->name, (int) (rc->next_update - now)); + return 0; + } + + return 1; +} + +static inline int rrdcalc_value2status(calculated_number n) { + if(isnan(n)) return RRDCALC_STATUS_UNDEFINED; + if(n) return RRDCALC_STATUS_RAISED; + return RRDCALC_STATUS_CLEAR; +} + +static inline void health_alarm_execute(ALARM_ENTRY *ae) { + if(ae->old_status == RRDCALC_STATUS_UNINITIALIZED && ae->new_status == RRDCALC_STATUS_CLEAR) + return; + + char buffer[FILENAME_MAX + 1]; + pid_t command_pid; + + const char *exec = ae->exec; + if(!exec) exec = health_default_exec; + + snprintfz(buffer, FILENAME_MAX, "exec %s '%s' '%s' '%s' '%s' '%s' '%0.0Lf' '%0.0Lf' '%s' '%u' '%u' '%s' '%s'", + exec, + ae->name, + ae->chart?ae->chart:"NOCAHRT", + ae->family?ae->family:"NOFAMILY", + rrdcalc_status2string(ae->new_status), + rrdcalc_status2string(ae->old_status), + ae->new_value, + ae->old_value, + ae->source?ae->source:"UNKNOWN", + (uint32_t)ae->duration, + (uint32_t)ae->non_clear_duration, + ae->units?ae->units:"", + ae->info?ae->info:"" + ); + + ae->notifications |= HEALTH_ENTRY_NOTIFICATIONS_EXEC_RUN; + + debug(D_HEALTH, "executing command '%s'", buffer); + FILE *fp = mypopen(buffer, &command_pid); + if(!fp) { + error("HEALTH: Cannot popen(\"%s\", \"r\").", buffer); + return; + } + debug(D_HEALTH, "HEALTH reading from command"); + char *s = fgets(buffer, FILENAME_MAX, fp); + (void)s; + debug(D_HEALTH, "HEALTH closing command"); + ae->exec_code = mypclose(fp, command_pid); + debug(D_HEALTH, "done executing command - returned with code %d", ae->exec_code); + + if(ae->exec_code != 0) + ae->notifications |= HEALTH_ENTRY_NOTIFICATIONS_EXEC_FAILED; +} + +static inline void health_process_notifications(ALARM_ENTRY *ae) { + info("Health alarm '%s.%s' = %0.2Lf - changed status from %s to %s", + ae->chart?ae->chart:"NOCHART", ae->name, + ae->new_value, + rrdcalc_status2string(ae->old_status), + rrdcalc_status2string(ae->new_status) + ); + + health_alarm_execute(ae); +} + +static inline void health_alarm_log(RRDHOST *host, time_t when, + const char *name, const char *chart, const char *family, + const char *exec, time_t duration, + calculated_number old_value, calculated_number new_value, + int old_status, int new_status, + const char *source, + const char *units, + const char *info +) { + ALARM_ENTRY *ae = callocz(1, sizeof(ALARM_ENTRY)); + ae->name = strdupz(name); + ae->hash_name = simple_hash(ae->name); + + if(chart) { + ae->chart = strdupz(chart); + ae->hash_chart = simple_hash(ae->chart); + } + + if(family) + ae->family = strdupz(family); + + if(exec) ae->exec = strdupz(exec); + if(source) ae->source = strdupz(source); + if(units) ae->units = strdupz(units); + if(info) ae->info = strdupz(info); + + ae->id = host->health_log.nextid++; + ae->when = when; + ae->old_value = old_value; + ae->new_value = new_value; + ae->old_status = old_status; + ae->new_status = new_status; + ae->duration = duration; + + if(ae->old_status == RRDCALC_STATUS_WARNING || ae->old_status == RRDCALC_STATUS_CRITICAL) + ae->non_clear_duration += ae->duration; + + // link it + pthread_rwlock_wrlock(&host->health_log.alarm_log_rwlock); + ae->next = host->health_log.alarms; + host->health_log.alarms = ae; + host->health_log.count++; + pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock); + + // match previous alarms + pthread_rwlock_rdlock(&host->health_log.alarm_log_rwlock); + ALARM_ENTRY *t; + for(t = host->health_log.alarms ; t ; t = t->next) { + if(t != ae && + t->hash_name == ae->hash_name && + t->hash_chart == ae->hash_chart && + !strcmp(t->name, ae->name) && + t->chart && ae->chart && !strcmp(t->chart, ae->chart)) { + + if(!(t->notifications & HEALTH_ENTRY_NOTIFICATIONS_UPDATED) && !t->updated_by) { + t->notifications |= HEALTH_ENTRY_NOTIFICATIONS_UPDATED; + t->updated_by = ae; + + if((t->new_status == RRDCALC_STATUS_WARNING || t->new_status == RRDCALC_STATUS_CRITICAL) && + (t->old_status == RRDCALC_STATUS_WARNING || t->old_status == RRDCALC_STATUS_CRITICAL)) + ae->non_clear_duration += t->non_clear_duration; + } + else { + // no need to continue + break; + } + } + } + pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock); +} + +static inline void health_alarm_log_process(RRDHOST *host) { + static uint32_t last_processed = 0; + ALARM_ENTRY *ae; + + pthread_rwlock_rdlock(&host->health_log.alarm_log_rwlock); + + for(ae = host->health_log.alarms; ae ;ae = ae->next) { + if(last_processed >= ae->id) break; + + if(!(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_PROCESSED) && + !(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_UPDATED)) { + ae->notifications |= HEALTH_ENTRY_NOTIFICATIONS_PROCESSED; + health_process_notifications(ae); + } + } + + if(host->health_log.alarms) + last_processed = host->health_log.alarms->id; + + pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock); + + if(host->health_log.count <= host->health_log.max) + return; + + // cleanup excess entries in the log + pthread_rwlock_wrlock(&host->health_log.alarm_log_rwlock); + + ALARM_ENTRY *last = NULL; + unsigned int count = host->health_log.max; + for(ae = host->health_log.alarms; ae && count ; count--, last = ae, ae = ae->next) ; + + if(ae && last && last->next == ae) + last->next = NULL; + else + ae = NULL; + + while(ae) { + ALARM_ENTRY *t = ae->next; + + freez(ae->name); + freez(ae->chart); + freez(ae->family); + freez(ae->exec); + freez(ae->source); + freez(ae->units); + freez(ae->info); + freez(ae); + + ae = t; + } + + pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock); +} + +void *health_main(void *ptr) { + (void)ptr; + + info("HEALTH 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."); + + int min_run_every = (int)config_get_number("health", "run at least every seconds", 10); + if(min_run_every < 1) min_run_every = 1; + + BUFFER *wb = buffer_create(100); + + unsigned int loop = 0; + while(health_enabled) { + loop++; + debug(D_HEALTH, "Health monitoring iteration no %u started", loop); + + int oldstate, runnable = 0; + time_t now = time(NULL); + time_t next_run = now + min_run_every; + RRDCALC *rc; + + if (unlikely(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate) != 0)) + error("Cannot set pthread cancel state to DISABLE."); + + rrdhost_rdlock(&localhost); + + // the first loop is to lookup values from the db + for (rc = localhost.alarms; rc; rc = rc->next) { + if (unlikely(!rrdcalc_isrunnable(rc, now, &next_run))) + continue; + + runnable++; + rc->old_value = rc->value; + + // 1. if there is database lookup, do it + // 2. if there is calculation expression, run it + + if (unlikely(RRDCALC_HAS_DB_LOOKUP(rc))) { + time_t old_db_timestamp = rc->db_before; + int value_is_null = 0; + + int ret = rrd2value(rc->rrdset, wb, &rc->value, + rc->dimensions, 1, rc->after, rc->before, rc->group, + rc->options, &rc->db_after, &rc->db_before, &value_is_null); + + if (unlikely(ret != 200)) { + // database lookup failed + rc->value = NAN; + + debug(D_HEALTH, "Health alarm '%s.%s': database lookup returned error %d", rc->chart?rc->chart:"NOCHART", rc->name, ret); + + if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_DB_ERROR))) { + rc->rrdcalc_flags |= RRDCALC_FLAG_DB_ERROR; + error("Health alarm '%s.%s': database lookup returned error %d", rc->chart?rc->chart:"NOCHART", rc->name, ret); + } + } + else if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_DB_ERROR)) + rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_ERROR; + + if (unlikely(old_db_timestamp == rc->db_before)) { + // database is stale + + debug(D_HEALTH, "Health alarm '%s.%s': database is stale", rc->chart?rc->chart:"NOCHART", rc->name); + + if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE))) { + rc->rrdcalc_flags |= RRDCALC_FLAG_DB_STALE; + error("Health alarm '%s.%s': database is stale", rc->chart?rc->chart:"NOCHART", rc->name); + } + } + else if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_DB_STALE)) + rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_STALE; + + if (unlikely(value_is_null)) { + // collected value is null + + rc->value = NAN; + + debug(D_HEALTH, "Health alarm '%s.%s': database lookup returned empty value (possibly value is not collected yet)", + rc->chart?rc->chart:"NOCHART", rc->name); + + if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_DB_NAN))) { + rc->rrdcalc_flags |= RRDCALC_FLAG_DB_NAN; + error("Health alarm '%s.%s': database lookup returned empty value (possibly value is not collected yet)", + rc->chart?rc->chart:"NOCHART", rc->name); + } + } + else if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_DB_NAN)) + rc->rrdcalc_flags &= ~RRDCALC_FLAG_DB_NAN; + + debug(D_HEALTH, "Health alarm '%s.%s': database lookup gave value " + CALCULATED_NUMBER_FORMAT, rc->chart?rc->chart:"NOCHART", rc->name, rc->value); + } + + if(unlikely(rc->calculation)) { + if (unlikely(!expression_evaluate(rc->calculation))) { + // calculation failed + + rc->value = NAN; + + debug(D_HEALTH, "Health alarm '%s.%s': failed to evaluate calculation with error: %s", + rc->chart?rc->chart:"NOCHART", rc->name, buffer_tostring(rc->calculation->error_msg)); + + if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_CALC_ERROR))) { + rc->rrdcalc_flags |= RRDCALC_FLAG_CALC_ERROR; + error("Health alarm '%s.%s': failed to evaluate calculation with error: %s", + rc->chart?rc->chart:"NOCHART", rc->name, buffer_tostring(rc->calculation->error_msg)); + } + } + else { + if (unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_CALC_ERROR)) + rc->rrdcalc_flags &= ~RRDCALC_FLAG_CALC_ERROR; + + debug(D_HEALTH, "Health alarm '%s.%s': calculation expression gave value " + CALCULATED_NUMBER_FORMAT + ": %s (source: %s)", + rc->chart?rc->chart:"NOCHART", rc->name, + rc->calculation->result, + buffer_tostring(rc->calculation->error_msg), + rc->source + ); + + rc->value = rc->calculation->result; + } + } + } + rrdhost_unlock(&localhost); + + if (runnable) { + rrdhost_rdlock(&localhost); + + for (rc = localhost.alarms; rc; rc = rc->next) { + if (unlikely(!rrdcalc_isrunnable(rc, now, &next_run))) + continue; + + int warning_status = RRDCALC_STATUS_UNDEFINED; + int critical_status = RRDCALC_STATUS_UNDEFINED; + + if(unlikely(rc->warning)) { + if(unlikely(!expression_evaluate(rc->warning))) { + // calculation failed + + debug(D_HEALTH, "Health alarm '%s.%s': warning expression failed with error: %s", + rc->chart?rc->chart:"NOCHART", rc->name, buffer_tostring(rc->warning->error_msg)); + + if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_WARN_ERROR))) { + rc->rrdcalc_flags |= RRDCALC_FLAG_WARN_ERROR; + error("Health alarm '%s.%s': warning expression failed with error: %s", + rc->chart?rc->chart:"NOCHART", rc->name, buffer_tostring(rc->warning->error_msg)); + } + } + else { + if(unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_WARN_ERROR)) + rc->rrdcalc_flags &= ~RRDCALC_FLAG_WARN_ERROR; + + debug(D_HEALTH, "Health alarm '%s.%s': warning expression gave value " + CALCULATED_NUMBER_FORMAT + ": %s (source: %s)", + rc->chart?rc->chart:"NOCHART", rc->name, + rc->warning->result, + buffer_tostring(rc->warning->error_msg), + rc->source + ); + + warning_status = rrdcalc_value2status(rc->warning->result); + } + } + + if(unlikely(rc->critical)) { + if(unlikely(!expression_evaluate(rc->critical))) { + // calculation failed + + debug(D_HEALTH, "Health alarm '%s.%s': critical expression failed with error: %s", + rc->chart?rc->chart:"NOCHART", rc->name, buffer_tostring(rc->critical->error_msg)); + + if (unlikely(!(rc->rrdcalc_flags & RRDCALC_FLAG_CRIT_ERROR))) { + rc->rrdcalc_flags |= RRDCALC_FLAG_CRIT_ERROR; + error("Health alarm '%s.%s': critical expression failed with error: %s", + rc->chart?rc->chart:"NOCHART", rc->name, buffer_tostring(rc->critical->error_msg)); + } + } + else { + if(unlikely(rc->rrdcalc_flags & RRDCALC_FLAG_CRIT_ERROR)) + rc->rrdcalc_flags &= ~RRDCALC_FLAG_CRIT_ERROR; + + debug(D_HEALTH, "Health alarm '%s.%s': critical expression gave value " + CALCULATED_NUMBER_FORMAT + ": %s (source: %s)", + rc->chart?rc->chart:"NOCHART", rc->name, + rc->critical->result, + buffer_tostring(rc->critical->error_msg), + rc->source + ); + + critical_status = rrdcalc_value2status(rc->critical->result); + } + } + + int status = RRDCALC_STATUS_UNDEFINED; + + switch(warning_status) { + case RRDCALC_STATUS_CLEAR: + status = RRDCALC_STATUS_CLEAR; + break; + + case RRDCALC_STATUS_RAISED: + status = RRDCALC_STATUS_WARNING; + break; + + default: + break; + } + + switch(critical_status) { + case RRDCALC_STATUS_CLEAR: + if(status == RRDCALC_STATUS_UNDEFINED) + status = RRDCALC_STATUS_CLEAR; + break; + + case RRDCALC_STATUS_RAISED: + status = RRDCALC_STATUS_CRITICAL; + break; + + default: + break; + } + + if(status != rc->status) { + health_alarm_log(&localhost, time(NULL), rc->name, rc->rrdset->id, rc->rrdset->family, rc->exec, now - rc->last_status_change, rc->old_value, rc->value, rc->status, status, rc->source, rc->units, rc->info); + rc->last_status_change = now; + rc->status = status; + } + + rc->last_updated = now; + rc->next_update = now + rc->update_every; + + if (next_run > rc->next_update) + next_run = rc->next_update; + } + + rrdhost_unlock(&localhost); + } + + if (unlikely(pthread_setcancelstate(oldstate, NULL) != 0)) + error("Cannot set pthread cancel state to RESTORE (%d).", oldstate); + + // execute notifications + // and cleanup + health_alarm_log_process(&localhost); + + now = time(NULL); + if(now < next_run) { + debug(D_HEALTH, "Health monitoring iteration no %u done. Next iteration in %d secs", + loop, (int) (next_run - now)); + sleep_usec(1000000 * (unsigned long long) (next_run - now)); + } + else { + debug(D_HEALTH, "Health monitoring iteration no %u done. Next iteration now", loop); + } + } + + buffer_free(wb); + + info("HEALTH thread exiting"); + pthread_exit(NULL); + return NULL; +} diff --git a/src/health.h b/src/health.h new file mode 100644 index 000000000..ef1158a29 --- /dev/null +++ b/src/health.h @@ -0,0 +1,283 @@ +#ifndef NETDATA_HEALTH_H +#define NETDATA_HEALTH_H + +extern int health_enabled; + +extern int rrdvar_compare(void *a, void *b); + +#define RRDVAR_TYPE_CALCULATED 1 +#define RRDVAR_TYPE_TIME_T 2 +#define RRDVAR_TYPE_COLLECTED 3 +#define RRDVAR_TYPE_TOTAL 4 +#define RRDVAR_TYPE_INT 5 + +// the variables as stored in the variables indexes +// there are 3 indexes: +// 1. at each chart (RRDSET.variables_root_index) +// 2. at each context (RRDFAMILY.variables_root_index) +// 3. at each host (RRDHOST.variables_root_index) +typedef struct rrdvar { + avl avl; + + char *name; + uint32_t hash; + + int type; + void *value; + + time_t last_updated; +} RRDVAR; + +// variables linked to charts +// We link variables to point to the values that are already +// calculated / processed by the normal data collection process +// This means, there will be no speed penalty for using +// these variables +typedef struct rrdsetvar { + char *fullid; // chart type.chart id.variable + char *fullname; // chart type.chart name.variable + char *variable; // variable + + int type; + void *value; + + uint32_t options; + + RRDVAR *local; + RRDVAR *family; + RRDVAR *host; + RRDVAR *family_name; + RRDVAR *host_name; + + struct rrdset *rrdset; + + struct rrdsetvar *next; +} RRDSETVAR; + + +// variables linked to individual dimensions +// We link variables to point the values that are already +// calculated / processed by the normal data collection process +// This means, there will be no speed penalty for using +// these variables +typedef struct rrddimvar { + char *prefix; + char *suffix; + + char *id; // dimension id + char *name; // dimension name + char *fullidid; // chart type.chart id.dimension id + char *fullidname; // chart type.chart id.dimension name + char *fullnameid; // chart type.chart name.dimension id + char *fullnamename; // chart type.chart name.dimension name + + int type; + void *value; + + uint32_t options; + + RRDVAR *local_id; + RRDVAR *local_name; + + RRDVAR *family_id; + RRDVAR *family_name; + + RRDVAR *host_fullidid; + RRDVAR *host_fullidname; + RRDVAR *host_fullnameid; + RRDVAR *host_fullnamename; + + struct rrddim *rrddim; + + struct rrddimvar *next; +} RRDDIMVAR; + +// calculated variables (defined in health configuration) +// These aggregate time-series data at fixed intervals +// (defined in their update_every member below) +// These increase the overhead of netdata. +// +// These calculations are allocated and linked (->next) +// under RRDHOST. +// Then are also linked to RRDSET (of course only when the +// chart is found, via ->rrdset_next and ->rrdset_prev). +// This double-linked list is maintained sorted at all times +// having as RRDSET.calculations the RRDCALC to be processed +// next. + +#define RRDCALC_STATUS_UNINITIALIZED 0 +#define RRDCALC_STATUS_UNDEFINED -1 +#define RRDCALC_STATUS_CLEAR 1 +#define RRDCALC_STATUS_RAISED 2 +#define RRDCALC_STATUS_WARNING 3 +#define RRDCALC_STATUS_CRITICAL 4 + +#define RRDCALC_FLAG_DB_ERROR 0x00000001 +#define RRDCALC_FLAG_DB_NAN 0x00000002 +#define RRDCALC_FLAG_DB_STALE 0x00000004 +#define RRDCALC_FLAG_CALC_ERROR 0x00000008 +#define RRDCALC_FLAG_WARN_ERROR 0x00000010 +#define RRDCALC_FLAG_CRIT_ERROR 0x00000020 + +typedef struct rrdcalc { + char *name; + uint32_t hash; + + char *exec; + + char *chart; // the chart id this should be linked to + uint32_t hash_chart; + + char *source; // the source of this calculation + char *units; + char *info; + + char *dimensions; // the chart dimensions + + int group; // grouping method: average, max, etc. + int before; // ending point in time-series + int after; // starting point in time-series + uint32_t options; // calculation options + int update_every; // update frequency for the calculation + + time_t last_updated; + time_t next_update; + + EVAL_EXPRESSION *calculation; + EVAL_EXPRESSION *warning; + EVAL_EXPRESSION *critical; + + uint32_t rrdcalc_flags; + int status; + + time_t db_after; + time_t db_before; + time_t last_status_change; + + calculated_number value; + calculated_number old_value; + + calculated_number green; + calculated_number red; + + RRDVAR *local; + RRDVAR *family; + RRDVAR *hostid; + RRDVAR *hostname; + + struct rrdset *rrdset; + struct rrdcalc *rrdset_next; + struct rrdcalc *rrdset_prev; + + struct rrdcalc *next; +} RRDCALC; + +#define RRDCALC_HAS_DB_LOOKUP(rc) ((rc)->after) + +// RRDCALCTEMPLATE +// these are to be applied to charts found dynamically +// based on their context. +typedef struct rrdcalctemplate { + char *name; + uint32_t hash_name; + + char *exec; + + char *context; + uint32_t hash_context; + + char *source; // the source of this template + char *units; + char *info; + + char *dimensions; + + int group; // grouping method: average, max, etc. + int before; // ending point in time-series + int after; // starting point in time-series + uint32_t options; // calculation options + int update_every; // update frequency for the calculation + + EVAL_EXPRESSION *calculation; + EVAL_EXPRESSION *warning; + EVAL_EXPRESSION *critical; + + calculated_number green; + calculated_number red; + + struct rrdcalctemplate *next; +} RRDCALCTEMPLATE; + +#define RRDCALCTEMPLATE_HAS_CALCULATION(rt) ((rt)->after) + +#define HEALTH_ENTRY_NOTIFICATIONS_PROCESSED 0x00000001 +#define HEALTH_ENTRY_NOTIFICATIONS_UPDATED 0x00000002 +#define HEALTH_ENTRY_NOTIFICATIONS_EXEC_RUN 0x00000004 +#define HEALTH_ENTRY_NOTIFICATIONS_EXEC_FAILED 0x00000008 + +typedef struct alarm_entry { + uint32_t id; + + time_t when; + time_t duration; + time_t non_clear_duration; + + char *name; + uint32_t hash_name; + + char *chart; + uint32_t hash_chart; + + char *family; + + char *exec; + int exec_code; + + char *source; + char *units; + char *info; + + calculated_number old_value; + calculated_number new_value; + int old_status; + int new_status; + + uint32_t notifications; + + struct alarm_entry *updated_by; + struct alarm_entry *next; +} ALARM_ENTRY; + +typedef struct alarm_log { + uint32_t nextid; + unsigned int count; + unsigned int max; + ALARM_ENTRY *alarms; + pthread_rwlock_t alarm_log_rwlock; +} ALARM_LOG; + +#include "rrd.h" + +extern void rrdsetvar_rename_all(RRDSET *st); +extern RRDSETVAR *rrdsetvar_create(RRDSET *st, const char *variable, int type, void *value, uint32_t options); +extern void rrdsetvar_free(RRDSETVAR *rs); + +extern void rrddimvar_rename_all(RRDDIM *rd); +extern RRDDIMVAR *rrddimvar_create(RRDDIM *rd, int type, const char *prefix, const char *suffix, void *value, uint32_t options); +extern void rrddimvar_free(RRDDIMVAR *rs); + +extern void rrdsetcalc_link_matching(RRDSET *st); +extern void rrdsetcalc_unlink(RRDCALC *rc); +extern void rrdcalctemplate_link_matching(RRDSET *st); +extern RRDCALC *rrdcalc_find(RRDSET *st, const char *name); + +extern void health_init(void); +extern void *health_main(void *ptr); + +extern void health_reload(void); + +extern int health_variable_lookup(const char *variable, uint32_t hash, RRDCALC *rc, calculated_number *result); +extern void health_alarms2json(RRDHOST *host, BUFFER *wb, int all); +extern void health_alarm_log2json(RRDHOST *host, BUFFER *wb); + +#endif //NETDATA_HEALTH_H @@ -1,241 +1,344 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <time.h> -#include <syslog.h> -#include <errno.h> -#include <string.h> -#include <unistd.h> -#include <stdlib.h> - -#include "log.h" #include "common.h" - -// ---------------------------------------------------------------------------- -// LOG - const char *program_name = ""; unsigned long long debug_flags = DEBUG; -int silent = 0; +int access_log_syslog = 1; +int error_log_syslog = 1; +int output_log_syslog = 1; // debug log -int access_fd = -1; +int stdaccess_fd = -1; FILE *stdaccess = NULL; -int access_log_syslog = 1; -int error_log_syslog = 1; -int output_log_syslog = 1; // debug log +const char *stdaccess_filename = NULL; +const char *stderr_filename = NULL; +const char *stdout_filename = NULL; + +void syslog_init(void) { + static int i = 0; + + if(!i) { + openlog(program_name, LOG_PID, LOG_DAEMON); + i = 1; + } +} + +int open_log_file(int fd, FILE **fp, const char *filename, int *enabled_syslog) { + int f, t; + + if(!filename || !*filename || !strcmp(filename, "none")) + filename = "/dev/null"; + + if(!strcmp(filename, "syslog")) { + filename = "/dev/null"; + syslog_init(); + if(enabled_syslog) *enabled_syslog = 1; + } + else if(enabled_syslog) *enabled_syslog = 0; + + // don't do anything if the user is willing + // to have the standard one + if(!strcmp(filename, "system")) { + if(fd != -1) return fd; + filename = "stdout"; + } + + if(!strcmp(filename, "stdout")) + f = STDOUT_FILENO; + + else if(!strcmp(filename, "stderr")) + f = STDERR_FILENO; + + else { + f = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0664); + if(f == -1) { + error("Cannot open file '%s'. Leaving %d to its default.", filename, fd); + return fd; + } + } + + // if there is a level-2 file pointer + // flush it before switching the level-1 fds + if(fp && *fp) + fflush(*fp); + + if(fd != f && fd != -1) { + // it automatically closes + t = dup2(f, fd); + if (t == -1) { + error("Cannot dup2() new fd %d to old fd %d for '%s'", f, fd, filename); + close(f); + return fd; + } + // info("dup2() new fd %d to old fd %d for '%s'", f, fd, filename); + close(f); + } + else fd = f; + + if(fp && !*fp) { + *fp = fdopen(fd, "a"); + if (!*fp) + error("Cannot fdopen() fd %d ('%s')", fd, filename); + else { + if (setvbuf(*fp, NULL, _IOLBF, 0) != 0) + error("Cannot set line buffering on fd %d ('%s')", fd, filename); + } + } + + return fd; +} + +void reopen_all_log_files() { + if(stdout_filename) + open_log_file(STDOUT_FILENO, &stdout, stdout_filename, &output_log_syslog); + + if(stderr_filename) + open_log_file(STDERR_FILENO, &stderr, stderr_filename, &error_log_syslog); + + if(stdaccess_filename) + stdaccess_fd = open_log_file(stdaccess_fd, &stdaccess, stdaccess_filename, &access_log_syslog); +} + +void open_all_log_files() { + // disable stdin + open_log_file(STDIN_FILENO, &stdin, "/dev/null", NULL); + + open_log_file(STDOUT_FILENO, &stdout, stdout_filename, &output_log_syslog); + open_log_file(STDERR_FILENO, &stderr, stderr_filename, &error_log_syslog); + stdaccess_fd = open_log_file(stdaccess_fd, &stdaccess, stdaccess_filename, &access_log_syslog); +} + +// ---------------------------------------------------------------------------- +// error log throttling time_t error_log_throttle_period = 1200; unsigned long error_log_errors_per_period = 200; int error_log_limit(int reset) { - static time_t start = 0; - static unsigned long counter = 0, prevented = 0; - - // do not throttle if the period is 0 - if(error_log_throttle_period == 0) - return 0; - - // prevent all logs if the errors per period is 0 - if(error_log_errors_per_period == 0) - return 1; - - time_t now = time(NULL); - if(!start) start = now; - - if(reset) { - if(prevented) { - log_date(stderr); - fprintf(stderr, "%s: Resetting logging for process '%s' (prevented %lu logs in the last %ld seconds).\n" - , program_name - , program_name - , prevented - , now - start - ); - } - - start = now; - counter = 0; - prevented = 0; - } - - // detect if we log too much - counter++; - - if(now - start > error_log_throttle_period) { - if(prevented) { - log_date(stderr); - fprintf(stderr, "%s: Resuming logging from process '%s' (prevented %lu logs in the last %ld seconds).\n" - , program_name - , program_name - , prevented - , error_log_throttle_period - ); - } - - // restart the period accounting - start = now; - counter = 1; - prevented = 0; - - // log this error - return 0; - } - - if(counter > error_log_errors_per_period) { - if(!prevented) { - log_date(stderr); - fprintf(stderr, "%s: Too many logs (%lu logs in %ld seconds, threshold is set to %lu logs in %ld seconds). Preventing more logs from process '%s' for %ld seconds.\n" - , program_name - , counter - , now - start - , error_log_errors_per_period - , error_log_throttle_period - , program_name - , start + error_log_throttle_period - now); - } - - prevented++; - - // prevent logging this error - return 1; - } - - return 0; + static time_t start = 0; + static unsigned long counter = 0, prevented = 0; + + // do not throttle if the period is 0 + if(error_log_throttle_period == 0) + return 0; + + // prevent all logs if the errors per period is 0 + if(error_log_errors_per_period == 0) + return 1; + + time_t now = time(NULL); + if(!start) start = now; + + if(reset) { + if(prevented) { + log_date(stderr); + fprintf(stderr, "%s: Resetting logging for process '%s' (prevented %lu logs in the last %ld seconds).\n" + , program_name + , program_name + , prevented + , now - start + ); + } + + start = now; + counter = 0; + prevented = 0; + } + + // detect if we log too much + counter++; + + if(now - start > error_log_throttle_period) { + if(prevented) { + log_date(stderr); + fprintf(stderr, "%s: Resuming logging from process '%s' (prevented %lu logs in the last %ld seconds).\n" + , program_name + , program_name + , prevented + , error_log_throttle_period + ); + } + + // restart the period accounting + start = now; + counter = 1; + prevented = 0; + + // log this error + return 0; + } + + if(counter > error_log_errors_per_period) { + if(!prevented) { + log_date(stderr); + fprintf(stderr, "%s: Too many logs (%lu logs in %ld seconds, threshold is set to %lu logs in %ld seconds). Preventing more logs from process '%s' for %ld seconds.\n" + , program_name + , counter + , now - start + , error_log_errors_per_period + , error_log_throttle_period + , program_name + , start + error_log_throttle_period - now); + } + + prevented++; + + // prevent logging this error + return 1; + } + + return 0; } +// ---------------------------------------------------------------------------- +// print the date + +// FIXME +// this should print the date in a buffer the way it +// is now, logs from multiple threads may be multiplexed + void log_date(FILE *out) { - char outstr[200]; - time_t t; - struct tm *tmp, tmbuf; + char outstr[24]; + time_t t; + struct tm *tmp, tmbuf; - t = time(NULL); - tmp = localtime_r(&t, &tmbuf); + t = time(NULL); + tmp = localtime_r(&t, &tmbuf); - if (tmp == NULL) return; - if (strftime(outstr, sizeof(outstr), "%y-%m-%d %H:%M:%S", tmp) == 0) return; + if (tmp == NULL) return; + if (unlikely(strftime(outstr, sizeof(outstr), "%y-%m-%d %H:%M:%S", tmp) == 0)) return; - fprintf(out, "%s: ", outstr); + fprintf(out, "%s: ", outstr); } +// ---------------------------------------------------------------------------- +// debug log + void debug_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) { - va_list args; - - log_date(stdout); - va_start( args, fmt ); - fprintf(stdout, "DEBUG (%04lu@%-10.10s:%-15.15s): %s: ", line, file, function, program_name); - vfprintf( stdout, fmt, args ); - va_end( args ); - fprintf(stdout, "\n"); - // fflush( stdout ); - - if(output_log_syslog) { - va_start( args, fmt ); - vsyslog(LOG_ERR, fmt, args ); - va_end( args ); - } + va_list args; + + log_date(stdout); + va_start( args, fmt ); + printf("DEBUG (%04lu@%-10.10s:%-15.15s): %s: ", line, file, function, program_name); + vprintf(fmt, args); + va_end( args ); + putchar('\n'); + + if(output_log_syslog) { + va_start( args, fmt ); + vsyslog(LOG_ERR, fmt, args ); + va_end( args ); + } + + fflush(stdout); } +// ---------------------------------------------------------------------------- +// info log + void info_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) { - va_list args; + va_list args; - // prevent logging too much - if(error_log_limit(0)) return; + // prevent logging too much + if(error_log_limit(0)) return; - log_date(stderr); + log_date(stderr); - va_start( args, fmt ); - if(debug_flags) fprintf(stderr, "INFO (%04lu@%-10.10s:%-15.15s): %s: ", line, file, function, program_name); - else fprintf(stderr, "INFO: %s: ", program_name); - vfprintf( stderr, fmt, args ); - va_end( args ); + va_start( args, fmt ); + if(debug_flags) fprintf(stderr, "INFO (%04lu@%-10.10s:%-15.15s): %s: ", line, file, function, program_name); + else fprintf(stderr, "INFO: %s: ", program_name); + vfprintf( stderr, fmt, args ); + va_end( args ); - fprintf(stderr, "\n"); + fputc('\n', stderr); - if(error_log_syslog) { - va_start( args, fmt ); - vsyslog(LOG_INFO, fmt, args ); - va_end( args ); - } + if(error_log_syslog) { + va_start( args, fmt ); + vsyslog(LOG_INFO, fmt, args ); + va_end( args ); + } } +// ---------------------------------------------------------------------------- +// error log + void error_int( const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ... ) { - va_list args; - - // prevent logging too much - if(error_log_limit(0)) return; - - log_date(stderr); - - va_start( args, fmt ); - if(debug_flags) fprintf(stderr, "%s (%04lu@%-10.10s:%-15.15s): %s: ", prefix, line, file, function, program_name); - else fprintf(stderr, "%s: %s: ", prefix, program_name); - vfprintf( stderr, fmt, args ); - va_end( args ); - - if(errno) { - char buf[200]; - char *s = strerror_r(errno, buf, 200); - fprintf(stderr, " (errno %d, %s)\n", errno, s); - errno = 0; - } - else fprintf(stderr, "\n"); - - if(error_log_syslog) { - va_start( args, fmt ); - vsyslog(LOG_ERR, fmt, args ); - va_end( args ); - } + va_list args; + + // prevent logging too much + if(error_log_limit(0)) return; + + log_date(stderr); + + va_start( args, fmt ); + if(debug_flags) fprintf(stderr, "%s (%04lu@%-10.10s:%-15.15s): %s: ", prefix, line, file, function, program_name); + else fprintf(stderr, "%s: %s: ", prefix, program_name); + vfprintf( stderr, fmt, args ); + va_end( args ); + + if(errno) { + char buf[1024]; + fprintf(stderr, " (errno %d, %s)\n", errno, strerror_r(errno, buf, 1023)); + errno = 0; + } + else + fputc('\n', stderr); + + if(error_log_syslog) { + va_start( args, fmt ); + vsyslog(LOG_ERR, fmt, args ); + va_end( args ); + } } void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) { - va_list args; + va_list args; - log_date(stderr); + log_date(stderr); - va_start( args, fmt ); - if(debug_flags) fprintf(stderr, "FATAL (%04lu@%-10.10s:%-15.15s): %s: ", line, file, function, program_name); - else fprintf(stderr, "FATAL: %s: ", program_name); - vfprintf( stderr, fmt, args ); - va_end( args ); + va_start( args, fmt ); + if(debug_flags) fprintf(stderr, "FATAL (%04lu@%-10.10s:%-15.15s): %s: ", line, file, function, program_name); + else fprintf(stderr, "FATAL: %s: ", program_name); + vfprintf( stderr, fmt, args ); + va_end( args ); - perror(" # "); - fprintf(stderr, "\n"); + perror(" # "); + fputc('\n', stderr); - if(error_log_syslog) { - va_start( args, fmt ); - vsyslog(LOG_CRIT, fmt, args ); - va_end( args ); - } + if(error_log_syslog) { + va_start( args, fmt ); + vsyslog(LOG_CRIT, fmt, args ); + va_end( args ); + } - exit(1); + netdata_cleanup_and_exit(1); } +// ---------------------------------------------------------------------------- +// access log + void log_access( const char *fmt, ... ) { - va_list args; - - if(stdaccess) { - log_date(stdaccess); - - va_start( args, fmt ); - vfprintf( stdaccess, fmt, args ); - va_end( args ); - fprintf( stdaccess, "\n"); - // fflush( stdaccess ); - } - - if(access_log_syslog) { - va_start( args, fmt ); - vsyslog(LOG_INFO, fmt, args ); - va_end( args ); - } + va_list args; + + if(stdaccess) { + log_date(stdaccess); + + va_start( args, fmt ); + vfprintf( stdaccess, fmt, args ); + va_end( args ); + fputc('\n', stdaccess); + } + + if(access_log_syslog) { + va_start( args, fmt ); + vsyslog(LOG_INFO, fmt, args ); + va_end( args ); + } } @@ -1,18 +1,14 @@ -#include <stdio.h> -#include <stdarg.h> -#include <time.h> - #ifndef NETDATA_LOG_H #define NETDATA_LOG_H 1 -#define D_WEB_BUFFER 0x00000001 -#define D_WEB_CLIENT 0x00000002 -#define D_LISTENER 0x00000004 -#define D_WEB_DATA 0x00000008 -#define D_OPTIONS 0x00000010 +#define D_WEB_BUFFER 0x00000001 +#define D_WEB_CLIENT 0x00000002 +#define D_LISTENER 0x00000004 +#define D_WEB_DATA 0x00000008 +#define D_OPTIONS 0x00000010 #define D_PROCNETDEV_LOOP 0x00000020 -#define D_RRD_STATS 0x00000040 -#define D_WEB_CLIENT_ACCESS 0x00000080 +#define D_RRD_STATS 0x00000040 +#define D_WEB_CLIENT_ACCESS 0x00000080 #define D_TC_LOOP 0x00000100 #define D_DEFLATE 0x00000200 #define D_CONFIG 0x00000400 @@ -20,13 +16,15 @@ #define D_CHILDS 0x00001000 #define D_EXIT 0x00002000 #define D_CHECKS 0x00004000 -#define D_NFACCT_LOOP 0x00008000 -#define D_PROCFILE 0x00010000 -#define D_RRD_CALLS 0x00020000 -#define D_DICTIONARY 0x00040000 -#define D_MEMORY 0x00080000 +#define D_NFACCT_LOOP 0x00008000 +#define D_PROCFILE 0x00010000 +#define D_RRD_CALLS 0x00020000 +#define D_DICTIONARY 0x00040000 +#define D_MEMORY 0x00080000 #define D_CGROUP 0x00100000 -#define D_REGISTRY 0x00200000 +#define D_REGISTRY 0x00200000 +#define D_VARIABLES 0x00400000 +#define D_HEALTH 0x00800000 //#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS) //#define DEBUG 0xffffffff @@ -36,11 +34,13 @@ extern unsigned long long debug_flags; extern const char *program_name; -extern int silent; - -extern int access_fd; +extern int stdaccess_fd; extern FILE *stdaccess; +extern const char *stdaccess_filename; +extern const char *stderr_filename; +extern const char *stdout_filename; + extern int access_log_syslog; extern int error_log_syslog; extern int output_log_syslog; @@ -49,19 +49,23 @@ extern time_t error_log_throttle_period; extern unsigned long error_log_errors_per_period; extern int error_log_limit(int reset); +extern void open_all_log_files(); +extern void reopen_all_log_files(); + #define error_log_limit_reset() do { error_log_limit(1); } while(0) +#define error_log_limit_unlimited() do { error_log_throttle_period = 0; } while(0) -#define debug(type, args...) do { if(unlikely(!silent && (debug_flags & type))) debug_int(__FILE__, __FUNCTION__, __LINE__, ##args); } while(0) +#define debug(type, args...) do { if(unlikely(debug_flags & type)) debug_int(__FILE__, __FUNCTION__, __LINE__, ##args); } while(0) #define info(args...) info_int(__FILE__, __FUNCTION__, __LINE__, ##args) #define infoerr(args...) error_int("INFO", __FILE__, __FUNCTION__, __LINE__, ##args) #define error(args...) error_int("ERROR", __FILE__, __FUNCTION__, __LINE__, ##args) #define fatal(args...) fatal_int(__FILE__, __FUNCTION__, __LINE__, ##args) extern void log_date(FILE *out); -extern void debug_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ); -extern void info_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ); -extern void error_int( const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ... ); -extern void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) __attribute__ ((noreturn)); -extern void log_access( const char *fmt, ... ); +extern void debug_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) __attribute__ (( format (printf, 4, 5))); +extern void info_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) __attribute__ (( format (printf, 4, 5))); +extern void error_int( const char *prefix, const char *file, const char *function, const unsigned long line, const char *fmt, ... ) __attribute__ (( format (printf, 5, 6))); +extern void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) __attribute__ ((noreturn, format (printf, 4, 5))); +extern void log_access( const char *fmt, ... ) __attribute__ (( format (printf, 1, 2))); #endif /* NETDATA_LOG_H */ diff --git a/src/main.c b/src/main.c index ec3c59ca7..4d55a1425 100644 --- a/src/main.c +++ b/src/main.c @@ -1,573 +1,702 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <signal.h> -#include <syslog.h> -#include <pthread.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <sys/mman.h> -#include <sys/prctl.h> - #include "common.h" -#include "log.h" -#include "daemon.h" -#include "web_server.h" -#include "popen.h" -#include "appconfig.h" -#include "web_client.h" -#include "rrd.h" -#include "rrd2json.h" - -#include "unit_test.h" - -#include "plugins_d.h" -#include "plugin_idlejitter.h" -#include "plugin_tc.h" -#include "plugin_checks.h" -#include "plugin_proc.h" -#include "plugin_nfacct.h" -#include "registry.h" - -#include "main.h" extern void *cgroups_main(void *ptr); -volatile sig_atomic_t netdata_exit = 0; - -void netdata_cleanup_and_exit(int ret) -{ - netdata_exit = 1; - rrdset_save_all(); - // kill_childs(); - - // let it log a few more error messages - error_log_limit_reset(); +void netdata_cleanup_and_exit(int ret) { + netdata_exit = 1; - if(pidfd != -1) { - if(ftruncate(pidfd, 0) != 0) - error("Cannot truncate pidfile '%s'.", pidfile); + error_log_limit_unlimited(); - close(pidfd); - pidfd = -1; - } + info("Called: netdata_cleanup_and_exit()"); +#ifdef NETDATA_INTERNAL_CHECKS + rrdset_free_all(); +#else + rrdset_save_all(); +#endif + // kill_childs(); - if(pidfile[0]) { - if(unlink(pidfile) != 0) - error("Cannot unlink pidfile '%s'.", pidfile); - } + if(pidfile[0]) { + if(unlink(pidfile) != 0) + error("Cannot unlink pidfile '%s'.", pidfile); + } - info("NetData exiting. Bye bye..."); - exit(ret); + info("NetData exiting. Bye bye..."); + exit(ret); } struct netdata_static_thread { - char *name; + char *name; - char *config_section; - char *config_name; + char *config_section; + char *config_name; - int enabled; + int enabled; - pthread_t *thread; - - void (*init_routine) (void); - void *(*start_routine) (void *); -}; - -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}, + pthread_t *thread; + void (*init_routine) (void); + void *(*start_routine) (void *); +} static_threads[] = { #ifdef INTERNAL_PLUGIN_NFACCT - // nfacct requires root access - // so, we build it as an external plugin with setuid to root - {"nfacct", "plugins", "nfacct", 1, NULL, NULL, nfacct_main}, +// nfacct requires root access + // so, we build it as an external plugin with setuid to root + {"nfacct", "plugins", "nfacct", 1, NULL, NULL, nfacct_main}, #endif - {"plugins.d", NULL, NULL, 1, NULL, NULL, pluginsd_main}, - {"check", "plugins", "checks", 0, NULL, NULL, checks_main}, - {"web", NULL, NULL, 1, NULL, NULL, socket_listen_main}, - {NULL, NULL, NULL, 0, NULL, NULL, NULL} + {"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}, + {"check", "plugins", "checks", 0, NULL, NULL, checks_main}, + {"health", NULL, NULL, 1, NULL, NULL, health_main}, + {"plugins.d", NULL, NULL, 1, NULL, NULL, pluginsd_main}, + {"web", NULL, NULL, 1, NULL, NULL, socket_listen_main_multi_threaded}, + {"web-single-threaded", NULL, NULL, 0, NULL, NULL, socket_listen_main_single_threaded}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL} }; +void web_server_threading_selection(void) { + int threaded = config_get_boolean("global", "multi threaded web server", 1); + + int i; + for(i = 0; static_threads[i].name ; i++) { + if(static_threads[i].start_routine == socket_listen_main_multi_threaded) + static_threads[i].enabled = threaded?1:0; + + if(static_threads[i].start_routine == socket_listen_main_single_threaded) + static_threads[i].enabled = threaded?0:1; + } + + web_client_timeout = (int) config_get_number("global", "disconnect idle web clients after seconds", DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS); + + web_donotrack_comply = config_get_boolean("global", "respect web browser do not track policy", web_donotrack_comply); + +#ifdef NETDATA_WITH_ZLIB + web_enable_gzip = config_get_boolean("global", "enable web responses gzip compression", web_enable_gzip); + + char *s = config_get("global", "web compression strategy", "default"); + if(!strcmp(s, "default")) + web_gzip_strategy = Z_DEFAULT_STRATEGY; + else if(!strcmp(s, "filtered")) + web_gzip_strategy = Z_FILTERED; + else if(!strcmp(s, "huffman only")) + web_gzip_strategy = Z_HUFFMAN_ONLY; + else if(!strcmp(s, "rle")) + web_gzip_strategy = Z_RLE; + else if(!strcmp(s, "fixed")) + web_gzip_strategy = Z_FIXED; + else { + error("Invalid compression strategy '%s'. Valid strategies are 'default', 'filtered', 'huffman only', 'rle' and 'fixed'. Proceeding with 'default'.", s); + web_gzip_strategy = Z_DEFAULT_STRATEGY; + } + + web_gzip_level = (int)config_get_number("global", "web compression level", 3); + if(web_gzip_level < 1) { + error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 1 (fastest compression).", web_gzip_level); + web_gzip_level = 1; + } + else if(web_gzip_level > 9) { + error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 9 (best compression).", web_gzip_level); + web_gzip_level = 9; + } +#endif /* NETDATA_WITH_ZLIB */ +} + + int killpid(pid_t pid, int sig) { - int ret = -1; - debug(D_EXIT, "Request to kill pid %d", pid); - - errno = 0; - if(kill(pid, 0) == -1) { - switch(errno) { - case ESRCH: - error("Request to kill pid %d, but it is not running.", pid); - break; - - case EPERM: - error("Request to kill pid %d, but I do not have enough permissions.", pid); - break; - - default: - error("Request to kill pid %d, but I received an error.", pid); - break; - } - } - else { - errno = 0; - ret = kill(pid, sig); - if(ret == -1) { - switch(errno) { - case ESRCH: - error("Cannot kill pid %d, but it is not running.", pid); - break; - - case EPERM: - error("Cannot kill pid %d, but I do not have enough permissions.", pid); - break; - - default: - error("Cannot kill pid %d, but I received an error.", pid); - break; - } - } - } - - return ret; + int ret = -1; + debug(D_EXIT, "Request to kill pid %d", pid); + + errno = 0; + if(kill(pid, 0) == -1) { + switch(errno) { + case ESRCH: + error("Request to kill pid %d, but it is not running.", pid); + break; + + case EPERM: + error("Request to kill pid %d, but I do not have enough permissions.", pid); + break; + + default: + error("Request to kill pid %d, but I received an error.", pid); + break; + } + } + else { + errno = 0; + ret = kill(pid, sig); + if(ret == -1) { + switch(errno) { + case ESRCH: + error("Cannot kill pid %d, but it is not running.", pid); + break; + + case EPERM: + error("Cannot kill pid %d, but I do not have enough permissions.", pid); + break; + + default: + error("Cannot kill pid %d, but I received an error.", pid); + break; + } + } + } + + return ret; } void kill_childs() { - siginfo_t info; - - struct web_client *w; - for(w = web_clients; w ; w = w->next) { - debug(D_EXIT, "Stopping web client %s", w->client_ip); - pthread_cancel(w->thread); - pthread_join(w->thread, NULL); - } - - int i; - for (i = 0; static_threads[i].name != NULL ; i++) { - if(static_threads[i].thread) { - debug(D_EXIT, "Stopping %s thread", static_threads[i].name); - pthread_cancel(*static_threads[i].thread); - pthread_join(*static_threads[i].thread, NULL); - static_threads[i].thread = NULL; - } - } - - if(tc_child_pid) { - info("Killing tc-qos-helper procees"); - if(killpid(tc_child_pid, SIGTERM) != -1) - waitid(P_PID, (id_t) tc_child_pid, &info, WEXITED); - } - tc_child_pid = 0; - - struct plugind *cd; - for(cd = pluginsd_root ; cd ; cd = cd->next) { - debug(D_EXIT, "Stopping %s plugin thread", cd->id); - pthread_cancel(cd->thread); - pthread_join(cd->thread, NULL); - - if(cd->pid && !cd->obsolete) { - debug(D_EXIT, "killing %s plugin process", cd->id); - if(killpid(cd->pid, SIGTERM) != -1) - waitid(P_PID, (id_t) cd->pid, &info, WEXITED); - } - } - - // if, for any reason there is any child exited - // catch it here - waitid(P_PID, 0, &info, WEXITED|WNOHANG); - - debug(D_EXIT, "All threads/childs stopped."); + siginfo_t info; + + struct web_client *w; + for(w = web_clients; w ; w = w->next) { + debug(D_EXIT, "Stopping web client %s", w->client_ip); + pthread_cancel(w->thread); + pthread_join(w->thread, NULL); + } + + int i; + for (i = 0; static_threads[i].name != NULL ; i++) { + if(static_threads[i].thread) { + debug(D_EXIT, "Stopping %s thread", static_threads[i].name); + pthread_cancel(*static_threads[i].thread); + pthread_join(*static_threads[i].thread, NULL); + static_threads[i].thread = NULL; + } + } + + if(tc_child_pid) { + info("Killing tc-qos-helper procees"); + if(killpid(tc_child_pid, SIGTERM) != -1) + waitid(P_PID, (id_t) tc_child_pid, &info, WEXITED); + } + tc_child_pid = 0; + + struct plugind *cd; + for(cd = pluginsd_root ; cd ; cd = cd->next) { + debug(D_EXIT, "Stopping %s plugin thread", cd->id); + pthread_cancel(cd->thread); + pthread_join(cd->thread, NULL); + + if(cd->pid && !cd->obsolete) { + debug(D_EXIT, "killing %s plugin process", cd->id); + if(killpid(cd->pid, SIGTERM) != -1) + waitid(P_PID, (id_t) cd->pid, &info, WEXITED); + } + } + + // if, for any reason there is any child exited + // catch it here + waitid(P_PID, 0, &info, WEXITED|WNOHANG); + + debug(D_EXIT, "All threads/childs stopped."); } +struct option_def options[] = { + // opt description arg name default value + {'c', "Load alternate configuration file", "config_file", CONFIG_DIR "/" CONFIG_FILENAME}, + {'D', "Disable fork into background", NULL, NULL}, + {'h', "Display help message", NULL, NULL}, + {'P', "File to save a pid while running", "FILE", NULL}, + {'i', "The IP address to listen to.", "address", "All addresses"}, + {'k', "Check daemon configuration.", NULL, NULL}, + {'p', "Port to listen. Can be from 1 to 65535.", "port_number", "19999"}, + {'s', "Path to access host /proc and /sys when running in a container.", "PATH", NULL}, + {'t', "The frequency in seconds, for data collection. \ +Same as 'update every' config file option.", "seconds", "1"}, + {'u', "System username to run as.", "username", "netdata"}, + {'v', "Version of the program", NULL, NULL}, + {'W', "vendor options.", "stacksize=N|unittest|debug_flags=N", NULL}, +}; + +void help(int exitcode) { + FILE *stream; + if(exitcode == 0) + stream = stdout; + else + stream = stderr; + + int num_opts = sizeof(options) / sizeof(struct option_def); + int i; + int max_len_arg = 0; + + // Compute maximum argument length + for( i = 0; i < num_opts; i++ ) { + if(options[i].arg_name) { + int len_arg = (int)strlen(options[i].arg_name); + if(len_arg > max_len_arg) max_len_arg = len_arg; + } + } + + fprintf(stream, "SYNOPSIS: netdata [options]\n"); + fprintf(stream, "\n"); + fprintf(stream, "Options:\n"); + + // Output options description. + for( i = 0; i < num_opts; i++ ) { + fprintf(stream, " -%c %-*s %s", options[i].val, max_len_arg, options[i].arg_name ? options[i].arg_name : "", options[i].description); + if(options[i].default_value) { + fprintf(stream, " Default: %s\n", options[i].default_value); + } else { + fprintf(stream, "\n"); + } + } + + fflush(stream); + exit(exitcode); +} + +// TODO: Remove this function with the nix major release. +void remove_option(int opt_index, int *argc, char **argv) { + int i = opt_index; + // remove the options. + do { + *argc = *argc - 1; + for(i = opt_index; i < *argc; i++) { + argv[i] = argv[i+1]; + } + i = opt_index; + } while(argv[i][0] != '-' && opt_index >= *argc); +} + +static const char *verify_required_directory(const char *dir) { + if(chdir(dir) == -1) + fatal("Cannot cd to directory '%s'", dir); + + DIR *d = opendir(dir); + if(!d) + fatal("Cannot examine the contents of directory '%s'", dir); + closedir(d); + + return dir; +} int main(int argc, char **argv) { - int i; - int config_loaded = 0; - int dont_fork = 0; - size_t wanted_stacksize = 0, stacksize = 0; - pthread_attr_t attr; - - // global initialization - get_HZ(); - - // set the name for logging - program_name = "netdata"; - - // parse the arguments - for(i = 1; i < argc ; i++) { - if(strcmp(argv[i], "-c") == 0 && (i+1) < argc) { - if(load_config(argv[i+1], 1) != 1) { - error("Cannot load configuration file %s.", argv[i+1]); - exit(1); - } - else { - debug(D_OPTIONS, "Configuration loaded from %s.", argv[i+1]); - config_loaded = 1; - } - i++; - } - else if(strcmp(argv[i], "-df") == 0 && (i+1) < argc) { config_set("global", "debug flags", argv[i+1]); debug_flags = strtoull(argv[i+1], NULL, 0); i++; } - else if(strcmp(argv[i], "-p") == 0 && (i+1) < argc) { config_set("global", "port", argv[i+1]); i++; } - else if(strcmp(argv[i], "-u") == 0 && (i+1) < argc) { config_set("global", "run as user", argv[i+1]); i++; } - else if(strcmp(argv[i], "-l") == 0 && (i+1) < argc) { config_set("global", "history", argv[i+1]); i++; } - else if(strcmp(argv[i], "-t") == 0 && (i+1) < argc) { config_set("global", "update every", argv[i+1]); i++; } - else if(strcmp(argv[i], "-ch") == 0 && (i+1) < argc) { config_set("global", "host access prefix", argv[i+1]); i++; } - else if(strcmp(argv[i], "-stacksize") == 0 && (i+1) < argc) { config_set("global", "pthread stack size", argv[i+1]); i++; } - 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++; - strncpyz(pidfile, argv[i], FILENAME_MAX); - } - else if(strcmp(argv[i], "--unittest") == 0) { - rrd_update_every = 1; - if(run_all_mockup_tests()) exit(1); - if(unit_test_storage()) exit(1); - fprintf(stderr, "\n\nALL TESTS PASSED\n\n"); - exit(0); - } - else { - fprintf(stderr, "Cannot understand option '%s'.\n", argv[i]); - fprintf(stderr, "\nUSAGE: %s [-d] [-l LINES_TO_SAVE] [-u UPDATE_TIMER] [-p LISTEN_PORT] [-df debug flags].\n\n", argv[0]); - fprintf(stderr, " -c CONFIG FILE the configuration file to load. Default: %s.\n", CONFIG_DIR "/" CONFIG_FILENAME); - fprintf(stderr, " -l LINES_TO_SAVE can be from 5 to %d lines in JSON data. Default: %d.\n", RRD_HISTORY_ENTRIES_MAX, RRD_DEFAULT_HISTORY_ENTRIES); - fprintf(stderr, " -t UPDATE_TIMER can be from 1 to %d seconds. Default: %d.\n", UPDATE_EVERY_MAX, UPDATE_EVERY); - fprintf(stderr, " -p LISTEN_PORT can be from 1 to %d. Default: %d.\n", 65535, LISTEN_PORT); - fprintf(stderr, " -u USERNAME can be any system username to run as. Default: none.\n"); - fprintf(stderr, " -ch path to access host /proc and /sys when running in a container. Default: empty.\n"); - fprintf(stderr, " -nd or -nodeamon to disable forking in the background. Default: unset.\n"); - fprintf(stderr, " -df FLAGS debug options. Default: 0x%08llx.\n", debug_flags); - fprintf(stderr, " -stacksize BYTES to overwrite the pthread stack size.\n"); - fprintf(stderr, " -pidfile FILENAME to save a pid while running.\n"); - exit(1); - } - } - - if(!config_loaded) load_config(NULL, 0); - - // prepare configuration environment variables for the plugins - setenv("NETDATA_CONFIG_DIR" , config_get("global", "config directory" , CONFIG_DIR) , 1); - 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 - setenv("TZ", ":/etc/localtime", 0); - - // cd to /tmp to avoid any plugins writing files at random places - if(chdir("/tmp")) error("netdata: ERROR: Cannot cd to /tmp"); - - char *input_log_file = NULL; - char *output_log_file = NULL; - char *error_log_file = NULL; - char *access_log_file = NULL; - char *user = NULL; - { - char buffer[1024]; - - // -------------------------------------------------------------------- - - sprintf(buffer, "0x%08llx", 0ULL); - char *flags = config_get("global", "debug flags", buffer); - setenv("NETDATA_DEBUG_FLAGS", flags, 1); - - debug_flags = strtoull(flags, NULL, 0); - debug(D_OPTIONS, "Debug flags set to '0x%8llx'.", debug_flags); - - 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); - } - - // -------------------------------------------------------------------- + int i, check_config = 0; + int config_loaded = 0; + int dont_fork = 0; + size_t wanted_stacksize = 0, stacksize = 0; + pthread_attr_t attr; + + // global initialization + get_HZ(); + + // set the name for logging + program_name = "netdata"; + + // parse command line. + + // parse depercated options + // TODO: Remove this block with the next major release. + { + i = 1; + while(i < argc) { + if(strcmp(argv[i], "-pidfile") == 0 && (i+1) < argc) { + strncpyz(pidfile, argv[i+1], FILENAME_MAX); + fprintf(stderr, "%s: deprecated option -- %s -- please use -P instead.\n", argv[0], argv[i]); + remove_option(i, &argc, argv); + } + else if(strcmp(argv[i], "-nodaemon") == 0 || strcmp(argv[i], "-nd") == 0) { + dont_fork = 1; + fprintf(stderr, "%s: deprecated option -- %s -- please use -D instead.\n ", argv[0], argv[i]); + remove_option(i, &argc, argv); + } + else if(strcmp(argv[i], "-ch") == 0 && (i+1) < argc) { + config_set("global", "host access prefix", argv[i+1]); + fprintf(stderr, "%s: deprecated option -- %s -- please use -s instead.\n", argv[0], argv[i]); + remove_option(i, &argc, argv); + } + else if(strcmp(argv[i], "-l") == 0 && (i+1) < argc) { + config_set("global", "history", argv[i+1]); + fprintf(stderr, "%s: deprecated option -- %s -- This option will be removed with V2.*.\n", argv[0], argv[i]); + remove_option(i, &argc, argv); + } + else i++; + } + } + + // parse options + { + int num_opts = sizeof(options) / sizeof(struct option_def); + char optstring[(num_opts * 2) + 1]; + + int string_i = 0; + for( i = 0; i < num_opts; i++ ) { + optstring[string_i] = options[i].val; + string_i++; + if(options[i].arg_name) { + optstring[string_i] = ':'; + string_i++; + } + } + + int opt; + while( (opt = getopt(argc, argv, optstring)) != -1 ) { + switch(opt) { + case 'c': + if(load_config(optarg, 1) != 1) { + error("Cannot load configuration file %s.", optarg); + exit(1); + } + else { + debug(D_OPTIONS, "Configuration loaded from %s.", optarg); + config_loaded = 1; + } + break; + case 'D': + dont_fork = 1; + break; + case 'h': + help(0); + break; + case 'i': + config_set("global", "bind to", optarg); + break; + case 'k': + dont_fork = 1; + check_config = 1; + break; + case 'P': + strncpy(pidfile, optarg, FILENAME_MAX); + pidfile[FILENAME_MAX] = '\0'; + break; + case 'p': + config_set("global", "default port", optarg); + break; + case 's': + config_set("global", "host access prefix", optarg); + break; + case 't': + config_set("global", "update every", optarg); + break; + case 'u': + config_set("global", "run as user", optarg); + break; + case 'v': + // TODO: Outsource version to makefile which can compute version from git. + printf("netdata 1.3.0\n"); + return 0; + case 'W': + { + char* stacksize_string = "stacksize="; + char* debug_flags_string = "debug_flags="; + if(strcmp(optarg, "unittest") == 0) { + rrd_update_every = 1; + if(run_all_mockup_tests()) exit(1); + if(unit_test_storage()) exit(1); + fprintf(stderr, "\n\nALL TESTS PASSED\n\n"); + exit(0); + } else if(strncmp(optarg, stacksize_string, strlen(stacksize_string)) == 0) { + optarg += strlen(stacksize_string); + config_set("global", "pthread stack size", optarg); + } else if(strncmp(optarg, debug_flags_string, strlen(debug_flags_string)) == 0) { + optarg += strlen(debug_flags_string); + config_set("global", "debug flags", optarg); + debug_flags = strtoull(optarg, NULL, 0); + } + } + break; + default: /* ? */ + help(1); + break; + } + } + } + + if(!config_loaded) + load_config(NULL, 0); + + { + char *config_dir = config_get("global", "config directory", CONFIG_DIR); + + // prepare configuration environment variables for the plugins + setenv("NETDATA_CONFIG_DIR" , verify_required_directory(config_dir) , 1); + setenv("NETDATA_PLUGINS_DIR", verify_required_directory(config_get("global", "plugins directory" , PLUGINS_DIR)), 1); + setenv("NETDATA_WEB_DIR" , verify_required_directory(config_get("global", "web files directory", WEB_DIR)) , 1); + setenv("NETDATA_CACHE_DIR" , verify_required_directory(config_get("global", "cache directory" , CACHE_DIR)) , 1); + setenv("NETDATA_LIB_DIR" , verify_required_directory(config_get("global", "lib directory" , VARLIB_DIR)) , 1); + setenv("NETDATA_LOG_DIR" , verify_required_directory(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); + + // disable buffering for python plugins + setenv("PYTHONUNBUFFERED", "1", 1); + + // avoid flood calls to stat(/etc/localtime) + // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux + setenv("TZ", ":/etc/localtime", 0); + + // work while we are cd into config_dir + // to allow the plugins refer to their config + // files using relative filenames + if(chdir(config_dir) == -1) + fatal("Cannot cd to '%s'", config_dir); + + char path[1024 + 1], *p = getenv("PATH"); + if(!p) p = "/bin:/usr/bin"; + snprintfz(path, 1024, "%s:%s", p, "/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin"); + setenv("PATH", config_get("plugins", "PATH environment variable", path), 1); + } + + char *user = NULL; + { + char *flags = config_get("global", "debug flags", "0x00000000"); + setenv("NETDATA_DEBUG_FLAGS", flags, 1); + + debug_flags = strtoull(flags, NULL, 0); + debug(D_OPTIONS, "Debug flags set to '0x%8llx'.", debug_flags); + + 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); + } + + // -------------------------------------------------------------------- #ifdef MADV_MERGEABLE - enable_ksm = config_get_boolean("global", "memory deduplication (ksm)", enable_ksm); + enable_ksm = config_get_boolean("global", "memory deduplication (ksm)", enable_ksm); #else #warning "Kernel memory deduplication (KSM) is not available" #endif - // -------------------------------------------------------------------- - - - global_host_prefix = config_get("global", "host access prefix", ""); - setenv("NETDATA_HOST_PREFIX", global_host_prefix, 1); - - // -------------------------------------------------------------------- - - output_log_file = config_get("global", "debug log", LOG_DIR "/debug.log"); - if(strcmp(output_log_file, "syslog") == 0) { - output_log_syslog = 1; - output_log_file = NULL; - } - else if(strcmp(output_log_file, "none") == 0) { - output_log_syslog = 0; - output_log_file = NULL; - } - else output_log_syslog = 0; - - // -------------------------------------------------------------------- - - error_log_file = config_get("global", "error log", LOG_DIR "/error.log"); - if(strcmp(error_log_file, "syslog") == 0) { - error_log_syslog = 1; - error_log_file = NULL; - } - else if(strcmp(error_log_file, "none") == 0) { - error_log_syslog = 0; - error_log_file = NULL; - // optimization - do not even generate debug log entries - } - else error_log_syslog = 0; - - error_log_throttle_period = config_get_number("global", "errors flood protection period", error_log_throttle_period); - setenv("NETDATA_ERRORS_THROTTLE_PERIOD", config_get("global", "errors flood protection period" , ""), 1); - - error_log_errors_per_period = config_get_number("global", "errors to trigger flood protection", error_log_errors_per_period); - setenv("NETDATA_ERRORS_PER_PERIOD" , config_get("global", "errors to trigger flood protection", ""), 1); - - // -------------------------------------------------------------------- - - access_log_file = config_get("global", "access log", LOG_DIR "/access.log"); - if(strcmp(access_log_file, "syslog") == 0) { - access_log_syslog = 1; - access_log_file = NULL; - } - else if(strcmp(access_log_file, "none") == 0) { - access_log_syslog = 0; - access_log_file = NULL; - } - else access_log_syslog = 0; - - // -------------------------------------------------------------------- - - rrd_memory_mode = rrd_memory_mode_id(config_get("global", "memory mode", rrd_memory_mode_name(rrd_memory_mode))); - - // -------------------------------------------------------------------- - - if(gethostname(buffer, HOSTNAME_MAX) == -1) - error("WARNING: Cannot get machine hostname."); - hostname = config_get("global", "hostname", buffer); - debug(D_OPTIONS, "hostname set to '%s'", hostname); - - // -------------------------------------------------------------------- - - rrd_default_history_entries = (int) config_get_number("global", "history", RRD_DEFAULT_HISTORY_ENTRIES); - if(rrd_default_history_entries < 5 || rrd_default_history_entries > RRD_HISTORY_ENTRIES_MAX) { - info("Invalid save lines %d given. Defaulting to %d.", rrd_default_history_entries, RRD_DEFAULT_HISTORY_ENTRIES); - rrd_default_history_entries = RRD_DEFAULT_HISTORY_ENTRIES; - } - else { - debug(D_OPTIONS, "save lines set to %d.", rrd_default_history_entries); - } - - // -------------------------------------------------------------------- - - rrd_update_every = (int) config_get_number("global", "update every", UPDATE_EVERY); - if(rrd_update_every < 1 || rrd_update_every > 600) { - info("Invalid update timer %d given. Defaulting to %d.", rrd_update_every, UPDATE_EVERY_MAX); - rrd_update_every = UPDATE_EVERY; - } - else debug(D_OPTIONS, "update timer set to %d.", rrd_update_every); - - // let the plugins know the min 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); - - i = pthread_attr_getstacksize(&attr, &stacksize); - if(i != 0) - fatal("pthread_attr_getstacksize() failed with code %d.", i); - else - debug(D_OPTIONS, "initial pthread stack size is %zu bytes", stacksize); - - wanted_stacksize = config_get_number("global", "pthread stack size", stacksize); - - // -------------------------------------------------------------------- - - for (i = 0; static_threads[i].name != NULL ; i++) { - struct netdata_static_thread *st = &static_threads[i]; - - if(st->config_name) st->enabled = config_get_boolean(st->config_section, st->config_name, st->enabled); - if(st->enabled && st->init_routine) st->init_routine(); - } - - // -------------------------------------------------------------------- - - // get the user we should run - // IMPORTANT: this is required before web_files_uid() - user = config_get("global", "run as user" , (getuid() == 0)?NETDATA_USER:""); - - // IMPORTANT: these have to run once, while single threaded - web_files_uid(); // IMPORTANT: web_files_uid() before web_files_gid() - web_files_gid(); - - // -------------------------------------------------------------------- - - listen_backlog = (int) config_get_number("global", "http port listen backlog", LISTEN_BACKLOG); - - listen_port = (int) config_get_number("global", "port", LISTEN_PORT); - if(listen_port < 1 || listen_port > 65535) { - info("Invalid listen port %d given. Defaulting to %d.", listen_port, LISTEN_PORT); - listen_port = LISTEN_PORT; - } - else debug(D_OPTIONS, "Listen port set to %d.", listen_port); - - int ip = 0; - char *ipv = config_get("global", "ip version", "any"); - if(!strcmp(ipv, "any") || !strcmp(ipv, "both") || !strcmp(ipv, "all")) ip = 0; - else if(!strcmp(ipv, "ipv4") || !strcmp(ipv, "IPV4") || !strcmp(ipv, "IPv4") || !strcmp(ipv, "4")) ip = 4; - else if(!strcmp(ipv, "ipv6") || !strcmp(ipv, "IPV6") || !strcmp(ipv, "IPv6") || !strcmp(ipv, "6")) ip = 6; - else info("Cannot understand ip version '%s'. Assuming 'any'.", ipv); - - if(ip == 0 || ip == 6) listen_fd = create_listen_socket6(config_get("global", "bind socket to IP", "*"), listen_port, listen_backlog); - if(listen_fd < 0) { - listen_fd = create_listen_socket4(config_get("global", "bind socket to IP", "*"), listen_port, listen_backlog); - if(listen_fd >= 0 && ip != 4) info("Managed to open an IPv4 socket on port %d.", listen_port); - } - - if(listen_fd < 0) fatal("Cannot listen socket."); - } - - // 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) - fatal("Cannot demonize myself."); + // -------------------------------------------------------------------- + + global_host_prefix = config_get("global", "host access prefix", ""); + setenv("NETDATA_HOST_PREFIX", global_host_prefix, 1); + + // -------------------------------------------------------------------- + + stdout_filename = config_get("global", "debug log", LOG_DIR "/debug.log"); + stderr_filename = config_get("global", "error log", LOG_DIR "/error.log"); + stdaccess_filename = config_get("global", "access log", LOG_DIR "/access.log"); + + error_log_throttle_period = config_get_number("global", "errors flood protection period", error_log_throttle_period); + setenv("NETDATA_ERRORS_THROTTLE_PERIOD", config_get("global", "errors flood protection period" , ""), 1); + + error_log_errors_per_period = (unsigned long)config_get_number("global", "errors to trigger flood protection", error_log_errors_per_period); + setenv("NETDATA_ERRORS_PER_PERIOD" , config_get("global", "errors to trigger flood protection", ""), 1); + + if(check_config) { + stdout_filename = stderr_filename = stdaccess_filename = "system"; + error_log_throttle_period = 0; + error_log_errors_per_period = 0; + } + + // -------------------------------------------------------------------- + + rrd_memory_mode = rrd_memory_mode_id(config_get("global", "memory mode", rrd_memory_mode_name(rrd_memory_mode))); + + // -------------------------------------------------------------------- + + { + char hostnamebuf[HOSTNAME_MAX + 1]; + if(gethostname(hostnamebuf, HOSTNAME_MAX) == -1) + error("WARNING: Cannot get machine hostname."); + hostname = config_get("global", "hostname", hostnamebuf); + debug(D_OPTIONS, "hostname set to '%s'", hostname); + setenv("NETDATA_HOSTNAME", hostname, 1); + } + + // -------------------------------------------------------------------- + + rrd_default_history_entries = (int) config_get_number("global", "history", RRD_DEFAULT_HISTORY_ENTRIES); + if(rrd_default_history_entries < 5 || rrd_default_history_entries > RRD_HISTORY_ENTRIES_MAX) { + info("Invalid save lines %d given. Defaulting to %d.", rrd_default_history_entries, RRD_DEFAULT_HISTORY_ENTRIES); + rrd_default_history_entries = RRD_DEFAULT_HISTORY_ENTRIES; + } + else { + debug(D_OPTIONS, "save lines set to %d.", rrd_default_history_entries); + } + + // -------------------------------------------------------------------- + + rrd_update_every = (int) config_get_number("global", "update every", UPDATE_EVERY); + if(rrd_update_every < 1 || rrd_update_every > 600) { + info("Invalid update timer %d given. Defaulting to %d.", rrd_update_every, UPDATE_EVERY_MAX); + rrd_update_every = UPDATE_EVERY; + } + else debug(D_OPTIONS, "update timer set to %d.", rrd_update_every); + + // let the plugins know the min update_every + { + char buf[16]; + snprintfz(buf, 15, "%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 + struct sigaction sa; + sa.sa_flags = 0; + + // ingore all signals while we run in a signal handler + sigfillset(&sa.sa_mask); + + // INFO: If we add signals here we have to unblock them + // at popen.c when running a external plugin. + + // Ignore SIGPIPE completely. + sa.sa_handler = SIG_IGN; + if(sigaction(SIGPIPE, &sa, NULL) == -1) + error("Failed to change signal handler for SIGPIPE"); + + sa.sa_handler = sig_handler_exit; + if(sigaction(SIGINT, &sa, NULL) == -1) + error("Failed to change signal handler for SIGINT"); + + sa.sa_handler = sig_handler_exit; + if(sigaction(SIGTERM, &sa, NULL) == -1) + error("Failed to change signal handler for SIGTERM"); + + sa.sa_handler = sig_handler_logrotate; + if(sigaction(SIGHUP, &sa, NULL) == -1) + error("Failed to change signal handler for SIGHUP"); + + // save database on SIGUSR1 + sa.sa_handler = sig_handler_save; + if(sigaction(SIGUSR1, &sa, NULL) == -1) + error("Failed to change signal handler for SIGUSR1"); + + // reload health configuration on SIGUSR2 + sa.sa_handler = sig_handler_reload_health; + if(sigaction(SIGUSR2, &sa, NULL) == -1) + error("Failed to change signal handler for SIGUSR2"); + + // -------------------------------------------------------------------- + + i = pthread_attr_init(&attr); + if(i != 0) + fatal("pthread_attr_init() failed with code %d.", i); + + i = pthread_attr_getstacksize(&attr, &stacksize); + if(i != 0) + fatal("pthread_attr_getstacksize() failed with code %d.", i); + else + debug(D_OPTIONS, "initial pthread stack size is %zu bytes", stacksize); + + wanted_stacksize = (size_t)config_get_number("global", "pthread stack size", (long)stacksize); + + // -------------------------------------------------------------------- + + for (i = 0; static_threads[i].name != NULL ; i++) { + struct netdata_static_thread *st = &static_threads[i]; + + if(st->config_name) st->enabled = config_get_boolean(st->config_section, st->config_name, st->enabled); + if(st->enabled && st->init_routine) st->init_routine(); + } + + // -------------------------------------------------------------------- + + // get the user we should run + // IMPORTANT: this is required before web_files_uid() + user = config_get("global", "run as user" , (getuid() == 0)?NETDATA_USER:""); + + // IMPORTANT: these have to run once, while single threaded + web_files_uid(); // IMPORTANT: web_files_uid() before web_files_gid() + web_files_gid(); + + // -------------------------------------------------------------------- + + if(!check_config) + create_listen_sockets(); + } + + // initialize the log files + open_all_log_files(); #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); - } + 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); + // fork, switch user, create pid file, set process priority + if(become_daemon(dont_fork, user) == -1) + fatal("Cannot demonize myself."); + + info("NetData started on pid %d", getpid()); + + + // ------------------------------------------------------------------------ + // get default pthread stack size + + if(stacksize < wanted_stacksize) { + i = pthread_attr_setstacksize(&attr, wanted_stacksize); + if(i != 0) + fatal("pthread_attr_setstacksize() to %zu bytes, failed with code %d.", wanted_stacksize, i); + else + info("Successfully set pthread stacksize to %zu bytes", wanted_stacksize); + } - info("NetData started on pid %d", getpid()); + // ------------------------------------------------------------------------ + // initialize the registry + registry_init(); - // ------------------------------------------------------------------------ - // get default pthread stack size + // ------------------------------------------------------------------------ + // initialize health monitoring - if(stacksize < wanted_stacksize) { - i = pthread_attr_setstacksize(&attr, wanted_stacksize); - if(i != 0) - fatal("pthread_attr_setstacksize() to %zu bytes, failed with code %d.", wanted_stacksize, i); - else - info("Successfully set pthread stacksize to %zu bytes", wanted_stacksize); - } + health_init(); - // -------------------------------------------------------------------- - // initialize the registry + if(check_config) + exit(1); - registry_init(); + // ------------------------------------------------------------------------ + // spawn the threads - // ------------------------------------------------------------------------ - // spawn the threads + web_server_threading_selection(); - for (i = 0; static_threads[i].name != NULL ; i++) { - struct netdata_static_thread *st = &static_threads[i]; + for (i = 0; static_threads[i].name != NULL ; i++) { + struct netdata_static_thread *st = &static_threads[i]; - if(st->enabled) { - st->thread = malloc(sizeof(pthread_t)); - if(!st->thread) - fatal("Cannot allocate pthread_t memory"); + if(st->enabled) { + st->thread = mallocz(sizeof(pthread_t)); - info("Starting thread %s.", st->name); + info("Starting thread %s.", st->name); - if(pthread_create(st->thread, &attr, st->start_routine, NULL)) - error("failed to create new thread for %s.", st->name); + if(pthread_create(st->thread, &attr, st->start_routine, NULL)) + error("failed to create new thread for %s.", st->name); - else if(pthread_detach(*st->thread)) - error("Cannot request detach of newly created %s thread.", st->name); - } - else info("Not starting thread %s.", st->name); - } + else if(pthread_detach(*st->thread)) + error("Cannot request detach of newly created %s thread.", st->name); + } + else info("Not starting thread %s.", st->name); + } - // ------------------------------------------------------------------------ - // block signals while initializing threads. - sigset_t sigset; - sigfillset(&sigset); + // ------------------------------------------------------------------------ + // block signals while initializing threads. + sigset_t sigset; + sigfillset(&sigset); - if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) { - error("Could not unblock signals for threads"); - } + if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) { + error("Could not unblock signals for threads"); + } - // 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); - } - } + // 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 d9edda58e..646827fbd 100644 --- a/src/main.h +++ b/src/main.h @@ -1,12 +1,31 @@ #ifndef NETDATA_MAIN_H #define NETDATA_MAIN_H 1 -#include <signal.h> - extern volatile sig_atomic_t netdata_exit; +/** + * This struct contains information about command line options. + */ +struct option_def { + /** The option character */ + const char val; + /** The name of the long option. */ + const char *description; + /** Short descripton what the option does */ + /** Name of the argument displayed in SYNOPSIS */ + const char *arg_name; + /** Default value if not set */ + const char *default_value; +}; + +/** + * List of command line options. + * This can be used to compute manpage, help messages, ect. + */ +extern struct option_def options[]; + extern void kill_childs(void); extern int killpid(pid_t pid, int signal); -extern void netdata_cleanup_and_exit(int ret); +extern void netdata_cleanup_and_exit(int ret) __attribute__ ((noreturn)); #endif /* NETDATA_MAIN_H */ diff --git a/src/plugin_checks.c b/src/plugin_checks.c index 379fb9a84..007d6565f 100644 --- a/src/plugin_checks.c +++ b/src/plugin_checks.c @@ -1,97 +1,84 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <pthread.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <strings.h> -#include <unistd.h> - #include "common.h" -#include "appconfig.h" -#include "log.h" -#include "rrd.h" -#include "plugin_checks.h" void *checks_main(void *ptr) { - if(ptr) { ; } + if(ptr) { ; } - info("CHECKS thread created with task id %d", gettid()); + info("CHECKS 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_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."); + if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) + error("Cannot set pthread cancel state to ENABLE."); - unsigned long long usec = 0, susec = rrd_update_every * 1000000ULL, loop_usec = 0, total_susec = 0; - struct timeval now, last, loop; + unsigned long long usec = 0, susec = rrd_update_every * 1000000ULL, loop_usec = 0, total_susec = 0; + struct timeval now, last, loop; - RRDSET *check1, *check2, *check3, *apps_cpu = NULL; + RRDSET *check1, *check2, *check3, *apps_cpu = NULL; - check1 = rrdset_create("netdata", "check1", NULL, "netdata", NULL, "Caller gives microseconds", "a million !", 99999, rrd_update_every, RRDSET_TYPE_LINE); - rrddim_add(check1, "absolute", NULL, -1, 1, RRDDIM_ABSOLUTE); - rrddim_add(check1, "incremental", NULL, 1, 1, RRDDIM_INCREMENTAL); + check1 = rrdset_create("netdata", "check1", NULL, "netdata", NULL, "Caller gives microseconds", "a million !", 99999, rrd_update_every, RRDSET_TYPE_LINE); + rrddim_add(check1, "absolute", NULL, -1, 1, RRDDIM_ABSOLUTE); + rrddim_add(check1, "incremental", NULL, 1, 1, RRDDIM_INCREMENTAL); - check2 = rrdset_create("netdata", "check2", NULL, "netdata", NULL, "Netdata calcs microseconds", "a million !", 99999, rrd_update_every, RRDSET_TYPE_LINE); - rrddim_add(check2, "absolute", NULL, -1, 1, RRDDIM_ABSOLUTE); - rrddim_add(check2, "incremental", NULL, 1, 1, RRDDIM_INCREMENTAL); + check2 = rrdset_create("netdata", "check2", NULL, "netdata", NULL, "Netdata calcs microseconds", "a million !", 99999, rrd_update_every, RRDSET_TYPE_LINE); + rrddim_add(check2, "absolute", NULL, -1, 1, RRDDIM_ABSOLUTE); + rrddim_add(check2, "incremental", NULL, 1, 1, RRDDIM_INCREMENTAL); - check3 = rrdset_create("netdata", "checkdt", NULL, "netdata", NULL, "Clock difference", "microseconds diff", 99999, rrd_update_every, RRDSET_TYPE_LINE); - rrddim_add(check3, "caller", NULL, 1, 1, RRDDIM_ABSOLUTE); - rrddim_add(check3, "netdata", NULL, 1, 1, RRDDIM_ABSOLUTE); - rrddim_add(check3, "apps.plugin", NULL, 1, 1, RRDDIM_ABSOLUTE); + check3 = rrdset_create("netdata", "checkdt", NULL, "netdata", NULL, "Clock difference", "microseconds diff", 99999, rrd_update_every, RRDSET_TYPE_LINE); + rrddim_add(check3, "caller", NULL, 1, 1, RRDDIM_ABSOLUTE); + rrddim_add(check3, "netdata", NULL, 1, 1, RRDDIM_ABSOLUTE); + rrddim_add(check3, "apps.plugin", NULL, 1, 1, RRDDIM_ABSOLUTE); - gettimeofday(&last, NULL); - while(1) { - usleep(susec); + gettimeofday(&last, NULL); + while(1) { + usleep(susec); - // find the time to sleep in order to wait exactly update_every seconds - gettimeofday(&now, NULL); - loop_usec = usecdiff(&now, &last); - usec = loop_usec - susec; - debug(D_PROCNETDEV_LOOP, "CHECK: last loop took %llu usec (worked for %llu, sleeped for %llu).", loop_usec, usec, susec); + // find the time to sleep in order to wait exactly update_every seconds + gettimeofday(&now, NULL); + loop_usec = usec_dt(&now, &last); + usec = loop_usec - susec; + debug(D_PROCNETDEV_LOOP, "CHECK: last loop took %llu usec (worked for %llu, sleeped for %llu).", loop_usec, usec, susec); - if(usec < (rrd_update_every * 1000000ULL / 2ULL)) susec = (rrd_update_every * 1000000ULL) - usec; - else susec = rrd_update_every * 1000000ULL / 2ULL; + if(usec < (rrd_update_every * 1000000ULL / 2ULL)) susec = (rrd_update_every * 1000000ULL) - usec; + else susec = rrd_update_every * 1000000ULL / 2ULL; - // -------------------------------------------------------------------- - // Calculate loop time + // -------------------------------------------------------------------- + // Calculate loop time - last.tv_sec = now.tv_sec; - last.tv_usec = now.tv_usec; - total_susec += loop_usec; + last.tv_sec = now.tv_sec; + last.tv_usec = now.tv_usec; + total_susec += loop_usec; - // -------------------------------------------------------------------- - // check chart 1 + // -------------------------------------------------------------------- + // check chart 1 - if(check1->counter_done) rrdset_next_usec(check1, loop_usec); - rrddim_set(check1, "absolute", 1000000); - rrddim_set(check1, "incremental", total_susec); - rrdset_done(check1); + if(check1->counter_done) rrdset_next_usec(check1, loop_usec); + rrddim_set(check1, "absolute", 1000000); + rrddim_set(check1, "incremental", total_susec); + rrdset_done(check1); - // -------------------------------------------------------------------- - // check chart 2 + // -------------------------------------------------------------------- + // check chart 2 - if(check2->counter_done) rrdset_next(check2); - rrddim_set(check2, "absolute", 1000000); - rrddim_set(check2, "incremental", total_susec); - rrdset_done(check2); + if(check2->counter_done) rrdset_next(check2); + rrddim_set(check2, "absolute", 1000000); + rrddim_set(check2, "incremental", total_susec); + rrdset_done(check2); - // -------------------------------------------------------------------- - // check chart 3 + // -------------------------------------------------------------------- + // check chart 3 - if(!apps_cpu) apps_cpu = rrdset_find("apps.cpu"); - if(check3->counter_done) rrdset_next_usec(check3, loop_usec); - gettimeofday(&loop, NULL); - rrddim_set(check3, "caller", (long long)usecdiff(&loop, &check1->last_collected_time)); - rrddim_set(check3, "netdata", (long long)usecdiff(&loop, &check2->last_collected_time)); - if(apps_cpu) rrddim_set(check3, "apps.plugin", (long long)usecdiff(&loop, &apps_cpu->last_collected_time)); - rrdset_done(check3); - } + if(!apps_cpu) apps_cpu = rrdset_find("apps.cpu"); + if(check3->counter_done) rrdset_next_usec(check3, loop_usec); + gettimeofday(&loop, NULL); + rrddim_set(check3, "caller", (long long) usec_dt(&loop, &check1->last_collected_time)); + rrddim_set(check3, "netdata", (long long) usec_dt(&loop, &check2->last_collected_time)); + if(apps_cpu) rrddim_set(check3, "apps.plugin", (long long) usec_dt(&loop, &apps_cpu->last_collected_time)); + rrdset_done(check3); + } - pthread_exit(NULL); - return NULL; + pthread_exit(NULL); + return NULL; } diff --git a/src/plugin_idlejitter.c b/src/plugin_idlejitter.c index 56c22a160..30c6d8708 100644 --- a/src/plugin_idlejitter.c +++ b/src/plugin_idlejitter.c @@ -1,68 +1,54 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <pthread.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <strings.h> -#include <unistd.h> - -#include "global_statistics.h" #include "common.h" -#include "appconfig.h" -#include "log.h" -#include "rrd.h" -#include "plugin_idlejitter.h" #define CPU_IDLEJITTER_SLEEP_TIME_MS 20 void *cpuidlejitter_main(void *ptr) { - if(ptr) { ; } + if(ptr) { ; } - info("CPU Idle Jitter thread created with task id %d", gettid()); + info("CPU Idle Jitter 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_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."); + if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) + error("Cannot set pthread cancel state to ENABLE."); - int sleep_ms = (int) config_get_number("plugin:idlejitter", "loop time in ms", CPU_IDLEJITTER_SLEEP_TIME_MS); - if(sleep_ms <= 0) { - config_set_number("plugin:idlejitter", "loop time in ms", CPU_IDLEJITTER_SLEEP_TIME_MS); - sleep_ms = CPU_IDLEJITTER_SLEEP_TIME_MS; - } + int sleep_ms = (int) config_get_number("plugin:idlejitter", "loop time in ms", CPU_IDLEJITTER_SLEEP_TIME_MS); + if(sleep_ms <= 0) { + config_set_number("plugin:idlejitter", "loop time in ms", CPU_IDLEJITTER_SLEEP_TIME_MS); + sleep_ms = CPU_IDLEJITTER_SLEEP_TIME_MS; + } - RRDSET *st = rrdset_find("system.idlejitter"); - if(!st) { - st = rrdset_create("system", "idlejitter", NULL, "processes", NULL, "CPU Idle Jitter", "microseconds lost/s", 9999, rrd_update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "jitter", NULL, 1, 1, RRDDIM_ABSOLUTE); - } + RRDSET *st = rrdset_find("system.idlejitter"); + if(!st) { + st = rrdset_create("system", "idlejitter", NULL, "processes", NULL, "CPU Idle Jitter", "microseconds lost/s", 9999, rrd_update_every, RRDSET_TYPE_LINE); + rrddim_add(st, "jitter", NULL, 1, 1, RRDDIM_ABSOLUTE); + } - struct timeval before, after; - unsigned long long counter; - for(counter = 0; 1 ;counter++) { - unsigned long long usec = 0, susec = 0; + struct timeval before, after; + unsigned long long counter; + for(counter = 0; 1 ;counter++) { + unsigned long long usec = 0, susec = 0; - while(susec < (rrd_update_every * 1000000ULL)) { + while(susec < (rrd_update_every * 1000000ULL)) { - gettimeofday(&before, NULL); - usleep(sleep_ms * 1000); - gettimeofday(&after, NULL); + gettimeofday(&before, NULL); + usleep(sleep_ms * 1000); + gettimeofday(&after, NULL); - // calculate the time it took for a full loop - usec = usecdiff(&after, &before); - susec += usec; - } - usec -= (sleep_ms * 1000); + // calculate the time it took for a full loop + usec = usec_dt(&after, &before); + susec += usec; + } + usec -= (sleep_ms * 1000); - if(counter) rrdset_next(st); - rrddim_set(st, "jitter", usec); - rrdset_done(st); - } + if(counter) rrdset_next(st); + rrddim_set(st, "jitter", usec); + rrdset_done(st); + } - pthread_exit(NULL); - return NULL; + pthread_exit(NULL); + return NULL; } diff --git a/src/plugin_nfacct.c b/src/plugin_nfacct.c index 6cde66e0c..b2396fac1 100644 --- a/src/plugin_nfacct.c +++ b/src/plugin_nfacct.c @@ -1,222 +1,202 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#ifdef INTERNAL_PLUGIN_NFACCT -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <dirent.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> -#include <time.h> -#include <errno.h> +#include "common.h" +#ifdef INTERNAL_PLUGIN_NFACCT #include <libmnl/libmnl.h> #include <libnetfilter_acct/libnetfilter_acct.h> -#include "main.h" -#include "global_statistics.h" -#include "common.h" -#include "appconfig.h" -#include "log.h" -#include "rrd.h" -#include "plugin_proc.h" - struct mynfacct { - const char *name; - uint64_t pkts; - uint64_t bytes; - struct nfacct *nfacct; + const char *name; + uint64_t pkts; + uint64_t bytes; + struct nfacct *nfacct; }; struct nfacct_list { - int size; - int len; - struct mynfacct data[]; + int size; + int len; + struct mynfacct data[]; } *nfacct_list = NULL; static int nfacct_callback(const struct nlmsghdr *nlh, void *data) { - if(data) {}; - - if(!nfacct_list || nfacct_list->len == nfacct_list->size) { - int size = (nfacct_list) ? nfacct_list->size : 0; - int len = (nfacct_list) ? nfacct_list->len : 0; - size++; - - info("nfacct.plugin: increasing nfacct_list to size %d", size); - - nfacct_list = realloc(nfacct_list, sizeof(struct nfacct_list) + (sizeof(struct mynfacct) * size)); - if(!nfacct_list) { - error("nfacct.plugin: cannot allocate nfacct_list."); - return MNL_CB_OK; - } - - nfacct_list->data[len].nfacct = nfacct_alloc(); - if(!nfacct_list->data[size - 1].nfacct) { - error("nfacct.plugin: nfacct_alloc() failed."); - free(nfacct_list); - nfacct_list = NULL; - return MNL_CB_OK; - } - - nfacct_list->size = size; - nfacct_list->len = len; - } - - if(nfacct_nlmsg_parse_payload(nlh, nfacct_list->data[nfacct_list->len].nfacct) < 0) { - error("nfacct.plugin: nfacct_nlmsg_parse_payload() failed."); - return MNL_CB_OK; - } - - nfacct_list->data[nfacct_list->len].name = nfacct_attr_get_str(nfacct_list->data[nfacct_list->len].nfacct, NFACCT_ATTR_NAME); - nfacct_list->data[nfacct_list->len].pkts = nfacct_attr_get_u64(nfacct_list->data[nfacct_list->len].nfacct, NFACCT_ATTR_PKTS); - nfacct_list->data[nfacct_list->len].bytes = nfacct_attr_get_u64(nfacct_list->data[nfacct_list->len].nfacct, NFACCT_ATTR_BYTES); - - nfacct_list->len++; - return MNL_CB_OK; + if(data) {}; + + if(!nfacct_list || nfacct_list->len == nfacct_list->size) { + int size = (nfacct_list) ? nfacct_list->size : 0; + int len = (nfacct_list) ? nfacct_list->len : 0; + size++; + + info("nfacct.plugin: increasing nfacct_list to size %d", size); + + nfacct_list = realloc(nfacct_list, sizeof(struct nfacct_list) + (sizeof(struct mynfacct) * size)); + if(!nfacct_list) { + error("nfacct.plugin: cannot allocate nfacct_list."); + return MNL_CB_OK; + } + + nfacct_list->data[len].nfacct = nfacct_alloc(); + if(!nfacct_list->data[size - 1].nfacct) { + error("nfacct.plugin: nfacct_alloc() failed."); + free(nfacct_list); + nfacct_list = NULL; + return MNL_CB_OK; + } + + nfacct_list->size = size; + nfacct_list->len = len; + } + + if(nfacct_nlmsg_parse_payload(nlh, nfacct_list->data[nfacct_list->len].nfacct) < 0) { + error("nfacct.plugin: nfacct_nlmsg_parse_payload() failed."); + return MNL_CB_OK; + } + + nfacct_list->data[nfacct_list->len].name = nfacct_attr_get_str(nfacct_list->data[nfacct_list->len].nfacct, NFACCT_ATTR_NAME); + nfacct_list->data[nfacct_list->len].pkts = nfacct_attr_get_u64(nfacct_list->data[nfacct_list->len].nfacct, NFACCT_ATTR_PKTS); + nfacct_list->data[nfacct_list->len].bytes = nfacct_attr_get_u64(nfacct_list->data[nfacct_list->len].nfacct, NFACCT_ATTR_BYTES); + + nfacct_list->len++; + return MNL_CB_OK; } void *nfacct_main(void *ptr) { - if(ptr) { ; } + if(ptr) { ; } - info("NFACCT thread created with task id %d", gettid()); + info("NFACCT thread created with task id %d", gettid()); - if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0) - error("nfacct.plugin: Cannot set pthread cancel type to DEFERRED."); + if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0) + error("nfacct.plugin: Cannot set pthread cancel type to DEFERRED."); - if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) - error("nfacct.plugin: Cannot set pthread cancel state to ENABLE."); + if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) + error("nfacct.plugin: Cannot set pthread cancel state to ENABLE."); - char buf[MNL_SOCKET_BUFFER_SIZE]; - struct mnl_socket *nl = NULL; - struct nlmsghdr *nlh = NULL; - unsigned int seq = 0, portid = 0; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct mnl_socket *nl = NULL; + struct nlmsghdr *nlh = NULL; + unsigned int seq = 0, portid = 0; - seq = time(NULL) - 1; + seq = time(NULL) - 1; - nl = mnl_socket_open(NETLINK_NETFILTER); - if(!nl) { - error("nfacct.plugin: mnl_socket_open() failed"); - pthread_exit(NULL); - return NULL; - } + nl = mnl_socket_open(NETLINK_NETFILTER); + if(!nl) { + error("nfacct.plugin: mnl_socket_open() failed"); + pthread_exit(NULL); + return NULL; + } - if(mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { - mnl_socket_close(nl); - error("nfacct.plugin: mnl_socket_bind() failed"); - pthread_exit(NULL); - return NULL; - } - portid = mnl_socket_get_portid(nl); + if(mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + mnl_socket_close(nl); + error("nfacct.plugin: mnl_socket_bind() failed"); + pthread_exit(NULL); + return NULL; + } + portid = mnl_socket_get_portid(nl); - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ - struct timeval last, now; - unsigned long long usec = 0, susec = 0; - RRDSET *st = NULL; + struct timeval last, now; + unsigned long long usec = 0, susec = 0; + RRDSET *st = NULL; - gettimeofday(&last, NULL); + gettimeofday(&last, NULL); - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ - while(1) { - if(unlikely(netdata_exit)) break; + while(1) { + if(unlikely(netdata_exit)) break; - seq++; + seq++; - nlh = nfacct_nlmsg_build_hdr(buf, NFNL_MSG_ACCT_GET, NLM_F_DUMP, seq); - if(!nlh) { - mnl_socket_close(nl); - error("nfacct.plugin: nfacct_nlmsg_build_hdr() failed"); - pthread_exit(NULL); - return NULL; - } + nlh = nfacct_nlmsg_build_hdr(buf, NFNL_MSG_ACCT_GET, NLM_F_DUMP, seq); + if(!nlh) { + mnl_socket_close(nl); + error("nfacct.plugin: nfacct_nlmsg_build_hdr() failed"); + pthread_exit(NULL); + return NULL; + } - if(mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { - error("nfacct.plugin: mnl_socket_send"); - pthread_exit(NULL); - return NULL; - } + if(mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + error("nfacct.plugin: mnl_socket_send"); + pthread_exit(NULL); + return NULL; + } - if(nfacct_list) nfacct_list->len = 0; + if(nfacct_list) nfacct_list->len = 0; - int ret; - while((ret = mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0) { - if((ret = mnl_cb_run(buf, ret, seq, portid, nfacct_callback, NULL)) <= 0) break; - } + int ret; + while((ret = mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0) { + if((ret = mnl_cb_run(buf, ret, seq, portid, nfacct_callback, NULL)) <= 0) break; + } - if (ret == -1) { - error("nfacct.plugin: error communicating with kernel."); - pthread_exit(NULL); - return NULL; - } + if (ret == -1) { + error("nfacct.plugin: error communicating with kernel."); + pthread_exit(NULL); + return NULL; + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - gettimeofday(&now, NULL); - usec = usecdiff(&now, &last) - susec; - debug(D_NFACCT_LOOP, "nfacct.plugin: last loop took %llu usec (worked for %llu, sleeped for %llu).", usec + susec, usec, susec); + gettimeofday(&now, NULL); + usec = usec_dt(&now, &last) - susec; + debug(D_NFACCT_LOOP, "nfacct.plugin: last loop took %llu usec (worked for %llu, sleeped for %llu).", usec + susec, usec, susec); - if(usec < (rrd_update_every * 1000000ULL / 2ULL)) susec = (rrd_update_every * 1000000ULL) - usec; - else susec = rrd_update_every * 1000000ULL / 2ULL; + if(usec < (rrd_update_every * 1000000ULL / 2ULL)) susec = (rrd_update_every * 1000000ULL) - usec; + else susec = rrd_update_every * 1000000ULL / 2ULL; - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(nfacct_list && nfacct_list->len) { - int i; + if(nfacct_list && nfacct_list->len) { + int i; - st = rrdset_find_bytype("netfilter", "nfacct_packets"); - if(!st) { - st = rrdset_create("netfilter", "nfacct_packets", NULL, "nfacct", NULL, "Netfilter Accounting Packets", "packets/s", 1006, rrd_update_every, RRDSET_TYPE_STACKED); + st = rrdset_find_bytype("netfilter", "nfacct_packets"); + if(!st) { + st = rrdset_create("netfilter", "nfacct_packets", NULL, "nfacct", NULL, "Netfilter Accounting Packets", "packets/s", 1006, rrd_update_every, RRDSET_TYPE_STACKED); - for(i = 0; i < nfacct_list->len ; i++) - rrddim_add(st, nfacct_list->data[i].name, NULL, 1, rrd_update_every, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + for(i = 0; i < nfacct_list->len ; i++) + rrddim_add(st, nfacct_list->data[i].name, NULL, 1, rrd_update_every, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - for(i = 0; i < nfacct_list->len ; i++) { - RRDDIM *rd = rrddim_find(st, nfacct_list->data[i].name); + for(i = 0; i < nfacct_list->len ; i++) { + RRDDIM *rd = rrddim_find(st, nfacct_list->data[i].name); - if(!rd) rd = rrddim_add(st, nfacct_list->data[i].name, NULL, 1, rrd_update_every, RRDDIM_INCREMENTAL); - if(rd) rrddim_set_by_pointer(st, rd, nfacct_list->data[i].pkts); - } + if(!rd) rd = rrddim_add(st, nfacct_list->data[i].name, NULL, 1, rrd_update_every, RRDDIM_INCREMENTAL); + if(rd) rrddim_set_by_pointer(st, rd, nfacct_list->data[i].pkts); + } - rrdset_done(st); + rrdset_done(st); - // ---------------------------------------------------------------- + // ---------------------------------------------------------------- - st = rrdset_find_bytype("netfilter", "nfacct_bytes"); - if(!st) { - st = rrdset_create("netfilter", "nfacct_bytes", NULL, "nfacct", NULL, "Netfilter Accounting Bandwidth", "kilobytes/s", 1007, rrd_update_every, RRDSET_TYPE_STACKED); + st = rrdset_find_bytype("netfilter", "nfacct_bytes"); + if(!st) { + st = rrdset_create("netfilter", "nfacct_bytes", NULL, "nfacct", NULL, "Netfilter Accounting Bandwidth", "kilobytes/s", 1007, rrd_update_every, RRDSET_TYPE_STACKED); - for(i = 0; i < nfacct_list->len ; i++) - rrddim_add(st, nfacct_list->data[i].name, NULL, 1, 1000 * rrd_update_every, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + for(i = 0; i < nfacct_list->len ; i++) + rrddim_add(st, nfacct_list->data[i].name, NULL, 1, 1000 * rrd_update_every, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - for(i = 0; i < nfacct_list->len ; i++) { - RRDDIM *rd = rrddim_find(st, nfacct_list->data[i].name); + for(i = 0; i < nfacct_list->len ; i++) { + RRDDIM *rd = rrddim_find(st, nfacct_list->data[i].name); - if(!rd) rd = rrddim_add(st, nfacct_list->data[i].name, NULL, 1, 1000 * rrd_update_every, RRDDIM_INCREMENTAL); - if(rd) rrddim_set_by_pointer(st, rd, nfacct_list->data[i].bytes); - } + if(!rd) rd = rrddim_add(st, nfacct_list->data[i].name, NULL, 1, 1000 * rrd_update_every, RRDDIM_INCREMENTAL); + if(rd) rrddim_set_by_pointer(st, rd, nfacct_list->data[i].bytes); + } - rrdset_done(st); - } + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - usleep(susec); + usleep(susec); - // copy current to last - bcopy(&now, &last, sizeof(struct timeval)); - } + // copy current to last + bcopy(&now, &last, sizeof(struct timeval)); + } - mnl_socket_close(nl); - pthread_exit(NULL); - return NULL; + mnl_socket_close(nl); + pthread_exit(NULL); + return NULL; } #endif diff --git a/src/plugin_proc.c b/src/plugin_proc.c index a147d971f..a1bf314de 100644 --- a/src/plugin_proc.c +++ b/src/plugin_proc.c @@ -1,319 +1,226 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <pthread.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <strings.h> -#include <unistd.h> - -#include "global_statistics.h" #include "common.h" -#include "appconfig.h" -#include "log.h" -#include "rrd.h" -#include "plugin_proc.h" -#include "main.h" -#include "registry.h" void *proc_main(void *ptr) { - if(ptr) { ; } - - info("PROC 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 me, thread; - - // disable (by default) various interface that are not needed - config_get_boolean("plugin:proc:/proc/net/dev:lo", "enabled", 0); - config_get_boolean("plugin:proc:/proc/net/dev:fireqos_monitor", "enabled", 0); - - // when ZERO, attempt to do it - int vdo_proc_net_dev = !config_get_boolean("plugin:proc", "/proc/net/dev", 1); - int vdo_proc_diskstats = !config_get_boolean("plugin:proc", "/proc/diskstats", 1); - int vdo_proc_net_snmp = !config_get_boolean("plugin:proc", "/proc/net/snmp", 1); - int vdo_proc_net_snmp6 = !config_get_boolean("plugin:proc", "/proc/net/snmp6", 1); - int vdo_proc_net_netstat = !config_get_boolean("plugin:proc", "/proc/net/netstat", 1); - int vdo_proc_net_stat_conntrack = !config_get_boolean("plugin:proc", "/proc/net/stat/conntrack", 1); - int vdo_proc_net_ip_vs_stats = !config_get_boolean("plugin:proc", "/proc/net/ip_vs/stats", 1); - int vdo_proc_net_stat_synproxy = !config_get_boolean("plugin:proc", "/proc/net/stat/synproxy", 1); - int vdo_proc_stat = !config_get_boolean("plugin:proc", "/proc/stat", 1); - int vdo_proc_meminfo = !config_get_boolean("plugin:proc", "/proc/meminfo", 1); - int vdo_proc_vmstat = !config_get_boolean("plugin:proc", "/proc/vmstat", 1); - int vdo_proc_net_rpc_nfsd = !config_get_boolean("plugin:proc", "/proc/net/rpc/nfsd", 1); - int vdo_proc_sys_kernel_random_entropy_avail = !config_get_boolean("plugin:proc", "/proc/sys/kernel/random/entropy_avail", 1); - int vdo_proc_interrupts = !config_get_boolean("plugin:proc", "/proc/interrupts", 1); - int vdo_proc_softirqs = !config_get_boolean("plugin:proc", "/proc/softirqs", 1); - int vdo_proc_loadavg = !config_get_boolean("plugin:proc", "/proc/loadavg", 1); - int vdo_sys_kernel_mm_ksm = !config_get_boolean("plugin:proc", "/sys/kernel/mm/ksm", 1); - int vdo_cpu_netdata = !config_get_boolean("plugin:proc", "netdata server resources", 1); - - // keep track of the time each module was called - unsigned long long sutime_proc_net_dev = 0ULL; - unsigned long long sutime_proc_diskstats = 0ULL; - unsigned long long sutime_proc_net_snmp = 0ULL; - unsigned long long sutime_proc_net_snmp6 = 0ULL; - unsigned long long sutime_proc_net_netstat = 0ULL; - unsigned long long sutime_proc_net_stat_conntrack = 0ULL; - unsigned long long sutime_proc_net_ip_vs_stats = 0ULL; - unsigned long long sutime_proc_net_stat_synproxy = 0ULL; - unsigned long long sutime_proc_stat = 0ULL; - unsigned long long sutime_proc_meminfo = 0ULL; - unsigned long long sutime_proc_vmstat = 0ULL; - unsigned long long sutime_proc_net_rpc_nfsd = 0ULL; - unsigned long long sutime_proc_sys_kernel_random_entropy_avail = 0ULL; - unsigned long long sutime_proc_interrupts = 0ULL; - unsigned long long sutime_proc_softirqs = 0ULL; - unsigned long long sutime_proc_loadavg = 0ULL; - unsigned long long sutime_sys_kernel_mm_ksm = 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 = NULL, *stcpu_thread = NULL, *stclients = NULL, *streqs = NULL, *stbytes = 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_kernel_mm_ksm) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_kernel_mm_ksm()."); - - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_loadavg) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_loadavg()."); - sunow = timems(); - vdo_proc_loadavg = do_proc_loadavg(rrd_update_every, (sutime_proc_loadavg > 0)?sunow - sutime_proc_loadavg:0ULL); - sutime_proc_loadavg = sunow; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_interrupts) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_interrupts()."); - sunow = timems(); - vdo_proc_interrupts = do_proc_interrupts(rrd_update_every, (sutime_proc_interrupts > 0)?sunow - sutime_proc_interrupts:0ULL); - sutime_proc_interrupts = sunow; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_softirqs) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_softirqs()."); - sunow = timems(); - vdo_proc_softirqs = do_proc_softirqs(rrd_update_every, (sutime_proc_softirqs > 0)?sunow - sutime_proc_softirqs:0ULL); - sutime_proc_softirqs = sunow; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_sys_kernel_random_entropy_avail) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_sys_kernel_random_entropy_avail()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_dev) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_dev()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_diskstats) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_diskstats()."); - sunow = timems(); - vdo_proc_diskstats = do_proc_diskstats(rrd_update_every, (sutime_proc_diskstats > 0)?sunow - sutime_proc_diskstats:0ULL); - sutime_proc_diskstats = sunow; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_snmp) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_snmp6) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp6()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_netstat) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_netstat()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_stat_conntrack) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_stat_conntrack()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_ip_vs_stats) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_ip_vs_stats()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_stat_synproxy) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_stat_synproxy()."); - 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; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_stat) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_stat()."); - sunow = timems(); - vdo_proc_stat = do_proc_stat(rrd_update_every, (sutime_proc_stat > 0)?sunow - sutime_proc_stat:0ULL); - sutime_proc_stat = sunow; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_meminfo) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_meminfo()."); - sunow = timems(); - vdo_proc_meminfo = do_proc_meminfo(rrd_update_every, (sutime_proc_meminfo > 0)?sunow - sutime_proc_meminfo:0ULL); - sutime_proc_meminfo = sunow; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_vmstat) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_vmstat()."); - sunow = timems(); - vdo_proc_vmstat = do_proc_vmstat(rrd_update_every, (sutime_proc_vmstat > 0)?sunow - sutime_proc_vmstat:0ULL); - sutime_proc_vmstat = sunow; - } - if(unlikely(netdata_exit)) break; - - if(!vdo_proc_net_rpc_nfsd) { - debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_rpc_nfsd()."); - 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; - } - if(unlikely(netdata_exit)) break; - - // END -- the job is done - - // -------------------------------------------------------------------- - - if(!vdo_cpu_netdata) { - getrusage(RUSAGE_THREAD, &thread); - getrusage(RUSAGE_SELF, &me); - - 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", 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); - - // ---------------------------------------------------------------- - - if(!stcpu) stcpu = rrdset_find("netdata.server_cpu"); - if(!stcpu) { - stcpu = rrdset_create("netdata", "server_cpu", NULL, "netdata", NULL, "NetData CPU usage", "milliseconds/s", 130000, rrd_update_every, RRDSET_TYPE_STACKED); - - rrddim_add(stcpu, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL); - rrddim_add(stcpu, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL); - } - else rrdset_next(stcpu); - - rrddim_set(stcpu, "user" , me.ru_utime.tv_sec * 1000000ULL + me.ru_utime.tv_usec); - rrddim_set(stcpu, "system", me.ru_stime.tv_sec * 1000000ULL + me.ru_stime.tv_usec); - rrdset_done(stcpu); - - // ---------------------------------------------------------------- - - if(!stclients) stclients = rrdset_find("netdata.clients"); - if(!stclients) { - 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); - } - else rrdset_next(stclients); - - rrddim_set(stclients, "clients", global_statistics.connected_clients); - rrdset_done(stclients); - - // ---------------------------------------------------------------- - - if(!streqs) streqs = rrdset_find("netdata.requests"); - if(!streqs) { - 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); - } - else rrdset_next(streqs); - - rrddim_set(streqs, "requests", global_statistics.web_requests); - rrdset_done(streqs); - - // ---------------------------------------------------------------- - - if(!stbytes) stbytes = rrdset_find("netdata.net"); - if(!stbytes) { - 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); - } - else rrdset_next(stbytes); - - rrddim_set(stbytes, "in", global_statistics.bytes_received); - rrddim_set(stbytes, "out", global_statistics.bytes_sent); - rrdset_done(stbytes); - - // ---------------------------------------------------------------- - - registry_statistics(); - } - } - - pthread_exit(NULL); - return NULL; + (void)ptr; + + info("PROC 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."); + + // disable (by default) various interface that are not needed + config_get_boolean("plugin:proc:/proc/net/dev:lo", "enabled", 0); + config_get_boolean("plugin:proc:/proc/net/dev:fireqos_monitor", "enabled", 0); + + // when ZERO, attempt to do it + int vdo_proc_net_dev = !config_get_boolean("plugin:proc", "/proc/net/dev", 1); + int vdo_proc_diskstats = !config_get_boolean("plugin:proc", "/proc/diskstats", 1); + int vdo_proc_net_snmp = !config_get_boolean("plugin:proc", "/proc/net/snmp", 1); + int vdo_proc_net_snmp6 = !config_get_boolean("plugin:proc", "/proc/net/snmp6", 1); + int vdo_proc_net_netstat = !config_get_boolean("plugin:proc", "/proc/net/netstat", 1); + int vdo_proc_net_stat_conntrack = !config_get_boolean("plugin:proc", "/proc/net/stat/conntrack", 1); + int vdo_proc_net_ip_vs_stats = !config_get_boolean("plugin:proc", "/proc/net/ip_vs/stats", 1); + int vdo_proc_net_stat_synproxy = !config_get_boolean("plugin:proc", "/proc/net/stat/synproxy", 1); + int vdo_proc_stat = !config_get_boolean("plugin:proc", "/proc/stat", 1); + int vdo_proc_meminfo = !config_get_boolean("plugin:proc", "/proc/meminfo", 1); + int vdo_proc_vmstat = !config_get_boolean("plugin:proc", "/proc/vmstat", 1); + int vdo_proc_net_rpc_nfsd = !config_get_boolean("plugin:proc", "/proc/net/rpc/nfsd", 1); + int vdo_proc_sys_kernel_random_entropy_avail = !config_get_boolean("plugin:proc", "/proc/sys/kernel/random/entropy_avail", 1); + int vdo_proc_interrupts = !config_get_boolean("plugin:proc", "/proc/interrupts", 1); + int vdo_proc_softirqs = !config_get_boolean("plugin:proc", "/proc/softirqs", 1); + int vdo_proc_loadavg = !config_get_boolean("plugin:proc", "/proc/loadavg", 1); + int vdo_sys_kernel_mm_ksm = !config_get_boolean("plugin:proc", "/sys/kernel/mm/ksm", 1); + int vdo_cpu_netdata = !config_get_boolean("plugin:proc", "netdata server resources", 1); + + // keep track of the time each module was called + unsigned long long sutime_proc_net_dev = 0ULL; + unsigned long long sutime_proc_diskstats = 0ULL; + unsigned long long sutime_proc_net_snmp = 0ULL; + unsigned long long sutime_proc_net_snmp6 = 0ULL; + unsigned long long sutime_proc_net_netstat = 0ULL; + unsigned long long sutime_proc_net_stat_conntrack = 0ULL; + unsigned long long sutime_proc_net_ip_vs_stats = 0ULL; + unsigned long long sutime_proc_net_stat_synproxy = 0ULL; + unsigned long long sutime_proc_stat = 0ULL; + unsigned long long sutime_proc_meminfo = 0ULL; + unsigned long long sutime_proc_vmstat = 0ULL; + unsigned long long sutime_proc_net_rpc_nfsd = 0ULL; + unsigned long long sutime_proc_sys_kernel_random_entropy_avail = 0ULL; + unsigned long long sutime_proc_interrupts = 0ULL; + unsigned long long sutime_proc_softirqs = 0ULL; + unsigned long long sutime_proc_loadavg = 0ULL; + unsigned long long sutime_sys_kernel_mm_ksm = 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; + + for(;1;) { + if(unlikely(netdata_exit)) break; + + // delay until it is our time to run + while((sunow = time_usec()) < sunext) + sleep_usec(sunext - sunow); + + // find the next time we need to run + while(time_usec() > sunext) + sunext += rrd_update_every * 1000000ULL; + + if(unlikely(netdata_exit)) break; + + // BEGIN -- the job to be done + + if(!vdo_sys_kernel_mm_ksm) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_kernel_mm_ksm()."); + + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_loadavg) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_loadavg()."); + sunow = time_usec(); + vdo_proc_loadavg = do_proc_loadavg(rrd_update_every, (sutime_proc_loadavg > 0)?sunow - sutime_proc_loadavg:0ULL); + sutime_proc_loadavg = sunow; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_interrupts) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_interrupts()."); + sunow = time_usec(); + vdo_proc_interrupts = do_proc_interrupts(rrd_update_every, (sutime_proc_interrupts > 0)?sunow - sutime_proc_interrupts:0ULL); + sutime_proc_interrupts = sunow; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_softirqs) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_softirqs()."); + sunow = time_usec(); + vdo_proc_softirqs = do_proc_softirqs(rrd_update_every, (sutime_proc_softirqs > 0)?sunow - sutime_proc_softirqs:0ULL); + sutime_proc_softirqs = sunow; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_sys_kernel_random_entropy_avail) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_sys_kernel_random_entropy_avail()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_dev) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_dev()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_diskstats) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_diskstats()."); + sunow = time_usec(); + vdo_proc_diskstats = do_proc_diskstats(rrd_update_every, (sutime_proc_diskstats > 0)?sunow - sutime_proc_diskstats:0ULL); + sutime_proc_diskstats = sunow; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_snmp) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_snmp6) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp6()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_netstat) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_netstat()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_stat_conntrack) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_stat_conntrack()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_ip_vs_stats) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_ip_vs_stats()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_stat_synproxy) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_stat_synproxy()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_stat) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_stat()."); + sunow = time_usec(); + vdo_proc_stat = do_proc_stat(rrd_update_every, (sutime_proc_stat > 0)?sunow - sutime_proc_stat:0ULL); + sutime_proc_stat = sunow; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_meminfo) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_meminfo()."); + sunow = time_usec(); + vdo_proc_meminfo = do_proc_meminfo(rrd_update_every, (sutime_proc_meminfo > 0)?sunow - sutime_proc_meminfo:0ULL); + sutime_proc_meminfo = sunow; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_vmstat) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_vmstat()."); + sunow = time_usec(); + vdo_proc_vmstat = do_proc_vmstat(rrd_update_every, (sutime_proc_vmstat > 0)?sunow - sutime_proc_vmstat:0ULL); + sutime_proc_vmstat = sunow; + } + if(unlikely(netdata_exit)) break; + + if(!vdo_proc_net_rpc_nfsd) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_rpc_nfsd()."); + sunow = time_usec(); + 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; + } + if(unlikely(netdata_exit)) break; + + // END -- the job is done + + // -------------------------------------------------------------------- + + if(!vdo_cpu_netdata) { + global_statistics_charts(); + registry_statistics(); + } + } + + pthread_exit(NULL); + return NULL; } diff --git a/src/plugin_tc.c b/src/plugin_tc.c index 3d3e35217..408069dba 100644 --- a/src/plugin_tc.c +++ b/src/plugin_tc.c @@ -1,24 +1,7 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/resource.h> - -#include "avl.h" -#include "log.h" #include "common.h" -#include "appconfig.h" -#include "rrd.h" -#include "popen.h" -#include "plugin_tc.h" -#include "main.h" -#include "../config.h" -#define RRD_TYPE_TC "tc" -#define RRD_TYPE_TC_LEN strlen(RRD_TYPE_TC) +#define RRD_TYPE_TC "tc" +#define RRD_TYPE_TC_LEN strlen(RRD_TYPE_TC) // ---------------------------------------------------------------------------- // /sbin/tc processor @@ -27,54 +10,71 @@ #define TC_LINE_MAX 1024 struct tc_class { - avl avl; - - char *id; - uint32_t hash; - - char *name; - - char *leafid; - uint32_t leaf_hash; - - char *parentid; - uint32_t parent_hash; + avl avl; + + char *id; + uint32_t hash; + + char *name; + + char *leafid; + uint32_t leaf_hash; + + char *parentid; + uint32_t parent_hash; + + char hasparent; + char isleaf; + unsigned long long bytes; + unsigned long long packets; + unsigned long long dropped; + unsigned long long overlimits; + unsigned long long requeues; + unsigned long long lended; + unsigned long long borrowed; + unsigned long long giants; + unsigned long long tokens; + unsigned long long ctokens; + + RRDDIM *rd_bytes; + RRDDIM *rd_packets; + RRDDIM *rd_dropped; + + char name_updated; + char updated; // updated bytes + int seen; // seen in the tc list (even without bytes) + + struct tc_class *next; + struct tc_class *prev; +}; - char hasparent; - char isleaf; - unsigned long long bytes; - unsigned long long packets; - unsigned long long dropped; - unsigned long long overlimits; - unsigned long long requeues; - unsigned long long lended; - unsigned long long borrowed; - unsigned long long giants; - unsigned long long tokens; - unsigned long long ctokens; +struct tc_device { + avl avl; - char updated; // updated bytes - char seen; // seen in the tc list (even without bytes) + char *id; + uint32_t hash; - struct tc_class *next; - struct tc_class *prev; -}; + char *name; + char *family; -struct tc_device { - avl avl; + char name_updated; + char family_updated; - char *id; - uint32_t hash; + char enabled; + char enabled_bytes; + char enabled_packets; + char enabled_dropped; - char *name; - char *family; + RRDSET *st_bytes; + RRDSET *st_packets; + RRDSET *st_dropped; - avl_tree classes_index; + avl_tree classes_index; - struct tc_class *classes; + struct tc_class *classes; - struct tc_device *next; - struct tc_device *prev; + struct tc_device *next; + struct tc_device *prev; }; @@ -84,25 +84,25 @@ struct tc_device *tc_device_root = NULL; // tc_device index 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; - else return strcmp(((struct tc_device *)a)->id, ((struct tc_device *)b)->id); + 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; + else return strcmp(((struct tc_device *)a)->id, ((struct tc_device *)b)->id); } avl_tree tc_device_root_index = { - NULL, - tc_device_compare + NULL, + 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 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); + struct tc_device tmp; + tmp.id = (char *)id; + tmp.hash = (hash)?hash:simple_hash(tmp.id); - return (struct tc_device *)avl_search(&(tc_device_root_index), (avl *)&tmp); + return (struct tc_device *)avl_search(&(tc_device_root_index), (avl *)&tmp); } @@ -110,621 +110,766 @@ static inline struct tc_device *tc_device_index_find(const char *id, uint32_t ha // tc_class index 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; - else return strcmp(((struct tc_class *)a)->id, ((struct tc_class *)b)->id); + 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; + else return strcmp(((struct tc_class *)a)->id, ((struct tc_class *)b)->id); } #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 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); + struct tc_class tmp; + tmp.id = (char *)id; + tmp.hash = (hash)?hash:simple_hash(tmp.id); - return (struct tc_class *)avl_search(&(st->classes_index), (avl *) &tmp); + return (struct tc_class *)avl_search(&(st->classes_index), (avl *) &tmp); } // ---------------------------------------------------------------------------- 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; - if(c->prev) c->prev->next = c->next; - if(n->classes == c) { - if(c->next) n->classes = c->next; - else n->classes = c->prev; - } - - tc_class_index_del(n, c); - - if(c->id) free(c->id); - if(c->name) free(c->name); - if(c->leafid) free(c->leafid); - if(c->parentid) free(c->parentid); - - free(c); + if(c == n->classes) { + if(c->next) + n->classes = c->next; + else + n->classes = c->prev; + } + if(c->next) c->next->prev = c->prev; + if(c->prev) c->prev->next = c->next; + + 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); + + tc_class_index_del(n, c); + + freez(c->id); + freez(c->name); + freez(c->leafid); + freez(c->parentid); + freez(c); } static inline void tc_device_classes_cleanup(struct tc_device *d) { - static int cleanup_every = 999; - - if(cleanup_every > 0) { - cleanup_every = (int) -config_get_number("plugin:tc", "cleanup unused classes every", 60); - if(cleanup_every > 0) cleanup_every = -cleanup_every; - if(cleanup_every == 0) cleanup_every = -1; - } - - struct tc_class *c = d->classes; - while(c) { - if(c->seen < cleanup_every) { - struct tc_class *nc = c->next; - tc_class_free(d, c); - c = nc; - } - else c = c->next; - - if(c) { - c->updated = 0; - c->seen--; - } - } + static int cleanup_every = 999; + + if(unlikely(cleanup_every > 0)) { + cleanup_every = (int) config_get_number("plugin:tc", "cleanup unused classes every", 60); + if(cleanup_every < 0) cleanup_every = -cleanup_every; + } + + d->name_updated = 0; + d->family_updated = 0; + + struct tc_class *c = d->classes; + while(c) { + if(unlikely(cleanup_every > 0 && c->seen >= cleanup_every)) { + struct tc_class *nc = c->next; + tc_class_free(d, c); + c = nc; + } + else { + c->updated = 0; + c->name_updated = 0; + + c = c->next; + } + } } -static inline void tc_device_commit(struct tc_device *d) -{ - static int enable_new_interfaces = -1; - - if(enable_new_interfaces == -1) enable_new_interfaces = config_get_boolean("plugin:tc", "enable new interfaces detected at runtime", 1); - - // we only need to add leaf classes - struct tc_class *c, *x; - - // set all classes - for(c = d->classes ; c ; c = c->next) { - c->isleaf = 1; - c->hasparent = 0; - } - - // mark the classes as leafs and parents - for(c = d->classes ; c ; c = c->next) { - if(!c->updated) continue; - - for(x = d->classes ; x ; x = x->next) { - if(!x->updated) continue; - - if(c == x) continue; - - if(x->parentid && ( - ( c->hash == x->parent_hash && strcmp(c->id, x->parentid) == 0) || - (c->leafid && c->leaf_hash == x->parent_hash && strcmp(c->leafid, x->parentid) == 0))) { - // debug(D_TC_LOOP, "TC: In device '%s', class '%s' (leafid: '%s') has as leaf class '%s' (parentid: '%s').", d->name?d->name:d->id, c->name?c->name:c->id, c->leafid?c->leafid:c->id, x->name?x->name:x->id, x->parentid?x->parentid:x->id); - c->isleaf = 0; - x->hasparent = 1; - } - } - } - - // debugging: - /* - for ( c = d->classes ; c ; c = c->next) { - if(c->isleaf && c->hasparent) debug(D_TC_LOOP, "TC: Device %s, class %s, OK", d->name, c->id); - else debug(D_TC_LOOP, "TC: Device %s, class %s, IGNORE (isleaf: %d, hasparent: %d, parent: %s)", d->name, c->id, c->isleaf, c->hasparent, c->parentid); - } - */ - - // we need at least a class - for(c = d->classes ; c ; c = c->next) { - // debug(D_TC_LOOP, "TC: Device '%s', class '%s', isLeaf=%d, HasParent=%d, Seen=%d", d->name?d->name:d->id, c->name?c->name:c->id, c->isleaf, c->hasparent, c->seen); - if(!c->updated) continue; - if(c->isleaf && c->hasparent) break; - } - if(!c) { - debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No leaf classes.", d->name?d->name:d->id); - tc_device_classes_cleanup(d); - return; - } - - char var_name[CONFIG_MAX_NAME + 1]; - 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) { - debug(D_TC_LOOP, "TC: Creating new chart for device '%s'", d->name?d->name:d->id); - - st = rrdset_create(RRD_TYPE_TC, d->id, d->name?d->name:d->id, d->family?d->family:d->id, RRD_TYPE_TC ".qos", "Class Usage", "kilobits/s", 7000, rrd_update_every, RRDSET_TYPE_STACKED); - - for(c = d->classes ; c ; c = c->next) { - if(!c->updated) continue; - - if(c->isleaf && c->hasparent) - rrddim_add(st, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL); - } - } - else { - debug(D_TC_LOOP, "TC: Updating chart for device '%s'", d->name?d->name:d->id); - rrdset_next_plugins(st); - - if(d->name && strcmp(d->id, d->name) != 0) rrdset_set_name(st, d->name); - } - - for(c = d->classes ; c ; c = c->next) { - if(!c->updated) continue; - - if(c->isleaf && c->hasparent) { - RRDDIM *rd = rrddim_find(st, c->id); - - if(!rd) { - debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s'", st->id, c->id, c->name); - - // new class, we have to add it - rd = rrddim_add(st, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL); - } - else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", st->id, c->id); - - rrddim_set_by_pointer(st, rd, c->bytes); - - // if it has a name, different to the id - if(c->name) { - // update the rrd dimension with the new name - debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", st->id, rd->id, c->name); - rrddim_set_name(st, rd, c->name); - - free(c->name); - c->name = NULL; - } - } - } - rrdset_done(st); - } - - tc_device_classes_cleanup(d); +static inline void tc_device_commit(struct tc_device *d) { + static int enable_new_interfaces = -1, enable_bytes = -1, enable_packets = -1, enable_dropped = -1; + + if(unlikely(enable_new_interfaces == -1)) { + enable_new_interfaces = config_get_boolean_ondemand("plugin:tc", "enable new interfaces detected at runtime", CONFIG_ONDEMAND_YES); + enable_bytes = config_get_boolean_ondemand("plugin:tc", "enable traffic charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + enable_packets = config_get_boolean_ondemand("plugin:tc", "enable packets charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + enable_dropped = config_get_boolean_ondemand("plugin:tc", "enable dropped charts for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + } + + // we only need to add leaf classes + struct tc_class *c, *x; + unsigned long long bytes_sum = 0, packets_sum = 0, dropped_sum = 0; + int active_classes = 0; + + // set all classes + for(c = d->classes ; c ; c = c->next) { + c->isleaf = 1; + c->hasparent = 0; + } + + // mark the classes as leafs and parents + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->updated)) continue; + + for(x = d->classes ; x ; x = x->next) { + if(unlikely(!x->updated)) continue; + + if(unlikely(c == x)) continue; + + if(x->parentid && ( + ( c->hash == x->parent_hash && strcmp(c->id, x->parentid) == 0) || + (c->leafid && c->leaf_hash == x->parent_hash && strcmp(c->leafid, x->parentid) == 0))) { + // debug(D_TC_LOOP, "TC: In device '%s', class '%s' (leafid: '%s') has as leaf class '%s' (parentid: '%s').", d->name?d->name:d->id, c->name?c->name:c->id, c->leafid?c->leafid:c->id, x->name?x->name:x->id, x->parentid?x->parentid:x->id); + c->isleaf = 0; + x->hasparent = 1; + } + } + } + + // debugging: + /* + for ( c = d->classes ; c ; c = c->next) { + if(c->isleaf && c->hasparent) debug(D_TC_LOOP, "TC: Device %s, class %s, OK", d->name, c->id); + else debug(D_TC_LOOP, "TC: Device %s, class %s, IGNORE (isleaf: %d, hasparent: %d, parent: %s)", d->name, c->id, c->isleaf, c->hasparent, c->parentid); + } + */ + + // we need at least a class + for(c = d->classes ; c ; c = c->next) { + // debug(D_TC_LOOP, "TC: Device '%s', class '%s', isLeaf=%d, HasParent=%d, Seen=%d", d->name?d->name:d->id, c->name?c->name:c->id, c->isleaf, c->hasparent, c->seen); + if(!c->updated) continue; + if(c->isleaf && c->hasparent) { + active_classes++; + bytes_sum += c->bytes; + packets_sum += c->packets; + dropped_sum += c->dropped; + } + } + + if(unlikely(!active_classes)) { + debug(D_TC_LOOP, "TC: Ignoring TC device '%s'. No leaf classes.", d->name?d->name:d->id); + tc_device_classes_cleanup(d); + return; + } + + if(unlikely(d->enabled == -1)) { + char var_name[CONFIG_MAX_NAME + 1]; + snprintfz(var_name, CONFIG_MAX_NAME, "qos for %s", d->id); + d->enabled = config_get_boolean_ondemand("plugin:tc", var_name, enable_new_interfaces); + + snprintfz(var_name, CONFIG_MAX_NAME, "traffic chart for %s", d->id); + d->enabled_bytes = config_get_boolean_ondemand("plugin:tc", var_name, enable_bytes); + + snprintfz(var_name, CONFIG_MAX_NAME, "packets chart for %s", d->id); + d->enabled_packets = config_get_boolean_ondemand("plugin:tc", var_name, enable_packets); + + snprintfz(var_name, CONFIG_MAX_NAME, "dropped packets chart for %s", d->id); + d->enabled_dropped = config_get_boolean_ondemand("plugin:tc", var_name, enable_dropped); + } + + if(likely(d->enabled)) { + // -------------------------------------------------------------------- + // bytes + + if(d->enabled_bytes == CONFIG_ONDEMAND_YES || (d->enabled_bytes == CONFIG_ONDEMAND_ONDEMAND && bytes_sum)) { + d->enabled_bytes = CONFIG_ONDEMAND_YES; + + if(unlikely(!d->st_bytes)) { + d->st_bytes = rrdset_find_bytype(RRD_TYPE_TC, d->id); + if(unlikely(!d->st_bytes)) { + debug(D_TC_LOOP, "TC: Creating new chart for device '%s'", d->name?d->name:d->id); + d->st_bytes = rrdset_create(RRD_TYPE_TC, d->id, d->name?d->name:d->id, d->family?d->family:d->id, RRD_TYPE_TC ".qos", "Class Usage", "kilobits/s", 7000, rrd_update_every, RRDSET_TYPE_STACKED); + } + } + else { + debug(D_TC_LOOP, "TC: Updating chart for device '%s'", d->name?d->name:d->id); + rrdset_next_plugins(d->st_bytes); + + if(unlikely(d->name_updated && d->name && strcmp(d->id, d->name) != 0)) { + rrdset_set_name(d->st_bytes, d->name); + d->name_updated = 0; + } + + // FIXME + // update the family + } + + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->updated)) continue; + + if(c->isleaf && c->hasparent) { + c->seen++; + + if(unlikely(!c->rd_bytes)) { + c->rd_bytes = rrddim_find(d->st_bytes, c->id); + if(unlikely(!c->rd_bytes)) { + debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_bytes->id, c->id, c->name); + + // new class, we have to add it + c->rd_bytes = rrddim_add(d->st_bytes, c->id, c->name?c->name:c->id, 8, 1024, RRDDIM_INCREMENTAL); + } + else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_bytes->id, c->id); + } + + rrddim_set_by_pointer(d->st_bytes, c->rd_bytes, c->bytes); + + // if it has a name, different to the id + if(unlikely(c->name_updated && c->name && strcmp(c->id, c->name) != 0)) { + // update the rrd dimension with the new name + debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", d->st_bytes->id, c->rd_bytes->id, c->name); + rrddim_set_name(d->st_bytes, c->rd_bytes, c->name); + } + } + } + rrdset_done(d->st_bytes); + } + + // -------------------------------------------------------------------- + // packets + + if(d->enabled_packets == CONFIG_ONDEMAND_YES || (d->enabled_packets == CONFIG_ONDEMAND_ONDEMAND && packets_sum)) { + d->enabled_packets = CONFIG_ONDEMAND_YES; + + if(unlikely(!d->st_packets)) { + char id[RRD_ID_LENGTH_MAX + 1]; + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s_packets", d->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_packets", d->name?d->name:d->id); + + d->st_packets = rrdset_find_bytype(RRD_TYPE_TC, id); + if(unlikely(!d->st_packets)) { + debug(D_TC_LOOP, "TC: Creating new _packets chart for device '%s'", d->name?d->name:d->id); + d->st_packets = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_packets", "Class Packets", "packets/s", 7010, rrd_update_every, RRDSET_TYPE_STACKED); + } + } + else { + debug(D_TC_LOOP, "TC: Updating _packets chart for device '%s'", d->name?d->name:d->id); + rrdset_next_plugins(d->st_packets); + + // FIXME + // update the family + } + + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->updated)) continue; + + if(c->isleaf && c->hasparent) { + if(unlikely(!c->rd_packets)) { + c->rd_packets = rrddim_find(d->st_packets, c->id); + if(unlikely(!c->rd_packets)) { + debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_packets->id, c->id, c->name); + + // new class, we have to add it + c->rd_packets = rrddim_add(d->st_packets, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_INCREMENTAL); + } + else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_packets->id, c->id); + } + + rrddim_set_by_pointer(d->st_packets, c->rd_packets, c->packets); + + // if it has a name, different to the id + if(unlikely(c->name_updated && c->name && strcmp(c->id, c->name) != 0)) { + // update the rrd dimension with the new name + debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", d->st_packets->id, c->rd_packets->id, c->name); + rrddim_set_name(d->st_packets, c->rd_packets, c->name); + } + } + } + rrdset_done(d->st_packets); + } + + // -------------------------------------------------------------------- + // dropped + + if(d->enabled_dropped == CONFIG_ONDEMAND_YES || (d->enabled_dropped == CONFIG_ONDEMAND_ONDEMAND && dropped_sum)) { + d->enabled_dropped = CONFIG_ONDEMAND_YES; + + if(unlikely(!d->st_dropped)) { + char id[RRD_ID_LENGTH_MAX + 1]; + char name[RRD_ID_LENGTH_MAX + 1]; + snprintfz(id, RRD_ID_LENGTH_MAX, "%s_dropped", d->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "%s_dropped", d->name?d->name:d->id); + + d->st_dropped = rrdset_find_bytype(RRD_TYPE_TC, id); + if(unlikely(!d->st_dropped)) { + debug(D_TC_LOOP, "TC: Creating new _dropped chart for device '%s'", d->name?d->name:d->id); + d->st_dropped = rrdset_create(RRD_TYPE_TC, id, name, d->family?d->family:d->id, RRD_TYPE_TC ".qos_dropped", "Class Dropped Packets", "packets/s", 7020, rrd_update_every, RRDSET_TYPE_STACKED); + } + } + else { + debug(D_TC_LOOP, "TC: Updating _dropped chart for device '%s'", d->name?d->name:d->id); + rrdset_next_plugins(d->st_dropped); + + // FIXME + // update the family + } + + for(c = d->classes ; c ; c = c->next) { + if(unlikely(!c->updated)) continue; + + if(c->isleaf && c->hasparent) { + if(unlikely(!c->rd_dropped)) { + c->rd_dropped = rrddim_find(d->st_dropped, c->id); + if(unlikely(!c->rd_dropped)) { + debug(D_TC_LOOP, "TC: Adding to chart '%s', dimension '%s' (name: '%s')", d->st_dropped->id, c->id, c->name); + + // new class, we have to add it + c->rd_dropped = rrddim_add(d->st_dropped, c->id, c->name?c->name:c->id, 1, 1, RRDDIM_INCREMENTAL); + } + else debug(D_TC_LOOP, "TC: Updating chart '%s', dimension '%s'", d->st_dropped->id, c->id); + } + + rrddim_set_by_pointer(d->st_dropped, c->rd_dropped, c->dropped); + + // if it has a name, different to the id + if(unlikely(c->name_updated && c->name && strcmp(c->id, c->name) != 0)) { + // update the rrd dimension with the new name + debug(D_TC_LOOP, "TC: Setting chart '%s', dimension '%s' name to '%s'", d->st_dropped->id, c->rd_dropped->id, c->name); + rrddim_set_name(d->st_dropped, c->rd_dropped, c->name); + } + } + } + rrdset_done(d->st_dropped); + } + } + + tc_device_classes_cleanup(d); } 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) { - if(c->name) free(c->name); - c->name = NULL; - - if(name && *name && strcmp(c->id, name) != 0) { - debug(D_TC_LOOP, "TC: Setting device '%s', class '%s' name to '%s'", d->id, id, name); - c->name = strdup(name); - } - } + struct tc_class *c = tc_class_index_find(d, id, 0); + if(likely(c)) { + freez(c->name); + c->name = NULL; + + if(likely(name && *name && strcmp(c->id, name) != 0)) { + debug(D_TC_LOOP, "TC: Setting device '%s', class '%s' name to '%s'", d->id, id, name); + c->name = strdupz(name); + c->name_updated = 1; + } + } } static inline void tc_device_set_device_name(struct tc_device *d, char *name) { - if(d->name) free(d->name); - d->name = NULL; - - if(name && *name && strcmp(d->id, name) != 0) { - debug(D_TC_LOOP, "TC: Setting device '%s' name to '%s'", d->id, name); - d->name = strdup(name); - } + freez(d->name); + d->name = NULL; + + if(likely(name && *name && strcmp(d->id, name) != 0)) { + debug(D_TC_LOOP, "TC: Setting device '%s' name to '%s'", d->id, name); + d->name = strdupz(name); + d->name_updated = 1; + } } static inline void tc_device_set_device_family(struct tc_device *d, char *family) { - if(d->family) free(d->family); - d->family = NULL; - - if(family && *family && strcmp(d->id, family) != 0) { - debug(D_TC_LOOP, "TC: Setting device '%s' family to '%s'", d->id, family); - d->family = strdup(family); - } - // no need for null termination - it is already null + freez(d->family); + d->family = NULL; + + if(likely(family && *family && strcmp(d->id, family) != 0)) { + debug(D_TC_LOOP, "TC: Setting device '%s' family to '%s'", d->id, family); + d->family = strdupz(family); + d->family_updated = 1; + } + // no need for null termination - it is already null } static inline struct tc_device *tc_device_create(char *id) { - struct tc_device *d = tc_device_index_find(id, 0); - - if(!d) { - debug(D_TC_LOOP, "TC: Creating device '%s'", id); - - d = calloc(1, sizeof(struct tc_device)); - if(!d) { - fatal("Cannot allocate memory for tc_device %s", id); - return NULL; - } - - d->id = strdup(id); - d->hash = simple_hash(d->id); - - avl_init(&d->classes_index, tc_class_compare); - tc_device_index_add(d); - - if(!tc_device_root) { - tc_device_root = d; - } - else { - d->next = tc_device_root; - tc_device_root->prev = d; - tc_device_root = d; - } - } - - return(d); + struct tc_device *d = tc_device_index_find(id, 0); + + if(!d) { + debug(D_TC_LOOP, "TC: Creating device '%s'", id); + + d = callocz(1, sizeof(struct tc_device)); + + d->id = strdupz(id); + d->hash = simple_hash(d->id); + d->enabled = -1; + + avl_init(&d->classes_index, tc_class_compare); + tc_device_index_add(d); + + if(!tc_device_root) { + tc_device_root = d; + } + else { + d->next = tc_device_root; + tc_device_root->prev = d; + tc_device_root = d; + } + } + + return(d); } 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); + struct tc_class *c = tc_class_index_find(n, id, 0); - if(!c) { - debug(D_TC_LOOP, "TC: Creating in device '%s', class id '%s', parentid '%s', leafid '%s'", n->id, id, parentid?parentid:"", leafid?leafid:""); + if(!c) { + debug(D_TC_LOOP, "TC: Creating in device '%s', class id '%s', parentid '%s', leafid '%s'", n->id, id, parentid?parentid:"", leafid?leafid:""); - c = calloc(1, sizeof(struct tc_class)); - if(!c) { - fatal("Cannot allocate memory for tc class"); - return NULL; - } + c = callocz(1, sizeof(struct tc_class)); - if(n->classes) n->classes->prev = c; - c->next = n->classes; - n->classes = c; + if(n->classes) n->classes->prev = c; + c->next = n->classes; + n->classes = c; - c->id = strdup(id); - if(!c->id) { - free(c); - return NULL; - } - c->hash = simple_hash(c->id); + c->id = strdupz(id); + c->hash = simple_hash(c->id); - if(parentid && *parentid) { - c->parentid = strdup(parentid); - c->parent_hash = simple_hash(c->parentid); - } + if(parentid && *parentid) { + c->parentid = strdupz(parentid); + c->parent_hash = simple_hash(c->parentid); + } - if(leafid && *leafid) { - c->leafid = strdup(leafid); - c->leaf_hash = simple_hash(c->leafid); - } + if(leafid && *leafid) { + c->leafid = strdupz(leafid); + c->leaf_hash = simple_hash(c->leafid); + } - tc_class_index_add(n, c); - } + tc_class_index_add(n, c); + } - c->seen = 1; + c->seen = 1; - return(c); + return(c); } 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; - if(tc_device_root == n) { - if(n->next) tc_device_root = n->next; - else tc_device_root = n->prev; - } + if(n->next) n->next->prev = n->prev; + if(n->prev) n->prev->next = n->next; + if(tc_device_root == n) { + if(n->next) tc_device_root = n->next; + else tc_device_root = n->prev; + } - tc_device_index_del(n); + tc_device_index_del(n); - while(n->classes) tc_class_free(n, n->classes); + while(n->classes) tc_class_free(n, n->classes); - if(n->id) free(n->id); - if(n->name) free(n->name); - if(n->family) free(n->family); - - free(n); + freez(n->id); + freez(n->name); + freez(n->family); + freez(n); } static inline void tc_device_free_all() { - while(tc_device_root) - tc_device_free(tc_device_root); + while(tc_device_root) + tc_device_free(tc_device_root); } #define MAX_WORDS 20 static inline int tc_space(char c) { - switch(c) { - case ' ': - case '\t': - case '\r': - case '\n': - return 1; - - default: - return 0; - } + switch(c) { + case ' ': + case '\t': + case '\r': + case '\n': + return 1; + + default: + return 0; + } } static inline void tc_split_words(char *str, char **words, int max_words) { - char *s = str; - int i = 0; + char *s = str; + int i = 0; - // skip all white space - while(tc_space(*s)) s++; + // skip all white space + while(tc_space(*s)) s++; - // store the first word - words[i++] = s; + // store the first word + words[i++] = s; - // while we have something - while(*s) { - // if it is a space - if(tc_space(*s)) { + // while we have something + while(*s) { + // if it is a space + if(unlikely(tc_space(*s))) { - // terminate the word - *s++ = '\0'; + // terminate the word + *s++ = '\0'; - // skip all white space - while(tc_space(*s)) s++; + // skip all white space + while(tc_space(*s)) s++; - // if we reached the end, stop - if(!*s) break; + // if we reached the end, stop + if(!*s) break; - // store the next word - if(i < max_words) words[i++] = s; - else break; - } - else s++; - } + // store the next word + if(i < max_words) words[i++] = s; + else break; + } + else s++; + } - // terminate the words - while(i < max_words) words[i++] = NULL; + // terminate the words + while(i < max_words) words[i++] = NULL; } pid_t tc_child_pid = 0; -void *tc_main(void *ptr) -{ - if(ptr) { ; } +void *tc_main(void *ptr) { + (void)ptr; - info("TC thread created with task id %d", gettid()); + info("TC 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_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."); + if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) + error("Cannot set pthread cancel state to ENABLE."); - struct rusage thread; - RRDSET *stcpu = NULL, *sttime = NULL; + struct rusage thread; + RRDSET *stcpu = NULL, *sttime = NULL; - char buffer[TC_LINE_MAX+1] = ""; - char *words[MAX_WORDS] = { NULL }; + char buffer[TC_LINE_MAX+1] = ""; + char *words[MAX_WORDS] = { NULL }; - uint32_t BEGIN_HASH = simple_hash("BEGIN"); - uint32_t END_HASH = simple_hash("END"); - uint32_t CLASS_HASH = simple_hash("class"); - uint32_t SENT_HASH = simple_hash("Sent"); - uint32_t LENDED_HASH = simple_hash("lended:"); - uint32_t TOKENS_HASH = simple_hash("tokens:"); - uint32_t SETDEVICENAME_HASH = simple_hash("SETDEVICENAME"); - uint32_t SETDEVICEGROUP_HASH = simple_hash("SETDEVICEGROUP"); - uint32_t SETCLASSNAME_HASH = simple_hash("SETCLASSNAME"); - uint32_t WORKTIME_HASH = simple_hash("WORKTIME"); + uint32_t BEGIN_HASH = simple_hash("BEGIN"); + uint32_t END_HASH = simple_hash("END"); + uint32_t CLASS_HASH = simple_hash("class"); + uint32_t SENT_HASH = simple_hash("Sent"); + uint32_t LENDED_HASH = simple_hash("lended:"); + uint32_t TOKENS_HASH = simple_hash("tokens:"); + uint32_t SETDEVICENAME_HASH = simple_hash("SETDEVICENAME"); + uint32_t SETDEVICEGROUP_HASH = simple_hash("SETDEVICEGROUP"); + uint32_t SETCLASSNAME_HASH = simple_hash("SETCLASSNAME"); + uint32_t WORKTIME_HASH = simple_hash("WORKTIME"); #ifdef DETACH_PLUGINS_FROM_NETDATA - uint32_t MYPID_HASH = simple_hash("MYPID"); + uint32_t MYPID_HASH = simple_hash("MYPID"); #endif - uint32_t first_hash; - - for(;1;) { - if(unlikely(netdata_exit)) break; - - FILE *fp; - struct tc_device *device = NULL; - struct tc_class *class = NULL; - - 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); - if(!fp) { - error("TC: Cannot popen(\"%s\", \"r\").", buffer); - pthread_exit(NULL); - return NULL; - } - - while(fgets(buffer, TC_LINE_MAX, fp) != NULL) { - if(unlikely(netdata_exit)) break; - - buffer[TC_LINE_MAX] = '\0'; - // debug(D_TC_LOOP, "TC: read '%s'", buffer); - - tc_split_words(buffer, words, MAX_WORDS); - if(!words[0] || !*words[0]) { - // debug(D_TC_LOOP, "empty line"); - continue; - } - // else debug(D_TC_LOOP, "First word is '%s'", words[0]); - - first_hash = simple_hash(words[0]); - - if(device && first_hash == CLASS_HASH && strcmp(words[0], "class") == 0) { - // debug(D_TC_LOOP, "CLASS line on class id='%s', parent='%s', parentid='%s', leaf='%s', leafid='%s'", words[2], words[3], words[4], words[5], words[6]); - - // clear the last class - class = NULL; - - // words[1] : class type - // words[2] : N:XX - // words[3] : parent or root - if(words[1] && words[2] && words[3] && (strcmp(words[3], "parent") == 0 || strcmp(words[3], "root") == 0)) { - //char *type = words[1]; // the class: htb, fq_codel, etc - - // we are only interested for HTB classes - //if(strcmp(type, "htb") != 0) continue; - - char *id = words[2]; // the class major:minor - char *parent = words[3]; // 'parent' or 'root' - char *parentid = words[4]; // the parent's id - char *leaf = words[5]; // 'leaf' - char *leafid = words[6]; // leafid - - if(strcmp(parent, "root") == 0) { - parentid = NULL; - leafid = NULL; - } - else if(!leaf || strcmp(leaf, "leaf") != 0) - leafid = NULL; - - char leafbuf[20 + 1] = ""; - if(leafid && leafid[strlen(leafid) - 1] == ':') { - strncpyz(leafbuf, leafid, 20 - 1); - strcat(leafbuf, "1"); - leafid = leafbuf; - } - - class = tc_class_add(device, id, parentid, leafid); - } - } - else if(first_hash == END_HASH && strcmp(words[0], "END") == 0) { - // debug(D_TC_LOOP, "END line"); - - if(device) { - if(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL) != 0) - error("Cannot set pthread cancel state to DISABLE."); - - tc_device_commit(device); - // tc_device_free(device); - device = NULL; - class = NULL; - - if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) - error("Cannot set pthread cancel state to ENABLE."); - } - } - else if(first_hash == BEGIN_HASH && strcmp(words[0], "BEGIN") == 0) { - // debug(D_TC_LOOP, "BEGIN line on device '%s'", words[1]); - - if(device) { - // tc_device_free(device); - device = NULL; - class = NULL; - } - - if(words[1] && *words[1]) { - device = tc_device_create(words[1]); - class = NULL; - } - } - else if(device && class && first_hash == SENT_HASH && strcmp(words[0], "Sent") == 0) { - // debug(D_TC_LOOP, "SENT line '%s'", words[1]); - if(words[1] && *words[1]) { - class->bytes = strtoull(words[1], NULL, 10); - class->updated = 1; - } - - if(words[3] && *words[3]) - class->packets = strtoull(words[3], NULL, 10); - - if(words[6] && *words[6]) - class->dropped = strtoull(words[6], NULL, 10); - - if(words[8] && *words[8]) - class->overlimits = strtoull(words[8], NULL, 10); - - if(words[10] && *words[10]) - class->requeues = strtoull(words[8], NULL, 10); - } - else if(device && class && class->updated && first_hash == LENDED_HASH && strcmp(words[0], "lended:") == 0) { - // debug(D_TC_LOOP, "LENDED line '%s'", words[1]); - if(words[1] && *words[1]) - class->lended = strtoull(words[1], NULL, 10); - - if(words[3] && *words[3]) - class->borrowed = strtoull(words[3], NULL, 10); - - if(words[5] && *words[5]) - class->giants = strtoull(words[5], NULL, 10); - } - else if(device && class && class->updated && first_hash == TOKENS_HASH && strcmp(words[0], "tokens:") == 0) { - // debug(D_TC_LOOP, "TOKENS line '%s'", words[1]); - if(words[1] && *words[1]) - class->tokens = strtoull(words[1], NULL, 10); - - if(words[3] && *words[3]) - class->ctokens = strtoull(words[3], NULL, 10); - } - else if(device && first_hash == SETDEVICENAME_HASH && strcmp(words[0], "SETDEVICENAME") == 0) { - // debug(D_TC_LOOP, "SETDEVICENAME line '%s'", words[1]); - if(words[1] && *words[1]) tc_device_set_device_name(device, words[1]); - } - else if(device && first_hash == SETDEVICEGROUP_HASH && strcmp(words[0], "SETDEVICEGROUP") == 0) { - // debug(D_TC_LOOP, "SETDEVICEGROUP line '%s'", words[1]); - if(words[1] && *words[1]) tc_device_set_device_family(device, words[1]); - } - else if(device && first_hash == SETCLASSNAME_HASH && strcmp(words[0], "SETCLASSNAME") == 0) { - // debug(D_TC_LOOP, "SETCLASSNAME line '%s' '%s'", words[1], words[2]); - char *id = words[1]; - char *path = words[2]; - if(id && *id && path && *path) tc_device_set_class_name(device, id, path); - } - else if(first_hash == WORKTIME_HASH && strcmp(words[0], "WORKTIME") == 0) { - // debug(D_TC_LOOP, "WORKTIME line '%s' '%s'", words[1], words[2]); - getrusage(RUSAGE_THREAD, &thread); - - if(!stcpu) stcpu = rrdset_find("netdata.plugin_tc_cpu"); - if(!stcpu) { - stcpu = rrdset_create("netdata", "plugin_tc_cpu", NULL, "tc.helper", NULL, "NetData TC CPU usage", "milliseconds/s", 135000, rrd_update_every, RRDSET_TYPE_STACKED); - rrddim_add(stcpu, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL); - rrddim_add(stcpu, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL); - } - else rrdset_next(stcpu); - - rrddim_set(stcpu, "user" , thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec); - rrddim_set(stcpu, "system", thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec); - rrdset_done(stcpu); - - if(!sttime) stcpu = rrdset_find("netdata.plugin_tc_time"); - if(!sttime) { - sttime = rrdset_create("netdata", "plugin_tc_time", NULL, "tc.helper", NULL, "NetData TC script execution", "milliseconds/run", 135001, rrd_update_every, RRDSET_TYPE_AREA); - rrddim_add(sttime, "run_time", "run time", 1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(sttime); - - rrddim_set(sttime, "run_time", atoll(words[1])); - rrdset_done(sttime); - - } + uint32_t first_hash; + + snprintfz(buffer, TC_LINE_MAX, "%s/tc-qos-helper.sh", config_get("plugins", "plugins directory", PLUGINS_DIR)); + char *tc_script = config_get("plugin:tc", "script to run to get tc values", buffer); + + for(;1;) { + if(unlikely(netdata_exit)) break; + + FILE *fp; + struct tc_device *device = NULL; + struct tc_class *class = NULL; + + snprintfz(buffer, TC_LINE_MAX, "exec %s %d", tc_script, rrd_update_every); + debug(D_TC_LOOP, "executing '%s'", buffer); + + fp = mypopen(buffer, &tc_child_pid); + if(unlikely(!fp)) { + error("TC: Cannot popen(\"%s\", \"r\").", buffer); + pthread_exit(NULL); + return NULL; + } + + while(fgets(buffer, TC_LINE_MAX, fp) != NULL) { + if(unlikely(netdata_exit)) break; + + buffer[TC_LINE_MAX] = '\0'; + // debug(D_TC_LOOP, "TC: read '%s'", buffer); + + tc_split_words(buffer, words, MAX_WORDS); + + if(unlikely(!words[0] || !*words[0])) { + // debug(D_TC_LOOP, "empty line"); + continue; + } + // else debug(D_TC_LOOP, "First word is '%s'", words[0]); + + first_hash = simple_hash(words[0]); + + if(unlikely(device && first_hash == CLASS_HASH && strcmp(words[0], "class") == 0)) { + // debug(D_TC_LOOP, "CLASS line on class id='%s', parent='%s', parentid='%s', leaf='%s', leafid='%s'", words[2], words[3], words[4], words[5], words[6]); + + // words[1] : class type + // words[2] : N:XX + // words[3] : parent or root + if(likely(words[1] && words[2] && words[3] && (strcmp(words[3], "parent") == 0 || strcmp(words[3], "root") == 0))) { + //char *type = words[1]; // the class: htb, fq_codel, etc + + // we are only interested for HTB classes + //if(strcmp(type, "htb") != 0) continue; + + char *id = words[2]; // the class major:minor + char *parent = words[3]; // 'parent' or 'root' + char *parentid = words[4]; // the parent's id + char *leaf = words[5]; // 'leaf' + char *leafid = words[6]; // leafid + + if(strcmp(parent, "root") == 0) { + parentid = NULL; + leafid = NULL; + } + else if(!leaf || strcmp(leaf, "leaf") != 0) + leafid = NULL; + + char leafbuf[20 + 1] = ""; + if(leafid && leafid[strlen(leafid) - 1] == ':') { + strncpyz(leafbuf, leafid, 20 - 1); + strcat(leafbuf, "1"); + leafid = leafbuf; + } + + class = tc_class_add(device, id, parentid, leafid); + } + else { + // clear the last class + class = NULL; + } + } + else if(unlikely(first_hash == END_HASH && strcmp(words[0], "END") == 0)) { + // debug(D_TC_LOOP, "END line"); + + if(likely(device)) { + if(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL) != 0) + error("Cannot set pthread cancel state to DISABLE."); + + tc_device_commit(device); + // tc_device_free(device); + + if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) + error("Cannot set pthread cancel state to ENABLE."); + } + + device = NULL; + class = NULL; + } + else if(unlikely(first_hash == BEGIN_HASH && strcmp(words[0], "BEGIN") == 0)) { + // debug(D_TC_LOOP, "BEGIN line on device '%s'", words[1]); + + if(likely(words[1] && *words[1])) { + device = tc_device_create(words[1]); + } + else { + // tc_device_free(device); + device = NULL; + } + + class = NULL; + } + else if(unlikely(device && class && first_hash == SENT_HASH && strcmp(words[0], "Sent") == 0)) { + // debug(D_TC_LOOP, "SENT line '%s'", words[1]); + if(likely(words[1] && *words[1])) { + class->bytes = strtoull(words[1], NULL, 10); + class->updated = 1; + } + else { + class->updated = 0; + } + + if(likely(words[3] && *words[3])) + class->packets = strtoull(words[3], NULL, 10); + + if(likely(words[6] && *words[6])) + class->dropped = strtoull(words[6], NULL, 10); + + if(likely(words[8] && *words[8])) + class->overlimits = strtoull(words[8], NULL, 10); + + if(likely(words[10] && *words[10])) + class->requeues = strtoull(words[8], NULL, 10); + } + else if(unlikely(device && class && class->updated && first_hash == LENDED_HASH && strcmp(words[0], "lended:") == 0)) { + // debug(D_TC_LOOP, "LENDED line '%s'", words[1]); + if(likely(words[1] && *words[1])) + class->lended = strtoull(words[1], NULL, 10); + + if(likely(words[3] && *words[3])) + class->borrowed = strtoull(words[3], NULL, 10); + + if(likely(words[5] && *words[5])) + class->giants = strtoull(words[5], NULL, 10); + } + else if(unlikely(device && class && class->updated && first_hash == TOKENS_HASH && strcmp(words[0], "tokens:") == 0)) { + // debug(D_TC_LOOP, "TOKENS line '%s'", words[1]); + if(likely(words[1] && *words[1])) + class->tokens = strtoull(words[1], NULL, 10); + + if(likely(words[3] && *words[3])) + class->ctokens = strtoull(words[3], NULL, 10); + } + else if(unlikely(device && first_hash == SETDEVICENAME_HASH && strcmp(words[0], "SETDEVICENAME") == 0)) { + // debug(D_TC_LOOP, "SETDEVICENAME line '%s'", words[1]); + if(likely(words[1] && *words[1])) + tc_device_set_device_name(device, words[1]); + } + else if(unlikely(device && first_hash == SETDEVICEGROUP_HASH && strcmp(words[0], "SETDEVICEGROUP") == 0)) { + // debug(D_TC_LOOP, "SETDEVICEGROUP line '%s'", words[1]); + if(likely(words[1] && *words[1])) + tc_device_set_device_family(device, words[1]); + } + else if(unlikely(device && first_hash == SETCLASSNAME_HASH && strcmp(words[0], "SETCLASSNAME") == 0)) { + // debug(D_TC_LOOP, "SETCLASSNAME line '%s' '%s'", words[1], words[2]); + char *id = words[1]; + char *path = words[2]; + if(likely(id && *id && path && *path)) + tc_device_set_class_name(device, id, path); + } + else if(unlikely(first_hash == WORKTIME_HASH && strcmp(words[0], "WORKTIME") == 0)) { + // debug(D_TC_LOOP, "WORKTIME line '%s' '%s'", words[1], words[2]); + getrusage(RUSAGE_THREAD, &thread); + + if(unlikely(!stcpu)) stcpu = rrdset_find("netdata.plugin_tc_cpu"); + if(unlikely(!stcpu)) { + stcpu = rrdset_create("netdata", "plugin_tc_cpu", NULL, "tc.helper", NULL, "NetData TC CPU usage", "milliseconds/s", 135000, rrd_update_every, RRDSET_TYPE_STACKED); + rrddim_add(stcpu, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL); + rrddim_add(stcpu, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL); + } + else rrdset_next(stcpu); + + rrddim_set(stcpu, "user" , thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec); + rrddim_set(stcpu, "system", thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec); + rrdset_done(stcpu); + + if(unlikely(!sttime)) stcpu = rrdset_find("netdata.plugin_tc_time"); + if(unlikely(!sttime)) { + sttime = rrdset_create("netdata", "plugin_tc_time", NULL, "tc.helper", NULL, "NetData TC script execution", "milliseconds/run", 135001, rrd_update_every, RRDSET_TYPE_AREA); + rrddim_add(sttime, "run_time", "run time", 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(sttime); + + rrddim_set(sttime, "run_time", atoll(words[1])); + rrdset_done(sttime); + + } #ifdef DETACH_PLUGINS_FROM_NETDATA - else if(first_hash == MYPID_HASH && (strcmp(words[0], "MYPID") == 0)) { - // debug(D_TC_LOOP, "MYPID line '%s'", words[1]); - char *id = words[1]; - pid_t pid = atol(id); + else if(unlikely(first_hash == MYPID_HASH && (strcmp(words[0], "MYPID") == 0))) { + // debug(D_TC_LOOP, "MYPID line '%s'", words[1]); + char *id = words[1]; + pid_t pid = atol(id); - if(pid) tc_child_pid = pid; + if(likely(pid)) tc_child_pid = pid; - debug(D_TC_LOOP, "TC: Child PID is %d.", tc_child_pid); - } + debug(D_TC_LOOP, "TC: Child PID is %d.", tc_child_pid); + } #endif - //else { - // debug(D_TC_LOOP, "IGNORED line"); - //} - } - // fgets() failed or loop broke - int code = mypclose(fp, tc_child_pid); - tc_child_pid = 0; - - if(device) { - // tc_device_free(device); - device = NULL; - class = NULL; - } - - if(netdata_exit) { - tc_device_free_all(); - pthread_exit(NULL); - return NULL; - } - - if(code == 1 || code == 127) { - // 1 = DISABLE - // 127 = cannot even run it - error("TC: tc-qos-helper.sh exited with code %d. Disabling it.", code); - - tc_device_free_all(); - pthread_exit(NULL); - return NULL; - } - - sleep((unsigned int) rrd_update_every); - } - - pthread_exit(NULL); - return NULL; + //else { + // debug(D_TC_LOOP, "IGNORED line"); + //} + } + + // fgets() failed or loop broke + int code = mypclose(fp, tc_child_pid); + tc_child_pid = 0; + + if(unlikely(device)) { + // tc_device_free(device); + device = NULL; + class = NULL; + } + + if(unlikely(netdata_exit)) { + tc_device_free_all(); + pthread_exit(NULL); + return NULL; + } + + if(code == 1 || code == 127) { + // 1 = DISABLE + // 127 = cannot even run it + error("TC: tc-qos-helper.sh exited with code %d. Disabling it.", code); + + tc_device_free_all(); + pthread_exit(NULL); + return NULL; + } + + sleep((unsigned int) rrd_update_every); + } + + pthread_exit(NULL); + return NULL; } diff --git a/src/plugins_d.c b/src/plugins_d.c index 0ccbd36e4..627cc90e5 100644 --- a/src/plugins_d.c +++ b/src/plugins_d.c @@ -1,531 +1,547 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <sys/types.h> -#include <dirent.h> -#include <pthread.h> -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <signal.h> - -#include "main.h" #include "common.h" -#include "appconfig.h" -#include "log.h" -#include "rrd.h" -#include "popen.h" -#include "plugins_d.h" -#include "../config.h" struct plugind *pluginsd_root = NULL; #define MAX_WORDS 20 static inline int pluginsd_space(char c) { - switch(c) { - case ' ': - case '\t': - case '\r': - case '\n': - case '=': - return 1; - - default: - return 0; - } + switch(c) { + case ' ': + case '\t': + case '\r': + case '\n': + case '=': + return 1; + + default: + return 0; + } } static int pluginsd_split_words(char *str, char **words, int max_words) { - char *s = str, quote = 0; - int i = 0, j; - - // skip all white space - while(unlikely(pluginsd_space(*s))) s++; - - // check for quote - if(unlikely(*s == '\'' || *s == '"')) { - quote = *s; // remember the quote - s++; // skip the quote - } - - // store the first word - words[i++] = s; - - // while we have something - while(likely(*s)) { - // if it is escape - if(unlikely(*s == '\\' && s[1])) { - s += 2; - continue; - } - - // if it is quote - else if(unlikely(*s == quote)) { - quote = 0; - *s = ' '; - continue; - } - - // if it is a space - else if(unlikely(quote == 0 && pluginsd_space(*s))) { - - // terminate the word - *s++ = '\0'; - - // skip all white space - while(likely(pluginsd_space(*s))) s++; - - // check for quote - if(unlikely(*s == '\'' || *s == '"')) { - quote = *s; // remember the quote - s++; // skip the quote - } - - // if we reached the end, stop - if(unlikely(!*s)) break; - - // store the next word - if(likely(i < max_words)) words[i++] = s; - else break; - } - - // anything else - else s++; - } - - // terminate the words - j = i; - while(likely(j < max_words)) words[j++] = NULL; - - return i; + char *s = str, quote = 0; + int i = 0, j; + + // skip all white space + while(unlikely(pluginsd_space(*s))) s++; + + // check for quote + if(unlikely(*s == '\'' || *s == '"')) { + quote = *s; // remember the quote + s++; // skip the quote + } + + // store the first word + words[i++] = s; + + // while we have something + while(likely(*s)) { + // if it is escape + if(unlikely(*s == '\\' && s[1])) { + s += 2; + continue; + } + + // if it is quote + else if(unlikely(*s == quote)) { + quote = 0; + *s = ' '; + continue; + } + + // if it is a space + else if(unlikely(quote == 0 && pluginsd_space(*s))) { + + // terminate the word + *s++ = '\0'; + + // skip all white space + while(likely(pluginsd_space(*s))) s++; + + // check for quote + if(unlikely(*s == '\'' || *s == '"')) { + quote = *s; // remember the quote + s++; // skip the quote + } + + // if we reached the end, stop + if(unlikely(!*s)) break; + + // store the next word + if(likely(i < max_words)) words[i++] = s; + else break; + } + + // anything else + else s++; + } + + // terminate the words + j = i; + while(likely(j < max_words)) words[j++] = NULL; + + return i; } void *pluginsd_worker_thread(void *arg) { - struct plugind *cd = (struct plugind *)arg; - char line[PLUGINSD_LINE_MAX + 1]; + struct plugind *cd = (struct plugind *)arg; + char line[PLUGINSD_LINE_MAX + 1]; #ifdef DETACH_PLUGINS_FROM_NETDATA - unsigned long long usec = 0, susec = 0; - struct timeval last = {0, 0} , now = {0, 0}; + unsigned long long usec = 0, susec = 0; + struct timeval last = {0, 0} , now = {0, 0}; #endif - char *words[MAX_WORDS] = { NULL }; - uint32_t SET_HASH = simple_hash("SET"); - uint32_t BEGIN_HASH = simple_hash("BEGIN"); - uint32_t END_HASH = simple_hash("END"); - uint32_t FLUSH_HASH = simple_hash("FLUSH"); - uint32_t CHART_HASH = simple_hash("CHART"); - uint32_t DIMENSION_HASH = simple_hash("DIMENSION"); - uint32_t DISABLE_HASH = simple_hash("DISABLE"); + char *words[MAX_WORDS] = { NULL }; + uint32_t SET_HASH = simple_hash("SET"); + uint32_t BEGIN_HASH = simple_hash("BEGIN"); + uint32_t END_HASH = simple_hash("END"); + uint32_t FLUSH_HASH = simple_hash("FLUSH"); + uint32_t CHART_HASH = simple_hash("CHART"); + uint32_t DIMENSION_HASH = simple_hash("DIMENSION"); + uint32_t DISABLE_HASH = simple_hash("DISABLE"); #ifdef DETACH_PLUGINS_FROM_NETDATA - uint32_t MYPID_HASH = simple_hash("MYPID"); - uint32_t STOPPING_WAKE_ME_UP_PLEASE_HASH = simple_hash("STOPPING_WAKE_ME_UP_PLEASE"); + uint32_t MYPID_HASH = simple_hash("MYPID"); + uint32_t STOPPING_WAKE_ME_UP_PLEASE_HASH = simple_hash("STOPPING_WAKE_ME_UP_PLEASE"); #endif - while(likely(1)) { - if(unlikely(netdata_exit)) break; - - FILE *fp = mypopen(cd->cmd, &cd->pid); - if(unlikely(!fp)) { - error("Cannot popen(\"%s\", \"r\").", cd->cmd); - break; - } - - info("PLUGINSD: '%s' running on pid %d", cd->fullfilename, cd->pid); - - RRDSET *st = NULL; - unsigned long long count = 0; - char *s; - uint32_t hash; - - while(likely(fgets(line, PLUGINSD_LINE_MAX, fp) != NULL)) { - if(unlikely(netdata_exit)) break; - - line[PLUGINSD_LINE_MAX] = '\0'; - - // debug(D_PLUGINSD, "PLUGINSD: %s: %s", cd->filename, line); - - int w = pluginsd_split_words(line, words, MAX_WORDS); - s = words[0]; - if(unlikely(!s || !*s || !w)) { - // debug(D_PLUGINSD, "PLUGINSD: empty line"); - continue; - } - - // debug(D_PLUGINSD, "PLUGINSD: words 0='%s' 1='%s' 2='%s' 3='%s' 4='%s' 5='%s' 6='%s' 7='%s' 8='%s' 9='%s'", words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]); - - hash = simple_hash(s); - - if(likely(hash == SET_HASH && !strcmp(s, "SET"))) { - char *dimension = words[1]; - char *value = words[2]; - - if(unlikely(!dimension || !*dimension)) { - error("PLUGINSD: '%s' is requesting a SET on chart '%s', without a dimension. Disabling it.", cd->fullfilename, st->id); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - if(unlikely(!value || !*value)) value = NULL; - - if(unlikely(!st)) { - error("PLUGINSD: '%s' is requesting a SET on dimension %s with value %s, without a BEGIN. Disabling it.", cd->fullfilename, dimension, value?value:"<nothing>"); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - 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, strtoll(value, NULL, 0)); - - count++; - } - else if(likely(hash == BEGIN_HASH && !strcmp(s, "BEGIN"))) { - char *id = words[1]; - char *microseconds_txt = words[2]; - - if(unlikely(!id)) { - error("PLUGINSD: '%s' is requesting a BEGIN without a chart id. Disabling it.", cd->fullfilename); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - st = rrdset_find(id); - if(unlikely(!st)) { - error("PLUGINSD: '%s' is requesting a BEGIN on chart '%s', which does not exist. Disabling it.", cd->fullfilename, id); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - if(likely(st->counter_done)) { - unsigned long long microseconds = 0; - if(microseconds_txt && *microseconds_txt) microseconds = strtoull(microseconds_txt, NULL, 10); - if(microseconds) rrdset_next_usec(st, microseconds); - else rrdset_next_plugins(st); - } - } - else if(likely(hash == END_HASH && !strcmp(s, "END"))) { - if(unlikely(!st)) { - error("PLUGINSD: '%s' is requesting an END, without a BEGIN. Disabling it.", cd->fullfilename); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting an END on chart %s", cd->fullfilename, st->id); - - rrdset_done(st); - st = NULL; - } - else if(likely(hash == FLUSH_HASH && !strcmp(s, "FLUSH"))) { - debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting a FLUSH", cd->fullfilename); - st = NULL; - } - else if(likely(hash == CHART_HASH && !strcmp(s, "CHART"))) { - int noname = 0; - st = NULL; - - if((words[1]) != NULL && (words[2]) != NULL && strcmp(words[1], words[2]) == 0) - noname = 1; - - char *type = words[1]; - char *id = NULL; - if(likely(type)) { - id = strchr(type, '.'); - if(likely(id)) { *id = '\0'; id++; } - } - char *name = words[2]; - char *title = words[3]; - char *units = words[4]; - char *family = words[5]; - char *context = words[6]; - char *chart = words[7]; - char *priority_s = words[8]; - char *update_every_s = words[9]; - - if(unlikely(!type || !*type || !id || !*id)) { - error("PLUGINSD: '%s' is requesting a CHART, without a type.id. Disabling it.", cd->fullfilename); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - int priority = 1000; - if(likely(priority_s)) priority = atoi(priority_s); - - int update_every = cd->update_every; - if(likely(update_every_s)) update_every = atoi(update_every_s); - if(unlikely(!update_every)) update_every = cd->update_every; - - int chart_type = RRDSET_TYPE_LINE; - if(unlikely(chart)) chart_type = rrdset_type_id(chart); - - if(unlikely(noname || !name || !*name || strcasecmp(name, "NULL") == 0 || strcasecmp(name, "(NULL)") == 0)) name = NULL; - if(unlikely(!family || !*family)) family = NULL; - if(unlikely(!context || !*context)) context = NULL; - - st = rrdset_find_bytype(type, id); - if(unlikely(!st)) { - debug(D_PLUGINSD, "PLUGINSD: Creating chart type='%s', id='%s', name='%s', family='%s', context='%s', chart='%s', priority=%d, update_every=%d" - , type, id - , name?name:"" - , family?family:"" - , context?context:"" - , rrdset_type_name(chart_type) - , priority - , update_every - ); - - st = rrdset_create(type, id, name, family, context, title, units, priority, update_every, chart_type); - cd->update_every = update_every; - } - else debug(D_PLUGINSD, "PLUGINSD: Chart '%s' already exists. Not adding it again.", st->id); - } - else if(likely(hash == DIMENSION_HASH && !strcmp(s, "DIMENSION"))) { - char *id = words[1]; - char *name = words[2]; - char *algorithm = words[3]; - char *multiplier_s = words[4]; - char *divisor_s = words[5]; - char *options = words[6]; - - if(unlikely(!id || !*id)) { - error("PLUGINSD: '%s' is requesting a DIMENSION, without an id. Disabling it.", cd->fullfilename); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - if(unlikely(!st)) { - error("PLUGINSD: '%s' is requesting a DIMENSION, without a CHART. Disabling it.", cd->fullfilename); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - - long multiplier = 1; - 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 = strtol(divisor_s, NULL, 0); - if(unlikely(!divisor)) divisor = 1; - - if(unlikely(!algorithm || !*algorithm)) algorithm = "absolute"; - - if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: Creating dimension in chart %s, id='%s', name='%s', algorithm='%s', multiplier=%ld, divisor=%ld, hidden='%s'" - , st->id - , id - , name?name:"" - , rrddim_algorithm_name(rrddim_algorithm_id(algorithm)) - , multiplier - , divisor - , options?options:"" - ); - - RRDDIM *rd = rrddim_find(st, id); - if(unlikely(!rd)) { - rd = rrddim_add(st, id, name, multiplier, divisor, rrddim_algorithm_id(algorithm)); - rd->flags = 0x00000000; - if(options && *options) { - if(strstr(options, "hidden") != NULL) rd->flags |= RRDDIM_FLAG_HIDDEN; - if(strstr(options, "noreset") != NULL) rd->flags |= RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS; - if(strstr(options, "nooverflow") != NULL) rd->flags |= RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS; - } - } - else if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: dimension %s/%s already exists. Not adding it again.", st->id, id); - } - else if(unlikely(hash == DISABLE_HASH && !strcmp(s, "DISABLE"))) { - error("PLUGINSD: '%s' called DISABLE. Disabling it.", cd->fullfilename); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } + size_t count = 0; + + while(likely(1)) { + if(unlikely(netdata_exit)) break; + + FILE *fp = mypopen(cd->cmd, &cd->pid); + if(unlikely(!fp)) { + error("Cannot popen(\"%s\", \"r\").", cd->cmd); + break; + } + + info("PLUGINSD: '%s' running on pid %d", cd->fullfilename, cd->pid); + + RRDSET *st = NULL; + char *s; + uint32_t hash; + + while(likely(fgets(line, PLUGINSD_LINE_MAX, fp) != NULL)) { + if(unlikely(netdata_exit)) break; + + line[PLUGINSD_LINE_MAX] = '\0'; + + // debug(D_PLUGINSD, "PLUGINSD: %s: %s", cd->filename, line); + + int w = pluginsd_split_words(line, words, MAX_WORDS); + s = words[0]; + if(unlikely(!s || !*s || !w)) { + // debug(D_PLUGINSD, "PLUGINSD: empty line"); + continue; + } + + // debug(D_PLUGINSD, "PLUGINSD: words 0='%s' 1='%s' 2='%s' 3='%s' 4='%s' 5='%s' 6='%s' 7='%s' 8='%s' 9='%s'", words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]); + + hash = simple_hash(s); + + if(likely(hash == SET_HASH && !strcmp(s, "SET"))) { + char *dimension = words[1]; + char *value = words[2]; + + if(unlikely(!dimension || !*dimension)) { + error("PLUGINSD: '%s' is requesting a SET on chart '%s', without a dimension. Disabling it.", cd->fullfilename, st->id); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + if(unlikely(!value || !*value)) value = NULL; + + if(unlikely(!st)) { + error("PLUGINSD: '%s' is requesting a SET on dimension %s with value %s, without a BEGIN. Disabling it.", cd->fullfilename, dimension, value?value:"<nothing>"); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + 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, strtoll(value, NULL, 0)); + } + else if(likely(hash == BEGIN_HASH && !strcmp(s, "BEGIN"))) { + char *id = words[1]; + char *microseconds_txt = words[2]; + + if(unlikely(!id)) { + error("PLUGINSD: '%s' is requesting a BEGIN without a chart id. Disabling it.", cd->fullfilename); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + st = rrdset_find(id); + if(unlikely(!st)) { + error("PLUGINSD: '%s' is requesting a BEGIN on chart '%s', which does not exist. Disabling it.", cd->fullfilename, id); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + if(likely(st->counter_done)) { + unsigned long long microseconds = 0; + if(microseconds_txt && *microseconds_txt) microseconds = strtoull(microseconds_txt, NULL, 10); + if(microseconds) rrdset_next_usec(st, microseconds); + else rrdset_next_plugins(st); + } + } + else if(likely(hash == END_HASH && !strcmp(s, "END"))) { + if(unlikely(!st)) { + error("PLUGINSD: '%s' is requesting an END, without a BEGIN. Disabling it.", cd->fullfilename); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting an END on chart %s", cd->fullfilename, st->id); + + rrdset_done(st); + st = NULL; + + count++; + } + else if(likely(hash == FLUSH_HASH && !strcmp(s, "FLUSH"))) { + debug(D_PLUGINSD, "PLUGINSD: '%s' is requesting a FLUSH", cd->fullfilename); + st = NULL; + } + else if(likely(hash == CHART_HASH && !strcmp(s, "CHART"))) { + int noname = 0; + st = NULL; + + if((words[1]) != NULL && (words[2]) != NULL && strcmp(words[1], words[2]) == 0) + noname = 1; + + char *type = words[1]; + char *id = NULL; + if(likely(type)) { + id = strchr(type, '.'); + if(likely(id)) { *id = '\0'; id++; } + } + char *name = words[2]; + char *title = words[3]; + char *units = words[4]; + char *family = words[5]; + char *context = words[6]; + char *chart = words[7]; + char *priority_s = words[8]; + char *update_every_s = words[9]; + + if(unlikely(!type || !*type || !id || !*id)) { + error("PLUGINSD: '%s' is requesting a CHART, without a type.id. Disabling it.", cd->fullfilename); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + int priority = 1000; + if(likely(priority_s)) priority = atoi(priority_s); + + int update_every = cd->update_every; + if(likely(update_every_s)) update_every = atoi(update_every_s); + if(unlikely(!update_every)) update_every = cd->update_every; + + int chart_type = RRDSET_TYPE_LINE; + if(unlikely(chart)) chart_type = rrdset_type_id(chart); + + if(unlikely(noname || !name || !*name || strcasecmp(name, "NULL") == 0 || strcasecmp(name, "(NULL)") == 0)) name = NULL; + if(unlikely(!family || !*family)) family = NULL; + if(unlikely(!context || !*context)) context = NULL; + + st = rrdset_find_bytype(type, id); + if(unlikely(!st)) { + debug(D_PLUGINSD, "PLUGINSD: Creating chart type='%s', id='%s', name='%s', family='%s', context='%s', chart='%s', priority=%d, update_every=%d" + , type, id + , name?name:"" + , family?family:"" + , context?context:"" + , rrdset_type_name(chart_type) + , priority + , update_every + ); + + st = rrdset_create(type, id, name, family, context, title, units, priority, update_every, chart_type); + cd->update_every = update_every; + } + else debug(D_PLUGINSD, "PLUGINSD: Chart '%s' already exists. Not adding it again.", st->id); + } + else if(likely(hash == DIMENSION_HASH && !strcmp(s, "DIMENSION"))) { + char *id = words[1]; + char *name = words[2]; + char *algorithm = words[3]; + char *multiplier_s = words[4]; + char *divisor_s = words[5]; + char *options = words[6]; + + if(unlikely(!id || !*id)) { + error("PLUGINSD: '%s' is requesting a DIMENSION, without an id. Disabling it.", cd->fullfilename); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + if(unlikely(!st)) { + error("PLUGINSD: '%s' is requesting a DIMENSION, without a CHART. Disabling it.", cd->fullfilename); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + + long multiplier = 1; + 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 = strtol(divisor_s, NULL, 0); + if(unlikely(!divisor)) divisor = 1; + + if(unlikely(!algorithm || !*algorithm)) algorithm = "absolute"; + + if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: Creating dimension in chart %s, id='%s', name='%s', algorithm='%s', multiplier=%ld, divisor=%ld, hidden='%s'" + , st->id + , id + , name?name:"" + , rrddim_algorithm_name(rrddim_algorithm_id(algorithm)) + , multiplier + , divisor + , options?options:"" + ); + + RRDDIM *rd = rrddim_find(st, id); + if(unlikely(!rd)) { + rd = rrddim_add(st, id, name, multiplier, divisor, rrddim_algorithm_id(algorithm)); + rd->flags = 0x00000000; + if(options && *options) { + if(strstr(options, "hidden") != NULL) rd->flags |= RRDDIM_FLAG_HIDDEN; + if(strstr(options, "noreset") != NULL) rd->flags |= RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS; + if(strstr(options, "nooverflow") != NULL) rd->flags |= RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS; + } + } + else if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: dimension %s/%s already exists. Not adding it again.", st->id, id); + } + else if(unlikely(hash == DISABLE_HASH && !strcmp(s, "DISABLE"))) { + error("PLUGINSD: '%s' called DISABLE. Disabling it.", cd->fullfilename); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } #ifdef DETACH_PLUGINS_FROM_NETDATA - else if(likely(hash == MYPID_HASH && !strcmp(s, "MYPID"))) { - char *pid_s = words[1]; - 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); - } - else if(likely(hash == STOPPING_WAKE_ME_UP_PLEASE_HASH && !strcmp(s, "STOPPING_WAKE_ME_UP_PLEASE"))) { - error("PLUGINSD: '%s' (pid %d) called STOPPING_WAKE_ME_UP_PLEASE.", cd->fullfilename, cd->pid); - - gettimeofday(&now, NULL); - if(unlikely(!usec && !susec)) { - // our first run - susec = cd->rrd_update_every * 1000000ULL; - } - else { - // second+ run - usec = usecdiff(&now, &last) - susec; - error("PLUGINSD: %s last loop took %llu usec (worked for %llu, sleeped for %llu).\n", cd->fullfilename, usec + susec, usec, susec); - if(unlikely(usec < (rrd_update_every * 1000000ULL / 2ULL))) susec = (rrd_update_every * 1000000ULL) - usec; - else susec = rrd_update_every * 1000000ULL / 2ULL; - } - - error("PLUGINSD: %s sleeping for %llu. Will kill with SIGCONT pid %d to wake it up.\n", cd->fullfilename, susec, cd->pid); - usleep(susec); - killpid(cd->pid, SIGCONT); - bcopy(&now, &last, sizeof(struct timeval)); - break; - } + else if(likely(hash == MYPID_HASH && !strcmp(s, "MYPID"))) { + char *pid_s = words[1]; + 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); + } + else if(likely(hash == STOPPING_WAKE_ME_UP_PLEASE_HASH && !strcmp(s, "STOPPING_WAKE_ME_UP_PLEASE"))) { + error("PLUGINSD: '%s' (pid %d) called STOPPING_WAKE_ME_UP_PLEASE.", cd->fullfilename, cd->pid); + + gettimeofday(&now, NULL); + if(unlikely(!usec && !susec)) { + // our first run + susec = cd->rrd_update_every * 1000000ULL; + } + else { + // second+ run + usec = usec_dt(&now, &last) - susec; + error("PLUGINSD: %s last loop took %llu usec (worked for %llu, sleeped for %llu).\n", cd->fullfilename, usec + susec, usec, susec); + if(unlikely(usec < (rrd_update_every * 1000000ULL / 2ULL))) susec = (rrd_update_every * 1000000ULL) - usec; + else susec = rrd_update_every * 1000000ULL / 2ULL; + } + + error("PLUGINSD: %s sleeping for %llu. Will kill with SIGCONT pid %d to wake it up.\n", cd->fullfilename, susec, cd->pid); + usleep(susec); + killpid(cd->pid, SIGCONT); + bcopy(&now, &last, sizeof(struct timeval)); + break; + } #endif - else { - error("PLUGINSD: '%s' is sending command '%s' which is not known by netdata. Disabling it.", cd->fullfilename, s); - cd->enabled = 0; - killpid(cd->pid, SIGTERM); - break; - } - } - - info("PLUGINSD: '%s' on pid %d stopped.", cd->fullfilename, cd->pid); - - // fgets() failed or loop broke - int code = mypclose(fp, cd->pid); - if(code == 1 || code == 127) { - // 1 = DISABLE - // 127 = cannot even run it - error("PLUGINSD: '%s' (pid %d) exited with code %d. Disabling it.", cd->fullfilename, cd->pid, code); - cd->enabled = 0; - } - - if(netdata_exit) { - cd->pid = 0; - cd->enabled = 0; - cd->obsolete = 1; - pthread_exit(NULL); - return NULL; - } - - if(unlikely(!count && cd->enabled)) { - error("PLUGINSD: '%s' (pid %d) does not generate useful output. Waiting a bit before starting it again.", cd->fullfilename, cd->pid); - sleep((unsigned int) (cd->update_every * 10)); - } - - cd->pid = 0; - if(likely(cd->enabled)) sleep((unsigned int) cd->update_every); - else break; - } - - cd->obsolete = 1; - pthread_exit(NULL); - return NULL; + else { + error("PLUGINSD: '%s' is sending command '%s' which is not known by netdata. Disabling it.", cd->fullfilename, s); + cd->enabled = 0; + killpid(cd->pid, SIGTERM); + break; + } + } + if(likely(count)) { + cd->successful_collections += count; + cd->serial_failures = 0; + } + else + cd->serial_failures++; + + info("PLUGINSD: '%s' on pid %d stopped after %zu successful data collections (ENDs).", cd->fullfilename, cd->pid, count); + + // get the return code + int code = mypclose(fp, cd->pid); + + if(netdata_exit) { + cd->pid = 0; + cd->enabled = 0; + cd->obsolete = 1; + pthread_exit(NULL); + return NULL; + } + + if(code != 0) { + // the plugin reports failure + + if(likely(!cd->successful_collections)) { + // nothing collected - disable it + error("PLUGINSD: '%s' exited with error code %d. Disabling it.", cd->fullfilename, code); + cd->enabled = 0; + } + else { + // we have collected something + + if(likely(cd->serial_failures <= 10)) { + error("PLUGINSD: '%s' exited with error code %d, but has given useful output in the past (%zu times). Waiting a bit before starting it again.", cd->fullfilename, code, cd->successful_collections); + sleep((unsigned int) (cd->update_every * 10)); + } + else { + error("PLUGINSD: '%s' exited with error code %d, but has given useful output in the past (%zu times). We tried %zu times to restart it, but it failed to generate data. Disabling it.", cd->fullfilename, code, cd->successful_collections, cd->serial_failures); + cd->enabled = 0; + } + } + } + else { + // the plugin reports success + + if(unlikely(!cd->successful_collections)) { + // we have collected nothing so far + + if(likely(cd->serial_failures <= 10)) { + error("PLUGINSD: '%s' (pid %d) does not generate useful output but it reports success (exits with 0). Waiting a bit before starting it again.", cd->fullfilename, cd->pid); + sleep((unsigned int) (cd->update_every * 10)); + } + else { + error("PLUGINSD: '%s' (pid %d) does not generate useful output, although it reports success (exits with 0), but we have tried %zu times to collect something. Disabling it.", cd->fullfilename, cd->pid, cd->serial_failures); + cd->enabled = 0; + } + } + else + sleep((unsigned int) cd->update_every); + } + cd->pid = 0; + + if(unlikely(!cd->enabled)) + break; + } + + cd->obsolete = 1; + pthread_exit(NULL); + return NULL; } -void *pluginsd_main(void *ptr) -{ - if(ptr) { ; } - - info("PLUGINS.D 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."); - - char *dir_name = config_get("plugins", "plugins directory", PLUGINS_DIR); - int automatic_run = config_get_boolean("plugins", "enable running new plugins", 1); - int scan_frequency = (int) config_get_number("plugins", "check for new plugins every", 60); - DIR *dir = NULL; - struct dirent *file = NULL; - struct plugind *cd; - - // enable the apps plugin by default - // config_get_boolean("plugins", "apps", 1); - - if(scan_frequency < 1) scan_frequency = 1; - - while(likely(1)) { - if(unlikely(netdata_exit)) break; - - dir = opendir(dir_name); - if(unlikely(!dir)) { - error("Cannot open directory '%s'.", dir_name); - pthread_exit(NULL); - return NULL; - } - - while(likely((file = readdir(dir)))) { - if(unlikely(netdata_exit)) break; - - debug(D_PLUGINSD, "PLUGINSD: Examining file '%s'", file->d_name); - - if(unlikely(strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0)) continue; - - int len = (int) strlen(file->d_name); - if(unlikely(len <= (int)PLUGINSD_FILE_SUFFIX_LEN)) continue; - if(unlikely(strcmp(PLUGINSD_FILE_SUFFIX, &file->d_name[len - (int)PLUGINSD_FILE_SUFFIX_LEN]) != 0)) { - debug(D_PLUGINSD, "PLUGINSD: File '%s' does not end in '%s'.", file->d_name, PLUGINSD_FILE_SUFFIX); - continue; - } - - char pluginname[CONFIG_MAX_NAME + 1]; - 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)) { - debug(D_PLUGINSD, "PLUGINSD: plugin '%s' is not enabled", file->d_name); - continue; - } - - // check if it runs already - for(cd = pluginsd_root ; likely(cd) ; cd = cd->next) { - if(unlikely(strcmp(cd->filename, file->d_name) == 0)) break; - } - if(likely(cd && !cd->obsolete)) { - debug(D_PLUGINSD, "PLUGINSD: plugin '%s' is already running", cd->filename); - continue; - } - - // it is not running - // allocate a new one, or use the obsolete one - if(unlikely(!cd)) { - cd = calloc(sizeof(struct plugind), 1); - if(unlikely(!cd)) fatal("Cannot allocate memory for plugin."); - - snprintfz(cd->id, CONFIG_MAX_NAME, "plugin:%s", pluginname); - - 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 = ""; - 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; - pluginsd_root = cd; - } - cd->obsolete = 0; - - if(unlikely(!cd->enabled)) continue; - - // spawn a new thread for it - if(unlikely(pthread_create(&cd->thread, NULL, pluginsd_worker_thread, cd) != 0)) { - error("PLUGINSD: failed to create new thread for plugin '%s'.", cd->filename); - cd->obsolete = 1; - } - else if(unlikely(pthread_detach(cd->thread) != 0)) - error("PLUGINSD: Cannot request detach of newly created thread for plugin '%s'.", cd->filename); - } - - closedir(dir); - sleep((unsigned int) scan_frequency); - } - - pthread_exit(NULL); - return NULL; +void *pluginsd_main(void *ptr) { + (void)ptr; + + info("PLUGINS.D 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."); + + char *dir_name = config_get("plugins", "plugins directory", PLUGINS_DIR); + int automatic_run = config_get_boolean("plugins", "enable running new plugins", 1); + int scan_frequency = (int) config_get_number("plugins", "check for new plugins every", 60); + DIR *dir = NULL; + struct dirent *file = NULL; + struct plugind *cd; + + // enable the apps plugin by default + // config_get_boolean("plugins", "apps", 1); + + if(scan_frequency < 1) scan_frequency = 1; + + while(likely(1)) { + if(unlikely(netdata_exit)) break; + + dir = opendir(dir_name); + if(unlikely(!dir)) { + error("Cannot open directory '%s'.", dir_name); + pthread_exit(NULL); + return NULL; + } + + while(likely((file = readdir(dir)))) { + if(unlikely(netdata_exit)) break; + + debug(D_PLUGINSD, "PLUGINSD: Examining file '%s'", file->d_name); + + if(unlikely(strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0)) continue; + + int len = (int) strlen(file->d_name); + if(unlikely(len <= (int)PLUGINSD_FILE_SUFFIX_LEN)) continue; + if(unlikely(strcmp(PLUGINSD_FILE_SUFFIX, &file->d_name[len - (int)PLUGINSD_FILE_SUFFIX_LEN]) != 0)) { + debug(D_PLUGINSD, "PLUGINSD: File '%s' does not end in '%s'.", file->d_name, PLUGINSD_FILE_SUFFIX); + continue; + } + + char pluginname[CONFIG_MAX_NAME + 1]; + 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)) { + debug(D_PLUGINSD, "PLUGINSD: plugin '%s' is not enabled", file->d_name); + continue; + } + + // check if it runs already + for(cd = pluginsd_root ; likely(cd) ; cd = cd->next) { + if(unlikely(strcmp(cd->filename, file->d_name) == 0)) break; + } + if(likely(cd && !cd->obsolete)) { + debug(D_PLUGINSD, "PLUGINSD: plugin '%s' is already running", cd->filename); + continue; + } + + // it is not running + // allocate a new one, or use the obsolete one + if(unlikely(!cd)) { + cd = callocz(sizeof(struct plugind), 1); + + snprintfz(cd->id, CONFIG_MAX_NAME, "plugin:%s", pluginname); + + 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 = ""; + 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; + pluginsd_root = cd; + } + cd->obsolete = 0; + + if(unlikely(!cd->enabled)) continue; + + // spawn a new thread for it + if(unlikely(pthread_create(&cd->thread, NULL, pluginsd_worker_thread, cd) != 0)) { + error("PLUGINSD: failed to create new thread for plugin '%s'.", cd->filename); + cd->obsolete = 1; + } + else if(unlikely(pthread_detach(cd->thread) != 0)) + error("PLUGINSD: Cannot request detach of newly created thread for plugin '%s'.", cd->filename); + } + + closedir(dir); + sleep((unsigned int) scan_frequency); + } + + pthread_exit(NULL); + return NULL; } diff --git a/src/plugins_d.h b/src/plugins_d.h index 11e89e0ac..6f1fbd6e1 100644 --- a/src/plugins_d.h +++ b/src/plugins_d.h @@ -1,7 +1,3 @@ -#include <sys/types.h> -#include <unistd.h> - - #ifndef NETDATA_PLUGINS_D_H #define NETDATA_PLUGINS_D_H 1 @@ -11,22 +7,28 @@ #define PLUGINSD_LINE_MAX 1024 struct plugind { - char id[CONFIG_MAX_NAME+1]; // config node id + char id[CONFIG_MAX_NAME+1]; // config node id + + char filename[FILENAME_MAX+1]; // just the filename + char fullfilename[FILENAME_MAX+1]; // with path + char cmd[PLUGINSD_CMD_MAX+1]; // the command that is executes + + pid_t pid; + pthread_t thread; - char filename[FILENAME_MAX+1]; // just the filename - char fullfilename[FILENAME_MAX+1]; // with path - char cmd[PLUGINSD_CMD_MAX+1]; // the command that is executes + size_t successful_collections; // the number of times we have seen + // values collected from this plugin - pid_t pid; - pthread_t thread; + size_t serial_failures; // the number of times the plugin started + // without collecting values - int update_every; - int obsolete; - int enabled; + int update_every; // the plugin default data collection frequency + int obsolete; // do not touch this structure after setting this to 1 + int enabled; // if this is enabled or not - time_t started_t; + time_t started_t; - struct plugind *next; + struct plugind *next; }; extern struct plugind *pluginsd_root; diff --git a/src/popen.c b/src/popen.c index 06f27c0b7..193efc0f3 100644 --- a/src/popen.c +++ b/src/popen.c @@ -1,54 +1,43 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <signal.h> -#include <unistd.h> -#include <stdlib.h> -#include <sys/types.h> -#include <sys/wait.h> - -#include "log.h" -#include "popen.h" #include "common.h" /* struct mypopen { - pid_t pid; - FILE *fp; - struct mypopen *next; - struct mypopen *prev; + pid_t pid; + FILE *fp; + struct mypopen *next; + struct mypopen *prev; }; static struct mypopen *mypopen_root = NULL; static void mypopen_add(FILE *fp, pid_t *pid) { - struct mypopen *mp = malloc(sizeof(struct mypopen)); - if(!mp) { - fatal("Cannot allocate %zu bytes", sizeof(struct mypopen)) - return; - } - - mp->fp = fp; - mp->pid = pid; - mp->next = popen_root; - mp->prev = NULL; - if(mypopen_root) mypopen_root->prev = mp; - mypopen_root = mp; + struct mypopen *mp = malloc(sizeof(struct mypopen)); + if(!mp) { + fatal("Cannot allocate %zu bytes", sizeof(struct mypopen)) + return; + } + + mp->fp = fp; + mp->pid = pid; + mp->next = popen_root; + mp->prev = NULL; + if(mypopen_root) mypopen_root->prev = mp; + mypopen_root = mp; } static void mypopen_del(FILE *fp) { - struct mypopen *mp; - - for(mp = mypopen_root; mp; mp = mp->next) - if(mp->fd == fp) break; - - if(!mp) error("Cannot find mypopen() file pointer in open childs."); - else { - if(mp->next) mp->next->prev = mp->prev; - if(mp->prev) mp->prev->next = mp->next; - if(mypopen_root == mp) mypopen_root = mp->next; - free(mp); - } + struct mypopen *mp; + + for(mp = mypopen_root; mp; mp = mp->next) + if(mp->fd == fp) break; + + if(!mp) error("Cannot find mypopen() file pointer in open childs."); + else { + if(mp->next) mp->next->prev = mp->prev; + if(mp->prev) mp->prev->next = mp->next; + if(mypopen_root == mp) mypopen_root = mp->next; + free(mp); + } } */ #define PIPE_READ 0 @@ -56,133 +45,152 @@ static void mypopen_del(FILE *fp) { FILE *mypopen(const char *command, pid_t *pidptr) { - int pipefd[2]; - - if(pipe(pipefd) == -1) return NULL; - - int pid = fork(); - if(pid == -1) { - close(pipefd[PIPE_READ]); - close(pipefd[PIPE_WRITE]); - return NULL; - } - if(pid != 0) { - // the parent - *pidptr = pid; - close(pipefd[PIPE_WRITE]); - FILE *fp = fdopen(pipefd[PIPE_READ], "r"); - /*mypopen_add(fp, pid);*/ - return(fp); - } - // the child - - // close all files - int i; - for(i = (int) (sysconf(_SC_OPEN_MAX) - 1); i > 0; i--) - if(i != STDIN_FILENO && i != STDERR_FILENO && i != pipefd[PIPE_WRITE]) close(i); - - // move the pipe to stdout - if(pipefd[PIPE_WRITE] != STDOUT_FILENO) { - dup2(pipefd[PIPE_WRITE], STDOUT_FILENO); - close(pipefd[PIPE_WRITE]); - } + int pipefd[2]; + + if(pipe(pipefd) == -1) return NULL; + + int pid = fork(); + if(pid == -1) { + close(pipefd[PIPE_READ]); + close(pipefd[PIPE_WRITE]); + return NULL; + } + if(pid != 0) { + // the parent + *pidptr = pid; + close(pipefd[PIPE_WRITE]); + FILE *fp = fdopen(pipefd[PIPE_READ], "r"); + /*mypopen_add(fp, pid);*/ + return(fp); + } + // the child + + // close all files + int i; + for(i = (int) (sysconf(_SC_OPEN_MAX) - 1); i > 0; i--) + if(i != STDIN_FILENO && i != STDERR_FILENO && i != pipefd[PIPE_WRITE]) close(i); + + // move the pipe to stdout + if(pipefd[PIPE_WRITE] != STDOUT_FILENO) { + dup2(pipefd[PIPE_WRITE], STDOUT_FILENO); + close(pipefd[PIPE_WRITE]); + } #ifdef DETACH_PLUGINS_FROM_NETDATA - // this was an attempt to detach the child and use the suspend mode charts.d - // unfortunatelly it does not work as expected. + // this was an attempt to detach the child and use the suspend mode charts.d + // unfortunatelly it does not work as expected. + + // fork again to become session leader + pid = fork(); + if(pid == -1) + error("pre-execution of command '%s' on pid %d: Cannot fork 2nd time.", command, getpid()); - // fork again to become session leader - pid = fork(); - if(pid == -1) fprintf(stderr, "Cannot fork again on pid %d\n", getpid()); - if(pid != 0) { - // the parent - exit(0); - } + if(pid != 0) { + // the parent + exit(0); + } - // set a new process group id for just this child - if( setpgid(0, 0) != 0 ) - error("Cannot set a new process group for pid %d (%s)", getpid(), strerror(errno)); + // set a new process group id for just this child + if( setpgid(0, 0) != 0 ) + error("pre-execution of command '%s' on pid %d: Cannot set a new process group.", command, getpid()); - if( getpgid(0) != getpid() ) - error("Process group set is incorrect. Expected %d, found %d", getpid(), getpgid(0)); + if( getpgid(0) != getpid() ) + error("pre-execution of command '%s' on pid %d: Cannot set a new process group. Process group set is incorrect. Expected %d, found %d", command, getpid(), getpid(), getpgid(0)); - if( setsid() != 0 ) - error("Cannot set session id for pid %d (%s)", getpid(), strerror(errno)); + if( setsid() != 0 ) + error("pre-execution of command '%s' on pid %d: Cannot set session id.", command, getpid()); - fprintf(stdout, "MYPID %d\n", getpid()); - fflush(NULL); + fprintf(stdout, "MYPID %d\n", getpid()); + fflush(NULL); #endif - // reset all signals - { - 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); - exit(1); + // reset all signals + { + sigset_t sigset; + sigfillset(&sigset); + + if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) + error("pre-execution of command '%s' on pid %d: could not unblock signals for threads.", command, getpid()); + + // 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(SIGINT, &sa, NULL) == -1) + error("pre-execution of command '%s' on pid %d: failed to set default signal handler for SIGINT.", command, getpid()); + + if(sigaction(SIGTERM, &sa, NULL) == -1) + error("pre-execution of command '%s' on pid %d: failed to set default signal handler for SIGTERM.", command, getpid()); + + if(sigaction(SIGPIPE, &sa, NULL) == -1) + error("pre-execution of command '%s' on pid %d: failed to set default signal handler for SIGPIPE.", command, getpid()); + + if(sigaction(SIGHUP, &sa, NULL) == -1) + error("pre-execution of command '%s' on pid %d: failed to set default signal handler for SIGHUP.", command, getpid()); + + if(sigaction(SIGUSR1, &sa, NULL) == -1) + error("pre-execution of command '%s' on pid %d: failed to set default signal handler for SIGUSR1.", command, getpid()); + + if(sigaction(SIGUSR2, &sa, NULL) == -1) + error("pre-execution of command '%s' on pid %d: failed to set default signal handler for SIGUSR2.", command, getpid()); + } + + info("executing command: '%s' on pid %d.", command, getpid()); + execl("/bin/sh", "sh", "-c", command, NULL); + exit(1); } int mypclose(FILE *fp, pid_t pid) { - debug(D_EXIT, "Request to mypclose() on pid %d", pid); - - /*mypopen_del(fp);*/ - fclose(fp); - - siginfo_t info; - if(waitid(P_PID, (id_t) pid, &info, WEXITED) != -1) { - switch(info.si_code) { - case CLD_EXITED: - error("pid %d exited with code %d.", info.si_pid, info.si_status); - return(info.si_status); - break; - - case CLD_KILLED: - error("pid %d killed by signal %d.", info.si_pid, info.si_status); - return(-1); - break; - - case CLD_DUMPED: - error("pid %d core dumped by signal %d.", info.si_pid, info.si_status); - return(-2); - break; - - case CLD_STOPPED: - error("pid %d stopped by signal %d.", info.si_pid, info.si_status); - return(0); - break; - - case CLD_TRAPPED: - error("pid %d trapped by signal %d.", info.si_pid, info.si_status); - return(-4); - break; - - case CLD_CONTINUED: - error("pid %d continued by signal %d.", info.si_pid, info.si_status); - return(0); - break; - - default: - error("pid %d gave us a SIGCHLD with code %d and status %d.", info.si_pid, info.si_code, info.si_status); - return(-5); - break; - } - } - else error("Cannot waitid() for pid %d", pid); - return 0; + debug(D_EXIT, "Request to mypclose() on pid %d", pid); + + /*mypopen_del(fp);*/ + fclose(fp); + + siginfo_t info; + if(waitid(P_PID, (id_t) pid, &info, WEXITED) != -1) { + switch(info.si_code) { + case CLD_EXITED: + if(info.si_status) + error("child pid %d exited with code %d.", info.si_pid, info.si_status); + return(info.si_status); + break; + + case CLD_KILLED: + error("child pid %d killed by signal %d.", info.si_pid, info.si_status); + return(-1); + break; + + case CLD_DUMPED: + error("child pid %d core dumped by signal %d.", info.si_pid, info.si_status); + return(-2); + break; + + case CLD_STOPPED: + error("child pid %d stopped by signal %d.", info.si_pid, info.si_status); + return(0); + break; + + case CLD_TRAPPED: + error("child pid %d trapped by signal %d.", info.si_pid, info.si_status); + return(-4); + break; + + case CLD_CONTINUED: + error("child pid %d continued by signal %d.", info.si_pid, info.si_status); + return(0); + break; + + default: + error("child pid %d gave us a SIGCHLD with code %d and status %d.", info.si_pid, info.si_code, info.si_status); + return(-5); + break; + } + } + else + error("Cannot waitid() for pid %d", pid); + + return 0; } diff --git a/src/popen.h b/src/popen.h index 10680f0c8..90845e1fb 100644 --- a/src/popen.h +++ b/src/popen.h @@ -1,7 +1,3 @@ -#include <stdio.h> -#include <unistd.h> -#include <sys/types.h> - #ifndef NETDATA_POPEN_H #define NETDATA_POPEN_H 1 diff --git a/src/proc_diskstats.c b/src/proc_diskstats.c index c62a1351c..459d5a133 100644 --- a/src/proc_diskstats.c +++ b/src/proc_diskstats.c @@ -1,578 +1,700 @@ -#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 "procfile.h" -#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; +#define DISK_TYPE_PHYSICAL 1 +#define DISK_TYPE_PARTITION 2 +#define DISK_TYPE_CONTAINER 3 + +static struct disk { + char *disk; // the name of the disk (sda, sdb, etc) + unsigned long major; + unsigned long minor; + int sector_size; + int type; + char *mount_point; + + // disk options caching + int configured; + int do_io; + int do_ops; + int do_mops; + int do_iotime; + int do_qops; + int do_util; + int do_backlog; + int do_space; + int do_inodes; + + 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. - // this is sequential, but since we just walk through - // and the number of disks / partitions in a system - // should not be that many, it should be acceptable - for(d = disk_root; d ; d = d->next) - if(unlikely(d->major == major && d->minor == minor)) - break; - - // if we found it, return it - if(likely(d)) - return d; - - if(unlikely(!path_find_block_device_partition[0])) { - char filename[FILENAME_MAX + 1]; - 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 - // create a new disk structure - d = (struct disk *)malloc(sizeof(struct disk)); - if(!d) fatal("Cannot allocate memory for struct disk in proc_diskstats."); - - d->major = major; - d->minor = minor; - d->partition_id = -1; - d->next = NULL; - - // append it to the list - if(!disk_root) - disk_root = d; - else { - struct disk *last; - for(last = disk_root; last->next ;last = last->next); - last->next = d; - } - - // find if it is a partition - // by reading /sys/dev/block/MAJOR:MINOR/partition - char buffer[FILENAME_MAX + 1]; - snprintfz(buffer, FILENAME_MAX, path_find_block_device_partition, major, minor); - - int fd = open(buffer, O_RDONLY, 0666); - if(likely(fd != -1)) { - // we opened it - int bytes = read(fd, buffer, FILENAME_MAX); - close(fd); - - if(bytes > 0) - d->partition_id = strtoul(buffer, NULL, 10); - } - // 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; +static struct mountinfo *disk_mountinfo_root = NULL; + +static struct disk *get_disk(unsigned long major, unsigned long minor, char *disk) { + static char path_to_get_hw_sector_size[FILENAME_MAX + 1] = ""; + static char path_to_get_hw_sector_size_partitions[FILENAME_MAX + 1] = ""; + static char path_find_block_device[FILENAME_MAX + 1] = ""; + struct disk *d; + + // search for it in our RAM list. + // this is sequential, but since we just walk through + // and the number of disks / partitions in a system + // should not be that many, it should be acceptable + for(d = disk_root; d ; d = d->next) + if(unlikely(d->major == major && d->minor == minor)) + break; + + // if we found it, return it + if(likely(d)) + return d; + + // not found + // create a new disk structure + d = (struct disk *)mallocz(sizeof(struct disk)); + + d->disk = strdupz(disk); + d->major = major; + d->minor = minor; + d->type = DISK_TYPE_PHYSICAL; // Default type. Changed later if not correct. + d->configured = 0; + d->sector_size = 512; // the default, will be changed below + d->next = NULL; + + // append it to the list + if(!disk_root) + disk_root = d; + else { + struct disk *last; + for(last = disk_root; last->next ;last = last->next); + last->next = d; + } + + // ------------------------------------------------------------------------ + // find the type of the device + + char buffer[FILENAME_MAX + 1]; + + // get the default path for finding info about the block device + if(unlikely(!path_find_block_device[0])) { + snprintfz(buffer, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/dev/block/%lu:%lu/%s"); + snprintfz(path_find_block_device, FILENAME_MAX, "%s", config_get("plugin:proc:/proc/diskstats", "path to get block device infos", buffer)); + } + + // find if it is a partition + // by checking if /sys/dev/block/MAJOR:MINOR/partition is readable. + snprintfz(buffer, FILENAME_MAX, path_find_block_device, major, minor, "partition"); + if(access(buffer, R_OK) == 0) { + d->type = DISK_TYPE_PARTITION; + } else { + // find if it is a container + // by checking if /sys/dev/block/MAJOR:MINOR/slaves has entries + snprintfz(buffer, FILENAME_MAX, path_find_block_device, major, minor, "slaves/"); + DIR *dirp = opendir(buffer); + if (dirp != NULL) { + struct dirent *dp; + while( (dp = readdir(dirp)) ) { + // . and .. are also files in empty folders. + if(strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + continue; + } + + d->type = DISK_TYPE_CONTAINER; + + // Stop the loop after we found one file. + break; + } + if(closedir(dirp) == -1) + error("Unable to close dir %s", buffer); + } + } + + // ------------------------------------------------------------------------ + // check if we can find its mount point + + // mountinfo_find() can be called with NULL disk_mountinfo_root + struct mountinfo *mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor); + if(unlikely(!mi)) { + // mountinfo_free() can be called with NULL disk_mountinfo_root + mountinfo_free(disk_mountinfo_root); + + // re-read mountinfo in case something changed + disk_mountinfo_root = mountinfo_read(); + + // search again for this disk + mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor); + } + + if(mi) + d->mount_point = strdupz(mi->mount_point); + // no need to check for NULL + else + d->mount_point = NULL; + + // ------------------------------------------------------------------------ + // find the disk sector size + + if(!path_to_get_hw_sector_size[0]) { + snprintfz(buffer, 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", buffer)); + } + if(!path_to_get_hw_sector_size_partitions[0]) { + snprintfz(buffer, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/dev/block/%lu:%lu/subsystem/%s/../queue/hw_sector_size"); + snprintfz(path_to_get_hw_sector_size_partitions, FILENAME_MAX, "%s", config_get("plugin:proc:/proc/diskstats", "path to get h/w sector size for partitions", buffer)); + } + + { + char tf[FILENAME_MAX + 1], *t; + strncpyz(tf, d->disk, FILENAME_MAX); + + // replace all / with ! + for(t = tf; *t ;t++) + if(*t == '/') *t = '!'; + + if(d->type == DISK_TYPE_PARTITION) + snprintfz(buffer, FILENAME_MAX, path_to_get_hw_sector_size_partitions, d->major, d->minor, tf); + else + snprintfz(buffer, FILENAME_MAX, path_to_get_hw_sector_size, tf); + + FILE *fpss = fopen(buffer, "r"); + if(fpss) { + char buffer2[1024 + 1]; + char *tmp = fgets(buffer2, 1024, fpss); + + if(tmp) { + d->sector_size = atoi(tmp); + if(d->sector_size <= 0) { + error("Invalid sector size %d for device %s in %s. Assuming 512.", d->sector_size, d->disk, buffer); + d->sector_size = 512; + } + } + else error("Cannot read data for sector size for device %s from %s. Assuming 512.", d->disk, buffer); + + fclose(fpss); + } + else error("Cannot read sector size for device %s from %s. Assuming 512.", d->disk, buffer); + } + + return d; +} + +static inline int select_positive_option(int option1, int option2) { + if(option1 == CONFIG_ONDEMAND_YES || option2 == CONFIG_ONDEMAND_YES) + return CONFIG_ONDEMAND_YES; + else if(option1 == CONFIG_ONDEMAND_ONDEMAND || option2 == CONFIG_ONDEMAND_ONDEMAND) + return CONFIG_ONDEMAND_ONDEMAND; + + return CONFIG_ONDEMAND_NO; } int do_proc_diskstats(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static char path_to_get_hw_sector_size[FILENAME_MAX + 1] = ""; - static int enable_new_disks = -1; - static int do_io = -1, do_ops = -1, do_mops = -1, do_iotime = -1, do_qops = -1, do_util = -1, do_backlog = -1; - - if(enable_new_disks == -1) enable_new_disks = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "enable new disks detected at runtime", CONFIG_ONDEMAND_ONDEMAND); - - if(do_io == -1) do_io = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "bandwidth for all disks", CONFIG_ONDEMAND_ONDEMAND); - if(do_ops == -1) do_ops = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "operations for all disks", CONFIG_ONDEMAND_ONDEMAND); - if(do_mops == -1) do_mops = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "merged operations for all disks", CONFIG_ONDEMAND_ONDEMAND); - if(do_iotime == -1) do_iotime = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "i/o time for all disks", CONFIG_ONDEMAND_ONDEMAND); - if(do_qops == -1) do_qops = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "queued operations for all disks", CONFIG_ONDEMAND_ONDEMAND); - if(do_util == -1) do_util = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "utilization percentage for all disks", CONFIG_ONDEMAND_ONDEMAND); - if(do_backlog == -1)do_backlog = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "backlog for all disks", CONFIG_ONDEMAND_ONDEMAND); - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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]; - 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); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - for(l = 0; l < lines ;l++) { - char *disk; - unsigned long long major = 0, minor = 0, - reads = 0, mreads = 0, readsectors = 0, readms = 0, - writes = 0, mwrites = 0, writesectors = 0, writems = 0, - queued_ios = 0, busy_ms = 0, backlog_ms = 0; - - unsigned long long last_reads = 0, last_readsectors = 0, last_readms = 0, - last_writes = 0, last_writesectors = 0, last_writems = 0, - last_busy_ms = 0; - - words = procfile_linewords(ff, l); - if(words < 14) continue; - - major = strtoull(procfile_lineword(ff, l, 0), NULL, 10); - minor = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - disk = procfile_lineword(ff, l, 2); - - // # of reads completed # of writes completed - // This is the total number of reads or writes completed successfully. - reads = strtoull(procfile_lineword(ff, l, 3), NULL, 10); // rd_ios - writes = strtoull(procfile_lineword(ff, l, 7), NULL, 10); // wr_ios - - // # of reads merged # of writes merged - // Reads and writes which are adjacent to each other may be merged for - // efficiency. Thus two 4K reads may become one 8K read before it is - // ultimately handed to the disk, and so it will be counted (and queued) - mreads = strtoull(procfile_lineword(ff, l, 4), NULL, 10); // rd_merges_or_rd_sec - mwrites = strtoull(procfile_lineword(ff, l, 8), NULL, 10); // wr_merges - - // # of sectors read # of sectors written - // This is the total number of sectors read or written successfully. - readsectors = strtoull(procfile_lineword(ff, l, 5), NULL, 10); // rd_sec_or_wr_ios - writesectors = strtoull(procfile_lineword(ff, l, 9), NULL, 10); // wr_sec - - // # of milliseconds spent reading # of milliseconds spent writing - // This is the total number of milliseconds spent by all reads or writes (as - // measured from __make_request() to end_that_request_last()). - readms = strtoull(procfile_lineword(ff, l, 6), NULL, 10); // rd_ticks_or_wr_sec - writems = strtoull(procfile_lineword(ff, l, 10), NULL, 10); // wr_ticks - - // # of I/Os currently in progress - // The only field that should go to zero. Incremented as requests are - // given to appropriate struct request_queue and decremented as they finish. - queued_ios = strtoull(procfile_lineword(ff, l, 11), NULL, 10); // ios_pgr - - // # of milliseconds spent doing I/Os - // This field increases so long as field queued_ios is nonzero. - busy_ms = strtoull(procfile_lineword(ff, l, 12), NULL, 10); // tot_ticks - - // weighted # of milliseconds spent doing I/Os - // This field is incremented at each I/O start, I/O completion, I/O - // merge, or read of these stats by the number of I/Os in progress - // (field queued_ios) times the number of milliseconds spent doing I/O since the - // last update of this field. This can provide an easy measure of both - // I/O completion time and the backlog that may be accumulating. - backlog_ms = strtoull(procfile_lineword(ff, l, 13), NULL, 10); // rq_ticks - - int def_enabled = 0; - - // remove slashes from disk names - char *s; - for(s = disk; *s ;s++) if(*s == '/') *s = '_'; - - struct disk *d = get_disk(major, minor); - if(d->partition_id == -1) - def_enabled = enable_new_disks; - else - def_enabled = 0; - - char *family = d->family; - if(!family) family = disk; - -/* - switch(major) { - case 9: // MDs - case 43: // network block - case 144: // nfs - case 145: // nfs - case 146: // nfs - case 199: // veritas - case 201: // veritas - case 251: // dm - case 253: // virtio - def_enabled = enable_new_disks; - break; - - case 48: // RAID - case 49: // RAID - case 50: // RAID - case 51: // RAID - case 52: // RAID - case 53: // RAID - case 54: // RAID - case 55: // RAID - case 112: // RAID - case 136: // RAID - case 137: // RAID - case 138: // RAID - case 139: // RAID - case 140: // RAID - case 141: // RAID - case 142: // RAID - case 143: // RAID - case 179: // MMC - case 180: // USB - if(minor % 8) def_enabled = 0; // partitions - else def_enabled = enable_new_disks; - break; - - case 8: // scsi disks - case 65: // scsi disks - case 66: // scsi disks - case 67: // scsi disks - case 68: // scsi disks - case 69: // scsi disks - case 70: // scsi disks - case 71: // scsi disks - case 72: // scsi disks - case 73: // scsi disks - case 74: // scsi disks - case 75: // scsi disks - case 76: // scsi disks - case 77: // scsi disks - case 78: // scsi disks - case 79: // scsi disks - case 80: // i2o - case 81: // i2o - case 82: // i2o - case 83: // i2o - case 84: // i2o - case 85: // i2o - case 86: // i2o - case 87: // i2o - case 101: // hyperdisk - case 102: // compressed - case 104: // scsi - case 105: // scsi - case 106: // scsi - case 107: // scsi - case 108: // scsi - case 109: // scsi - case 110: // scsi - case 111: // scsi - case 114: // bios raid - case 116: // ram board - case 128: // scsi - case 129: // scsi - case 130: // scsi - case 131: // scsi - case 132: // scsi - case 133: // scsi - case 134: // scsi - case 135: // scsi - case 153: // raid - case 202: // xen - case 254: // virtio3 - case 256: // flash - case 257: // flash - case 259: // nvme0n1 issue #119 - if(minor % 16) def_enabled = 0; // partitions - else def_enabled = enable_new_disks; - break; - - case 160: // raid - case 161: // raid - if(minor % 32) def_enabled = 0; // partitions - else def_enabled = enable_new_disks; - break; - - case 3: // ide - case 13: // 8bit ide - case 22: // ide - case 33: // ide - case 34: // ide - case 56: // ide - case 57: // ide - case 88: // ide - case 89: // ide - case 90: // ide - case 91: // ide - if(minor % 64) def_enabled = 0; // partitions - else def_enabled = enable_new_disks; - break; - - case 252: // zram - def_enabled = 0; - break; - - default: - def_enabled = 0; - break; - } -*/ - - int ddo_io = do_io, ddo_ops = do_ops, ddo_mops = do_mops, ddo_iotime = do_iotime, ddo_qops = do_qops, ddo_util = do_util, ddo_backlog = do_backlog; - - // check which charts are enabled for this disk - { - char var_name[4096 + 1]; - 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; - - - ddo_io = config_get_boolean_ondemand(var_name, "bandwidth", ddo_io); - ddo_ops = config_get_boolean_ondemand(var_name, "operations", ddo_ops); - ddo_mops = config_get_boolean_ondemand(var_name, "merged operations", ddo_mops); - ddo_iotime = config_get_boolean_ondemand(var_name, "i/o time", ddo_iotime); - ddo_qops = config_get_boolean_ondemand(var_name, "queued operations", ddo_qops); - ddo_util = config_get_boolean_ondemand(var_name, "utilization percentage", ddo_util); - ddo_backlog = config_get_boolean_ondemand(var_name, "backlog", ddo_backlog); - - // by default, do not add charts that do not have values - if(ddo_io == CONFIG_ONDEMAND_ONDEMAND && !reads && !writes) ddo_io = 0; - if(ddo_mops == CONFIG_ONDEMAND_ONDEMAND && mreads == 0 && mwrites == 0) ddo_mops = 0; - if(ddo_iotime == CONFIG_ONDEMAND_ONDEMAND && readms == 0 && writems == 0) ddo_iotime = 0; - if(ddo_util == CONFIG_ONDEMAND_ONDEMAND && busy_ms == 0) ddo_util = 0; - if(ddo_backlog == CONFIG_ONDEMAND_ONDEMAND && backlog_ms == 0) ddo_backlog = 0; - if(ddo_qops == CONFIG_ONDEMAND_ONDEMAND && backlog_ms == 0) ddo_qops = 0; - - // for absolute values, we need to switch the setting to 'yes' - // to allow it refresh from now on - if(ddo_qops == CONFIG_ONDEMAND_ONDEMAND) config_set(var_name, "queued operations", "yes"); - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - int sector_size = 512; - if(ddo_io) { - st = rrdset_find_bytype(RRD_TYPE_DISK, disk); - if(!st) { - char tf[FILENAME_MAX + 1], *t; - char ssfilename[FILENAME_MAX + 1]; - - strncpyz(tf, disk, FILENAME_MAX); - - // replace all / with ! - while((t = strchr(tf, '/'))) *t = '!'; - - snprintfz(ssfilename, FILENAME_MAX, path_to_get_hw_sector_size, tf); - FILE *fpss = fopen(ssfilename, "r"); - if(fpss) { - char ssbuffer[1025]; - char *tmp = fgets(ssbuffer, 1024, fpss); - - if(tmp) { - sector_size = atoi(tmp); - if(sector_size <= 0) { - error("Invalid sector size %d for device %s in %s. Assuming 512.", sector_size, disk, ssfilename); - sector_size = 512; - } - } - else error("Cannot read data for sector size for device %s from %s. Assuming 512.", disk, ssfilename); - - fclose(fpss); - } - else error("Cannot read sector size for device %s from %s. Assuming 512.", disk, ssfilename); - - 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); - } - else rrdset_next_usec(st, dt); - - last_readsectors = rrddim_set(st, "reads", readsectors); - last_writesectors = rrddim_set(st, "writes", writesectors); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_ops) { - st = rrdset_find_bytype("disk_ops", disk); - if(!st) { - 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); - rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next_usec(st, dt); - - last_reads = rrddim_set(st, "reads", reads); - last_writes = rrddim_set(st, "writes", writes); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_qops) { - st = rrdset_find_bytype("disk_qops", disk); - if(!st) { - 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); - } - else rrdset_next_usec(st, dt); - - rrddim_set(st, "operations", queued_ios); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_backlog) { - st = rrdset_find_bytype("disk_backlog", disk); - if(!st) { - 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); - } - else rrdset_next_usec(st, dt); - - rrddim_set(st, "backlog", backlog_ms); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_util) { - st = rrdset_find_bytype("disk_util", disk); - if(!st) { - 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); - } - else rrdset_next_usec(st, dt); - - last_busy_ms = rrddim_set(st, "utilization", busy_ms); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_mops) { - st = rrdset_find_bytype("disk_mops", disk); - if(!st) { - 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); - rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next_usec(st, dt); - - rrddim_set(st, "reads", mreads); - rrddim_set(st, "writes", mwrites); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_iotime) { - st = rrdset_find_bytype("disk_iotime", disk); - if(!st) { - 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); - rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next_usec(st, dt); - - last_readms = rrddim_set(st, "reads", readms); - last_writems = rrddim_set(st, "writes", writems); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - // calculate differential charts - // only if this is not the first time we run - - if(dt) { - if(ddo_iotime && ddo_ops) { - st = rrdset_find_bytype("disk_await", disk); - if(!st) { - 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); - rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next_usec(st, dt); - - rrddim_set(st, "reads", (reads - last_reads) ? (readms - last_readms) / (reads - last_reads) : 0); - rrddim_set(st, "writes", (writes - last_writes) ? (writems - last_writems) / (writes - last_writes) : 0); - rrdset_done(st); - } - - if(ddo_io && ddo_ops) { - st = rrdset_find_bytype("disk_avgsz", disk); - if(!st) { - 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); - rrddim_add(st, "writes", NULL, -sector_size, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next_usec(st, dt); - - rrddim_set(st, "reads", (reads - last_reads) ? (readsectors - last_readsectors) / (reads - last_reads) : 0); - rrddim_set(st, "writes", (writes - last_writes) ? (writesectors - last_writesectors) / (writes - last_writes) : 0); - rrdset_done(st); - } - - if(ddo_util && ddo_ops) { - st = rrdset_find_bytype("disk_svctm", disk); - if(!st) { - 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); - } - else rrdset_next_usec(st, dt); - - rrddim_set(st, "svctm", ((reads - last_reads) + (writes - last_writes)) ? (busy_ms - last_busy_ms) / ((reads - last_reads) + (writes - last_writes)) : 0); - rrdset_done(st); - } - } - } - - return 0; + static procfile *ff = NULL; + static struct statvfs buff_statvfs; + static struct stat buff_stat; + static int global_enable_new_disks_detected_at_runtime = CONFIG_ONDEMAND_YES, + global_enable_performance_for_physical_disks = CONFIG_ONDEMAND_ONDEMAND, + global_enable_performance_for_virtual_disks = CONFIG_ONDEMAND_NO, + global_enable_performance_for_partitions = CONFIG_ONDEMAND_NO, + global_enable_performance_for_mountpoints = CONFIG_ONDEMAND_NO, + global_enable_performance_for_virtual_mountpoints = CONFIG_ONDEMAND_ONDEMAND, + global_enable_space_for_mountpoints = CONFIG_ONDEMAND_ONDEMAND, + global_do_io = CONFIG_ONDEMAND_ONDEMAND, + global_do_ops = CONFIG_ONDEMAND_ONDEMAND, + global_do_mops = CONFIG_ONDEMAND_ONDEMAND, + global_do_iotime = CONFIG_ONDEMAND_ONDEMAND, + global_do_qops = CONFIG_ONDEMAND_ONDEMAND, + global_do_util = CONFIG_ONDEMAND_ONDEMAND, + global_do_backlog = CONFIG_ONDEMAND_ONDEMAND, + global_do_space = CONFIG_ONDEMAND_ONDEMAND, + global_do_inodes = CONFIG_ONDEMAND_ONDEMAND, + globals_initialized = 0; + + if(unlikely(!globals_initialized)) { + global_enable_new_disks_detected_at_runtime = config_get_boolean("plugin:proc:/proc/diskstats", "enable new disks detected at runtime", global_enable_new_disks_detected_at_runtime); + + global_enable_performance_for_physical_disks = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "performance metrics for physical disks", global_enable_performance_for_physical_disks); + global_enable_performance_for_virtual_disks = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "performance metrics for virtual disks", global_enable_performance_for_virtual_disks); + global_enable_performance_for_partitions = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "performance metrics for partitions", global_enable_performance_for_partitions); + global_enable_performance_for_mountpoints = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "performance metrics for mounted filesystems", global_enable_performance_for_mountpoints); + global_enable_performance_for_virtual_mountpoints = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "performance metrics for mounted virtual disks", global_enable_performance_for_virtual_mountpoints); + global_enable_space_for_mountpoints = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "space metrics for mounted filesystems", global_enable_space_for_mountpoints); + + global_do_io = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "bandwidth for all disks", global_do_io); + global_do_ops = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "operations for all disks", global_do_ops); + global_do_mops = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "merged operations for all disks", global_do_mops); + global_do_iotime = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "i/o time for all disks", global_do_iotime); + global_do_qops = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "queued operations for all disks", global_do_qops); + global_do_util = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "utilization percentage for all disks", global_do_util); + global_do_backlog = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "backlog for all disks", global_do_backlog); + global_do_space = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "space usage for all disks", global_do_space); + global_do_inodes = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "inodes usage for all disks", global_do_inodes); + + globals_initialized = 1; + } + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + for(l = 0; l < lines ;l++) { + // -------------------------------------------------------------------------- + // Read parameters + + char *disk; + unsigned long long major = 0, minor = 0, + reads = 0, mreads = 0, readsectors = 0, readms = 0, + writes = 0, mwrites = 0, writesectors = 0, writems = 0, + queued_ios = 0, busy_ms = 0, backlog_ms = 0, + space_avail = 0, space_avail_root = 0, space_used = 0, + inodes_avail = 0, inodes_avail_root = 0, inodes_used = 0; + + unsigned long long last_reads = 0, last_readsectors = 0, last_readms = 0, + last_writes = 0, last_writesectors = 0, last_writems = 0, + last_busy_ms = 0; + + words = procfile_linewords(ff, l); + if(words < 14) continue; + + major = strtoull(procfile_lineword(ff, l, 0), NULL, 10); + minor = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + disk = procfile_lineword(ff, l, 2); + + // # of reads completed # of writes completed + // This is the total number of reads or writes completed successfully. + reads = strtoull(procfile_lineword(ff, l, 3), NULL, 10); // rd_ios + writes = strtoull(procfile_lineword(ff, l, 7), NULL, 10); // wr_ios + + // # of reads merged # of writes merged + // Reads and writes which are adjacent to each other may be merged for + // efficiency. Thus two 4K reads may become one 8K read before it is + // ultimately handed to the disk, and so it will be counted (and queued) + mreads = strtoull(procfile_lineword(ff, l, 4), NULL, 10); // rd_merges_or_rd_sec + mwrites = strtoull(procfile_lineword(ff, l, 8), NULL, 10); // wr_merges + + // # of sectors read # of sectors written + // This is the total number of sectors read or written successfully. + readsectors = strtoull(procfile_lineword(ff, l, 5), NULL, 10); // rd_sec_or_wr_ios + writesectors = strtoull(procfile_lineword(ff, l, 9), NULL, 10); // wr_sec + + // # of milliseconds spent reading # of milliseconds spent writing + // This is the total number of milliseconds spent by all reads or writes (as + // measured from __make_request() to end_that_request_last()). + readms = strtoull(procfile_lineword(ff, l, 6), NULL, 10); // rd_ticks_or_wr_sec + writems = strtoull(procfile_lineword(ff, l, 10), NULL, 10); // wr_ticks + + // # of I/Os currently in progress + // The only field that should go to zero. Incremented as requests are + // given to appropriate struct request_queue and decremented as they finish. + queued_ios = strtoull(procfile_lineword(ff, l, 11), NULL, 10); // ios_pgr + + // # of milliseconds spent doing I/Os + // This field increases so long as field queued_ios is nonzero. + busy_ms = strtoull(procfile_lineword(ff, l, 12), NULL, 10); // tot_ticks + + // weighted # of milliseconds spent doing I/Os + // This field is incremented at each I/O start, I/O completion, I/O + // merge, or read of these stats by the number of I/Os in progress + // (field queued_ios) times the number of milliseconds spent doing I/O since the + // last update of this field. This can provide an easy measure of both + // I/O completion time and the backlog that may be accumulating. + backlog_ms = strtoull(procfile_lineword(ff, l, 13), NULL, 10); // rq_ticks + + + // -------------------------------------------------------------------------- + // remove slashes from disk names + char *s; + for(s = disk; *s ;s++) + if(*s == '/') *s = '_'; + + // -------------------------------------------------------------------------- + // get a disk structure for the disk + + struct disk *d = get_disk(major, minor, disk); + + + // -------------------------------------------------------------------------- + // Set its family based on mount point + + char *family = d->mount_point; + if(!family) family = disk; + + + // -------------------------------------------------------------------------- + // Check the configuration for the device + + if(unlikely(!d->configured)) { + char var_name[4096 + 1]; + snprintfz(var_name, 4096, "plugin:proc:/proc/diskstats:%s", disk); + + int def_enable = config_get_boolean_ondemand(var_name, "enable", global_enable_new_disks_detected_at_runtime); + if(def_enable == CONFIG_ONDEMAND_NO) { + // the user does not want any metrics for this disk + d->do_io = CONFIG_ONDEMAND_NO; + d->do_ops = CONFIG_ONDEMAND_NO; + d->do_mops = CONFIG_ONDEMAND_NO; + d->do_iotime = CONFIG_ONDEMAND_NO; + d->do_qops = CONFIG_ONDEMAND_NO; + d->do_util = CONFIG_ONDEMAND_NO; + d->do_backlog = CONFIG_ONDEMAND_NO; + d->do_space = CONFIG_ONDEMAND_NO; + d->do_inodes = CONFIG_ONDEMAND_NO; + } + else { + // this disk is enabled + // check its direct settings + + int def_performance = CONFIG_ONDEMAND_ONDEMAND; + int def_space = (d->mount_point)?CONFIG_ONDEMAND_ONDEMAND:CONFIG_ONDEMAND_NO; + + // since this is 'on demand' we can figure the performance settings + // based on the type of disk + + switch(d->type) { + case DISK_TYPE_PHYSICAL: + def_performance = global_enable_performance_for_physical_disks; + break; + + case DISK_TYPE_PARTITION: + def_performance = global_enable_performance_for_partitions; + break; + + case DISK_TYPE_CONTAINER: + def_performance = global_enable_performance_for_virtual_disks; + + if(d->mount_point) + def_performance = select_positive_option(def_performance, global_enable_performance_for_virtual_mountpoints); + + break; + } + + if(d->mount_point) + def_performance = select_positive_option(def_performance, global_enable_performance_for_mountpoints); + + // ------------------------------------------------------------ + // now we have def_performance and def_space + // to work further + + // def_performance + // check the user configuration (this will also show our 'on demand' decision) + def_performance = config_get_boolean_ondemand(var_name, "enable performance metrics", def_performance); + + int ddo_io = CONFIG_ONDEMAND_NO, + ddo_ops = CONFIG_ONDEMAND_NO, + ddo_mops = CONFIG_ONDEMAND_NO, + ddo_iotime = CONFIG_ONDEMAND_NO, + ddo_qops = CONFIG_ONDEMAND_NO, + ddo_util = CONFIG_ONDEMAND_NO, + ddo_backlog = CONFIG_ONDEMAND_NO; + + // we enable individual performance charts only when def_performance is not disabled + if(def_performance != CONFIG_ONDEMAND_NO) { + ddo_io = global_do_io, + ddo_ops = global_do_ops, + ddo_mops = global_do_mops, + ddo_iotime = global_do_iotime, + ddo_qops = global_do_qops, + ddo_util = global_do_util, + ddo_backlog = global_do_backlog; + } + + d->do_io = config_get_boolean_ondemand(var_name, "bandwidth", ddo_io); + d->do_ops = config_get_boolean_ondemand(var_name, "operations", ddo_ops); + d->do_mops = config_get_boolean_ondemand(var_name, "merged operations", ddo_mops); + d->do_iotime = config_get_boolean_ondemand(var_name, "i/o time", ddo_iotime); + d->do_qops = config_get_boolean_ondemand(var_name, "queued operations", ddo_qops); + d->do_util = config_get_boolean_ondemand(var_name, "utilization percentage", ddo_util); + d->do_backlog = config_get_boolean_ondemand(var_name, "backlog", ddo_backlog); + + // def_space + if(d->mount_point) { + // check the user configuration (this will also show our 'on demand' decision) + def_space = config_get_boolean_ondemand(var_name, "enable space metrics", def_space); + + int ddo_space = def_space, + ddo_inodes = def_space; + + d->do_space = config_get_boolean_ondemand(var_name, "space usage", ddo_space); + d->do_inodes = config_get_boolean_ondemand(var_name, "inodes usage", ddo_inodes); + } + else { + // don't show settings for this disk + d->do_space = CONFIG_ONDEMAND_NO; + d->do_inodes = CONFIG_ONDEMAND_NO; + } + } + + d->configured = 1; + } + + RRDSET *st; + + // -------------------------------------------------------------------------- + // Do performance metrics + + if(d->do_io == CONFIG_ONDEMAND_YES || (d->do_io == CONFIG_ONDEMAND_ONDEMAND && (readsectors || writesectors))) { + d->do_io = CONFIG_ONDEMAND_YES; + + st = rrdset_find_bytype(RRD_TYPE_DISK, disk); + if(!st) { + 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, d->sector_size, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "writes", NULL, d->sector_size * -1, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next_usec(st, dt); + + last_readsectors = rrddim_set(st, "reads", readsectors); + last_writesectors = rrddim_set(st, "writes", writesectors); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(d->do_ops == CONFIG_ONDEMAND_YES || (d->do_ops == CONFIG_ONDEMAND_ONDEMAND && (reads || writes))) { + d->do_ops = CONFIG_ONDEMAND_YES; + + st = rrdset_find_bytype("disk_ops", disk); + if(!st) { + 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); + rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next_usec(st, dt); + + last_reads = rrddim_set(st, "reads", reads); + last_writes = rrddim_set(st, "writes", writes); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(d->do_qops == CONFIG_ONDEMAND_YES || (d->do_qops == CONFIG_ONDEMAND_ONDEMAND && queued_ios)) { + d->do_qops = CONFIG_ONDEMAND_YES; + + st = rrdset_find_bytype("disk_qops", disk); + if(!st) { + 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); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "operations", queued_ios); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(d->do_backlog == CONFIG_ONDEMAND_YES || (d->do_backlog == CONFIG_ONDEMAND_ONDEMAND && backlog_ms)) { + d->do_backlog = CONFIG_ONDEMAND_YES; + + st = rrdset_find_bytype("disk_backlog", disk); + if(!st) { + 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); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "backlog", backlog_ms); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(d->do_util == CONFIG_ONDEMAND_YES || (d->do_util == CONFIG_ONDEMAND_ONDEMAND && busy_ms)) { + d->do_util = CONFIG_ONDEMAND_YES; + + st = rrdset_find_bytype("disk_util", disk); + if(!st) { + 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); + } + else rrdset_next_usec(st, dt); + + last_busy_ms = rrddim_set(st, "utilization", busy_ms); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(d->do_mops == CONFIG_ONDEMAND_YES || (d->do_mops == CONFIG_ONDEMAND_ONDEMAND && (mreads || mwrites))) { + d->do_mops = CONFIG_ONDEMAND_YES; + + st = rrdset_find_bytype("disk_mops", disk); + if(!st) { + 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); + rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "reads", mreads); + rrddim_set(st, "writes", mwrites); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(d->do_iotime == CONFIG_ONDEMAND_YES || (d->do_iotime == CONFIG_ONDEMAND_ONDEMAND && (readms || writems))) { + d->do_iotime = CONFIG_ONDEMAND_YES; + + st = rrdset_find_bytype("disk_iotime", disk); + if(!st) { + 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); + rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next_usec(st, dt); + + last_readms = rrddim_set(st, "reads", readms); + last_writems = rrddim_set(st, "writes", writems); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + // calculate differential charts + // only if this is not the first time we run + + if(dt) { + if( (d->do_iotime == CONFIG_ONDEMAND_YES || (d->do_iotime == CONFIG_ONDEMAND_ONDEMAND && (readms || writems))) && + (d->do_ops == CONFIG_ONDEMAND_YES || (d->do_ops == CONFIG_ONDEMAND_ONDEMAND && (reads || writes)))) { + st = rrdset_find_bytype("disk_await", disk); + if(!st) { + 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); + rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "reads", (reads - last_reads) ? (readms - last_readms) / (reads - last_reads) : 0); + rrddim_set(st, "writes", (writes - last_writes) ? (writems - last_writems) / (writes - last_writes) : 0); + rrdset_done(st); + } + + if( (d->do_io == CONFIG_ONDEMAND_YES || (d->do_io == CONFIG_ONDEMAND_ONDEMAND && (readsectors || writesectors))) && + (d->do_ops == CONFIG_ONDEMAND_YES || (d->do_ops == CONFIG_ONDEMAND_ONDEMAND && (reads || writes)))) { + st = rrdset_find_bytype("disk_avgsz", disk); + if(!st) { + 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, d->sector_size, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "writes", NULL, d->sector_size * -1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "reads", (reads - last_reads) ? (readsectors - last_readsectors) / (reads - last_reads) : 0); + rrddim_set(st, "writes", (writes - last_writes) ? (writesectors - last_writesectors) / (writes - last_writes) : 0); + rrdset_done(st); + } + + if( (d->do_util == CONFIG_ONDEMAND_YES || (d->do_util == CONFIG_ONDEMAND_ONDEMAND && busy_ms)) && + (d->do_ops == CONFIG_ONDEMAND_YES || (d->do_ops == CONFIG_ONDEMAND_ONDEMAND && (reads || writes)))) { + st = rrdset_find_bytype("disk_svctm", disk); + if(!st) { + 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); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "svctm", ((reads - last_reads) + (writes - last_writes)) ? (busy_ms - last_busy_ms) / ((reads - last_reads) + (writes - last_writes)) : 0); + rrdset_done(st); + } + } + + // -------------------------------------------------------------------------- + // space metrics + + if(d->mount_point && (d->do_space || d->do_inodes) ) { + // collect space metrics using statvfs + + if (statvfs(d->mount_point, &buff_statvfs) < 0) + error("Failed statvfs() for '%s' (disk '%s')", d->mount_point, d->disk); + else { + space_avail = buff_statvfs.f_bavail * buff_statvfs.f_bsize; + space_avail_root = (buff_statvfs.f_bfree - buff_statvfs.f_bavail) * buff_statvfs.f_bsize; + space_used = (buff_statvfs.f_blocks - buff_statvfs.f_bfree) * buff_statvfs.f_bsize; + + inodes_avail = buff_statvfs.f_favail; + inodes_avail_root = buff_statvfs.f_ffree - buff_statvfs.f_favail; + inodes_used = buff_statvfs.f_files - buff_statvfs.f_ffree; + + // verify we collected the metrics for the right disk. + // if not the mountpoint has changed. + + if(stat(d->mount_point, &buff_stat) == -1) + error("Failed to stat() for '%s' (disk '%s')", d->mount_point, d->disk); + else { + if(major(buff_stat.st_dev) == major && minor(buff_stat.st_dev) == minor) { + + // -------------------------------------------------------------------------- + + if(d->do_space == CONFIG_ONDEMAND_YES || (d->do_space == CONFIG_ONDEMAND_ONDEMAND && (space_avail || space_avail_root || space_used))) { + st = rrdset_find_bytype("disk_space", disk); + if(!st) { + st = rrdset_create("disk_space", disk, NULL, family, "disk.space", "Disk Space Usage", "GB", 2023, update_every, RRDSET_TYPE_STACKED); + st->isdetail = 1; + + rrddim_add(st, "avail", NULL, 1, 1024*1024*1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "used" , NULL, 1, 1024*1024*1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "reserved_for_root", "reserved for root", 1, 1024*1024*1024, RRDDIM_ABSOLUTE); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "avail", space_avail); + rrddim_set(st, "used", space_used); + rrddim_set(st, "reserved_for_root", space_avail_root); + rrdset_done(st); + } + + // -------------------------------------------------------------------------- + + if(d->do_inodes == CONFIG_ONDEMAND_YES || (d->do_inodes == CONFIG_ONDEMAND_ONDEMAND && (inodes_avail || inodes_avail_root || inodes_used))) { + st = rrdset_find_bytype("disk_inodes", disk); + if(!st) { + st = rrdset_create("disk_inodes", disk, NULL, family, "disk.inodes", "Disk Inodes Usage", "Inodes", 2024, update_every, RRDSET_TYPE_STACKED); + st->isdetail = 1; + + rrddim_add(st, "avail", NULL, 1, 1, RRDDIM_ABSOLUTE); + rrddim_add(st, "used" , NULL, 1, 1, RRDDIM_ABSOLUTE); + rrddim_add(st, "reserved_for_root", "reserved for root", 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next_usec(st, dt); + + rrddim_set(st, "avail", inodes_avail); + rrddim_set(st, "used", inodes_used); + rrddim_set(st, "reserved_for_root", inodes_avail_root); + rrdset_done(st); + } + } + } + } + } + } + + return 0; } diff --git a/src/proc_interrupts.c b/src/proc_interrupts.c index ad00c2022..fe0e9b1a5 100644 --- a/src/proc_interrupts.c +++ b/src/proc_interrupts.c @@ -1,26 +1,13 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <ctype.h> - #include "common.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -#include "log.h" #define MAX_INTERRUPT_NAME 50 struct interrupt { - int used; - char *id; - char name[MAX_INTERRUPT_NAME + 1]; - unsigned long long total; - unsigned long long value[]; + int used; + char *id; + char name[MAX_INTERRUPT_NAME + 1]; + unsigned long long total; + unsigned long long value[]; }; // since each interrupt is variable in size @@ -31,160 +18,157 @@ struct interrupt { #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 allocated = 0; - - if(lines < allocated) return irrs; - else { - irrs = (struct interrupt *)realloc(irrs, lines * recordsize(cpus)); - if(!irrs) - fatal("Cannot allocate memory for %d interrupts", lines); + static struct interrupt *irrs = NULL; + static int allocated = 0; - allocated = lines; - } + if(lines < allocated) return irrs; + else { + irrs = (struct interrupt *)reallocz(irrs, lines * recordsize(cpus)); + allocated = lines; + } - return irrs; + return irrs; } int do_proc_interrupts(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int cpus = -1, do_per_core = -1; - struct interrupt *irrs = NULL; - - if(dt) {}; - - if(do_per_core == -1) do_per_core = config_get_boolean("plugin:proc:/proc/interrupts", "interrupts per core", 1); - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words = procfile_linewords(ff, 0), w; - - if(!lines) { - error("Cannot read /proc/interrupts, zero lines reported."); - return 1; - } - - // find how many CPUs are there - if(cpus == -1) { - cpus = 0; - for(w = 0; w < words ; w++) { - if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0) - cpus++; - } - } - - if(!cpus) { - error("PLUGIN: PROC_INTERRUPTS: Cannot find the number of CPUs in /proc/interrupts"); - return 1; - } - - // allocate the size we need; - irrs = get_interrupts_array(lines, cpus); - irrs[0].used = 0; - - // loop through all lines - for(l = 1; l < lines ;l++) { - struct interrupt *irr = irrindex(irrs, l, cpus); - irr->used = 0; - irr->total = 0; - - words = procfile_linewords(ff, l); - if(!words) continue; - - irr->id = procfile_lineword(ff, l, 0); - if(!irr->id || !irr->id[0]) continue; - - int idlen = strlen(irr->id); - if(irr->id[idlen - 1] == ':') - irr->id[idlen - 1] = '\0'; - - int c; - for(c = 0; c < cpus ;c++) { - if((c + 1) < (int)words) - irr->value[c] = strtoull(procfile_lineword(ff, l, (uint32_t)(c + 1)), NULL, 10); - else - irr->value[c] = 0; - - irr->total += irr->value[c]; - } - - if(isdigit(irr->id[0]) && (uint32_t)(cpus + 2) < words) { - 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] = '_'; - strncpyz(&irr->name[nlen + 1], irr->id, MAX_INTERRUPT_NAME - nlen); - } - } - else { - strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME); - } - - irr->used = 1; - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - st = rrdset_find_bytype("system", "interrupts"); - if(!st) { - st = rrdset_create("system", "interrupts", NULL, "interrupts", NULL, "System interrupts", "interrupts/s", 1000, update_every, RRDSET_TYPE_STACKED); - - for(l = 0; l < lines ;l++) { - 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++) { - struct interrupt *irr = irrindex(irrs, l, cpus); - if(!irr->used) continue; - rrddim_set(st, irr->id, irr->total); - } - rrdset_done(st); - - if(do_per_core) { - int c; - - for(c = 0; c < cpus ; c++) { - char id[256+1]; - snprintfz(id, 256, "cpu%d_interrupts", c); - - st = rrdset_find_bytype("cpu", id); - if(!st) { - 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++) { - 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++) { - struct interrupt *irr = irrindex(irrs, l, cpus); - if(!irr->used) continue; - rrddim_set(st, irr->id, irr->value[c]); - } - rrdset_done(st); - } - } - - return 0; + static procfile *ff = NULL; + static int cpus = -1, do_per_core = -1; + struct interrupt *irrs = NULL; + + if(dt) {}; + + if(do_per_core == -1) do_per_core = config_get_boolean("plugin:proc:/proc/interrupts", "interrupts per core", 1); + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words = procfile_linewords(ff, 0), w; + + if(!lines) { + error("Cannot read /proc/interrupts, zero lines reported."); + return 1; + } + + // find how many CPUs are there + if(cpus == -1) { + cpus = 0; + for(w = 0; w < words ; w++) { + if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0) + cpus++; + } + } + + if(!cpus) { + error("PLUGIN: PROC_INTERRUPTS: Cannot find the number of CPUs in /proc/interrupts"); + return 1; + } + + // allocate the size we need; + irrs = get_interrupts_array(lines, cpus); + irrs[0].used = 0; + + // loop through all lines + for(l = 1; l < lines ;l++) { + struct interrupt *irr = irrindex(irrs, l, cpus); + irr->used = 0; + irr->total = 0; + + words = procfile_linewords(ff, l); + if(!words) continue; + + irr->id = procfile_lineword(ff, l, 0); + if(!irr->id || !irr->id[0]) continue; + + int idlen = strlen(irr->id); + if(irr->id[idlen - 1] == ':') + irr->id[idlen - 1] = '\0'; + + int c; + for(c = 0; c < cpus ;c++) { + if((c + 1) < (int)words) + irr->value[c] = strtoull(procfile_lineword(ff, l, (uint32_t)(c + 1)), NULL, 10); + else + irr->value[c] = 0; + + irr->total += irr->value[c]; + } + + if(isdigit(irr->id[0]) && (uint32_t)(cpus + 2) < words) { + 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] = '_'; + strncpyz(&irr->name[nlen + 1], irr->id, MAX_INTERRUPT_NAME - nlen); + } + } + else { + strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME); + } + + irr->used = 1; + } + + RRDSET *st; + + // -------------------------------------------------------------------- + + st = rrdset_find_bytype("system", "interrupts"); + if(!st) { + st = rrdset_create("system", "interrupts", NULL, "interrupts", NULL, "System interrupts", "interrupts/s", 1000, update_every, RRDSET_TYPE_STACKED); + + for(l = 0; l < lines ;l++) { + 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++) { + struct interrupt *irr = irrindex(irrs, l, cpus); + if(!irr->used) continue; + rrddim_set(st, irr->id, irr->total); + } + rrdset_done(st); + + if(do_per_core) { + int c; + + for(c = 0; c < cpus ; c++) { + char id[256+1]; + snprintfz(id, 256, "cpu%d_interrupts", c); + + st = rrdset_find_bytype("cpu", id); + if(!st) { + 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++) { + 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++) { + struct interrupt *irr = irrindex(irrs, l, cpus); + if(!irr->used) continue; + rrddim_set(st, irr->id, irr->value[c]); + } + rrdset_done(st); + } + } + + return 0; } diff --git a/src/proc_loadavg.c b/src/proc_loadavg.c index c8e893b99..44ea70191 100644 --- a/src/proc_loadavg.c +++ b/src/proc_loadavg.c @@ -1,89 +1,80 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "log.h" #include "common.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" + +// linux calculates this once every 5 seconds +#define MIN_LOADAVG_UPDATE_EVERY 5 int do_proc_loadavg(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int do_loadavg = -1, do_all_processes = -1; + static procfile *ff = NULL; + static int do_loadavg = -1, do_all_processes = -1; - if(dt) {}; + if(dt) {}; - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time - if(do_loadavg == -1) do_loadavg = config_get_boolean("plugin:proc:/proc/loadavg", "enable load average", 1); - if(do_all_processes == -1) do_all_processes = config_get_boolean("plugin:proc:/proc/loadavg", "enable total processes", 1); + if(do_loadavg == -1) do_loadavg = config_get_boolean("plugin:proc:/proc/loadavg", "enable load average", 1); + if(do_all_processes == -1) do_all_processes = config_get_boolean("plugin:proc:/proc/loadavg", "enable total processes", 1); - if(procfile_lines(ff) < 1) { - error("/proc/loadavg has no lines."); - return 1; - } - if(procfile_linewords(ff, 0) < 6) { - error("/proc/loadavg has less than 6 words in it."); - return 1; - } + if(procfile_lines(ff) < 1) { + error("/proc/loadavg has no lines."); + return 1; + } + if(procfile_linewords(ff, 0) < 6) { + error("/proc/loadavg has less than 6 words in it."); + return 1; + } - double load1 = strtod(procfile_lineword(ff, 0, 0), NULL); - double load5 = strtod(procfile_lineword(ff, 0, 1), NULL); - double load15 = strtod(procfile_lineword(ff, 0, 2), NULL); + double load1 = strtod(procfile_lineword(ff, 0, 0), NULL); + double load5 = strtod(procfile_lineword(ff, 0, 1), NULL); + double load15 = strtod(procfile_lineword(ff, 0, 2), NULL); - //unsigned long long running_processes = strtoull(procfile_lineword(ff, 0, 3), NULL, 10); - unsigned long long active_processes = strtoull(procfile_lineword(ff, 0, 4), NULL, 10); - //unsigned long long next_pid = strtoull(procfile_lineword(ff, 0, 5), NULL, 10); + //unsigned long long running_processes = strtoull(procfile_lineword(ff, 0, 3), NULL, 10); + unsigned long long active_processes = strtoull(procfile_lineword(ff, 0, 4), NULL, 10); + //unsigned long long next_pid = strtoull(procfile_lineword(ff, 0, 5), NULL, 10); - RRDSET *st; + RRDSET *st; - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_loadavg) { - st = rrdset_find_byname("system.load"); - if(!st) { - st = rrdset_create("system", "load", NULL, "load", NULL, "System Load Average", "load", 100, update_every, RRDSET_TYPE_LINE); + if(do_loadavg) { + st = rrdset_find_byname("system.load"); + if(!st) { + st = rrdset_create("system", "load", NULL, "load", NULL, "System Load Average", "load", 100, (update_every < MIN_LOADAVG_UPDATE_EVERY)?MIN_LOADAVG_UPDATE_EVERY:update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "load1", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "load5", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "load15", NULL, 1, 1000, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); + rrddim_add(st, "load1", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "load5", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "load15", NULL, 1, 1000, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); - rrddim_set(st, "load1", load1 * 1000); - rrddim_set(st, "load5", load5 * 1000); - rrddim_set(st, "load15", load15 * 1000); - rrdset_done(st); - } + rrddim_set(st, "load1", load1 * 1000); + rrddim_set(st, "load5", load5 * 1000); + rrddim_set(st, "load15", load15 * 1000); + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_all_processes) { - st = rrdset_find_byname("system.active_processes"); - if(!st) { - st = rrdset_create("system", "active_processes", NULL, "processes", NULL, "System Active Processes", "processes", 750, update_every, RRDSET_TYPE_LINE); + if(do_all_processes) { + st = rrdset_find_byname("system.active_processes"); + if(!st) { + st = rrdset_create("system", "active_processes", NULL, "processes", NULL, "System Active Processes", "processes", 750, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "active", NULL, 1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); + rrddim_add(st, "active", NULL, 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); - rrddim_set(st, "active", active_processes); - rrdset_done(st); - } + rrddim_set(st, "active", active_processes); + rrdset_done(st); + } - return 0; + return 0; } diff --git a/src/proc_meminfo.c b/src/proc_meminfo.c index 611b4ed21..4295cd6da 100644 --- a/src/proc_meminfo.c +++ b/src/proc_meminfo.c @@ -1,254 +1,242 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" #define MAX_PROC_MEMINFO_LINE 4096 #define MAX_PROC_MEMINFO_NAME 1024 int do_proc_meminfo(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - - static int do_ram = -1, do_swap = -1, do_hwcorrupt = -1, do_committed = -1, do_writeback = -1, do_kernel = -1, do_slab = -1; - - if(do_ram == -1) do_ram = config_get_boolean("plugin:proc:/proc/meminfo", "system ram", 1); - if(do_swap == -1) do_swap = config_get_boolean("plugin:proc:/proc/meminfo", "system swap", 1); - if(do_hwcorrupt == -1) do_hwcorrupt = config_get_boolean_ondemand("plugin:proc:/proc/meminfo", "hardware corrupted ECC", CONFIG_ONDEMAND_ONDEMAND); - if(do_committed == -1) do_committed = config_get_boolean("plugin:proc:/proc/meminfo", "committed memory", 1); - if(do_writeback == -1) do_writeback = config_get_boolean("plugin:proc:/proc/meminfo", "writeback memory", 1); - if(do_kernel == -1) do_kernel = config_get_boolean("plugin:proc:/proc/meminfo", "kernel memory", 1); - if(do_slab == -1) do_slab = config_get_boolean("plugin:proc:/proc/meminfo", "slab memory", 1); - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - int hwcorrupted = 0; - - unsigned long long MemTotal = 0, MemFree = 0, Buffers = 0, Cached = 0, SwapCached = 0, - Active = 0, Inactive = 0, ActiveAnon = 0, InactiveAnon = 0, ActiveFile = 0, InactiveFile = 0, - Unevictable = 0, Mlocked = 0, SwapTotal = 0, SwapFree = 0, Dirty = 0, Writeback = 0, AnonPages = 0, - Mapped = 0, Shmem = 0, Slab = 0, SReclaimable = 0, SUnreclaim = 0, KernelStack = 0, PageTables = 0, - NFS_Unstable = 0, Bounce = 0, WritebackTmp = 0, CommitLimit = 0, Committed_AS = 0, - VmallocTotal = 0, VmallocUsed = 0, VmallocChunk = 0, - AnonHugePages = 0, HugePages_Total = 0, HugePages_Free = 0, HugePages_Rsvd = 0, HugePages_Surp = 0, Hugepagesize = 0, - DirectMap4k = 0, DirectMap2M = 0, HardwareCorrupted = 0; - - for(l = 0; l < lines ;l++) { - words = procfile_linewords(ff, l); - if(words < 2) continue; - - char *name = procfile_lineword(ff, l, 0); - unsigned long long value = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - - if(!MemTotal && strcmp(name, "MemTotal") == 0) MemTotal = value; - else if(!MemFree && strcmp(name, "MemFree") == 0) MemFree = value; - else if(!Buffers && strcmp(name, "Buffers") == 0) Buffers = value; - else if(!Cached && strcmp(name, "Cached") == 0) Cached = value; - else if(!SwapCached && strcmp(name, "SwapCached") == 0) SwapCached = value; - else if(!Active && strcmp(name, "Active") == 0) Active = value; - else if(!Inactive && strcmp(name, "Inactive") == 0) Inactive = value; - else if(!ActiveAnon && strcmp(name, "ActiveAnon") == 0) ActiveAnon = value; - else if(!InactiveAnon && strcmp(name, "InactiveAnon") == 0) InactiveAnon = value; - else if(!ActiveFile && strcmp(name, "ActiveFile") == 0) ActiveFile = value; - else if(!InactiveFile && strcmp(name, "InactiveFile") == 0) InactiveFile = value; - else if(!Unevictable && strcmp(name, "Unevictable") == 0) Unevictable = value; - else if(!Mlocked && strcmp(name, "Mlocked") == 0) Mlocked = value; - else if(!SwapTotal && strcmp(name, "SwapTotal") == 0) SwapTotal = value; - else if(!SwapFree && strcmp(name, "SwapFree") == 0) SwapFree = value; - else if(!Dirty && strcmp(name, "Dirty") == 0) Dirty = value; - else if(!Writeback && strcmp(name, "Writeback") == 0) Writeback = value; - else if(!AnonPages && strcmp(name, "AnonPages") == 0) AnonPages = value; - else if(!Mapped && strcmp(name, "Mapped") == 0) Mapped = value; - else if(!Shmem && strcmp(name, "Shmem") == 0) Shmem = value; - else if(!Slab && strcmp(name, "Slab") == 0) Slab = value; - else if(!SReclaimable && strcmp(name, "SReclaimable") == 0) SReclaimable = value; - else if(!SUnreclaim && strcmp(name, "SUnreclaim") == 0) SUnreclaim = value; - else if(!KernelStack && strcmp(name, "KernelStack") == 0) KernelStack = value; - else if(!PageTables && strcmp(name, "PageTables") == 0) PageTables = value; - else if(!NFS_Unstable && strcmp(name, "NFS_Unstable") == 0) NFS_Unstable = value; - else if(!Bounce && strcmp(name, "Bounce") == 0) Bounce = value; - else if(!WritebackTmp && strcmp(name, "WritebackTmp") == 0) WritebackTmp = value; - else if(!CommitLimit && strcmp(name, "CommitLimit") == 0) CommitLimit = value; - else if(!Committed_AS && strcmp(name, "Committed_AS") == 0) Committed_AS = value; - else if(!VmallocTotal && strcmp(name, "VmallocTotal") == 0) VmallocTotal = value; - else if(!VmallocUsed && strcmp(name, "VmallocUsed") == 0) VmallocUsed = value; - else if(!VmallocChunk && strcmp(name, "VmallocChunk") == 0) VmallocChunk = value; - else if(!HardwareCorrupted && strcmp(name, "HardwareCorrupted") == 0) { HardwareCorrupted = value; hwcorrupted = 1; } - else if(!AnonHugePages && strcmp(name, "AnonHugePages") == 0) AnonHugePages = value; - else if(!HugePages_Total && strcmp(name, "HugePages_Total") == 0) HugePages_Total = value; - else if(!HugePages_Free && strcmp(name, "HugePages_Free") == 0) HugePages_Free = value; - else if(!HugePages_Rsvd && strcmp(name, "HugePages_Rsvd") == 0) HugePages_Rsvd = value; - else if(!HugePages_Surp && strcmp(name, "HugePages_Surp") == 0) HugePages_Surp = value; - else if(!Hugepagesize && strcmp(name, "Hugepagesize") == 0) Hugepagesize = value; - else if(!DirectMap4k && strcmp(name, "DirectMap4k") == 0) DirectMap4k = value; - else if(!DirectMap2M && strcmp(name, "DirectMap2M") == 0) DirectMap2M = value; - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - // http://stackoverflow.com/questions/3019748/how-to-reliably-measure-available-memory-in-linux - unsigned long long MemUsed = MemTotal - MemFree - Cached - Buffers; - - if(do_ram) { - st = rrdset_find("system.ram"); - if(!st) { - st = rrdset_create("system", "ram", NULL, "ram", NULL, "System RAM", "MB", 200, update_every, RRDSET_TYPE_STACKED); - - rrddim_add(st, "buffers", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "used", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "cached", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "free", NULL, 1, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "used", MemUsed); - rrddim_set(st, "free", MemFree); - rrddim_set(st, "cached", Cached); - rrddim_set(st, "buffers", Buffers); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - unsigned long long SwapUsed = SwapTotal - SwapFree; - - if(do_swap) { - st = rrdset_find("system.swap"); - if(!st) { - st = rrdset_create("system", "swap", NULL, "swap", NULL, "System Swap", "MB", 201, update_every, RRDSET_TYPE_STACKED); - st->isdetail = 1; - - rrddim_add(st, "free", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "used", NULL, 1, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "used", SwapUsed); - rrddim_set(st, "free", SwapFree); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(hwcorrupted && do_hwcorrupt && HardwareCorrupted > 0) { - do_hwcorrupt = CONFIG_ONDEMAND_YES; - - st = rrdset_find("mem.hwcorrupt"); - if(!st) { - st = rrdset_create("mem", "hwcorrupt", NULL, "errors", NULL, "Hardware Corrupted ECC", "MB", 9000, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "HardwareCorrupted", NULL, 1, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "HardwareCorrupted", HardwareCorrupted); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_committed) { - st = rrdset_find("mem.committed"); - if(!st) { - st = rrdset_create("mem", "committed", NULL, "system", NULL, "Committed (Allocated) Memory", "MB", 5000, update_every, RRDSET_TYPE_AREA); - st->isdetail = 1; - - rrddim_add(st, "Committed_AS", NULL, 1, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "Committed_AS", Committed_AS); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_writeback) { - st = rrdset_find("mem.writeback"); - if(!st) { - st = rrdset_create("mem", "writeback", NULL, "kernel", NULL, "Writeback Memory", "MB", 4000, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "Dirty", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "Writeback", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "FuseWriteback", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "NfsWriteback", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "Bounce", NULL, 1, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "Dirty", Dirty); - rrddim_set(st, "Writeback", Writeback); - rrddim_set(st, "FuseWriteback", WritebackTmp); - rrddim_set(st, "NfsWriteback", NFS_Unstable); - rrddim_set(st, "Bounce", Bounce); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_kernel) { - st = rrdset_find("mem.kernel"); - if(!st) { - st = rrdset_create("mem", "kernel", NULL, "kernel", NULL, "Memory Used by Kernel", "MB", 6000, update_every, RRDSET_TYPE_STACKED); - st->isdetail = 1; - - rrddim_add(st, "Slab", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "KernelStack", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "PageTables", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "VmallocUsed", NULL, 1, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "KernelStack", KernelStack); - rrddim_set(st, "Slab", Slab); - rrddim_set(st, "PageTables", PageTables); - rrddim_set(st, "VmallocUsed", VmallocUsed); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_slab) { - st = rrdset_find("mem.slab"); - if(!st) { - st = rrdset_create("mem", "slab", NULL, "slab", NULL, "Reclaimable Kernel Memory", "MB", 6500, update_every, RRDSET_TYPE_STACKED); - st->isdetail = 1; - - rrddim_add(st, "reclaimable", NULL, 1, 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "unreclaimable", NULL, 1, 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "reclaimable", SReclaimable); - rrddim_set(st, "unreclaimable", SUnreclaim); - rrdset_done(st); - } - - return 0; + static procfile *ff = NULL; + + static int do_ram = -1, do_swap = -1, do_hwcorrupt = -1, do_committed = -1, do_writeback = -1, do_kernel = -1, do_slab = -1; + + if(do_ram == -1) do_ram = config_get_boolean("plugin:proc:/proc/meminfo", "system ram", 1); + if(do_swap == -1) do_swap = config_get_boolean("plugin:proc:/proc/meminfo", "system swap", 1); + if(do_hwcorrupt == -1) do_hwcorrupt = config_get_boolean_ondemand("plugin:proc:/proc/meminfo", "hardware corrupted ECC", CONFIG_ONDEMAND_ONDEMAND); + if(do_committed == -1) do_committed = config_get_boolean("plugin:proc:/proc/meminfo", "committed memory", 1); + if(do_writeback == -1) do_writeback = config_get_boolean("plugin:proc:/proc/meminfo", "writeback memory", 1); + if(do_kernel == -1) do_kernel = config_get_boolean("plugin:proc:/proc/meminfo", "kernel memory", 1); + if(do_slab == -1) do_slab = config_get_boolean("plugin:proc:/proc/meminfo", "slab memory", 1); + + if(dt) {}; + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + int hwcorrupted = 0; + + unsigned long long MemTotal = 0, MemFree = 0, Buffers = 0, Cached = 0, SwapCached = 0, + Active = 0, Inactive = 0, ActiveAnon = 0, InactiveAnon = 0, ActiveFile = 0, InactiveFile = 0, + Unevictable = 0, Mlocked = 0, SwapTotal = 0, SwapFree = 0, Dirty = 0, Writeback = 0, AnonPages = 0, + Mapped = 0, Shmem = 0, Slab = 0, SReclaimable = 0, SUnreclaim = 0, KernelStack = 0, PageTables = 0, + NFS_Unstable = 0, Bounce = 0, WritebackTmp = 0, CommitLimit = 0, Committed_AS = 0, + VmallocTotal = 0, VmallocUsed = 0, VmallocChunk = 0, + AnonHugePages = 0, HugePages_Total = 0, HugePages_Free = 0, HugePages_Rsvd = 0, HugePages_Surp = 0, Hugepagesize = 0, + DirectMap4k = 0, DirectMap2M = 0, HardwareCorrupted = 0; + + for(l = 0; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(words < 2) continue; + + char *name = procfile_lineword(ff, l, 0); + unsigned long long value = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + + if(!MemTotal && strcmp(name, "MemTotal") == 0) MemTotal = value; + else if(!MemFree && strcmp(name, "MemFree") == 0) MemFree = value; + else if(!Buffers && strcmp(name, "Buffers") == 0) Buffers = value; + else if(!Cached && strcmp(name, "Cached") == 0) Cached = value; + else if(!SwapCached && strcmp(name, "SwapCached") == 0) SwapCached = value; + else if(!Active && strcmp(name, "Active") == 0) Active = value; + else if(!Inactive && strcmp(name, "Inactive") == 0) Inactive = value; + else if(!ActiveAnon && strcmp(name, "ActiveAnon") == 0) ActiveAnon = value; + else if(!InactiveAnon && strcmp(name, "InactiveAnon") == 0) InactiveAnon = value; + else if(!ActiveFile && strcmp(name, "ActiveFile") == 0) ActiveFile = value; + else if(!InactiveFile && strcmp(name, "InactiveFile") == 0) InactiveFile = value; + else if(!Unevictable && strcmp(name, "Unevictable") == 0) Unevictable = value; + else if(!Mlocked && strcmp(name, "Mlocked") == 0) Mlocked = value; + else if(!SwapTotal && strcmp(name, "SwapTotal") == 0) SwapTotal = value; + else if(!SwapFree && strcmp(name, "SwapFree") == 0) SwapFree = value; + else if(!Dirty && strcmp(name, "Dirty") == 0) Dirty = value; + else if(!Writeback && strcmp(name, "Writeback") == 0) Writeback = value; + else if(!AnonPages && strcmp(name, "AnonPages") == 0) AnonPages = value; + else if(!Mapped && strcmp(name, "Mapped") == 0) Mapped = value; + else if(!Shmem && strcmp(name, "Shmem") == 0) Shmem = value; + else if(!Slab && strcmp(name, "Slab") == 0) Slab = value; + else if(!SReclaimable && strcmp(name, "SReclaimable") == 0) SReclaimable = value; + else if(!SUnreclaim && strcmp(name, "SUnreclaim") == 0) SUnreclaim = value; + else if(!KernelStack && strcmp(name, "KernelStack") == 0) KernelStack = value; + else if(!PageTables && strcmp(name, "PageTables") == 0) PageTables = value; + else if(!NFS_Unstable && strcmp(name, "NFS_Unstable") == 0) NFS_Unstable = value; + else if(!Bounce && strcmp(name, "Bounce") == 0) Bounce = value; + else if(!WritebackTmp && strcmp(name, "WritebackTmp") == 0) WritebackTmp = value; + else if(!CommitLimit && strcmp(name, "CommitLimit") == 0) CommitLimit = value; + else if(!Committed_AS && strcmp(name, "Committed_AS") == 0) Committed_AS = value; + else if(!VmallocTotal && strcmp(name, "VmallocTotal") == 0) VmallocTotal = value; + else if(!VmallocUsed && strcmp(name, "VmallocUsed") == 0) VmallocUsed = value; + else if(!VmallocChunk && strcmp(name, "VmallocChunk") == 0) VmallocChunk = value; + else if(!HardwareCorrupted && strcmp(name, "HardwareCorrupted") == 0) { HardwareCorrupted = value; hwcorrupted = 1; } + else if(!AnonHugePages && strcmp(name, "AnonHugePages") == 0) AnonHugePages = value; + else if(!HugePages_Total && strcmp(name, "HugePages_Total") == 0) HugePages_Total = value; + else if(!HugePages_Free && strcmp(name, "HugePages_Free") == 0) HugePages_Free = value; + else if(!HugePages_Rsvd && strcmp(name, "HugePages_Rsvd") == 0) HugePages_Rsvd = value; + else if(!HugePages_Surp && strcmp(name, "HugePages_Surp") == 0) HugePages_Surp = value; + else if(!Hugepagesize && strcmp(name, "Hugepagesize") == 0) Hugepagesize = value; + else if(!DirectMap4k && strcmp(name, "DirectMap4k") == 0) DirectMap4k = value; + else if(!DirectMap2M && strcmp(name, "DirectMap2M") == 0) DirectMap2M = value; + } + + RRDSET *st; + + // -------------------------------------------------------------------- + + // http://stackoverflow.com/questions/3019748/how-to-reliably-measure-available-memory-in-linux + unsigned long long MemUsed = MemTotal - MemFree - Cached - Buffers; + + if(do_ram) { + st = rrdset_find("system.ram"); + if(!st) { + st = rrdset_create("system", "ram", NULL, "ram", NULL, "System RAM", "MB", 200, update_every, RRDSET_TYPE_STACKED); + + rrddim_add(st, "buffers", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "used", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "cached", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "free", NULL, 1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "used", MemUsed); + rrddim_set(st, "free", MemFree); + rrddim_set(st, "cached", Cached); + rrddim_set(st, "buffers", Buffers); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + unsigned long long SwapUsed = SwapTotal - SwapFree; + + if(do_swap) { + st = rrdset_find("system.swap"); + if(!st) { + st = rrdset_create("system", "swap", NULL, "swap", NULL, "System Swap", "MB", 201, update_every, RRDSET_TYPE_STACKED); + st->isdetail = 1; + + rrddim_add(st, "free", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "used", NULL, 1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "used", SwapUsed); + rrddim_set(st, "free", SwapFree); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(hwcorrupted && do_hwcorrupt && HardwareCorrupted > 0) { + do_hwcorrupt = CONFIG_ONDEMAND_YES; + + st = rrdset_find("mem.hwcorrupt"); + if(!st) { + st = rrdset_create("mem", "hwcorrupt", NULL, "errors", NULL, "Hardware Corrupted ECC", "MB", 9000, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "HardwareCorrupted", NULL, 1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "HardwareCorrupted", HardwareCorrupted); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_committed) { + st = rrdset_find("mem.committed"); + if(!st) { + st = rrdset_create("mem", "committed", NULL, "system", NULL, "Committed (Allocated) Memory", "MB", 5000, update_every, RRDSET_TYPE_AREA); + st->isdetail = 1; + + rrddim_add(st, "Committed_AS", NULL, 1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "Committed_AS", Committed_AS); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_writeback) { + st = rrdset_find("mem.writeback"); + if(!st) { + st = rrdset_create("mem", "writeback", NULL, "kernel", NULL, "Writeback Memory", "MB", 4000, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "Dirty", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "Writeback", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "FuseWriteback", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "NfsWriteback", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "Bounce", NULL, 1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "Dirty", Dirty); + rrddim_set(st, "Writeback", Writeback); + rrddim_set(st, "FuseWriteback", WritebackTmp); + rrddim_set(st, "NfsWriteback", NFS_Unstable); + rrddim_set(st, "Bounce", Bounce); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_kernel) { + st = rrdset_find("mem.kernel"); + if(!st) { + st = rrdset_create("mem", "kernel", NULL, "kernel", NULL, "Memory Used by Kernel", "MB", 6000, update_every, RRDSET_TYPE_STACKED); + st->isdetail = 1; + + rrddim_add(st, "Slab", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "KernelStack", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "PageTables", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "VmallocUsed", NULL, 1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "KernelStack", KernelStack); + rrddim_set(st, "Slab", Slab); + rrddim_set(st, "PageTables", PageTables); + rrddim_set(st, "VmallocUsed", VmallocUsed); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_slab) { + st = rrdset_find("mem.slab"); + if(!st) { + st = rrdset_create("mem", "slab", NULL, "slab", NULL, "Reclaimable Kernel Memory", "MB", 6500, update_every, RRDSET_TYPE_STACKED); + st->isdetail = 1; + + rrddim_add(st, "reclaimable", NULL, 1, 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "unreclaimable", NULL, 1, 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "reclaimable", SReclaimable); + rrddim_set(st, "unreclaimable", SUnreclaim); + rrdset_done(st); + } + + return 0; } diff --git a/src/proc_net_dev.c b/src/proc_net_dev.c index 12d8078c7..53981182a 100644 --- a/src/proc_net_dev.c +++ b/src/proc_net_dev.c @@ -1,247 +1,236 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" int do_proc_net_dev(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int enable_new_interfaces = -1, enable_ifb_interfaces = -1; - static int do_bandwidth = -1, do_packets = -1, do_errors = -1, do_drops = -1, do_fifo = -1, do_compressed = -1, do_events = -1; - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - if(enable_new_interfaces == -1) enable_new_interfaces = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "enable new interfaces detected at runtime", CONFIG_ONDEMAND_ONDEMAND); - if(enable_ifb_interfaces == -1) enable_ifb_interfaces = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "enable ifb interfaces", CONFIG_ONDEMAND_NO); - - if(do_bandwidth == -1) do_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "bandwidth for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - if(do_packets == -1) do_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "packets for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - if(do_errors == -1) do_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "errors for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - if(do_drops == -1) do_drops = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "drops for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - if(do_fifo == -1) do_fifo = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "fifo for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - if(do_compressed == -1) do_compressed = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "compressed packets for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - if(do_events == -1) do_events = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "frames, collisions, carrier counters for all interfaces", CONFIG_ONDEMAND_ONDEMAND); - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - char *iface; - unsigned long long rbytes, rpackets, rerrors, rdrops, rfifo, rframe, rcompressed, rmulticast; - unsigned long long tbytes, tpackets, terrors, tdrops, tfifo, tcollisions, tcarrier, tcompressed; - - for(l = 2; l < lines ;l++) { - words = procfile_linewords(ff, l); - if(words < 17) continue; - - iface = procfile_lineword(ff, l, 0); - - rbytes = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - rpackets = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - rerrors = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - rdrops = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - rfifo = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - rframe = strtoull(procfile_lineword(ff, l, 6), NULL, 10); - rcompressed = strtoull(procfile_lineword(ff, l, 7), NULL, 10); - rmulticast = strtoull(procfile_lineword(ff, l, 8), NULL, 10); - - tbytes = strtoull(procfile_lineword(ff, l, 9), NULL, 10); - tpackets = strtoull(procfile_lineword(ff, l, 10), NULL, 10); - terrors = strtoull(procfile_lineword(ff, l, 11), NULL, 10); - tdrops = strtoull(procfile_lineword(ff, l, 12), NULL, 10); - tfifo = strtoull(procfile_lineword(ff, l, 13), NULL, 10); - tcollisions = strtoull(procfile_lineword(ff, l, 14), NULL, 10); - tcarrier = strtoull(procfile_lineword(ff, l, 15), NULL, 10); - tcompressed = strtoull(procfile_lineword(ff, l, 16), NULL, 10); - - int ddo_bandwidth = do_bandwidth, ddo_packets = do_packets, ddo_errors = do_errors, ddo_drops = do_drops, ddo_fifo = do_fifo, ddo_compressed = do_compressed, ddo_events = do_events; - - int default_enable = enable_new_interfaces; - - // prevent unused interfaces from creating charts - if(strcmp(iface, "lo") == 0) - default_enable = 0; - else { - int len = strlen(iface); - if(len >= 4 && strcmp(&iface[len-4], "-ifb") == 0) - default_enable = enable_ifb_interfaces; - } - - // check if the user wants it - { - char var_name[512 + 1]; - 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; - - ddo_bandwidth = config_get_boolean_ondemand(var_name, "bandwidth", ddo_bandwidth); - ddo_packets = config_get_boolean_ondemand(var_name, "packets", ddo_packets); - ddo_errors = config_get_boolean_ondemand(var_name, "errors", ddo_errors); - ddo_drops = config_get_boolean_ondemand(var_name, "drops", ddo_drops); - ddo_fifo = config_get_boolean_ondemand(var_name, "fifo", ddo_fifo); - ddo_compressed = config_get_boolean_ondemand(var_name, "compressed", ddo_compressed); - ddo_events = config_get_boolean_ondemand(var_name, "events", ddo_events); - - if(ddo_bandwidth == CONFIG_ONDEMAND_ONDEMAND && rbytes == 0 && tbytes == 0) ddo_bandwidth = 0; - if(ddo_errors == CONFIG_ONDEMAND_ONDEMAND && rerrors == 0 && terrors == 0) ddo_errors = 0; - if(ddo_drops == CONFIG_ONDEMAND_ONDEMAND && rdrops == 0 && tdrops == 0) ddo_drops = 0; - if(ddo_fifo == CONFIG_ONDEMAND_ONDEMAND && rfifo == 0 && tfifo == 0) ddo_fifo = 0; - if(ddo_compressed == CONFIG_ONDEMAND_ONDEMAND && rcompressed == 0 && tcompressed == 0) ddo_compressed = 0; - if(ddo_events == CONFIG_ONDEMAND_ONDEMAND && rframe == 0 && tcollisions == 0 && tcarrier == 0) ddo_events = 0; - - // for absolute values, we need to switch the setting to 'yes' - // to allow it refresh from now on - // if(ddo_fifo == CONFIG_ONDEMAND_ONDEMAND) config_set(var_name, "fifo", "yes"); - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - if(ddo_bandwidth) { - st = rrdset_find_bytype("net", iface); - if(!st) { - st = rrdset_create("net", iface, NULL, iface, "net.net", "Bandwidth", "kilobits/s", 7000, update_every, RRDSET_TYPE_AREA); - - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "received", rbytes); - rrddim_set(st, "sent", tbytes); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_packets) { - st = rrdset_find_bytype("net_packets", iface); - if(!st) { - st = rrdset_create("net_packets", iface, NULL, iface, "net.packets", "Packets", "packets/s", 7001, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "multicast", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "received", rpackets); - rrddim_set(st, "sent", tpackets); - rrddim_set(st, "multicast", rmulticast); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_errors) { - st = rrdset_find_bytype("net_errors", iface); - if(!st) { - st = rrdset_create("net_errors", iface, NULL, iface, "net.errors", "Interface Errors", "errors/s", 7002, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "inbound", rerrors); - rrddim_set(st, "outbound", terrors); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_drops) { - st = rrdset_find_bytype("net_drops", iface); - if(!st) { - st = rrdset_create("net_drops", iface, NULL, iface, "net.drops", "Interface Drops", "drops/s", 7003, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "inbound", rdrops); - rrddim_set(st, "outbound", tdrops); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_fifo) { - st = rrdset_find_bytype("net_fifo", iface); - if(!st) { - st = rrdset_create("net_fifo", iface, NULL, iface, "net.fifo", "Interface FIFO Buffer Errors", "errors", 7004, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "receive", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "transmit", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "receive", rfifo); - rrddim_set(st, "transmit", tfifo); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_compressed) { - st = rrdset_find_bytype("net_compressed", iface); - if(!st) { - st = rrdset_create("net_compressed", iface, NULL, iface, "net.compressed", "Compressed Packets", "packets/s", 7005, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "received", rcompressed); - rrddim_set(st, "sent", tcompressed); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(ddo_events) { - st = rrdset_find_bytype("net_events", iface); - if(!st) { - st = rrdset_create("net_events", iface, NULL, iface, "net.events", "Network Interface Events", "events/s", 7006, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "frames", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "collisions", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "carrier", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "frames", rframe); - rrddim_set(st, "collisions", tcollisions); - rrddim_set(st, "carrier", tcarrier); - rrdset_done(st); - } - } - - return 0; + static procfile *ff = NULL; + static int enable_new_interfaces = -1, enable_ifb_interfaces = -1; + static int do_bandwidth = -1, do_packets = -1, do_errors = -1, do_drops = -1, do_fifo = -1, do_compressed = -1, do_events = -1; + + if(dt) {}; + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + if(enable_new_interfaces == -1) enable_new_interfaces = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "enable new interfaces detected at runtime", CONFIG_ONDEMAND_ONDEMAND); + if(enable_ifb_interfaces == -1) enable_ifb_interfaces = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "enable ifb interfaces", CONFIG_ONDEMAND_NO); + + if(do_bandwidth == -1) do_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "bandwidth for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + if(do_packets == -1) do_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "packets for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + if(do_errors == -1) do_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "errors for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + if(do_drops == -1) do_drops = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "drops for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + if(do_fifo == -1) do_fifo = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "fifo for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + if(do_compressed == -1) do_compressed = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "compressed packets for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + if(do_events == -1) do_events = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "frames, collisions, carrier counters for all interfaces", CONFIG_ONDEMAND_ONDEMAND); + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + char *iface; + unsigned long long rbytes, rpackets, rerrors, rdrops, rfifo, rframe, rcompressed, rmulticast; + unsigned long long tbytes, tpackets, terrors, tdrops, tfifo, tcollisions, tcarrier, tcompressed; + + for(l = 2; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(words < 17) continue; + + iface = procfile_lineword(ff, l, 0); + + rbytes = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + rpackets = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + rerrors = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + rdrops = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + rfifo = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + rframe = strtoull(procfile_lineword(ff, l, 6), NULL, 10); + rcompressed = strtoull(procfile_lineword(ff, l, 7), NULL, 10); + rmulticast = strtoull(procfile_lineword(ff, l, 8), NULL, 10); + + tbytes = strtoull(procfile_lineword(ff, l, 9), NULL, 10); + tpackets = strtoull(procfile_lineword(ff, l, 10), NULL, 10); + terrors = strtoull(procfile_lineword(ff, l, 11), NULL, 10); + tdrops = strtoull(procfile_lineword(ff, l, 12), NULL, 10); + tfifo = strtoull(procfile_lineword(ff, l, 13), NULL, 10); + tcollisions = strtoull(procfile_lineword(ff, l, 14), NULL, 10); + tcarrier = strtoull(procfile_lineword(ff, l, 15), NULL, 10); + tcompressed = strtoull(procfile_lineword(ff, l, 16), NULL, 10); + + int ddo_bandwidth = do_bandwidth, ddo_packets = do_packets, ddo_errors = do_errors, ddo_drops = do_drops, ddo_fifo = do_fifo, ddo_compressed = do_compressed, ddo_events = do_events; + + int default_enable = enable_new_interfaces; + + // prevent unused interfaces from creating charts + if(strcmp(iface, "lo") == 0) + default_enable = 0; + else { + int len = strlen(iface); + if(len >= 4 && strcmp(&iface[len-4], "-ifb") == 0) + default_enable = enable_ifb_interfaces; + } + + // check if the user wants it + { + char var_name[512 + 1]; + 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; + + ddo_bandwidth = config_get_boolean_ondemand(var_name, "bandwidth", ddo_bandwidth); + ddo_packets = config_get_boolean_ondemand(var_name, "packets", ddo_packets); + ddo_errors = config_get_boolean_ondemand(var_name, "errors", ddo_errors); + ddo_drops = config_get_boolean_ondemand(var_name, "drops", ddo_drops); + ddo_fifo = config_get_boolean_ondemand(var_name, "fifo", ddo_fifo); + ddo_compressed = config_get_boolean_ondemand(var_name, "compressed", ddo_compressed); + ddo_events = config_get_boolean_ondemand(var_name, "events", ddo_events); + + if(ddo_bandwidth == CONFIG_ONDEMAND_ONDEMAND && rbytes == 0 && tbytes == 0) ddo_bandwidth = 0; + if(ddo_errors == CONFIG_ONDEMAND_ONDEMAND && rerrors == 0 && terrors == 0) ddo_errors = 0; + if(ddo_drops == CONFIG_ONDEMAND_ONDEMAND && rdrops == 0 && tdrops == 0) ddo_drops = 0; + if(ddo_fifo == CONFIG_ONDEMAND_ONDEMAND && rfifo == 0 && tfifo == 0) ddo_fifo = 0; + if(ddo_compressed == CONFIG_ONDEMAND_ONDEMAND && rcompressed == 0 && tcompressed == 0) ddo_compressed = 0; + if(ddo_events == CONFIG_ONDEMAND_ONDEMAND && rframe == 0 && tcollisions == 0 && tcarrier == 0) ddo_events = 0; + + // for absolute values, we need to switch the setting to 'yes' + // to allow it refresh from now on + // if(ddo_fifo == CONFIG_ONDEMAND_ONDEMAND) config_set(var_name, "fifo", "yes"); + } + + RRDSET *st; + + // -------------------------------------------------------------------- + + if(ddo_bandwidth) { + st = rrdset_find_bytype("net", iface); + if(!st) { + st = rrdset_create("net", iface, NULL, iface, "net.net", "Bandwidth", "kilobits/s", 7000, update_every, RRDSET_TYPE_AREA); + + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "received", rbytes); + rrddim_set(st, "sent", tbytes); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(ddo_packets) { + st = rrdset_find_bytype("net_packets", iface); + if(!st) { + st = rrdset_create("net_packets", iface, NULL, iface, "net.packets", "Packets", "packets/s", 7001, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "multicast", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "received", rpackets); + rrddim_set(st, "sent", tpackets); + rrddim_set(st, "multicast", rmulticast); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(ddo_errors) { + st = rrdset_find_bytype("net_errors", iface); + if(!st) { + st = rrdset_create("net_errors", iface, NULL, iface, "net.errors", "Interface Errors", "errors/s", 7002, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "inbound", rerrors); + rrddim_set(st, "outbound", terrors); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(ddo_drops) { + st = rrdset_find_bytype("net_drops", iface); + if(!st) { + st = rrdset_create("net_drops", iface, NULL, iface, "net.drops", "Interface Drops", "drops/s", 7003, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "inbound", rdrops); + rrddim_set(st, "outbound", tdrops); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(ddo_fifo) { + st = rrdset_find_bytype("net_fifo", iface); + if(!st) { + st = rrdset_create("net_fifo", iface, NULL, iface, "net.fifo", "Interface FIFO Buffer Errors", "errors", 7004, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "receive", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "transmit", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "receive", rfifo); + rrddim_set(st, "transmit", tfifo); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(ddo_compressed) { + st = rrdset_find_bytype("net_compressed", iface); + if(!st) { + st = rrdset_create("net_compressed", iface, NULL, iface, "net.compressed", "Compressed Packets", "packets/s", 7005, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "received", rcompressed); + rrddim_set(st, "sent", tcompressed); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(ddo_events) { + st = rrdset_find_bytype("net_events", iface); + if(!st) { + st = rrdset_create("net_events", iface, NULL, iface, "net.events", "Network Interface Events", "events/s", 7006, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "frames", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "collisions", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "carrier", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "frames", rframe); + rrddim_set(st, "collisions", tcollisions); + rrddim_set(st, "carrier", tcarrier); + rrdset_done(st); + } + } + + return 0; } diff --git a/src/proc_net_ip_vs_stats.c b/src/proc_net_ip_vs_stats.c index ffb5da7b5..96efd7925 100644 --- a/src/proc_net_ip_vs_stats.c +++ b/src/proc_net_ip_vs_stats.c @@ -1,102 +1,92 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> - #include "common.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -#define RRD_TYPE_NET_IPVS "ipvs" -#define RRD_TYPE_NET_IPVS_LEN strlen(RRD_TYPE_NET_IPVS) +#define RRD_TYPE_NET_IPVS "ipvs" +#define RRD_TYPE_NET_IPVS_LEN strlen(RRD_TYPE_NET_IPVS) int do_proc_net_ip_vs_stats(int update_every, unsigned long long dt) { - static int do_bandwidth = -1, do_sockets = -1, do_packets = -1; - static procfile *ff = NULL; + static int do_bandwidth = -1, do_sockets = -1, do_packets = -1; + static procfile *ff = NULL; - if(do_bandwidth == -1) do_bandwidth = config_get_boolean("plugin:proc:/proc/net/ip_vs_stats", "IPVS bandwidth", 1); - if(do_sockets == -1) do_sockets = config_get_boolean("plugin:proc:/proc/net/ip_vs_stats", "IPVS connections", 1); - if(do_packets == -1) do_packets = config_get_boolean("plugin:proc:/proc/net/ip_vs_stats", "IPVS packets", 1); + if(do_bandwidth == -1) do_bandwidth = config_get_boolean("plugin:proc:/proc/net/ip_vs_stats", "IPVS bandwidth", 1); + if(do_sockets == -1) do_sockets = config_get_boolean("plugin:proc:/proc/net/ip_vs_stats", "IPVS connections", 1); + if(do_packets == -1) do_packets = config_get_boolean("plugin:proc:/proc/net/ip_vs_stats", "IPVS packets", 1); - if(dt) {}; + if(dt) {}; - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time - // make sure we have 3 lines - if(procfile_lines(ff) < 3) return 1; + // make sure we have 3 lines + if(procfile_lines(ff) < 3) return 1; - // make sure we have 5 words on the 3rd line - if(procfile_linewords(ff, 2) < 5) return 1; + // make sure we have 5 words on the 3rd line + if(procfile_linewords(ff, 2) < 5) return 1; - unsigned long long entries, InPackets, OutPackets, InBytes, OutBytes; + unsigned long long entries, InPackets, OutPackets, InBytes, OutBytes; - entries = strtoull(procfile_lineword(ff, 2, 0), NULL, 16); - InPackets = strtoull(procfile_lineword(ff, 2, 1), NULL, 16); - OutPackets = strtoull(procfile_lineword(ff, 2, 2), NULL, 16); - InBytes = strtoull(procfile_lineword(ff, 2, 3), NULL, 16); - OutBytes = strtoull(procfile_lineword(ff, 2, 4), NULL, 16); + entries = strtoull(procfile_lineword(ff, 2, 0), NULL, 16); + InPackets = strtoull(procfile_lineword(ff, 2, 1), NULL, 16); + OutPackets = strtoull(procfile_lineword(ff, 2, 2), NULL, 16); + InBytes = strtoull(procfile_lineword(ff, 2, 3), NULL, 16); + OutBytes = strtoull(procfile_lineword(ff, 2, 4), NULL, 16); - RRDSET *st; + RRDSET *st; - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_sockets) { - st = rrdset_find(RRD_TYPE_NET_IPVS ".sockets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_IPVS, "sockets", NULL, RRD_TYPE_NET_IPVS, NULL, "IPVS New Connections", "connections/s", 1001, update_every, RRDSET_TYPE_LINE); + if(do_sockets) { + st = rrdset_find(RRD_TYPE_NET_IPVS ".sockets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_IPVS, "sockets", NULL, RRD_TYPE_NET_IPVS, NULL, "IPVS New Connections", "connections/s", 1001, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "connections", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + rrddim_add(st, "connections", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - rrddim_set(st, "connections", entries); - rrdset_done(st); - } + rrddim_set(st, "connections", entries); + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_packets) { - st = rrdset_find(RRD_TYPE_NET_IPVS ".packets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_IPVS, "packets", NULL, RRD_TYPE_NET_IPVS, NULL, "IPVS Packets", "packets/s", 1002, update_every, RRDSET_TYPE_LINE); + if(do_packets) { + st = rrdset_find(RRD_TYPE_NET_IPVS ".packets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_IPVS, "packets", NULL, RRD_TYPE_NET_IPVS, NULL, "IPVS Packets", "packets/s", 1002, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - rrddim_set(st, "received", InPackets); - rrddim_set(st, "sent", OutPackets); - rrdset_done(st); - } + rrddim_set(st, "received", InPackets); + rrddim_set(st, "sent", OutPackets); + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_bandwidth) { - st = rrdset_find(RRD_TYPE_NET_IPVS ".net"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_IPVS, "net", NULL, RRD_TYPE_NET_IPVS, NULL, "IPVS Bandwidth", "kilobits/s", 1000, update_every, RRDSET_TYPE_AREA); + if(do_bandwidth) { + st = rrdset_find(RRD_TYPE_NET_IPVS ".net"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_IPVS, "net", NULL, RRD_TYPE_NET_IPVS, NULL, "IPVS Bandwidth", "kilobits/s", 1000, update_every, RRDSET_TYPE_AREA); - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - rrddim_set(st, "received", InBytes); - rrddim_set(st, "sent", OutBytes); - rrdset_done(st); - } + rrddim_set(st, "received", InBytes); + rrddim_set(st, "sent", OutBytes); + rrdset_done(st); + } - return 0; + return 0; } diff --git a/src/proc_net_netstat.c b/src/proc_net_netstat.c index c8c12c1db..fe1c4d979 100644 --- a/src/proc_net_netstat.c +++ b/src/proc_net_netstat.c @@ -1,191 +1,179 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" int do_proc_net_netstat(int update_every, unsigned long long dt) { - static int do_bandwidth = -1, do_inerrors = -1, do_mcast = -1, do_bcast = -1, do_mcast_p = -1, do_bcast_p = -1; - static procfile *ff = NULL; - - if(do_bandwidth == -1) do_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "bandwidth", CONFIG_ONDEMAND_ONDEMAND); - if(do_inerrors == -1) do_inerrors = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "input errors", CONFIG_ONDEMAND_ONDEMAND); - if(do_mcast == -1) do_mcast = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "multicast bandwidth", CONFIG_ONDEMAND_ONDEMAND); - if(do_bcast == -1) do_bcast = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "broadcast bandwidth", CONFIG_ONDEMAND_ONDEMAND); - if(do_mcast_p == -1) do_mcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "multicast packets", CONFIG_ONDEMAND_ONDEMAND); - if(do_bcast_p == -1) do_bcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "broadcast packets", CONFIG_ONDEMAND_ONDEMAND); - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - for(l = 0; l < lines ;l++) { - if(strcmp(procfile_lineword(ff, l, 0), "IpExt") == 0) { - l++; // we need the next line - - if(strcmp(procfile_lineword(ff, l, 0), "IpExt") != 0) { - error("Cannot read IpExt line from /proc/net/netstat."); - break; - } - words = procfile_linewords(ff, l); - if(words < 12) { - error("Cannot read /proc/net/netstat IpExt line. Expected 12 params, read %d.", words); - continue; - } - - unsigned long long - InNoRoutes = 0, InTruncatedPkts = 0, - InOctets = 0, InMcastPkts = 0, InBcastPkts = 0, InMcastOctets = 0, InBcastOctets = 0, - OutOctets = 0, OutMcastPkts = 0, OutBcastPkts = 0, OutMcastOctets = 0, OutBcastOctets = 0; - - InNoRoutes = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - InTruncatedPkts = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - InMcastPkts = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - OutMcastPkts = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - InBcastPkts = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - OutBcastPkts = strtoull(procfile_lineword(ff, l, 6), NULL, 10); - InOctets = strtoull(procfile_lineword(ff, l, 7), NULL, 10); - OutOctets = strtoull(procfile_lineword(ff, l, 8), NULL, 10); - InMcastOctets = strtoull(procfile_lineword(ff, l, 9), NULL, 10); - OutMcastOctets = strtoull(procfile_lineword(ff, l, 10), NULL, 10); - InBcastOctets = strtoull(procfile_lineword(ff, l, 11), NULL, 10); - OutBcastOctets = strtoull(procfile_lineword(ff, l, 12), NULL, 10); - - RRDSET *st; - - // -------------------------------------------------------------------- - - if(do_bandwidth == CONFIG_ONDEMAND_YES || (do_bandwidth == CONFIG_ONDEMAND_ONDEMAND && (InOctets || OutOctets))) { - do_bandwidth = CONFIG_ONDEMAND_YES; - st = rrdset_find("system.ipv4"); - if(!st) { - st = rrdset_create("system", "ipv4", NULL, "network", NULL, "IPv4 Bandwidth", "kilobits/s", 500, update_every, RRDSET_TYPE_AREA); - - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", OutOctets); - rrddim_set(st, "received", InOctets); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_inerrors == CONFIG_ONDEMAND_YES || (do_inerrors == CONFIG_ONDEMAND_ONDEMAND && (InNoRoutes || InTruncatedPkts))) { - do_inerrors = CONFIG_ONDEMAND_YES; - st = rrdset_find("ipv4.inerrors"); - if(!st) { - st = rrdset_create("ipv4", "inerrors", NULL, "errors", NULL, "IPv4 Input Errors", "packets/s", 4000, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "noroutes", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "truncated", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "noroutes", InNoRoutes); - rrddim_set(st, "truncated", InTruncatedPkts); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_mcast == CONFIG_ONDEMAND_YES || (do_mcast == CONFIG_ONDEMAND_ONDEMAND && (InMcastOctets || OutMcastOctets))) { - do_mcast = CONFIG_ONDEMAND_YES; - st = rrdset_find("ipv4.mcast"); - if(!st) { - st = rrdset_create("ipv4", "mcast", NULL, "multicast", NULL, "IPv4 Multicast Bandwidth", "kilobits/s", 9000, update_every, RRDSET_TYPE_AREA); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", OutMcastOctets); - rrddim_set(st, "received", InMcastOctets); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_bcast == CONFIG_ONDEMAND_YES || (do_bcast == CONFIG_ONDEMAND_ONDEMAND && (InBcastOctets || OutBcastOctets))) { - do_bcast = CONFIG_ONDEMAND_YES; - st = rrdset_find("ipv4.bcast"); - if(!st) { - st = rrdset_create("ipv4", "bcast", NULL, "broadcast", NULL, "IPv4 Broadcast Bandwidth", "kilobits/s", 8000, update_every, RRDSET_TYPE_AREA); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", OutBcastOctets); - rrddim_set(st, "received", InBcastOctets); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_mcast_p == CONFIG_ONDEMAND_YES || (do_mcast_p == CONFIG_ONDEMAND_ONDEMAND && (InMcastPkts || OutMcastPkts))) { - do_mcast_p = CONFIG_ONDEMAND_YES; - st = rrdset_find("ipv4.mcastpkts"); - if(!st) { - st = rrdset_create("ipv4", "mcastpkts", NULL, "multicast", NULL, "IPv4 Multicast Packets", "packets/s", 9500, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", OutMcastPkts); - rrddim_set(st, "received", InMcastPkts); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_bcast_p == CONFIG_ONDEMAND_YES || (do_bcast_p == CONFIG_ONDEMAND_ONDEMAND && (InBcastPkts || OutBcastPkts))) { - do_bcast_p = CONFIG_ONDEMAND_YES; - st = rrdset_find("ipv4.bcastpkts"); - if(!st) { - st = rrdset_create("ipv4", "bcastpkts", NULL, "broadcast", NULL, "IPv4 Broadcast Packets", "packets/s", 8500, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", OutBcastPkts); - rrddim_set(st, "received", InBcastPkts); - rrdset_done(st); - } - } - } - - return 0; + static int do_bandwidth = -1, do_inerrors = -1, do_mcast = -1, do_bcast = -1, do_mcast_p = -1, do_bcast_p = -1; + static procfile *ff = NULL; + + if(do_bandwidth == -1) do_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "bandwidth", CONFIG_ONDEMAND_ONDEMAND); + if(do_inerrors == -1) do_inerrors = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "input errors", CONFIG_ONDEMAND_ONDEMAND); + if(do_mcast == -1) do_mcast = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "multicast bandwidth", CONFIG_ONDEMAND_ONDEMAND); + if(do_bcast == -1) do_bcast = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "broadcast bandwidth", CONFIG_ONDEMAND_ONDEMAND); + if(do_mcast_p == -1) do_mcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "multicast packets", CONFIG_ONDEMAND_ONDEMAND); + if(do_bcast_p == -1) do_bcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/netstat", "broadcast packets", CONFIG_ONDEMAND_ONDEMAND); + + if(dt) {}; + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + for(l = 0; l < lines ;l++) { + if(strcmp(procfile_lineword(ff, l, 0), "IpExt") == 0) { + l++; // we need the next line + + if(strcmp(procfile_lineword(ff, l, 0), "IpExt") != 0) { + error("Cannot read IpExt line from /proc/net/netstat."); + break; + } + words = procfile_linewords(ff, l); + if(words < 12) { + error("Cannot read /proc/net/netstat IpExt line. Expected 12 params, read %u.", words); + continue; + } + + unsigned long long + InNoRoutes = 0, InTruncatedPkts = 0, + InOctets = 0, InMcastPkts = 0, InBcastPkts = 0, InMcastOctets = 0, InBcastOctets = 0, + OutOctets = 0, OutMcastPkts = 0, OutBcastPkts = 0, OutMcastOctets = 0, OutBcastOctets = 0; + + InNoRoutes = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + InTruncatedPkts = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + InMcastPkts = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + OutMcastPkts = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + InBcastPkts = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + OutBcastPkts = strtoull(procfile_lineword(ff, l, 6), NULL, 10); + InOctets = strtoull(procfile_lineword(ff, l, 7), NULL, 10); + OutOctets = strtoull(procfile_lineword(ff, l, 8), NULL, 10); + InMcastOctets = strtoull(procfile_lineword(ff, l, 9), NULL, 10); + OutMcastOctets = strtoull(procfile_lineword(ff, l, 10), NULL, 10); + InBcastOctets = strtoull(procfile_lineword(ff, l, 11), NULL, 10); + OutBcastOctets = strtoull(procfile_lineword(ff, l, 12), NULL, 10); + + RRDSET *st; + + // -------------------------------------------------------------------- + + if(do_bandwidth == CONFIG_ONDEMAND_YES || (do_bandwidth == CONFIG_ONDEMAND_ONDEMAND && (InOctets || OutOctets))) { + do_bandwidth = CONFIG_ONDEMAND_YES; + st = rrdset_find("system.ipv4"); + if(!st) { + st = rrdset_create("system", "ipv4", NULL, "network", NULL, "IPv4 Bandwidth", "kilobits/s", 500, update_every, RRDSET_TYPE_AREA); + + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", OutOctets); + rrddim_set(st, "received", InOctets); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_inerrors == CONFIG_ONDEMAND_YES || (do_inerrors == CONFIG_ONDEMAND_ONDEMAND && (InNoRoutes || InTruncatedPkts))) { + do_inerrors = CONFIG_ONDEMAND_YES; + st = rrdset_find("ipv4.inerrors"); + if(!st) { + st = rrdset_create("ipv4", "inerrors", NULL, "errors", NULL, "IPv4 Input Errors", "packets/s", 4000, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "noroutes", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "truncated", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "noroutes", InNoRoutes); + rrddim_set(st, "truncated", InTruncatedPkts); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_mcast == CONFIG_ONDEMAND_YES || (do_mcast == CONFIG_ONDEMAND_ONDEMAND && (InMcastOctets || OutMcastOctets))) { + do_mcast = CONFIG_ONDEMAND_YES; + st = rrdset_find("ipv4.mcast"); + if(!st) { + st = rrdset_create("ipv4", "mcast", NULL, "multicast", NULL, "IPv4 Multicast Bandwidth", "kilobits/s", 9000, update_every, RRDSET_TYPE_AREA); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", OutMcastOctets); + rrddim_set(st, "received", InMcastOctets); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_bcast == CONFIG_ONDEMAND_YES || (do_bcast == CONFIG_ONDEMAND_ONDEMAND && (InBcastOctets || OutBcastOctets))) { + do_bcast = CONFIG_ONDEMAND_YES; + st = rrdset_find("ipv4.bcast"); + if(!st) { + st = rrdset_create("ipv4", "bcast", NULL, "broadcast", NULL, "IPv4 Broadcast Bandwidth", "kilobits/s", 8000, update_every, RRDSET_TYPE_AREA); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", OutBcastOctets); + rrddim_set(st, "received", InBcastOctets); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_mcast_p == CONFIG_ONDEMAND_YES || (do_mcast_p == CONFIG_ONDEMAND_ONDEMAND && (InMcastPkts || OutMcastPkts))) { + do_mcast_p = CONFIG_ONDEMAND_YES; + st = rrdset_find("ipv4.mcastpkts"); + if(!st) { + st = rrdset_create("ipv4", "mcastpkts", NULL, "multicast", NULL, "IPv4 Multicast Packets", "packets/s", 9500, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", OutMcastPkts); + rrddim_set(st, "received", InMcastPkts); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_bcast_p == CONFIG_ONDEMAND_YES || (do_bcast_p == CONFIG_ONDEMAND_ONDEMAND && (InBcastPkts || OutBcastPkts))) { + do_bcast_p = CONFIG_ONDEMAND_YES; + st = rrdset_find("ipv4.bcastpkts"); + if(!st) { + st = rrdset_create("ipv4", "bcastpkts", NULL, "broadcast", NULL, "IPv4 Broadcast Packets", "packets/s", 8500, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", OutBcastPkts); + rrddim_set(st, "received", InBcastPkts); + rrdset_done(st); + } + } + } + + return 0; } diff --git a/src/proc_net_rpc_nfsd.c b/src/proc_net_rpc_nfsd.c index 6c6dd7066..0323b4dfb 100644 --- a/src/proc_net_rpc_nfsd.c +++ b/src/proc_net_rpc_nfsd.c @@ -1,693 +1,681 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" struct nfsd_procs { - char name[30]; - unsigned long long proc2; - unsigned long long proc3; - unsigned long long proc4; - int present2; - int present3; - int present4; + char name[30]; + unsigned long long proc2; + unsigned long long proc3; + unsigned long long proc4; + int present2; + int present3; + int present4; }; struct nfsd_procs nfsd_proc_values[] = { - { "null", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "getattr", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "setattr", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "lookup", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "access", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "readlink", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "read", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "write", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "create", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "mkdir", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "symlink", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "mknod", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "remove", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "rmdir", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "rename", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "link", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "readdir", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "readdirplus", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "fsstat", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "fsinfo", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "pathconf", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "commit", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, - { "", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "null", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "getattr", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "setattr", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "lookup", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "access", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "readlink", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "read", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "write", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "create", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "mkdir", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "symlink", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "mknod", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "remove", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "rmdir", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "rename", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "link", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "readdir", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "readdirplus", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "fsstat", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "fsinfo", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "pathconf", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "commit", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, + { "", 0ULL, 0ULL, 0ULL, 0, 0, 0 }, }; struct nfsd4_ops { - char name[30]; - unsigned long long value; - int present; + char name[30]; + unsigned long long value; + int present; }; struct nfsd4_ops nfsd4_ops_values[] = { - { "access", 0ULL, 0}, - { "close", 0ULL, 0}, - { "commit", 0ULL, 0}, - { "create", 0ULL, 0}, - { "delegpurge", 0ULL, 0}, - { "delegreturn", 0ULL, 0}, - { "getattr", 0ULL, 0}, - { "getfh", 0ULL, 0}, - { "link", 0ULL, 0}, - { "lock", 0ULL, 0}, - { "lockt", 0ULL, 0}, - { "locku", 0ULL, 0}, - { "lookup", 0ULL, 0}, - { "lookupp", 0ULL, 0}, - { "nverify", 0ULL, 0}, - { "open", 0ULL, 0}, - { "openattr", 0ULL, 0}, - { "open_confirm", 0ULL, 0}, - { "open_downgrade", 0ULL, 0}, - { "putfh", 0ULL, 0}, - { "putpubfh", 0ULL, 0}, - { "putrootfh", 0ULL, 0}, - { "read", 0ULL, 0}, - { "readdir", 0ULL, 0}, - { "readlink", 0ULL, 0}, - { "remove", 0ULL, 0}, - { "rename", 0ULL, 0}, - { "renew", 0ULL, 0}, - { "restorefh", 0ULL, 0}, - { "savefh", 0ULL, 0}, - { "secinfo", 0ULL, 0}, - { "setattr", 0ULL, 0}, - { "setclientid", 0ULL, 0}, - { "setclientid_confirm", 0ULL, 0}, - { "verify", 0ULL, 0}, - { "write", 0ULL, 0}, - { "release_lockowner", 0ULL, 0}, - - /* nfs41 */ - { "backchannel_ctl", 0ULL, 0}, - { "bind_conn_to_session", 0ULL, 0}, - { "exchange_id", 0ULL, 0}, - { "create_session", 0ULL, 0}, - { "destroy_session", 0ULL, 0}, - { "free_stateid", 0ULL, 0}, - { "get_dir_delegation", 0ULL, 0}, - { "getdeviceinfo", 0ULL, 0}, - { "getdevicelist", 0ULL, 0}, - { "layoutcommit", 0ULL, 0}, - { "layoutget", 0ULL, 0}, - { "layoutreturn", 0ULL, 0}, - { "secinfo_no_name", 0ULL, 0}, - { "sequence", 0ULL, 0}, - { "set_ssv", 0ULL, 0}, - { "test_stateid", 0ULL, 0}, - { "want_delegation", 0ULL, 0}, - { "destroy_clientid", 0ULL, 0}, - { "reclaim_complete", 0ULL, 0}, - - /* nfs42 */ - { "allocate", 0ULL, 0}, - { "copy", 0ULL, 0}, - { "copy_notify", 0ULL, 0}, - { "deallocate", 0ULL, 0}, - { "io_advise", 0ULL, 0}, - { "layouterror", 0ULL, 0}, - { "layoutstats", 0ULL, 0}, - { "offload_cancel", 0ULL, 0}, - { "offload_status", 0ULL, 0}, - { "read_plus", 0ULL, 0}, - { "seek", 0ULL, 0}, - { "write_same", 0ULL, 0}, - - /* termination */ - { "", 0ULL, 0 } + { "access", 0ULL, 0}, + { "close", 0ULL, 0}, + { "commit", 0ULL, 0}, + { "create", 0ULL, 0}, + { "delegpurge", 0ULL, 0}, + { "delegreturn", 0ULL, 0}, + { "getattr", 0ULL, 0}, + { "getfh", 0ULL, 0}, + { "link", 0ULL, 0}, + { "lock", 0ULL, 0}, + { "lockt", 0ULL, 0}, + { "locku", 0ULL, 0}, + { "lookup", 0ULL, 0}, + { "lookupp", 0ULL, 0}, + { "nverify", 0ULL, 0}, + { "open", 0ULL, 0}, + { "openattr", 0ULL, 0}, + { "open_confirm", 0ULL, 0}, + { "open_downgrade", 0ULL, 0}, + { "putfh", 0ULL, 0}, + { "putpubfh", 0ULL, 0}, + { "putrootfh", 0ULL, 0}, + { "read", 0ULL, 0}, + { "readdir", 0ULL, 0}, + { "readlink", 0ULL, 0}, + { "remove", 0ULL, 0}, + { "rename", 0ULL, 0}, + { "renew", 0ULL, 0}, + { "restorefh", 0ULL, 0}, + { "savefh", 0ULL, 0}, + { "secinfo", 0ULL, 0}, + { "setattr", 0ULL, 0}, + { "setclientid", 0ULL, 0}, + { "setclientid_confirm", 0ULL, 0}, + { "verify", 0ULL, 0}, + { "write", 0ULL, 0}, + { "release_lockowner", 0ULL, 0}, + + /* nfs41 */ + { "backchannel_ctl", 0ULL, 0}, + { "bind_conn_to_session", 0ULL, 0}, + { "exchange_id", 0ULL, 0}, + { "create_session", 0ULL, 0}, + { "destroy_session", 0ULL, 0}, + { "free_stateid", 0ULL, 0}, + { "get_dir_delegation", 0ULL, 0}, + { "getdeviceinfo", 0ULL, 0}, + { "getdevicelist", 0ULL, 0}, + { "layoutcommit", 0ULL, 0}, + { "layoutget", 0ULL, 0}, + { "layoutreturn", 0ULL, 0}, + { "secinfo_no_name", 0ULL, 0}, + { "sequence", 0ULL, 0}, + { "set_ssv", 0ULL, 0}, + { "test_stateid", 0ULL, 0}, + { "want_delegation", 0ULL, 0}, + { "destroy_clientid", 0ULL, 0}, + { "reclaim_complete", 0ULL, 0}, + + /* nfs42 */ + { "allocate", 0ULL, 0}, + { "copy", 0ULL, 0}, + { "copy_notify", 0ULL, 0}, + { "deallocate", 0ULL, 0}, + { "io_advise", 0ULL, 0}, + { "layouterror", 0ULL, 0}, + { "layoutstats", 0ULL, 0}, + { "offload_cancel", 0ULL, 0}, + { "offload_status", 0ULL, 0}, + { "read_plus", 0ULL, 0}, + { "seek", 0ULL, 0}, + { "write_same", 0ULL, 0}, + + /* termination */ + { "", 0ULL, 0 } }; int do_proc_net_rpc_nfsd(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int do_rc = -1, do_fh = -1, do_io = -1, do_th = -1, do_ra = -1, do_net = -1, do_rpc = -1, do_proc2 = -1, do_proc3 = -1, do_proc4 = -1, do_proc4ops = -1; - static int ra_warning = 0, th_warning = 0, proc2_warning = 0, proc3_warning = 0, proc4_warning = 0, proc4ops_warning = 0; - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - if(do_rc == -1) do_rc = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "read cache", 1); - if(do_fh == -1) do_fh = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "file handles", 1); - if(do_io == -1) do_io = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "I/O", 1); - if(do_th == -1) do_th = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "threads", 1); - if(do_ra == -1) do_ra = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "read ahead", 1); - if(do_net == -1) do_net = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "network", 1); - if(do_rpc == -1) do_rpc = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "rpc", 1); - if(do_proc2 == -1) do_proc2 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v2 procedures", 1); - if(do_proc3 == -1) do_proc3 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v3 procedures", 1); - if(do_proc4 == -1) do_proc4 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v4 procedures", 1); - if(do_proc4ops == -1) do_proc4ops = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v4 operations", 1); - - // if they are enabled, reset them to 1 - // later we do them =2 to avoid doing strcmp for all lines - if(do_rc) do_rc = 1; - if(do_fh) do_fh = 1; - if(do_io) do_io = 1; - if(do_th) do_th = 1; - if(do_ra) do_ra = 1; - if(do_net) do_net = 1; - if(do_rpc) do_rpc = 1; - if(do_proc2) do_proc2 = 1; - if(do_proc3) do_proc3 = 1; - if(do_proc4) do_proc4 = 1; - if(do_proc4ops) do_proc4ops = 1; - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - char *type; - unsigned long long rc_hits = 0, rc_misses = 0, rc_nocache = 0; - unsigned long long fh_stale = 0, fh_total_lookups = 0, fh_anonymous_lookups = 0, fh_dir_not_in_dcache = 0, fh_non_dir_not_in_dcache = 0; - unsigned long long io_read = 0, io_write = 0; - unsigned long long th_threads = 0, th_fullcnt = 0, th_hist10 = 0, th_hist20 = 0, th_hist30 = 0, th_hist40 = 0, th_hist50 = 0, th_hist60 = 0, th_hist70 = 0, th_hist80 = 0, th_hist90 = 0, th_hist100 = 0; - unsigned long long ra_size = 0, ra_hist10 = 0, ra_hist20 = 0, ra_hist30 = 0, ra_hist40 = 0, ra_hist50 = 0, ra_hist60 = 0, ra_hist70 = 0, ra_hist80 = 0, ra_hist90 = 0, ra_hist100 = 0, ra_none = 0; - unsigned long long net_count = 0, net_udp_count = 0, net_tcp_count = 0, net_tcp_connections = 0; - unsigned long long rpc_count = 0, rpc_bad_format = 0, rpc_bad_auth = 0, rpc_bad_client = 0; - - for(l = 0; l < lines ;l++) { - words = procfile_linewords(ff, l); - if(!words) continue; - - type = procfile_lineword(ff, l, 0); - - if(do_rc == 1 && strcmp(type, "rc") == 0) { - if(words < 4) { - error("%s line of /proc/net/rpc/nfsd has %d words, expected %d", type, words, 4); - continue; - } - - rc_hits = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - rc_misses = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - rc_nocache = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - - unsigned long long sum = rc_hits + rc_misses + rc_nocache; - if(sum == 0ULL) do_rc = -1; - else do_rc = 2; - } - else if(do_fh == 1 && strcmp(type, "fh") == 0) { - if(words < 6) { - error("%s line of /proc/net/rpc/nfsd has %d words, expected %d", type, words, 6); - continue; - } - - fh_stale = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - fh_total_lookups = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - fh_anonymous_lookups = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - fh_dir_not_in_dcache = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - fh_non_dir_not_in_dcache = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - - unsigned long long sum = fh_stale + fh_total_lookups + fh_anonymous_lookups + fh_dir_not_in_dcache + fh_non_dir_not_in_dcache; - if(sum == 0ULL) do_fh = -1; - else do_fh = 2; - } - else if(do_io == 1 && strcmp(type, "io") == 0) { - if(words < 3) { - error("%s line of /proc/net/rpc/nfsd has %d words, expected %d", type, words, 3); - continue; - } - - io_read = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - io_write = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - - unsigned long long sum = io_read + io_write; - if(sum == 0ULL) do_io = -1; - else do_io = 2; - } - else if(do_th == 1 && strcmp(type, "th") == 0) { - if(words < 13) { - error("%s line of /proc/net/rpc/nfsd has %d words, expected %d", type, words, 13); - continue; - } - - th_threads = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - th_fullcnt = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - th_hist10 = (unsigned long long)(atof(procfile_lineword(ff, l, 3)) * 1000.0); - th_hist20 = (unsigned long long)(atof(procfile_lineword(ff, l, 4)) * 1000.0); - th_hist30 = (unsigned long long)(atof(procfile_lineword(ff, l, 5)) * 1000.0); - th_hist40 = (unsigned long long)(atof(procfile_lineword(ff, l, 6)) * 1000.0); - th_hist50 = (unsigned long long)(atof(procfile_lineword(ff, l, 7)) * 1000.0); - th_hist60 = (unsigned long long)(atof(procfile_lineword(ff, l, 8)) * 1000.0); - th_hist70 = (unsigned long long)(atof(procfile_lineword(ff, l, 9)) * 1000.0); - th_hist80 = (unsigned long long)(atof(procfile_lineword(ff, l, 10)) * 1000.0); - th_hist90 = (unsigned long long)(atof(procfile_lineword(ff, l, 11)) * 1000.0); - th_hist100 = (unsigned long long)(atof(procfile_lineword(ff, l, 12)) * 1000.0); - - // threads histogram has been disabled on recent kernels - // http://permalink.gmane.org/gmane.linux.nfs/24528 - unsigned long long sum = th_hist10 + th_hist20 + th_hist30 + th_hist40 + th_hist50 + th_hist60 + th_hist70 + th_hist80 + th_hist90 + th_hist100; - if(sum == 0ULL) { - if(!th_warning) { - info("Disabling /proc/net/rpc/nfsd threads histogram. It seems unused on this machine. It will be enabled automatically when found with data in it."); - th_warning = 1; - } - do_th = -1; - } - else do_th = 2; - } - else if(do_ra == 1 && strcmp(type, "ra") == 0) { - if(words < 13) { - error("%s line of /proc/net/rpc/nfsd has %d words, expected %d", type, words, 13); - continue; - } - - ra_size = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - ra_hist10 = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - ra_hist20 = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - ra_hist30 = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - ra_hist40 = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - ra_hist50 = strtoull(procfile_lineword(ff, l, 6), NULL, 10); - ra_hist60 = strtoull(procfile_lineword(ff, l, 7), NULL, 10); - ra_hist70 = strtoull(procfile_lineword(ff, l, 8), NULL, 10); - ra_hist80 = strtoull(procfile_lineword(ff, l, 9), NULL, 10); - ra_hist90 = strtoull(procfile_lineword(ff, l, 10), NULL, 10); - ra_hist100 = strtoull(procfile_lineword(ff, l, 11), NULL, 10); - ra_none = strtoull(procfile_lineword(ff, l, 12), NULL, 10); - - unsigned long long sum = ra_hist10 + ra_hist20 + ra_hist30 + ra_hist40 + ra_hist50 + ra_hist60 + ra_hist70 + ra_hist80 + ra_hist90 + ra_hist100 + ra_none; - if(sum == 0ULL) { - if(!ra_warning) { - info("Disabling /proc/net/rpc/nfsd read ahead histogram. It seems unused on this machine. It will be enabled automatically when found with data in it."); - ra_warning = 1; - } - do_ra = -1; - } - else do_ra = 2; - } - else if(do_net == 1 && strcmp(type, "net") == 0) { - if(words < 5) { - error("%s line of /proc/net/rpc/nfsd has %d words, expected %d", type, words, 5); - continue; - } - - net_count = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - net_udp_count = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - net_tcp_count = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - net_tcp_connections = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - - unsigned long long sum = net_count + net_udp_count + net_tcp_count + net_tcp_connections; - if(sum == 0ULL) do_net = -1; - else do_net = 2; - } - else if(do_rpc == 1 && strcmp(type, "rpc") == 0) { - if(words < 6) { - error("%s line of /proc/net/rpc/nfsd has %d words, expected %d", type, words, 6); - continue; - } - - rpc_count = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - rpc_bad_format = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - rpc_bad_auth = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - rpc_bad_client = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - - unsigned long long sum = rpc_count + rpc_bad_format + rpc_bad_auth + rpc_bad_client; - if(sum == 0ULL) do_rpc = -1; - else do_rpc = 2; - } - else if(do_proc2 == 1 && strcmp(type, "proc2") == 0) { - // the first number is the count of numbers present - // so we start for word 2 - - unsigned long long sum = 0; - unsigned int i, j; - for(i = 0, j = 2; j < words && nfsd_proc_values[i].name[0] ; i++, j++) { - nfsd_proc_values[i].proc2 = strtoull(procfile_lineword(ff, l, j), NULL, 10); - nfsd_proc_values[i].present2 = 1; - sum += nfsd_proc_values[i].proc2; - } - - if(sum == 0ULL) { - if(!proc2_warning) { - error("Disabling /proc/net/rpc/nfsd v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); - proc2_warning = 1; - } - do_proc2 = 0; - } - else do_proc2 = 2; - } - else if(do_proc3 == 1 && strcmp(type, "proc3") == 0) { - // the first number is the count of numbers present - // so we start for word 2 - - unsigned long long sum = 0; - unsigned int i, j; - for(i = 0, j = 2; j < words && nfsd_proc_values[i].name[0] ; i++, j++) { - nfsd_proc_values[i].proc3 = strtoull(procfile_lineword(ff, l, j), NULL, 10); - nfsd_proc_values[i].present3 = 1; - sum += nfsd_proc_values[i].proc3; - } - - if(sum == 0ULL) { - if(!proc3_warning) { - info("Disabling /proc/net/rpc/nfsd v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); - proc3_warning = 1; - } - do_proc3 = 0; - } - else do_proc3 = 2; - } - else if(do_proc4 == 1 && strcmp(type, "proc4") == 0) { - // the first number is the count of numbers present - // so we start for word 2 - - unsigned long long sum = 0; - unsigned int i, j; - for(i = 0, j = 2; j < words && nfsd_proc_values[i].name[0] ; i++, j++) { - nfsd_proc_values[i].proc4 = strtoull(procfile_lineword(ff, l, j), NULL, 10); - nfsd_proc_values[i].present4 = 1; - sum += nfsd_proc_values[i].proc4; - } - - if(sum == 0ULL) { - if(!proc4_warning) { - info("Disabling /proc/net/rpc/nfsd v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); - proc4_warning = 1; - } - do_proc4 = 0; - } - else do_proc4 = 2; - } - else if(do_proc4ops == 1 && strcmp(type, "proc4ops") == 0) { - // the first number is the count of numbers present - // so we start for word 2 - - unsigned long long sum = 0; - unsigned int i, j; - for(i = 0, j = 2; j < words && nfsd4_ops_values[i].name[0] ; i++, j++) { - nfsd4_ops_values[i].value = strtoull(procfile_lineword(ff, l, j), NULL, 10); - nfsd4_ops_values[i].present = 1; - sum += nfsd4_ops_values[i].value; - } - - if(sum == 0ULL) { - if(!proc4ops_warning) { - info("Disabling /proc/net/rpc/nfsd v4 operations chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); - proc4ops_warning = 1; - } - do_proc4ops = 0; - } - else do_proc4ops = 2; - } - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - if(do_rc == 2) { - st = rrdset_find_bytype("nfsd", "readcache"); - if(!st) { - st = rrdset_create("nfsd", "readcache", NULL, "nfsd", NULL, "Read Cache", "reads/s", 5000, update_every, RRDSET_TYPE_STACKED); - - rrddim_add(st, "hits", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "misses", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "nocache", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "hits", rc_hits); - rrddim_set(st, "misses", rc_misses); - rrddim_set(st, "nocache", rc_nocache); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_fh == 2) { - st = rrdset_find_bytype("nfsd", "filehandles"); - if(!st) { - st = rrdset_create("nfsd", "filehandles", NULL, "nfsd", NULL, "File Handles", "handles/s", 5001, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "stale", NULL, 1, 1, RRDDIM_ABSOLUTE); - rrddim_add(st, "total_lookups", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "anonymous_lookups", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "dir_not_in_dcache", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "non_dir_not_in_dcache", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "stale", fh_stale); - rrddim_set(st, "total_lookups", fh_total_lookups); - rrddim_set(st, "anonymous_lookups", fh_anonymous_lookups); - rrddim_set(st, "dir_not_in_dcache", fh_dir_not_in_dcache); - rrddim_set(st, "non_dir_not_in_dcache", fh_non_dir_not_in_dcache); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_io == 2) { - st = rrdset_find_bytype("nfsd", "io"); - if(!st) { - st = rrdset_create("nfsd", "io", NULL, "nfsd", NULL, "I/O", "kilobytes/s", 5002, update_every, RRDSET_TYPE_AREA); - - rrddim_add(st, "read", NULL, 1, 1000, RRDDIM_INCREMENTAL); - rrddim_add(st, "write", NULL, -1, 1000, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "read", io_read); - rrddim_set(st, "write", io_write); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_th == 2) { - st = rrdset_find_bytype("nfsd", "threads"); - if(!st) { - st = rrdset_create("nfsd", "threads", NULL, "nfsd", NULL, "Threads", "threads", 5003, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "threads", NULL, 1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "threads", th_threads); - rrdset_done(st); - - st = rrdset_find_bytype("nfsd", "threads_fullcnt"); - if(!st) { - st = rrdset_create("nfsd", "threads_fullcnt", NULL, "nfsd", NULL, "Threads Full Count", "ops/s", 5004, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "full_count", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "full_count", th_fullcnt); - rrdset_done(st); - - st = rrdset_find_bytype("nfsd", "threads_histogram"); - if(!st) { - st = rrdset_create("nfsd", "threads_histogram", NULL, "nfsd", NULL, "Threads Usage Histogram", "percentage", 5005, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "0%-10%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "10%-20%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "20%-30%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "30%-40%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "40%-50%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "50%-60%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "60%-70%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "70%-80%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "80%-90%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - rrddim_add(st, "90%-100%", NULL, 1, 1000, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "0%-10%", th_hist10); - rrddim_set(st, "10%-20%", th_hist20); - rrddim_set(st, "20%-30%", th_hist30); - rrddim_set(st, "30%-40%", th_hist40); - rrddim_set(st, "40%-50%", th_hist50); - rrddim_set(st, "50%-60%", th_hist60); - rrddim_set(st, "60%-70%", th_hist70); - rrddim_set(st, "70%-80%", th_hist80); - rrddim_set(st, "80%-90%", th_hist90); - rrddim_set(st, "90%-100%", th_hist100); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ra == 2) { - st = rrdset_find_bytype("nfsd", "readahead"); - if(!st) { - st = rrdset_create("nfsd", "readahead", NULL, "nfsd", NULL, "Read Ahead Depth", "percentage", 5005, update_every, RRDSET_TYPE_STACKED); - - rrddim_add(st, "10%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "20%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "30%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "40%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "50%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "60%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "70%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "80%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "90%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "100%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "misses", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - } - else rrdset_next(st); - - // ignore ra_size - if(ra_size) {}; - - rrddim_set(st, "10%", ra_hist10); - rrddim_set(st, "20%", ra_hist20); - rrddim_set(st, "30%", ra_hist30); - rrddim_set(st, "40%", ra_hist40); - rrddim_set(st, "50%", ra_hist50); - rrddim_set(st, "60%", ra_hist60); - rrddim_set(st, "70%", ra_hist70); - rrddim_set(st, "80%", ra_hist80); - rrddim_set(st, "90%", ra_hist90); - rrddim_set(st, "100%", ra_hist100); - rrddim_set(st, "misses", ra_none); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_net == 2) { - st = rrdset_find_bytype("nfsd", "net"); - if(!st) { - st = rrdset_create("nfsd", "net", NULL, "nfsd", NULL, "Network Reads", "reads/s", 5007, update_every, RRDSET_TYPE_STACKED); - st->isdetail = 1; - - rrddim_add(st, "udp", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "tcp", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - // ignore net_count, net_tcp_connections - if(net_count) {}; - if(net_tcp_connections) {}; - - rrddim_set(st, "udp", net_udp_count); - rrddim_set(st, "tcp", net_tcp_count); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_rpc == 2) { - st = rrdset_find_bytype("nfsd", "rpc"); - if(!st) { - st = rrdset_create("nfsd", "rpc", NULL, "nfsd", NULL, "Remote Procedure Calls", "calls/s", 5008, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "bad_format", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "bad_auth", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - // ignore rpc_bad_client - if(rpc_bad_client) {}; - - rrddim_set(st, "all", rpc_count); - rrddim_set(st, "bad_format", rpc_bad_format); - rrddim_set(st, "bad_auth", rpc_bad_auth); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_proc2 == 2) { - unsigned int i; - st = rrdset_find_bytype("nfsd", "proc2"); - if(!st) { - st = rrdset_create("nfsd", "proc2", NULL, "nfsd", NULL, "NFS v2 Calls", "calls/s", 5009, update_every, RRDSET_TYPE_STACKED); - - for(i = 0; nfsd_proc_values[i].present2 ; i++) - rrddim_add(st, nfsd_proc_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - for(i = 0; nfsd_proc_values[i].present2 ; i++) - rrddim_set(st, nfsd_proc_values[i].name, nfsd_proc_values[i].proc2); - - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_proc3 == 2) { - unsigned int i; - st = rrdset_find_bytype("nfsd", "proc3"); - if(!st) { - st = rrdset_create("nfsd", "proc3", NULL, "nfsd", NULL, "NFS v3 Calls", "calls/s", 5010, update_every, RRDSET_TYPE_STACKED); - - for(i = 0; nfsd_proc_values[i].present3 ; i++) - rrddim_add(st, nfsd_proc_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - for(i = 0; nfsd_proc_values[i].present3 ; i++) - rrddim_set(st, nfsd_proc_values[i].name, nfsd_proc_values[i].proc3); - - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_proc4 == 2) { - unsigned int i; - st = rrdset_find_bytype("nfsd", "proc4"); - if(!st) { - st = rrdset_create("nfsd", "proc4", NULL, "nfsd", NULL, "NFS v4 Calls", "calls/s", 5011, update_every, RRDSET_TYPE_STACKED); - - for(i = 0; nfsd_proc_values[i].present4 ; i++) - rrddim_add(st, nfsd_proc_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + static procfile *ff = NULL; + static int do_rc = -1, do_fh = -1, do_io = -1, do_th = -1, do_ra = -1, do_net = -1, do_rpc = -1, do_proc2 = -1, do_proc3 = -1, do_proc4 = -1, do_proc4ops = -1; + static int ra_warning = 0, th_warning = 0, proc2_warning = 0, proc3_warning = 0, proc4_warning = 0, proc4ops_warning = 0; + + if(dt) {}; + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + if(do_rc == -1) do_rc = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "read cache", 1); + if(do_fh == -1) do_fh = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "file handles", 1); + if(do_io == -1) do_io = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "I/O", 1); + if(do_th == -1) do_th = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "threads", 1); + if(do_ra == -1) do_ra = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "read ahead", 1); + if(do_net == -1) do_net = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "network", 1); + if(do_rpc == -1) do_rpc = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "rpc", 1); + if(do_proc2 == -1) do_proc2 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v2 procedures", 1); + if(do_proc3 == -1) do_proc3 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v3 procedures", 1); + if(do_proc4 == -1) do_proc4 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v4 procedures", 1); + if(do_proc4ops == -1) do_proc4ops = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v4 operations", 1); + + // if they are enabled, reset them to 1 + // later we do them =2 to avoid doing strcmp for all lines + if(do_rc) do_rc = 1; + if(do_fh) do_fh = 1; + if(do_io) do_io = 1; + if(do_th) do_th = 1; + if(do_ra) do_ra = 1; + if(do_net) do_net = 1; + if(do_rpc) do_rpc = 1; + if(do_proc2) do_proc2 = 1; + if(do_proc3) do_proc3 = 1; + if(do_proc4) do_proc4 = 1; + if(do_proc4ops) do_proc4ops = 1; + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + char *type; + unsigned long long rc_hits = 0, rc_misses = 0, rc_nocache = 0; + unsigned long long fh_stale = 0, fh_total_lookups = 0, fh_anonymous_lookups = 0, fh_dir_not_in_dcache = 0, fh_non_dir_not_in_dcache = 0; + unsigned long long io_read = 0, io_write = 0; + unsigned long long th_threads = 0, th_fullcnt = 0, th_hist10 = 0, th_hist20 = 0, th_hist30 = 0, th_hist40 = 0, th_hist50 = 0, th_hist60 = 0, th_hist70 = 0, th_hist80 = 0, th_hist90 = 0, th_hist100 = 0; + unsigned long long ra_size = 0, ra_hist10 = 0, ra_hist20 = 0, ra_hist30 = 0, ra_hist40 = 0, ra_hist50 = 0, ra_hist60 = 0, ra_hist70 = 0, ra_hist80 = 0, ra_hist90 = 0, ra_hist100 = 0, ra_none = 0; + unsigned long long net_count = 0, net_udp_count = 0, net_tcp_count = 0, net_tcp_connections = 0; + unsigned long long rpc_count = 0, rpc_bad_format = 0, rpc_bad_auth = 0, rpc_bad_client = 0; + + for(l = 0; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(!words) continue; + + type = procfile_lineword(ff, l, 0); + + if(do_rc == 1 && strcmp(type, "rc") == 0) { + if(words < 4) { + error("%s line of /proc/net/rpc/nfsd has %u words, expected %d", type, words, 4); + continue; + } + + rc_hits = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + rc_misses = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + rc_nocache = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + + unsigned long long sum = rc_hits + rc_misses + rc_nocache; + if(sum == 0ULL) do_rc = -1; + else do_rc = 2; + } + else if(do_fh == 1 && strcmp(type, "fh") == 0) { + if(words < 6) { + error("%s line of /proc/net/rpc/nfsd has %u words, expected %d", type, words, 6); + continue; + } + + fh_stale = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + fh_total_lookups = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + fh_anonymous_lookups = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + fh_dir_not_in_dcache = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + fh_non_dir_not_in_dcache = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + + unsigned long long sum = fh_stale + fh_total_lookups + fh_anonymous_lookups + fh_dir_not_in_dcache + fh_non_dir_not_in_dcache; + if(sum == 0ULL) do_fh = -1; + else do_fh = 2; + } + else if(do_io == 1 && strcmp(type, "io") == 0) { + if(words < 3) { + error("%s line of /proc/net/rpc/nfsd has %u words, expected %d", type, words, 3); + continue; + } + + io_read = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + io_write = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + + unsigned long long sum = io_read + io_write; + if(sum == 0ULL) do_io = -1; + else do_io = 2; + } + else if(do_th == 1 && strcmp(type, "th") == 0) { + if(words < 13) { + error("%s line of /proc/net/rpc/nfsd has %u words, expected %d", type, words, 13); + continue; + } + + th_threads = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + th_fullcnt = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + th_hist10 = (unsigned long long)(atof(procfile_lineword(ff, l, 3)) * 1000.0); + th_hist20 = (unsigned long long)(atof(procfile_lineword(ff, l, 4)) * 1000.0); + th_hist30 = (unsigned long long)(atof(procfile_lineword(ff, l, 5)) * 1000.0); + th_hist40 = (unsigned long long)(atof(procfile_lineword(ff, l, 6)) * 1000.0); + th_hist50 = (unsigned long long)(atof(procfile_lineword(ff, l, 7)) * 1000.0); + th_hist60 = (unsigned long long)(atof(procfile_lineword(ff, l, 8)) * 1000.0); + th_hist70 = (unsigned long long)(atof(procfile_lineword(ff, l, 9)) * 1000.0); + th_hist80 = (unsigned long long)(atof(procfile_lineword(ff, l, 10)) * 1000.0); + th_hist90 = (unsigned long long)(atof(procfile_lineword(ff, l, 11)) * 1000.0); + th_hist100 = (unsigned long long)(atof(procfile_lineword(ff, l, 12)) * 1000.0); + + // threads histogram has been disabled on recent kernels + // http://permalink.gmane.org/gmane.linux.nfs/24528 + unsigned long long sum = th_hist10 + th_hist20 + th_hist30 + th_hist40 + th_hist50 + th_hist60 + th_hist70 + th_hist80 + th_hist90 + th_hist100; + if(sum == 0ULL) { + if(!th_warning) { + info("Disabling /proc/net/rpc/nfsd threads histogram. It seems unused on this machine. It will be enabled automatically when found with data in it."); + th_warning = 1; + } + do_th = -1; + } + else do_th = 2; + } + else if(do_ra == 1 && strcmp(type, "ra") == 0) { + if(words < 13) { + error("%s line of /proc/net/rpc/nfsd has %u words, expected %d", type, words, 13); + continue; + } + + ra_size = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + ra_hist10 = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + ra_hist20 = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + ra_hist30 = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + ra_hist40 = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + ra_hist50 = strtoull(procfile_lineword(ff, l, 6), NULL, 10); + ra_hist60 = strtoull(procfile_lineword(ff, l, 7), NULL, 10); + ra_hist70 = strtoull(procfile_lineword(ff, l, 8), NULL, 10); + ra_hist80 = strtoull(procfile_lineword(ff, l, 9), NULL, 10); + ra_hist90 = strtoull(procfile_lineword(ff, l, 10), NULL, 10); + ra_hist100 = strtoull(procfile_lineword(ff, l, 11), NULL, 10); + ra_none = strtoull(procfile_lineword(ff, l, 12), NULL, 10); + + unsigned long long sum = ra_hist10 + ra_hist20 + ra_hist30 + ra_hist40 + ra_hist50 + ra_hist60 + ra_hist70 + ra_hist80 + ra_hist90 + ra_hist100 + ra_none; + if(sum == 0ULL) { + if(!ra_warning) { + info("Disabling /proc/net/rpc/nfsd read ahead histogram. It seems unused on this machine. It will be enabled automatically when found with data in it."); + ra_warning = 1; + } + do_ra = -1; + } + else do_ra = 2; + } + else if(do_net == 1 && strcmp(type, "net") == 0) { + if(words < 5) { + error("%s line of /proc/net/rpc/nfsd has %u words, expected %d", type, words, 5); + continue; + } + + net_count = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + net_udp_count = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + net_tcp_count = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + net_tcp_connections = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + + unsigned long long sum = net_count + net_udp_count + net_tcp_count + net_tcp_connections; + if(sum == 0ULL) do_net = -1; + else do_net = 2; + } + else if(do_rpc == 1 && strcmp(type, "rpc") == 0) { + if(words < 6) { + error("%s line of /proc/net/rpc/nfsd has %u words, expected %d", type, words, 6); + continue; + } + + rpc_count = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + rpc_bad_format = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + rpc_bad_auth = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + rpc_bad_client = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + + unsigned long long sum = rpc_count + rpc_bad_format + rpc_bad_auth + rpc_bad_client; + if(sum == 0ULL) do_rpc = -1; + else do_rpc = 2; + } + else if(do_proc2 == 1 && strcmp(type, "proc2") == 0) { + // the first number is the count of numbers present + // so we start for word 2 + + unsigned long long sum = 0; + unsigned int i, j; + for(i = 0, j = 2; j < words && nfsd_proc_values[i].name[0] ; i++, j++) { + nfsd_proc_values[i].proc2 = strtoull(procfile_lineword(ff, l, j), NULL, 10); + nfsd_proc_values[i].present2 = 1; + sum += nfsd_proc_values[i].proc2; + } + + if(sum == 0ULL) { + if(!proc2_warning) { + error("Disabling /proc/net/rpc/nfsd v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); + proc2_warning = 1; + } + do_proc2 = 0; + } + else do_proc2 = 2; + } + else if(do_proc3 == 1 && strcmp(type, "proc3") == 0) { + // the first number is the count of numbers present + // so we start for word 2 + + unsigned long long sum = 0; + unsigned int i, j; + for(i = 0, j = 2; j < words && nfsd_proc_values[i].name[0] ; i++, j++) { + nfsd_proc_values[i].proc3 = strtoull(procfile_lineword(ff, l, j), NULL, 10); + nfsd_proc_values[i].present3 = 1; + sum += nfsd_proc_values[i].proc3; + } + + if(sum == 0ULL) { + if(!proc3_warning) { + info("Disabling /proc/net/rpc/nfsd v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); + proc3_warning = 1; + } + do_proc3 = 0; + } + else do_proc3 = 2; + } + else if(do_proc4 == 1 && strcmp(type, "proc4") == 0) { + // the first number is the count of numbers present + // so we start for word 2 + + unsigned long long sum = 0; + unsigned int i, j; + for(i = 0, j = 2; j < words && nfsd_proc_values[i].name[0] ; i++, j++) { + nfsd_proc_values[i].proc4 = strtoull(procfile_lineword(ff, l, j), NULL, 10); + nfsd_proc_values[i].present4 = 1; + sum += nfsd_proc_values[i].proc4; + } + + if(sum == 0ULL) { + if(!proc4_warning) { + info("Disabling /proc/net/rpc/nfsd v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); + proc4_warning = 1; + } + do_proc4 = 0; + } + else do_proc4 = 2; + } + else if(do_proc4ops == 1 && strcmp(type, "proc4ops") == 0) { + // the first number is the count of numbers present + // so we start for word 2 + + unsigned long long sum = 0; + unsigned int i, j; + for(i = 0, j = 2; j < words && nfsd4_ops_values[i].name[0] ; i++, j++) { + nfsd4_ops_values[i].value = strtoull(procfile_lineword(ff, l, j), NULL, 10); + nfsd4_ops_values[i].present = 1; + sum += nfsd4_ops_values[i].value; + } + + if(sum == 0ULL) { + if(!proc4ops_warning) { + info("Disabling /proc/net/rpc/nfsd v4 operations chart. It seems unused on this machine. It will be enabled automatically when found with data in it."); + proc4ops_warning = 1; + } + do_proc4ops = 0; + } + else do_proc4ops = 2; + } + } + + RRDSET *st; + + // -------------------------------------------------------------------- + + if(do_rc == 2) { + st = rrdset_find_bytype("nfsd", "readcache"); + if(!st) { + st = rrdset_create("nfsd", "readcache", NULL, "nfsd", NULL, "Read Cache", "reads/s", 5000, update_every, RRDSET_TYPE_STACKED); + + rrddim_add(st, "hits", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "misses", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "nocache", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "hits", rc_hits); + rrddim_set(st, "misses", rc_misses); + rrddim_set(st, "nocache", rc_nocache); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_fh == 2) { + st = rrdset_find_bytype("nfsd", "filehandles"); + if(!st) { + st = rrdset_create("nfsd", "filehandles", NULL, "nfsd", NULL, "File Handles", "handles/s", 5001, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "stale", NULL, 1, 1, RRDDIM_ABSOLUTE); + rrddim_add(st, "total_lookups", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "anonymous_lookups", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "dir_not_in_dcache", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "non_dir_not_in_dcache", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "stale", fh_stale); + rrddim_set(st, "total_lookups", fh_total_lookups); + rrddim_set(st, "anonymous_lookups", fh_anonymous_lookups); + rrddim_set(st, "dir_not_in_dcache", fh_dir_not_in_dcache); + rrddim_set(st, "non_dir_not_in_dcache", fh_non_dir_not_in_dcache); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_io == 2) { + st = rrdset_find_bytype("nfsd", "io"); + if(!st) { + st = rrdset_create("nfsd", "io", NULL, "nfsd", NULL, "I/O", "kilobytes/s", 5002, update_every, RRDSET_TYPE_AREA); + + rrddim_add(st, "read", NULL, 1, 1000, RRDDIM_INCREMENTAL); + rrddim_add(st, "write", NULL, -1, 1000, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "read", io_read); + rrddim_set(st, "write", io_write); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_th == 2) { + st = rrdset_find_bytype("nfsd", "threads"); + if(!st) { + st = rrdset_create("nfsd", "threads", NULL, "nfsd", NULL, "Threads", "threads", 5003, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "threads", NULL, 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "threads", th_threads); + rrdset_done(st); + + st = rrdset_find_bytype("nfsd", "threads_fullcnt"); + if(!st) { + st = rrdset_create("nfsd", "threads_fullcnt", NULL, "nfsd", NULL, "Threads Full Count", "ops/s", 5004, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "full_count", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "full_count", th_fullcnt); + rrdset_done(st); + + st = rrdset_find_bytype("nfsd", "threads_histogram"); + if(!st) { + st = rrdset_create("nfsd", "threads_histogram", NULL, "nfsd", NULL, "Threads Usage Histogram", "percentage", 5005, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "0%-10%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "10%-20%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "20%-30%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "30%-40%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "40%-50%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "50%-60%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "60%-70%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "70%-80%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "80%-90%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + rrddim_add(st, "90%-100%", NULL, 1, 1000, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "0%-10%", th_hist10); + rrddim_set(st, "10%-20%", th_hist20); + rrddim_set(st, "20%-30%", th_hist30); + rrddim_set(st, "30%-40%", th_hist40); + rrddim_set(st, "40%-50%", th_hist50); + rrddim_set(st, "50%-60%", th_hist60); + rrddim_set(st, "60%-70%", th_hist70); + rrddim_set(st, "70%-80%", th_hist80); + rrddim_set(st, "80%-90%", th_hist90); + rrddim_set(st, "90%-100%", th_hist100); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ra == 2) { + st = rrdset_find_bytype("nfsd", "readahead"); + if(!st) { + st = rrdset_create("nfsd", "readahead", NULL, "nfsd", NULL, "Read Ahead Depth", "percentage", 5005, update_every, RRDSET_TYPE_STACKED); + + rrddim_add(st, "10%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "20%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "30%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "40%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "50%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "60%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "70%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "80%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "90%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "100%", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "misses", NULL, 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + } + else rrdset_next(st); + + // ignore ra_size + if(ra_size) {}; + + rrddim_set(st, "10%", ra_hist10); + rrddim_set(st, "20%", ra_hist20); + rrddim_set(st, "30%", ra_hist30); + rrddim_set(st, "40%", ra_hist40); + rrddim_set(st, "50%", ra_hist50); + rrddim_set(st, "60%", ra_hist60); + rrddim_set(st, "70%", ra_hist70); + rrddim_set(st, "80%", ra_hist80); + rrddim_set(st, "90%", ra_hist90); + rrddim_set(st, "100%", ra_hist100); + rrddim_set(st, "misses", ra_none); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_net == 2) { + st = rrdset_find_bytype("nfsd", "net"); + if(!st) { + st = rrdset_create("nfsd", "net", NULL, "nfsd", NULL, "Network Reads", "reads/s", 5007, update_every, RRDSET_TYPE_STACKED); + st->isdetail = 1; + + rrddim_add(st, "udp", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "tcp", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + // ignore net_count, net_tcp_connections + if(net_count) {}; + if(net_tcp_connections) {}; + + rrddim_set(st, "udp", net_udp_count); + rrddim_set(st, "tcp", net_tcp_count); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_rpc == 2) { + st = rrdset_find_bytype("nfsd", "rpc"); + if(!st) { + st = rrdset_create("nfsd", "rpc", NULL, "nfsd", NULL, "Remote Procedure Calls", "calls/s", 5008, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "bad_format", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "bad_auth", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + // ignore rpc_bad_client + if(rpc_bad_client) {}; + + rrddim_set(st, "all", rpc_count); + rrddim_set(st, "bad_format", rpc_bad_format); + rrddim_set(st, "bad_auth", rpc_bad_auth); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_proc2 == 2) { + unsigned int i; + st = rrdset_find_bytype("nfsd", "proc2"); + if(!st) { + st = rrdset_create("nfsd", "proc2", NULL, "nfsd", NULL, "NFS v2 Calls", "calls/s", 5009, update_every, RRDSET_TYPE_STACKED); + + for(i = 0; nfsd_proc_values[i].present2 ; i++) + rrddim_add(st, nfsd_proc_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + for(i = 0; nfsd_proc_values[i].present2 ; i++) + rrddim_set(st, nfsd_proc_values[i].name, nfsd_proc_values[i].proc2); + + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_proc3 == 2) { + unsigned int i; + st = rrdset_find_bytype("nfsd", "proc3"); + if(!st) { + st = rrdset_create("nfsd", "proc3", NULL, "nfsd", NULL, "NFS v3 Calls", "calls/s", 5010, update_every, RRDSET_TYPE_STACKED); + + for(i = 0; nfsd_proc_values[i].present3 ; i++) + rrddim_add(st, nfsd_proc_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + for(i = 0; nfsd_proc_values[i].present3 ; i++) + rrddim_set(st, nfsd_proc_values[i].name, nfsd_proc_values[i].proc3); + + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_proc4 == 2) { + unsigned int i; + st = rrdset_find_bytype("nfsd", "proc4"); + if(!st) { + st = rrdset_create("nfsd", "proc4", NULL, "nfsd", NULL, "NFS v4 Calls", "calls/s", 5011, update_every, RRDSET_TYPE_STACKED); + + for(i = 0; nfsd_proc_values[i].present4 ; i++) + rrddim_add(st, nfsd_proc_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - for(i = 0; nfsd_proc_values[i].present4 ; i++) - rrddim_set(st, nfsd_proc_values[i].name, nfsd_proc_values[i].proc4); + for(i = 0; nfsd_proc_values[i].present4 ; i++) + rrddim_set(st, nfsd_proc_values[i].name, nfsd_proc_values[i].proc4); - rrdset_done(st); - } + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_proc4ops == 2) { - unsigned int i; - st = rrdset_find_bytype("nfsd", "proc4ops"); - if(!st) { - st = rrdset_create("nfsd", "proc4ops", NULL, "nfsd", NULL, "NFS v4 Operations", "operations/s", 5012, update_every, RRDSET_TYPE_STACKED); + if(do_proc4ops == 2) { + unsigned int i; + st = rrdset_find_bytype("nfsd", "proc4ops"); + if(!st) { + st = rrdset_create("nfsd", "proc4ops", NULL, "nfsd", NULL, "NFS v4 Operations", "operations/s", 5012, update_every, RRDSET_TYPE_STACKED); - for(i = 0; nfsd4_ops_values[i].present ; i++) - rrddim_add(st, nfsd4_ops_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + for(i = 0; nfsd4_ops_values[i].present ; i++) + rrddim_add(st, nfsd4_ops_values[i].name, NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - for(i = 0; nfsd4_ops_values[i].present ; i++) - rrddim_set(st, nfsd4_ops_values[i].name, nfsd4_ops_values[i].value); + for(i = 0; nfsd4_ops_values[i].present ; i++) + rrddim_set(st, nfsd4_ops_values[i].name, nfsd4_ops_values[i].value); - rrdset_done(st); - } + rrdset_done(st); + } - return 0; + return 0; } diff --git a/src/proc_net_snmp.c b/src/proc_net_snmp.c index e0ac6a263..a773f55f6 100644 --- a/src/proc_net_snmp.c +++ b/src/proc_net_snmp.c @@ -1,366 +1,354 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -#define RRD_TYPE_NET_SNMP "ipv4" -#define RRD_TYPE_NET_SNMP_LEN strlen(RRD_TYPE_NET_SNMP) +#define RRD_TYPE_NET_SNMP "ipv4" +#define RRD_TYPE_NET_SNMP_LEN strlen(RRD_TYPE_NET_SNMP) int do_proc_net_snmp(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int do_ip_packets = -1, do_ip_fragsout = -1, do_ip_fragsin = -1, do_ip_errors = -1, - do_tcp_sockets = -1, do_tcp_packets = -1, do_tcp_errors = -1, do_tcp_handshake = -1, - do_udp_packets = -1, do_udp_errors = -1; - - if(do_ip_packets == -1) do_ip_packets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 packets", 1); - if(do_ip_fragsout == -1) do_ip_fragsout = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 fragments sent", 1); - if(do_ip_fragsin == -1) do_ip_fragsin = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 fragments assembly", 1); - if(do_ip_errors == -1) do_ip_errors = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 errors", 1); - if(do_tcp_sockets == -1) do_tcp_sockets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP connections", 1); - if(do_tcp_packets == -1) do_tcp_packets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP packets", 1); - if(do_tcp_errors == -1) do_tcp_errors = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP errors", 1); - if(do_tcp_handshake == -1) do_tcp_handshake = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP handshake issues", 1); - if(do_udp_packets == -1) do_udp_packets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 UDP packets", 1); - if(do_udp_errors == -1) do_udp_errors = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 UDP errors", 1); - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - RRDSET *st; - - for(l = 0; l < lines ;l++) { - if(strcmp(procfile_lineword(ff, l, 0), "Ip") == 0) { - l++; - - if(strcmp(procfile_lineword(ff, l, 0), "Ip") != 0) { - error("Cannot read Ip line from /proc/net/snmp."); - break; - } - - words = procfile_linewords(ff, l); - if(words < 20) { - error("Cannot read /proc/net/snmp Ip line. Expected 20 params, read %d.", words); - continue; - } - - // see also http://net-snmp.sourceforge.net/docs/mibs/ip.html - unsigned long long Forwarding, DefaultTTL, InReceives, InHdrErrors, InAddrErrors, ForwDatagrams, InUnknownProtos, InDiscards, InDelivers, - OutRequests, OutDiscards, OutNoRoutes, ReasmTimeout, ReasmReqds, ReasmOKs, ReasmFails, FragOKs, FragFails, FragCreates; - - Forwarding = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - DefaultTTL = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - InReceives = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - InHdrErrors = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - InAddrErrors = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - ForwDatagrams = strtoull(procfile_lineword(ff, l, 6), NULL, 10); - InUnknownProtos = strtoull(procfile_lineword(ff, l, 7), NULL, 10); - InDiscards = strtoull(procfile_lineword(ff, l, 8), NULL, 10); - InDelivers = strtoull(procfile_lineword(ff, l, 9), NULL, 10); - OutRequests = strtoull(procfile_lineword(ff, l, 10), NULL, 10); - OutDiscards = strtoull(procfile_lineword(ff, l, 11), NULL, 10); - OutNoRoutes = strtoull(procfile_lineword(ff, l, 12), NULL, 10); - ReasmTimeout = strtoull(procfile_lineword(ff, l, 13), NULL, 10); - ReasmReqds = strtoull(procfile_lineword(ff, l, 14), NULL, 10); - ReasmOKs = strtoull(procfile_lineword(ff, l, 15), NULL, 10); - ReasmFails = strtoull(procfile_lineword(ff, l, 16), NULL, 10); - FragOKs = strtoull(procfile_lineword(ff, l, 17), NULL, 10); - FragFails = strtoull(procfile_lineword(ff, l, 18), NULL, 10); - FragCreates = strtoull(procfile_lineword(ff, l, 19), NULL, 10); - - // these are not counters - if(Forwarding) {}; // is forwarding enabled? - if(DefaultTTL) {}; // the default ttl on packets - if(ReasmTimeout) {}; // Reassembly timeout - - // this counter is not used - if(InDelivers) {}; // total number of packets delivered to IP user-protocols - - // -------------------------------------------------------------------- - - if(do_ip_packets) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".packets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "packets", NULL, "packets", NULL, "IPv4 Packets", "packets/s", 3000, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "forwarded", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", OutRequests); - rrddim_set(st, "received", InReceives); - rrddim_set(st, "forwarded", ForwDatagrams); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ip_fragsout) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".fragsout"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "fragsout", NULL, "fragments", NULL, "IPv4 Fragments Sent", "packets/s", 3010, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "ok", FragOKs); - rrddim_set(st, "failed", FragFails); - rrddim_set(st, "all", FragCreates); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ip_fragsin) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".fragsin"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "fragsin", NULL, "fragments", NULL, "IPv4 Fragments Reassembly", "packets/s", 3011, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "ok", ReasmOKs); - rrddim_set(st, "failed", ReasmFails); - rrddim_set(st, "all", ReasmReqds); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ip_errors) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".errors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "errors", NULL, "errors", NULL, "IPv4 Errors", "packets/s", 3002, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "InDiscards", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutDiscards", NULL, -1, 1, RRDDIM_INCREMENTAL); - - rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRDDIM_INCREMENTAL); - - rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InDiscards", InDiscards); - rrddim_set(st, "OutDiscards", OutDiscards); - rrddim_set(st, "InHdrErrors", InHdrErrors); - rrddim_set(st, "InAddrErrors", InAddrErrors); - rrddim_set(st, "InUnknownProtos", InUnknownProtos); - rrddim_set(st, "OutNoRoutes", OutNoRoutes); - rrdset_done(st); - } - } - else if(strcmp(procfile_lineword(ff, l, 0), "Tcp") == 0) { - l++; - - if(strcmp(procfile_lineword(ff, l, 0), "Tcp") != 0) { - error("Cannot read Tcp line from /proc/net/snmp."); - break; - } - - words = procfile_linewords(ff, l); - if(words < 15) { - error("Cannot read /proc/net/snmp Tcp line. Expected 15 params, read %d.", words); - continue; - } - - unsigned long long RtoAlgorithm, RtoMin, RtoMax, MaxConn, ActiveOpens, PassiveOpens, AttemptFails, EstabResets, - CurrEstab, InSegs, OutSegs, RetransSegs, InErrs, OutRsts; - - RtoAlgorithm = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - RtoMin = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - RtoMax = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - MaxConn = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - ActiveOpens = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - PassiveOpens = strtoull(procfile_lineword(ff, l, 6), NULL, 10); - AttemptFails = strtoull(procfile_lineword(ff, l, 7), NULL, 10); - EstabResets = strtoull(procfile_lineword(ff, l, 8), NULL, 10); - CurrEstab = strtoull(procfile_lineword(ff, l, 9), NULL, 10); - InSegs = strtoull(procfile_lineword(ff, l, 10), NULL, 10); - OutSegs = strtoull(procfile_lineword(ff, l, 11), NULL, 10); - RetransSegs = strtoull(procfile_lineword(ff, l, 12), NULL, 10); - InErrs = strtoull(procfile_lineword(ff, l, 13), NULL, 10); - OutRsts = strtoull(procfile_lineword(ff, l, 14), NULL, 10); - - // these are not counters - if(RtoAlgorithm) {}; - if(RtoMin) {}; - if(RtoMax) {}; - if(MaxConn) {}; - - // -------------------------------------------------------------------- - - // see http://net-snmp.sourceforge.net/docs/mibs/tcp.html - if(do_tcp_sockets) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".tcpsock"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "tcpsock", NULL, "tcp", NULL, "IPv4 TCP Connections", "active connections", 2500, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "connections", NULL, 1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "connections", CurrEstab); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_tcp_packets) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".tcppackets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "tcppackets", NULL, "tcp", NULL, "IPv4 TCP Packets", "packets/s", 2600, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "received", InSegs); - rrddim_set(st, "sent", OutSegs); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_tcp_errors) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".tcperrors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "tcperrors", NULL, "tcp", NULL, "IPv4 TCP Errors", "packets/s", 2700, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "InErrs", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "RetransSegs", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InErrs", InErrs); - rrddim_set(st, "RetransSegs", RetransSegs); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_tcp_handshake) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".tcphandshake"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "tcphandshake", NULL, "tcp", NULL, "IPv4 TCP Handshake Issues", "events/s", 2900, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "EstabResets", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutRsts", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "ActiveOpens", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "PassiveOpens", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "AttemptFails", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "EstabResets", EstabResets); - rrddim_set(st, "OutRsts", OutRsts); - rrddim_set(st, "ActiveOpens", ActiveOpens); - rrddim_set(st, "PassiveOpens", PassiveOpens); - rrddim_set(st, "AttemptFails", AttemptFails); - rrdset_done(st); - } - } - else if(strcmp(procfile_lineword(ff, l, 0), "Udp") == 0) { - l++; - - if(strcmp(procfile_lineword(ff, l, 0), "Udp") != 0) { - error("Cannot read Udp line from /proc/net/snmp."); - break; - } - - words = procfile_linewords(ff, l); - if(words < 7) { - error("Cannot read /proc/net/snmp Udp line. Expected 7 params, read %d.", words); - continue; - } - - unsigned long long InDatagrams, NoPorts, InErrors, OutDatagrams, RcvbufErrors, SndbufErrors; - - InDatagrams = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - NoPorts = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - InErrors = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - OutDatagrams = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - RcvbufErrors = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - SndbufErrors = strtoull(procfile_lineword(ff, l, 6), NULL, 10); - - // -------------------------------------------------------------------- - - // see http://net-snmp.sourceforge.net/docs/mibs/udp.html - if(do_udp_packets) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".udppackets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "udppackets", NULL, "udp", NULL, "IPv4 UDP Packets", "packets/s", 2601, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "received", InDatagrams); - rrddim_set(st, "sent", OutDatagrams); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_udp_errors) { - st = rrdset_find(RRD_TYPE_NET_SNMP ".udperrors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP, "udperrors", NULL, "udp", NULL, "IPv4 UDP Errors", "events/s", 2701, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "NoPorts", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InErrors", InErrors); - rrddim_set(st, "NoPorts", NoPorts); - rrddim_set(st, "RcvbufErrors", RcvbufErrors); - rrddim_set(st, "SndbufErrors", SndbufErrors); - rrdset_done(st); - } - } - } - - return 0; + static procfile *ff = NULL; + static int do_ip_packets = -1, do_ip_fragsout = -1, do_ip_fragsin = -1, do_ip_errors = -1, + do_tcp_sockets = -1, do_tcp_packets = -1, do_tcp_errors = -1, do_tcp_handshake = -1, + do_udp_packets = -1, do_udp_errors = -1; + + if(do_ip_packets == -1) do_ip_packets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 packets", 1); + if(do_ip_fragsout == -1) do_ip_fragsout = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 fragments sent", 1); + if(do_ip_fragsin == -1) do_ip_fragsin = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 fragments assembly", 1); + if(do_ip_errors == -1) do_ip_errors = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 errors", 1); + if(do_tcp_sockets == -1) do_tcp_sockets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP connections", 1); + if(do_tcp_packets == -1) do_tcp_packets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP packets", 1); + if(do_tcp_errors == -1) do_tcp_errors = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP errors", 1); + if(do_tcp_handshake == -1) do_tcp_handshake = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 TCP handshake issues", 1); + if(do_udp_packets == -1) do_udp_packets = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 UDP packets", 1); + if(do_udp_errors == -1) do_udp_errors = config_get_boolean("plugin:proc:/proc/net/snmp", "ipv4 UDP errors", 1); + + if(dt) {}; + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + RRDSET *st; + + for(l = 0; l < lines ;l++) { + if(strcmp(procfile_lineword(ff, l, 0), "Ip") == 0) { + l++; + + if(strcmp(procfile_lineword(ff, l, 0), "Ip") != 0) { + error("Cannot read Ip line from /proc/net/snmp."); + break; + } + + words = procfile_linewords(ff, l); + if(words < 20) { + error("Cannot read /proc/net/snmp Ip line. Expected 20 params, read %u.", words); + continue; + } + + // see also http://net-snmp.sourceforge.net/docs/mibs/ip.html + unsigned long long Forwarding, DefaultTTL, InReceives, InHdrErrors, InAddrErrors, ForwDatagrams, InUnknownProtos, InDiscards, InDelivers, + OutRequests, OutDiscards, OutNoRoutes, ReasmTimeout, ReasmReqds, ReasmOKs, ReasmFails, FragOKs, FragFails, FragCreates; + + Forwarding = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + DefaultTTL = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + InReceives = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + InHdrErrors = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + InAddrErrors = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + ForwDatagrams = strtoull(procfile_lineword(ff, l, 6), NULL, 10); + InUnknownProtos = strtoull(procfile_lineword(ff, l, 7), NULL, 10); + InDiscards = strtoull(procfile_lineword(ff, l, 8), NULL, 10); + InDelivers = strtoull(procfile_lineword(ff, l, 9), NULL, 10); + OutRequests = strtoull(procfile_lineword(ff, l, 10), NULL, 10); + OutDiscards = strtoull(procfile_lineword(ff, l, 11), NULL, 10); + OutNoRoutes = strtoull(procfile_lineword(ff, l, 12), NULL, 10); + ReasmTimeout = strtoull(procfile_lineword(ff, l, 13), NULL, 10); + ReasmReqds = strtoull(procfile_lineword(ff, l, 14), NULL, 10); + ReasmOKs = strtoull(procfile_lineword(ff, l, 15), NULL, 10); + ReasmFails = strtoull(procfile_lineword(ff, l, 16), NULL, 10); + FragOKs = strtoull(procfile_lineword(ff, l, 17), NULL, 10); + FragFails = strtoull(procfile_lineword(ff, l, 18), NULL, 10); + FragCreates = strtoull(procfile_lineword(ff, l, 19), NULL, 10); + + // these are not counters + if(Forwarding) {}; // is forwarding enabled? + if(DefaultTTL) {}; // the default ttl on packets + if(ReasmTimeout) {}; // Reassembly timeout + + // this counter is not used + if(InDelivers) {}; // total number of packets delivered to IP user-protocols + + // -------------------------------------------------------------------- + + if(do_ip_packets) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".packets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "packets", NULL, "packets", NULL, "IPv4 Packets", "packets/s", 3000, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "forwarded", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", OutRequests); + rrddim_set(st, "received", InReceives); + rrddim_set(st, "forwarded", ForwDatagrams); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ip_fragsout) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".fragsout"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "fragsout", NULL, "fragments", NULL, "IPv4 Fragments Sent", "packets/s", 3010, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "ok", FragOKs); + rrddim_set(st, "failed", FragFails); + rrddim_set(st, "all", FragCreates); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ip_fragsin) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".fragsin"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "fragsin", NULL, "fragments", NULL, "IPv4 Fragments Reassembly", "packets/s", 3011, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "ok", ReasmOKs); + rrddim_set(st, "failed", ReasmFails); + rrddim_set(st, "all", ReasmReqds); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ip_errors) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".errors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "errors", NULL, "errors", NULL, "IPv4 Errors", "packets/s", 3002, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "InDiscards", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutDiscards", NULL, -1, 1, RRDDIM_INCREMENTAL); + + rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRDDIM_INCREMENTAL); + + rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InDiscards", InDiscards); + rrddim_set(st, "OutDiscards", OutDiscards); + rrddim_set(st, "InHdrErrors", InHdrErrors); + rrddim_set(st, "InAddrErrors", InAddrErrors); + rrddim_set(st, "InUnknownProtos", InUnknownProtos); + rrddim_set(st, "OutNoRoutes", OutNoRoutes); + rrdset_done(st); + } + } + else if(strcmp(procfile_lineword(ff, l, 0), "Tcp") == 0) { + l++; + + if(strcmp(procfile_lineword(ff, l, 0), "Tcp") != 0) { + error("Cannot read Tcp line from /proc/net/snmp."); + break; + } + + words = procfile_linewords(ff, l); + if(words < 15) { + error("Cannot read /proc/net/snmp Tcp line. Expected 15 params, read %u.", words); + continue; + } + + unsigned long long RtoAlgorithm, RtoMin, RtoMax, MaxConn, ActiveOpens, PassiveOpens, AttemptFails, EstabResets, + CurrEstab, InSegs, OutSegs, RetransSegs, InErrs, OutRsts; + + RtoAlgorithm = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + RtoMin = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + RtoMax = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + MaxConn = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + ActiveOpens = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + PassiveOpens = strtoull(procfile_lineword(ff, l, 6), NULL, 10); + AttemptFails = strtoull(procfile_lineword(ff, l, 7), NULL, 10); + EstabResets = strtoull(procfile_lineword(ff, l, 8), NULL, 10); + CurrEstab = strtoull(procfile_lineword(ff, l, 9), NULL, 10); + InSegs = strtoull(procfile_lineword(ff, l, 10), NULL, 10); + OutSegs = strtoull(procfile_lineword(ff, l, 11), NULL, 10); + RetransSegs = strtoull(procfile_lineword(ff, l, 12), NULL, 10); + InErrs = strtoull(procfile_lineword(ff, l, 13), NULL, 10); + OutRsts = strtoull(procfile_lineword(ff, l, 14), NULL, 10); + + // these are not counters + if(RtoAlgorithm) {}; + if(RtoMin) {}; + if(RtoMax) {}; + if(MaxConn) {}; + + // -------------------------------------------------------------------- + + // see http://net-snmp.sourceforge.net/docs/mibs/tcp.html + if(do_tcp_sockets) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".tcpsock"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "tcpsock", NULL, "tcp", NULL, "IPv4 TCP Connections", "active connections", 2500, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "connections", NULL, 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "connections", CurrEstab); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_tcp_packets) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".tcppackets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "tcppackets", NULL, "tcp", NULL, "IPv4 TCP Packets", "packets/s", 2600, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "received", InSegs); + rrddim_set(st, "sent", OutSegs); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_tcp_errors) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".tcperrors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "tcperrors", NULL, "tcp", NULL, "IPv4 TCP Errors", "packets/s", 2700, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "InErrs", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "RetransSegs", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InErrs", InErrs); + rrddim_set(st, "RetransSegs", RetransSegs); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_tcp_handshake) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".tcphandshake"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "tcphandshake", NULL, "tcp", NULL, "IPv4 TCP Handshake Issues", "events/s", 2900, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "EstabResets", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutRsts", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "ActiveOpens", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "PassiveOpens", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "AttemptFails", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "EstabResets", EstabResets); + rrddim_set(st, "OutRsts", OutRsts); + rrddim_set(st, "ActiveOpens", ActiveOpens); + rrddim_set(st, "PassiveOpens", PassiveOpens); + rrddim_set(st, "AttemptFails", AttemptFails); + rrdset_done(st); + } + } + else if(strcmp(procfile_lineword(ff, l, 0), "Udp") == 0) { + l++; + + if(strcmp(procfile_lineword(ff, l, 0), "Udp") != 0) { + error("Cannot read Udp line from /proc/net/snmp."); + break; + } + + words = procfile_linewords(ff, l); + if(words < 7) { + error("Cannot read /proc/net/snmp Udp line. Expected 7 params, read %u.", words); + continue; + } + + unsigned long long InDatagrams, NoPorts, InErrors, OutDatagrams, RcvbufErrors, SndbufErrors; + + InDatagrams = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + NoPorts = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + InErrors = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + OutDatagrams = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + RcvbufErrors = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + SndbufErrors = strtoull(procfile_lineword(ff, l, 6), NULL, 10); + + // -------------------------------------------------------------------- + + // see http://net-snmp.sourceforge.net/docs/mibs/udp.html + if(do_udp_packets) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".udppackets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "udppackets", NULL, "udp", NULL, "IPv4 UDP Packets", "packets/s", 2601, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "received", InDatagrams); + rrddim_set(st, "sent", OutDatagrams); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_udp_errors) { + st = rrdset_find(RRD_TYPE_NET_SNMP ".udperrors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP, "udperrors", NULL, "udp", NULL, "IPv4 UDP Errors", "events/s", 2701, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "NoPorts", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InErrors", InErrors); + rrddim_set(st, "NoPorts", NoPorts); + rrddim_set(st, "RcvbufErrors", RcvbufErrors); + rrddim_set(st, "SndbufErrors", SndbufErrors); + rrdset_done(st); + } + } + } + + return 0; } diff --git a/src/proc_net_snmp6.c b/src/proc_net_snmp6.c index 885835a8c..97dc20edd 100644 --- a/src/proc_net_snmp6.c +++ b/src/proc_net_snmp6.c @@ -1,1067 +1,1054 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -#define RRD_TYPE_NET_SNMP6 "ipv6" -#define RRD_TYPE_NET_SNMP6_LEN strlen(RRD_TYPE_NET_SNMP6) +#define RRD_TYPE_NET_SNMP6 "ipv6" +#define RRD_TYPE_NET_SNMP6_LEN strlen(RRD_TYPE_NET_SNMP6) int do_proc_net_snmp6(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int gen_hashes = -1; - - static int do_ip_packets = -1, do_ip_fragsout = -1, do_ip_fragsin = -1, do_ip_errors = -1, - do_udplite_packets = -1, do_udplite_errors = -1, - do_udp_packets = -1, do_udp_errors = -1, - do_bandwidth = -1, do_mcast = -1, do_bcast = -1, do_mcast_p = -1, - do_icmp = -1, do_icmp_redir = -1, do_icmp_errors = -1, do_icmp_echos = -1, do_icmp_groupmemb = -1, - do_icmp_router = -1, do_icmp_neighbor = -1, do_icmp_mldv2 = -1, do_icmp_types = -1, do_ect = -1; - - static uint32_t hash_Ip6InReceives = -1; - - static uint32_t hash_Ip6InHdrErrors = -1; - static uint32_t hash_Ip6InTooBigErrors = -1; - static uint32_t hash_Ip6InNoRoutes = -1; - static uint32_t hash_Ip6InAddrErrors = -1; - static uint32_t hash_Ip6InUnknownProtos = -1; - static uint32_t hash_Ip6InTruncatedPkts = -1; - static uint32_t hash_Ip6InDiscards = -1; - static uint32_t hash_Ip6InDelivers = -1; - - static uint32_t hash_Ip6OutForwDatagrams = -1; - static uint32_t hash_Ip6OutRequests = -1; - static uint32_t hash_Ip6OutDiscards = -1; - static uint32_t hash_Ip6OutNoRoutes = -1; - - static uint32_t hash_Ip6ReasmTimeout = -1; - static uint32_t hash_Ip6ReasmReqds = -1; - static uint32_t hash_Ip6ReasmOKs = -1; - static uint32_t hash_Ip6ReasmFails = -1; - - static uint32_t hash_Ip6FragOKs = -1; - static uint32_t hash_Ip6FragFails = -1; - static uint32_t hash_Ip6FragCreates = -1; - - static uint32_t hash_Ip6InMcastPkts = -1; - static uint32_t hash_Ip6OutMcastPkts = -1; - - static uint32_t hash_Ip6InOctets = -1; - static uint32_t hash_Ip6OutOctets = -1; - - static uint32_t hash_Ip6InMcastOctets = -1; - static uint32_t hash_Ip6OutMcastOctets = -1; - static uint32_t hash_Ip6InBcastOctets = -1; - static uint32_t hash_Ip6OutBcastOctets = -1; - - static uint32_t hash_Ip6InNoECTPkts = -1; - static uint32_t hash_Ip6InECT1Pkts = -1; - static uint32_t hash_Ip6InECT0Pkts = -1; - static uint32_t hash_Ip6InCEPkts = -1; - - static uint32_t hash_Icmp6InMsgs = -1; - static uint32_t hash_Icmp6InErrors = -1; - static uint32_t hash_Icmp6OutMsgs = -1; - static uint32_t hash_Icmp6OutErrors = -1; - static uint32_t hash_Icmp6InCsumErrors = -1; - static uint32_t hash_Icmp6InDestUnreachs = -1; - static uint32_t hash_Icmp6InPktTooBigs = -1; - static uint32_t hash_Icmp6InTimeExcds = -1; - static uint32_t hash_Icmp6InParmProblems = -1; - static uint32_t hash_Icmp6InEchos = -1; - static uint32_t hash_Icmp6InEchoReplies = -1; - static uint32_t hash_Icmp6InGroupMembQueries = -1; - static uint32_t hash_Icmp6InGroupMembResponses = -1; - static uint32_t hash_Icmp6InGroupMembReductions = -1; - static uint32_t hash_Icmp6InRouterSolicits = -1; - static uint32_t hash_Icmp6InRouterAdvertisements = -1; - static uint32_t hash_Icmp6InNeighborSolicits = -1; - static uint32_t hash_Icmp6InNeighborAdvertisements = -1; - static uint32_t hash_Icmp6InRedirects = -1; - static uint32_t hash_Icmp6InMLDv2Reports = -1; - static uint32_t hash_Icmp6OutDestUnreachs = -1; - static uint32_t hash_Icmp6OutPktTooBigs = -1; - static uint32_t hash_Icmp6OutTimeExcds = -1; - static uint32_t hash_Icmp6OutParmProblems = -1; - static uint32_t hash_Icmp6OutEchos = -1; - static uint32_t hash_Icmp6OutEchoReplies = -1; - static uint32_t hash_Icmp6OutGroupMembQueries = -1; - static uint32_t hash_Icmp6OutGroupMembResponses = -1; - static uint32_t hash_Icmp6OutGroupMembReductions = -1; - static uint32_t hash_Icmp6OutRouterSolicits = -1; - static uint32_t hash_Icmp6OutRouterAdvertisements = -1; - static uint32_t hash_Icmp6OutNeighborSolicits = -1; - static uint32_t hash_Icmp6OutNeighborAdvertisements = -1; - static uint32_t hash_Icmp6OutRedirects = -1; - static uint32_t hash_Icmp6OutMLDv2Reports = -1; - static uint32_t hash_Icmp6InType1 = -1; - static uint32_t hash_Icmp6InType128 = -1; - static uint32_t hash_Icmp6InType129 = -1; - static uint32_t hash_Icmp6InType136 = -1; - static uint32_t hash_Icmp6OutType1 = -1; - static uint32_t hash_Icmp6OutType128 = -1; - static uint32_t hash_Icmp6OutType129 = -1; - static uint32_t hash_Icmp6OutType133 = -1; - static uint32_t hash_Icmp6OutType135 = -1; - static uint32_t hash_Icmp6OutType143 = -1; - - static uint32_t hash_Udp6InDatagrams = -1; - static uint32_t hash_Udp6NoPorts = -1; - static uint32_t hash_Udp6InErrors = -1; - static uint32_t hash_Udp6OutDatagrams = -1; - static uint32_t hash_Udp6RcvbufErrors = -1; - static uint32_t hash_Udp6SndbufErrors = -1; - static uint32_t hash_Udp6InCsumErrors = -1; - static uint32_t hash_Udp6IgnoredMulti = -1; - - static uint32_t hash_UdpLite6InDatagrams = -1; - static uint32_t hash_UdpLite6NoPorts = -1; - static uint32_t hash_UdpLite6InErrors = -1; - static uint32_t hash_UdpLite6OutDatagrams = -1; - static uint32_t hash_UdpLite6RcvbufErrors = -1; - static uint32_t hash_UdpLite6SndbufErrors = -1; - static uint32_t hash_UdpLite6InCsumErrors = -1; - - if(gen_hashes != 1) { - gen_hashes = 1; - hash_Ip6InReceives = simple_hash("Ip6InReceives"); - hash_Ip6InHdrErrors = simple_hash("Ip6InHdrErrors"); - hash_Ip6InTooBigErrors = simple_hash("Ip6InTooBigErrors"); - hash_Ip6InNoRoutes = simple_hash("Ip6InNoRoutes"); - hash_Ip6InAddrErrors = simple_hash("Ip6InAddrErrors"); - hash_Ip6InUnknownProtos = simple_hash("Ip6InUnknownProtos"); - hash_Ip6InTruncatedPkts = simple_hash("Ip6InTruncatedPkts"); - hash_Ip6InDiscards = simple_hash("Ip6InDiscards"); - hash_Ip6InDelivers = simple_hash("Ip6InDelivers"); - hash_Ip6OutForwDatagrams = simple_hash("Ip6OutForwDatagrams"); - hash_Ip6OutRequests = simple_hash("Ip6OutRequests"); - hash_Ip6OutDiscards = simple_hash("Ip6OutDiscards"); - hash_Ip6OutNoRoutes = simple_hash("Ip6OutNoRoutes"); - hash_Ip6ReasmTimeout = simple_hash("Ip6ReasmTimeout"); - hash_Ip6ReasmReqds = simple_hash("Ip6ReasmReqds"); - hash_Ip6ReasmOKs = simple_hash("Ip6ReasmOKs"); - hash_Ip6ReasmFails = simple_hash("Ip6ReasmFails"); - hash_Ip6FragOKs = simple_hash("Ip6FragOKs"); - hash_Ip6FragFails = simple_hash("Ip6FragFails"); - hash_Ip6FragCreates = simple_hash("Ip6FragCreates"); - hash_Ip6InMcastPkts = simple_hash("Ip6InMcastPkts"); - hash_Ip6OutMcastPkts = simple_hash("Ip6OutMcastPkts"); - hash_Ip6InOctets = simple_hash("Ip6InOctets"); - hash_Ip6OutOctets = simple_hash("Ip6OutOctets"); - hash_Ip6InMcastOctets = simple_hash("Ip6InMcastOctets"); - hash_Ip6OutMcastOctets = simple_hash("Ip6OutMcastOctets"); - hash_Ip6InBcastOctets = simple_hash("Ip6InBcastOctets"); - hash_Ip6OutBcastOctets = simple_hash("Ip6OutBcastOctets"); - hash_Ip6InNoECTPkts = simple_hash("Ip6InNoECTPkts"); - hash_Ip6InECT1Pkts = simple_hash("Ip6InECT1Pkts"); - hash_Ip6InECT0Pkts = simple_hash("Ip6InECT0Pkts"); - hash_Ip6InCEPkts = simple_hash("Ip6InCEPkts"); - hash_Icmp6InMsgs = simple_hash("Icmp6InMsgs"); - hash_Icmp6InErrors = simple_hash("Icmp6InErrors"); - hash_Icmp6OutMsgs = simple_hash("Icmp6OutMsgs"); - hash_Icmp6OutErrors = simple_hash("Icmp6OutErrors"); - hash_Icmp6InCsumErrors = simple_hash("Icmp6InCsumErrors"); - hash_Icmp6InDestUnreachs = simple_hash("Icmp6InDestUnreachs"); - hash_Icmp6InPktTooBigs = simple_hash("Icmp6InPktTooBigs"); - hash_Icmp6InTimeExcds = simple_hash("Icmp6InTimeExcds"); - hash_Icmp6InParmProblems = simple_hash("Icmp6InParmProblems"); - hash_Icmp6InEchos = simple_hash("Icmp6InEchos"); - hash_Icmp6InEchoReplies = simple_hash("Icmp6InEchoReplies"); - hash_Icmp6InGroupMembQueries = simple_hash("Icmp6InGroupMembQueries"); - hash_Icmp6InGroupMembResponses = simple_hash("Icmp6InGroupMembResponses"); - hash_Icmp6InGroupMembReductions = simple_hash("Icmp6InGroupMembReductions"); - hash_Icmp6InRouterSolicits = simple_hash("Icmp6InRouterSolicits"); - hash_Icmp6InRouterAdvertisements = simple_hash("Icmp6InRouterAdvertisements"); - hash_Icmp6InNeighborSolicits = simple_hash("Icmp6InNeighborSolicits"); - hash_Icmp6InNeighborAdvertisements = simple_hash("Icmp6InNeighborAdvertisements"); - hash_Icmp6InRedirects = simple_hash("Icmp6InRedirects"); - hash_Icmp6InMLDv2Reports = simple_hash("Icmp6InMLDv2Reports"); - hash_Icmp6OutDestUnreachs = simple_hash("Icmp6OutDestUnreachs"); - hash_Icmp6OutPktTooBigs = simple_hash("Icmp6OutPktTooBigs"); - hash_Icmp6OutTimeExcds = simple_hash("Icmp6OutTimeExcds"); - hash_Icmp6OutParmProblems = simple_hash("Icmp6OutParmProblems"); - hash_Icmp6OutEchos = simple_hash("Icmp6OutEchos"); - hash_Icmp6OutEchoReplies = simple_hash("Icmp6OutEchoReplies"); - hash_Icmp6OutGroupMembQueries = simple_hash("Icmp6OutGroupMembQueries"); - hash_Icmp6OutGroupMembResponses = simple_hash("Icmp6OutGroupMembResponses"); - hash_Icmp6OutGroupMembReductions = simple_hash("Icmp6OutGroupMembReductions"); - hash_Icmp6OutRouterSolicits = simple_hash("Icmp6OutRouterSolicits"); - hash_Icmp6OutRouterAdvertisements = simple_hash("Icmp6OutRouterAdvertisements"); - hash_Icmp6OutNeighborSolicits = simple_hash("Icmp6OutNeighborSolicits"); - hash_Icmp6OutNeighborAdvertisements = simple_hash("Icmp6OutNeighborAdvertisements"); - hash_Icmp6OutRedirects = simple_hash("Icmp6OutRedirects"); - hash_Icmp6OutMLDv2Reports = simple_hash("Icmp6OutMLDv2Reports"); - hash_Icmp6InType1 = simple_hash("Icmp6InType1"); - hash_Icmp6InType128 = simple_hash("Icmp6InType128"); - hash_Icmp6InType129 = simple_hash("Icmp6InType129"); - hash_Icmp6InType136 = simple_hash("Icmp6InType136"); - hash_Icmp6OutType1 = simple_hash("Icmp6OutType1"); - hash_Icmp6OutType128 = simple_hash("Icmp6OutType128"); - hash_Icmp6OutType129 = simple_hash("Icmp6OutType129"); - hash_Icmp6OutType133 = simple_hash("Icmp6OutType133"); - hash_Icmp6OutType135 = simple_hash("Icmp6OutType135"); - hash_Icmp6OutType143 = simple_hash("Icmp6OutType143"); - hash_Udp6InDatagrams = simple_hash("Udp6InDatagrams"); - hash_Udp6NoPorts = simple_hash("Udp6NoPorts"); - hash_Udp6InErrors = simple_hash("Udp6InErrors"); - hash_Udp6OutDatagrams = simple_hash("Udp6OutDatagrams"); - hash_Udp6RcvbufErrors = simple_hash("Udp6RcvbufErrors"); - hash_Udp6SndbufErrors = simple_hash("Udp6SndbufErrors"); - hash_Udp6InCsumErrors = simple_hash("Udp6InCsumErrors"); - hash_Udp6IgnoredMulti = simple_hash("Udp6IgnoredMulti"); - hash_UdpLite6InDatagrams = simple_hash("UdpLite6InDatagrams"); - hash_UdpLite6NoPorts = simple_hash("UdpLite6NoPorts"); - hash_UdpLite6InErrors = simple_hash("UdpLite6InErrors"); - hash_UdpLite6OutDatagrams = simple_hash("UdpLite6OutDatagrams"); - hash_UdpLite6RcvbufErrors = simple_hash("UdpLite6RcvbufErrors"); - hash_UdpLite6SndbufErrors = simple_hash("UdpLite6SndbufErrors"); - hash_UdpLite6InCsumErrors = simple_hash("UdpLite6InCsumErrors"); - } - - if(do_ip_packets == -1) do_ip_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 packets", CONFIG_ONDEMAND_ONDEMAND); - if(do_ip_fragsout == -1) do_ip_fragsout = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments sent", CONFIG_ONDEMAND_ONDEMAND); - if(do_ip_fragsin == -1) do_ip_fragsin = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments assembly", CONFIG_ONDEMAND_ONDEMAND); - if(do_ip_errors == -1) do_ip_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 errors", CONFIG_ONDEMAND_ONDEMAND); - if(do_udp_packets == -1) do_udp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP packets", CONFIG_ONDEMAND_ONDEMAND); - if(do_udp_errors == -1) do_udp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP errors", CONFIG_ONDEMAND_ONDEMAND); - if(do_udplite_packets == -1) do_udplite_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite packets", CONFIG_ONDEMAND_ONDEMAND); - if(do_udplite_errors == -1) do_udplite_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite errors", CONFIG_ONDEMAND_ONDEMAND); - if(do_bandwidth == -1) do_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "bandwidth", CONFIG_ONDEMAND_ONDEMAND); - if(do_mcast == -1) do_mcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast bandwidth", CONFIG_ONDEMAND_ONDEMAND); - if(do_bcast == -1) do_bcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "broadcast bandwidth", CONFIG_ONDEMAND_ONDEMAND); - if(do_mcast_p == -1) do_mcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast packets", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp == -1) do_icmp = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_redir == -1) do_icmp_redir = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp redirects", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_errors == -1) do_icmp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp errors", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_echos == -1) do_icmp_echos = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp echos", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_groupmemb == -1) do_icmp_groupmemb = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp group membership", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_router == -1) do_icmp_router = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp router", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_neighbor == -1) do_icmp_neighbor = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp neighbor", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_mldv2 == -1) do_icmp_mldv2 = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp mldv2", CONFIG_ONDEMAND_ONDEMAND); - if(do_icmp_types == -1) do_icmp_types = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp types", CONFIG_ONDEMAND_ONDEMAND); - if(do_ect == -1) do_ect = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ect", CONFIG_ONDEMAND_ONDEMAND); - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - unsigned long long Ip6InReceives = 0ULL; - unsigned long long Ip6InHdrErrors = 0ULL; - unsigned long long Ip6InTooBigErrors = 0ULL; - unsigned long long Ip6InNoRoutes = 0ULL; - unsigned long long Ip6InAddrErrors = 0ULL; - unsigned long long Ip6InUnknownProtos = 0ULL; - unsigned long long Ip6InTruncatedPkts = 0ULL; - unsigned long long Ip6InDiscards = 0ULL; - unsigned long long Ip6InDelivers = 0ULL; - unsigned long long Ip6OutForwDatagrams = 0ULL; - unsigned long long Ip6OutRequests = 0ULL; - unsigned long long Ip6OutDiscards = 0ULL; - unsigned long long Ip6OutNoRoutes = 0ULL; - unsigned long long Ip6ReasmTimeout = 0ULL; - unsigned long long Ip6ReasmReqds = 0ULL; - unsigned long long Ip6ReasmOKs = 0ULL; - unsigned long long Ip6ReasmFails = 0ULL; - unsigned long long Ip6FragOKs = 0ULL; - unsigned long long Ip6FragFails = 0ULL; - unsigned long long Ip6FragCreates = 0ULL; - unsigned long long Ip6InMcastPkts = 0ULL; - unsigned long long Ip6OutMcastPkts = 0ULL; - unsigned long long Ip6InOctets = 0ULL; - unsigned long long Ip6OutOctets = 0ULL; - unsigned long long Ip6InMcastOctets = 0ULL; - unsigned long long Ip6OutMcastOctets = 0ULL; - unsigned long long Ip6InBcastOctets = 0ULL; - unsigned long long Ip6OutBcastOctets = 0ULL; - unsigned long long Ip6InNoECTPkts = 0ULL; - unsigned long long Ip6InECT1Pkts = 0ULL; - unsigned long long Ip6InECT0Pkts = 0ULL; - unsigned long long Ip6InCEPkts = 0ULL; - unsigned long long Icmp6InMsgs = 0ULL; - unsigned long long Icmp6InErrors = 0ULL; - unsigned long long Icmp6OutMsgs = 0ULL; - unsigned long long Icmp6OutErrors = 0ULL; - unsigned long long Icmp6InCsumErrors = 0ULL; - unsigned long long Icmp6InDestUnreachs = 0ULL; - unsigned long long Icmp6InPktTooBigs = 0ULL; - unsigned long long Icmp6InTimeExcds = 0ULL; - unsigned long long Icmp6InParmProblems = 0ULL; - unsigned long long Icmp6InEchos = 0ULL; - unsigned long long Icmp6InEchoReplies = 0ULL; - unsigned long long Icmp6InGroupMembQueries = 0ULL; - unsigned long long Icmp6InGroupMembResponses = 0ULL; - unsigned long long Icmp6InGroupMembReductions = 0ULL; - unsigned long long Icmp6InRouterSolicits = 0ULL; - unsigned long long Icmp6InRouterAdvertisements = 0ULL; - unsigned long long Icmp6InNeighborSolicits = 0ULL; - unsigned long long Icmp6InNeighborAdvertisements = 0ULL; - unsigned long long Icmp6InRedirects = 0ULL; - unsigned long long Icmp6InMLDv2Reports = 0ULL; - unsigned long long Icmp6OutDestUnreachs = 0ULL; - unsigned long long Icmp6OutPktTooBigs = 0ULL; - unsigned long long Icmp6OutTimeExcds = 0ULL; - unsigned long long Icmp6OutParmProblems = 0ULL; - unsigned long long Icmp6OutEchos = 0ULL; - unsigned long long Icmp6OutEchoReplies = 0ULL; - unsigned long long Icmp6OutGroupMembQueries = 0ULL; - unsigned long long Icmp6OutGroupMembResponses = 0ULL; - unsigned long long Icmp6OutGroupMembReductions = 0ULL; - unsigned long long Icmp6OutRouterSolicits = 0ULL; - unsigned long long Icmp6OutRouterAdvertisements = 0ULL; - unsigned long long Icmp6OutNeighborSolicits = 0ULL; - unsigned long long Icmp6OutNeighborAdvertisements = 0ULL; - unsigned long long Icmp6OutRedirects = 0ULL; - unsigned long long Icmp6OutMLDv2Reports = 0ULL; - unsigned long long Icmp6InType1 = 0ULL; - unsigned long long Icmp6InType128 = 0ULL; - unsigned long long Icmp6InType129 = 0ULL; - unsigned long long Icmp6InType136 = 0ULL; - unsigned long long Icmp6OutType1 = 0ULL; - unsigned long long Icmp6OutType128 = 0ULL; - unsigned long long Icmp6OutType129 = 0ULL; - unsigned long long Icmp6OutType133 = 0ULL; - unsigned long long Icmp6OutType135 = 0ULL; - unsigned long long Icmp6OutType143 = 0ULL; - unsigned long long Udp6InDatagrams = 0ULL; - unsigned long long Udp6NoPorts = 0ULL; - unsigned long long Udp6InErrors = 0ULL; - unsigned long long Udp6OutDatagrams = 0ULL; - unsigned long long Udp6RcvbufErrors = 0ULL; - unsigned long long Udp6SndbufErrors = 0ULL; - unsigned long long Udp6InCsumErrors = 0ULL; - unsigned long long Udp6IgnoredMulti = 0ULL; - unsigned long long UdpLite6InDatagrams = 0ULL; - unsigned long long UdpLite6NoPorts = 0ULL; - unsigned long long UdpLite6InErrors = 0ULL; - unsigned long long UdpLite6OutDatagrams = 0ULL; - unsigned long long UdpLite6RcvbufErrors = 0ULL; - unsigned long long UdpLite6SndbufErrors = 0ULL; - unsigned long long UdpLite6InCsumErrors = 0ULL; - - for(l = 0; l < lines ;l++) { - words = procfile_linewords(ff, l); - if(words < 2) { - if(words) error("Cannot read /proc/net/snmp6 line %d. Expected 2 params, read %d.", l, words); - continue; - } - - char *name = procfile_lineword(ff, l, 0); - char * value = procfile_lineword(ff, l, 1); - if(!name || !*name || !value || !*value) continue; - - uint32_t hash = simple_hash(name); - - if(0) ; - else if(hash == hash_Ip6InReceives && strcmp(name, "Ip6InReceives") == 0) Ip6InReceives = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InHdrErrors && strcmp(name, "Ip6InHdrErrors") == 0) Ip6InHdrErrors = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InTooBigErrors && strcmp(name, "Ip6InTooBigErrors") == 0) Ip6InTooBigErrors = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InNoRoutes && strcmp(name, "Ip6InNoRoutes") == 0) Ip6InNoRoutes = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InAddrErrors && strcmp(name, "Ip6InAddrErrors") == 0) Ip6InAddrErrors = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InUnknownProtos && strcmp(name, "Ip6InUnknownProtos") == 0) Ip6InUnknownProtos = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InTruncatedPkts && strcmp(name, "Ip6InTruncatedPkts") == 0) Ip6InTruncatedPkts = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InDiscards && strcmp(name, "Ip6InDiscards") == 0) Ip6InDiscards = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InDelivers && strcmp(name, "Ip6InDelivers") == 0) Ip6InDelivers = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutForwDatagrams && strcmp(name, "Ip6OutForwDatagrams") == 0) Ip6OutForwDatagrams = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutRequests && strcmp(name, "Ip6OutRequests") == 0) Ip6OutRequests = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutDiscards && strcmp(name, "Ip6OutDiscards") == 0) Ip6OutDiscards = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutNoRoutes && strcmp(name, "Ip6OutNoRoutes") == 0) Ip6OutNoRoutes = strtoull(value, NULL, 10); - else if(hash == hash_Ip6ReasmTimeout && strcmp(name, "Ip6ReasmTimeout") == 0) Ip6ReasmTimeout = strtoull(value, NULL, 10); - else if(hash == hash_Ip6ReasmReqds && strcmp(name, "Ip6ReasmReqds") == 0) Ip6ReasmReqds = strtoull(value, NULL, 10); - else if(hash == hash_Ip6ReasmOKs && strcmp(name, "Ip6ReasmOKs") == 0) Ip6ReasmOKs = strtoull(value, NULL, 10); - else if(hash == hash_Ip6ReasmFails && strcmp(name, "Ip6ReasmFails") == 0) Ip6ReasmFails = strtoull(value, NULL, 10); - else if(hash == hash_Ip6FragOKs && strcmp(name, "Ip6FragOKs") == 0) Ip6FragOKs = strtoull(value, NULL, 10); - else if(hash == hash_Ip6FragFails && strcmp(name, "Ip6FragFails") == 0) Ip6FragFails = strtoull(value, NULL, 10); - else if(hash == hash_Ip6FragCreates && strcmp(name, "Ip6FragCreates") == 0) Ip6FragCreates = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InMcastPkts && strcmp(name, "Ip6InMcastPkts") == 0) Ip6InMcastPkts = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutMcastPkts && strcmp(name, "Ip6OutMcastPkts") == 0) Ip6OutMcastPkts = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InOctets && strcmp(name, "Ip6InOctets") == 0) Ip6InOctets = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutOctets && strcmp(name, "Ip6OutOctets") == 0) Ip6OutOctets = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InMcastOctets && strcmp(name, "Ip6InMcastOctets") == 0) Ip6InMcastOctets = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutMcastOctets && strcmp(name, "Ip6OutMcastOctets") == 0) Ip6OutMcastOctets = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InBcastOctets && strcmp(name, "Ip6InBcastOctets") == 0) Ip6InBcastOctets = strtoull(value, NULL, 10); - else if(hash == hash_Ip6OutBcastOctets && strcmp(name, "Ip6OutBcastOctets") == 0) Ip6OutBcastOctets = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InNoECTPkts && strcmp(name, "Ip6InNoECTPkts") == 0) Ip6InNoECTPkts = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InECT1Pkts && strcmp(name, "Ip6InECT1Pkts") == 0) Ip6InECT1Pkts = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InECT0Pkts && strcmp(name, "Ip6InECT0Pkts") == 0) Ip6InECT0Pkts = strtoull(value, NULL, 10); - else if(hash == hash_Ip6InCEPkts && strcmp(name, "Ip6InCEPkts") == 0) Ip6InCEPkts = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InMsgs && strcmp(name, "Icmp6InMsgs") == 0) Icmp6InMsgs = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InErrors && strcmp(name, "Icmp6InErrors") == 0) Icmp6InErrors = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutMsgs && strcmp(name, "Icmp6OutMsgs") == 0) Icmp6OutMsgs = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutErrors && strcmp(name, "Icmp6OutErrors") == 0) Icmp6OutErrors = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InCsumErrors && strcmp(name, "Icmp6InCsumErrors") == 0) Icmp6InCsumErrors = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InDestUnreachs && strcmp(name, "Icmp6InDestUnreachs") == 0) Icmp6InDestUnreachs = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InPktTooBigs && strcmp(name, "Icmp6InPktTooBigs") == 0) Icmp6InPktTooBigs = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InTimeExcds && strcmp(name, "Icmp6InTimeExcds") == 0) Icmp6InTimeExcds = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InParmProblems && strcmp(name, "Icmp6InParmProblems") == 0) Icmp6InParmProblems = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InEchos && strcmp(name, "Icmp6InEchos") == 0) Icmp6InEchos = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InEchoReplies && strcmp(name, "Icmp6InEchoReplies") == 0) Icmp6InEchoReplies = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InGroupMembQueries && strcmp(name, "Icmp6InGroupMembQueries") == 0) Icmp6InGroupMembQueries = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InGroupMembResponses && strcmp(name, "Icmp6InGroupMembResponses") == 0) Icmp6InGroupMembResponses = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InGroupMembReductions && strcmp(name, "Icmp6InGroupMembReductions") == 0) Icmp6InGroupMembReductions = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InRouterSolicits && strcmp(name, "Icmp6InRouterSolicits") == 0) Icmp6InRouterSolicits = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InRouterAdvertisements && strcmp(name, "Icmp6InRouterAdvertisements") == 0) Icmp6InRouterAdvertisements = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InNeighborSolicits && strcmp(name, "Icmp6InNeighborSolicits") == 0) Icmp6InNeighborSolicits = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InNeighborAdvertisements && strcmp(name, "Icmp6InNeighborAdvertisements") == 0) Icmp6InNeighborAdvertisements = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InRedirects && strcmp(name, "Icmp6InRedirects") == 0) Icmp6InRedirects = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InMLDv2Reports && strcmp(name, "Icmp6InMLDv2Reports") == 0) Icmp6InMLDv2Reports = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutDestUnreachs && strcmp(name, "Icmp6OutDestUnreachs") == 0) Icmp6OutDestUnreachs = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutPktTooBigs && strcmp(name, "Icmp6OutPktTooBigs") == 0) Icmp6OutPktTooBigs = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutTimeExcds && strcmp(name, "Icmp6OutTimeExcds") == 0) Icmp6OutTimeExcds = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutParmProblems && strcmp(name, "Icmp6OutParmProblems") == 0) Icmp6OutParmProblems = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutEchos && strcmp(name, "Icmp6OutEchos") == 0) Icmp6OutEchos = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutEchoReplies && strcmp(name, "Icmp6OutEchoReplies") == 0) Icmp6OutEchoReplies = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutGroupMembQueries && strcmp(name, "Icmp6OutGroupMembQueries") == 0) Icmp6OutGroupMembQueries = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutGroupMembResponses && strcmp(name, "Icmp6OutGroupMembResponses") == 0) Icmp6OutGroupMembResponses = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutGroupMembReductions && strcmp(name, "Icmp6OutGroupMembReductions") == 0) Icmp6OutGroupMembReductions = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutRouterSolicits && strcmp(name, "Icmp6OutRouterSolicits") == 0) Icmp6OutRouterSolicits = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutRouterAdvertisements && strcmp(name, "Icmp6OutRouterAdvertisements") == 0) Icmp6OutRouterAdvertisements = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutNeighborSolicits && strcmp(name, "Icmp6OutNeighborSolicits") == 0) Icmp6OutNeighborSolicits = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutNeighborAdvertisements && strcmp(name, "Icmp6OutNeighborAdvertisements") == 0) Icmp6OutNeighborAdvertisements = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutRedirects && strcmp(name, "Icmp6OutRedirects") == 0) Icmp6OutRedirects = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutMLDv2Reports && strcmp(name, "Icmp6OutMLDv2Reports") == 0) Icmp6OutMLDv2Reports = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InType1 && strcmp(name, "Icmp6InType1") == 0) Icmp6InType1 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InType128 && strcmp(name, "Icmp6InType128") == 0) Icmp6InType128 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InType129 && strcmp(name, "Icmp6InType129") == 0) Icmp6InType129 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6InType136 && strcmp(name, "Icmp6InType136") == 0) Icmp6InType136 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutType1 && strcmp(name, "Icmp6OutType1") == 0) Icmp6OutType1 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutType128 && strcmp(name, "Icmp6OutType128") == 0) Icmp6OutType128 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutType129 && strcmp(name, "Icmp6OutType129") == 0) Icmp6OutType129 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutType133 && strcmp(name, "Icmp6OutType133") == 0) Icmp6OutType133 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutType135 && strcmp(name, "Icmp6OutType135") == 0) Icmp6OutType135 = strtoull(value, NULL, 10); - else if(hash == hash_Icmp6OutType143 && strcmp(name, "Icmp6OutType143") == 0) Icmp6OutType143 = strtoull(value, NULL, 10); - else if(hash == hash_Udp6InDatagrams && strcmp(name, "Udp6InDatagrams") == 0) Udp6InDatagrams = strtoull(value, NULL, 10); - else if(hash == hash_Udp6NoPorts && strcmp(name, "Udp6NoPorts") == 0) Udp6NoPorts = strtoull(value, NULL, 10); - else if(hash == hash_Udp6InErrors && strcmp(name, "Udp6InErrors") == 0) Udp6InErrors = strtoull(value, NULL, 10); - else if(hash == hash_Udp6OutDatagrams && strcmp(name, "Udp6OutDatagrams") == 0) Udp6OutDatagrams = strtoull(value, NULL, 10); - else if(hash == hash_Udp6RcvbufErrors && strcmp(name, "Udp6RcvbufErrors") == 0) Udp6RcvbufErrors = strtoull(value, NULL, 10); - else if(hash == hash_Udp6SndbufErrors && strcmp(name, "Udp6SndbufErrors") == 0) Udp6SndbufErrors = strtoull(value, NULL, 10); - else if(hash == hash_Udp6InCsumErrors && strcmp(name, "Udp6InCsumErrors") == 0) Udp6InCsumErrors = strtoull(value, NULL, 10); - else if(hash == hash_Udp6IgnoredMulti && strcmp(name, "Udp6IgnoredMulti") == 0) Udp6IgnoredMulti = strtoull(value, NULL, 10); - else if(hash == hash_UdpLite6InDatagrams && strcmp(name, "UdpLite6InDatagrams") == 0) UdpLite6InDatagrams = strtoull(value, NULL, 10); - else if(hash == hash_UdpLite6NoPorts && strcmp(name, "UdpLite6NoPorts") == 0) UdpLite6NoPorts = strtoull(value, NULL, 10); - else if(hash == hash_UdpLite6InErrors && strcmp(name, "UdpLite6InErrors") == 0) UdpLite6InErrors = strtoull(value, NULL, 10); - else if(hash == hash_UdpLite6OutDatagrams && strcmp(name, "UdpLite6OutDatagrams") == 0) UdpLite6OutDatagrams = strtoull(value, NULL, 10); - else if(hash == hash_UdpLite6RcvbufErrors && strcmp(name, "UdpLite6RcvbufErrors") == 0) UdpLite6RcvbufErrors = strtoull(value, NULL, 10); - else if(hash == hash_UdpLite6SndbufErrors && strcmp(name, "UdpLite6SndbufErrors") == 0) UdpLite6SndbufErrors = strtoull(value, NULL, 10); - else if(hash == hash_UdpLite6InCsumErrors && strcmp(name, "UdpLite6InCsumErrors") == 0) UdpLite6InCsumErrors = strtoull(value, NULL, 10); - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - if(do_bandwidth == CONFIG_ONDEMAND_YES || (do_bandwidth == CONFIG_ONDEMAND_ONDEMAND && (Ip6InOctets || Ip6OutOctets))) { - do_bandwidth = CONFIG_ONDEMAND_YES; - st = rrdset_find("system.ipv6"); - if(!st) { - st = rrdset_create("system", "ipv6", NULL, "network", NULL, "IPv6 Bandwidth", "kilobits/s", 500, update_every, RRDSET_TYPE_AREA); - - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Ip6OutOctets); - rrddim_set(st, "received", Ip6InOctets); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ip_packets == CONFIG_ONDEMAND_YES || (do_ip_packets == CONFIG_ONDEMAND_ONDEMAND && (Ip6InReceives || Ip6OutRequests || Ip6InDelivers || Ip6OutForwDatagrams))) { - do_ip_packets = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".packets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "packets", NULL, "packets", NULL, "IPv6 Packets", "packets/s", 3000, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "forwarded", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "delivers", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Ip6OutRequests); - rrddim_set(st, "received", Ip6InReceives); - rrddim_set(st, "forwarded", Ip6InDelivers); - rrddim_set(st, "delivers", Ip6OutForwDatagrams); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ip_fragsout == CONFIG_ONDEMAND_YES || (do_ip_fragsout == CONFIG_ONDEMAND_ONDEMAND && (Ip6FragOKs || Ip6FragFails || Ip6FragCreates))) { - do_ip_fragsout = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".fragsout"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "fragsout", NULL, "fragments", NULL, "IPv6 Fragments Sent", "packets/s", 3010, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "ok", Ip6FragOKs); - rrddim_set(st, "failed", Ip6FragFails); - rrddim_set(st, "all", Ip6FragCreates); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ip_fragsin == CONFIG_ONDEMAND_YES || (do_ip_fragsin == CONFIG_ONDEMAND_ONDEMAND - && ( - Ip6ReasmOKs - || Ip6ReasmFails - || Ip6ReasmTimeout - || Ip6ReasmReqds - ))) { - do_ip_fragsin = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".fragsin"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "fragsin", NULL, "fragments", NULL, "IPv6 Fragments Reassembly", "packets/s", 3011, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "timeout", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "ok", Ip6ReasmOKs); - rrddim_set(st, "failed", Ip6ReasmFails); - rrddim_set(st, "timeout", Ip6ReasmTimeout); - rrddim_set(st, "all", Ip6ReasmReqds); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ip_errors == CONFIG_ONDEMAND_YES || (do_ip_errors == CONFIG_ONDEMAND_ONDEMAND - && ( - Ip6InDiscards - || Ip6OutDiscards - || Ip6InHdrErrors - || Ip6InAddrErrors - || Ip6InUnknownProtos - || Ip6InTooBigErrors - || Ip6InTruncatedPkts - || Ip6InNoRoutes - ))) { - do_ip_errors = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".errors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "errors", NULL, "errors", NULL, "IPv6 Errors", "packets/s", 3002, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "InDiscards", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutDiscards", NULL, -1, 1, RRDDIM_INCREMENTAL); - - rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InTooBigErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InTruncatedPkts", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InNoRoutes", NULL, 1, 1, RRDDIM_INCREMENTAL); - - rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InDiscards", Ip6InDiscards); - rrddim_set(st, "OutDiscards", Ip6OutDiscards); - - rrddim_set(st, "InHdrErrors", Ip6InHdrErrors); - rrddim_set(st, "InAddrErrors", Ip6InAddrErrors); - rrddim_set(st, "InUnknownProtos", Ip6InUnknownProtos); - rrddim_set(st, "InTooBigErrors", Ip6InTooBigErrors); - rrddim_set(st, "InTruncatedPkts", Ip6InTruncatedPkts); - rrddim_set(st, "InNoRoutes", Ip6InNoRoutes); - - rrddim_set(st, "OutNoRoutes", Ip6OutNoRoutes); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_udp_packets == CONFIG_ONDEMAND_YES || (do_udp_packets == CONFIG_ONDEMAND_ONDEMAND && (Udp6InDatagrams || Udp6OutDatagrams))) { - do_udp_packets = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udppackets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "udppackets", NULL, "udp", NULL, "IPv6 UDP Packets", "packets/s", 3601, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "received", Udp6InDatagrams); - rrddim_set(st, "sent", Udp6OutDatagrams); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_udp_errors == CONFIG_ONDEMAND_YES || (do_udp_errors == CONFIG_ONDEMAND_ONDEMAND - && ( - Udp6InErrors - || Udp6NoPorts - || Udp6RcvbufErrors - || Udp6SndbufErrors - || Udp6InCsumErrors - || Udp6IgnoredMulti - ))) { - do_udp_errors = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udperrors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "udperrors", NULL, "udp", NULL, "IPv6 UDP Errors", "events/s", 3701, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "NoPorts", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "IgnoredMulti", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InErrors", Udp6InErrors); - rrddim_set(st, "NoPorts", Udp6NoPorts); - rrddim_set(st, "RcvbufErrors", Udp6RcvbufErrors); - rrddim_set(st, "SndbufErrors", Udp6SndbufErrors); - rrddim_set(st, "InCsumErrors", Udp6InCsumErrors); - rrddim_set(st, "IgnoredMulti", Udp6IgnoredMulti); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_udplite_packets == CONFIG_ONDEMAND_YES || (do_udplite_packets == CONFIG_ONDEMAND_ONDEMAND && (UdpLite6InDatagrams || UdpLite6OutDatagrams))) { - do_udplite_packets = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udplitepackets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "udplitepackets", NULL, "udplite", NULL, "IPv6 UDPlite Packets", "packets/s", 3601, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "received", UdpLite6InDatagrams); - rrddim_set(st, "sent", UdpLite6OutDatagrams); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_udplite_errors == CONFIG_ONDEMAND_YES || (do_udplite_errors == CONFIG_ONDEMAND_ONDEMAND - && ( - UdpLite6InErrors - || UdpLite6NoPorts - || UdpLite6RcvbufErrors - || UdpLite6SndbufErrors - || Udp6InCsumErrors - || UdpLite6InCsumErrors - ))) { - do_udplite_errors = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udpliteerrors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "udpliteerrors", NULL, "udplite", NULL, "IPv6 UDP Lite Errors", "events/s", 3701, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "NoPorts", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InErrors", UdpLite6InErrors); - rrddim_set(st, "NoPorts", UdpLite6NoPorts); - rrddim_set(st, "RcvbufErrors", UdpLite6RcvbufErrors); - rrddim_set(st, "SndbufErrors", UdpLite6SndbufErrors); - rrddim_set(st, "InCsumErrors", UdpLite6InCsumErrors); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_mcast == CONFIG_ONDEMAND_YES || (do_mcast == CONFIG_ONDEMAND_ONDEMAND && (Ip6OutMcastOctets || Ip6InMcastOctets))) { - do_mcast = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".mcast"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "mcast", NULL, "multicast", NULL, "IPv6 Multicast Bandwidth", "kilobits/s", 9000, update_every, RRDSET_TYPE_AREA); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Ip6OutMcastOctets); - rrddim_set(st, "received", Ip6InMcastOctets); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_bcast == CONFIG_ONDEMAND_YES || (do_bcast == CONFIG_ONDEMAND_ONDEMAND && (Ip6OutBcastOctets || Ip6InBcastOctets))) { - do_bcast = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".bcast"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "bcast", NULL, "broadcast", NULL, "IPv6 Broadcast Bandwidth", "kilobits/s", 8000, update_every, RRDSET_TYPE_AREA); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Ip6OutBcastOctets); - rrddim_set(st, "received", Ip6InBcastOctets); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_mcast_p == CONFIG_ONDEMAND_YES || (do_mcast_p == CONFIG_ONDEMAND_ONDEMAND && (Ip6OutMcastPkts || Ip6InMcastPkts))) { - do_mcast_p = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".mcastpkts"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "mcastpkts", NULL, "multicast", NULL, "IPv6 Multicast Packets", "packets/s", 9500, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Ip6OutMcastPkts); - rrddim_set(st, "received", Ip6InMcastPkts); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp == CONFIG_ONDEMAND_YES || (do_icmp == CONFIG_ONDEMAND_ONDEMAND && (Icmp6InMsgs || Icmp6OutMsgs))) { - do_icmp = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmp"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmp", NULL, "icmp", NULL, "IPv6 ICMP Messages", "messages/s", 10000, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Icmp6InMsgs); - rrddim_set(st, "received", Icmp6OutMsgs); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_redir == CONFIG_ONDEMAND_YES || (do_icmp_redir == CONFIG_ONDEMAND_ONDEMAND && (Icmp6InRedirects || Icmp6OutRedirects))) { - do_icmp_redir = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpredir"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpredir", NULL, "icmp", NULL, "IPv6 ICMP Redirects", "redirects/s", 10050, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Icmp6InRedirects); - rrddim_set(st, "received", Icmp6OutRedirects); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_errors == CONFIG_ONDEMAND_YES || (do_icmp_errors == CONFIG_ONDEMAND_ONDEMAND - && ( - Icmp6InErrors - || Icmp6OutErrors - || Icmp6InCsumErrors - || Icmp6InDestUnreachs - || Icmp6InPktTooBigs - || Icmp6InTimeExcds - || Icmp6InParmProblems - || Icmp6OutDestUnreachs - || Icmp6OutPktTooBigs - || Icmp6OutTimeExcds - || Icmp6OutParmProblems - ))) { - do_icmp_errors = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmperrors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmperrors", NULL, "icmp", NULL, "IPv6 ICMP Errors", "errors/s", 10100, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); - - rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InDestUnreachs", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InPktTooBigs", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InTimeExcds", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InParmProblems", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutDestUnreachs", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutPktTooBigs", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutTimeExcds", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutParmProblems", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InErrors", Icmp6InErrors); - rrddim_set(st, "OutErrors", Icmp6OutErrors); - rrddim_set(st, "InCsumErrors", Icmp6InCsumErrors); - rrddim_set(st, "InDestUnreachs", Icmp6InDestUnreachs); - rrddim_set(st, "InPktTooBigs", Icmp6InPktTooBigs); - rrddim_set(st, "InTimeExcds", Icmp6InTimeExcds); - rrddim_set(st, "InParmProblems", Icmp6InParmProblems); - rrddim_set(st, "OutDestUnreachs", Icmp6OutDestUnreachs); - rrddim_set(st, "OutPktTooBigs", Icmp6OutPktTooBigs); - rrddim_set(st, "OutTimeExcds", Icmp6OutTimeExcds); - rrddim_set(st, "OutParmProblems", Icmp6OutParmProblems); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_echos == CONFIG_ONDEMAND_YES || (do_icmp_echos == CONFIG_ONDEMAND_ONDEMAND - && ( - Icmp6InEchos - || Icmp6OutEchos - || Icmp6InEchoReplies - || Icmp6OutEchoReplies - ))) { - do_icmp_echos = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpechos"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpechos", NULL, "icmp", NULL, "IPv6 ICMP Echo", "messages/s", 10200, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "InEchos", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutEchos", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InEchoReplies", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutEchoReplies", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InEchos", Icmp6InEchos); - rrddim_set(st, "OutEchos", Icmp6OutEchos); - rrddim_set(st, "InEchoReplies", Icmp6InEchoReplies); - rrddim_set(st, "OutEchoReplies", Icmp6OutEchoReplies); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_groupmemb == CONFIG_ONDEMAND_YES || (do_icmp_groupmemb == CONFIG_ONDEMAND_ONDEMAND - && ( - Icmp6InGroupMembQueries - || Icmp6OutGroupMembQueries - || Icmp6InGroupMembResponses - || Icmp6OutGroupMembResponses - || Icmp6InGroupMembReductions - || Icmp6OutGroupMembReductions - ))) { - do_icmp_groupmemb = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".groupmemb"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "groupmemb", NULL, "icmp", NULL, "IPv6 ICMP Group Membership", "messages/s", 10300, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "InQueries", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutQueries", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InResponses", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutResponses", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InReductions", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutReductions", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InQueries", Icmp6InGroupMembQueries); - rrddim_set(st, "OutQueries", Icmp6OutGroupMembQueries); - rrddim_set(st, "InResponses", Icmp6InGroupMembResponses); - rrddim_set(st, "OutResponses", Icmp6OutGroupMembResponses); - rrddim_set(st, "InReductions", Icmp6InGroupMembReductions); - rrddim_set(st, "OutReductions", Icmp6OutGroupMembReductions); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_router == CONFIG_ONDEMAND_YES || (do_icmp_router == CONFIG_ONDEMAND_ONDEMAND - && ( - Icmp6InRouterSolicits - || Icmp6OutRouterSolicits - || Icmp6InRouterAdvertisements - || Icmp6OutRouterAdvertisements - ))) { - do_icmp_router = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmprouter"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmprouter", NULL, "icmp", NULL, "IPv6 Router Messages", "messages/s", 10400, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "InSolicits", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutSolicits", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InSolicits", Icmp6InRouterSolicits); - rrddim_set(st, "OutSolicits", Icmp6OutRouterSolicits); - rrddim_set(st, "InAdvertisements", Icmp6InRouterAdvertisements); - rrddim_set(st, "OutAdvertisements", Icmp6OutRouterAdvertisements); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_neighbor == CONFIG_ONDEMAND_YES || (do_icmp_neighbor == CONFIG_ONDEMAND_ONDEMAND - && ( - Icmp6InNeighborSolicits - || Icmp6OutNeighborSolicits - || Icmp6InNeighborAdvertisements - || Icmp6OutNeighborAdvertisements - ))) { - do_icmp_neighbor = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpneighbor"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpneighbor", NULL, "icmp", NULL, "IPv6 Neighbor Messages", "messages/s", 10500, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "InSolicits", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutSolicits", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InSolicits", Icmp6InNeighborSolicits); - rrddim_set(st, "OutSolicits", Icmp6OutNeighborSolicits); - rrddim_set(st, "InAdvertisements", Icmp6InNeighborAdvertisements); - rrddim_set(st, "OutAdvertisements", Icmp6OutNeighborAdvertisements); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_mldv2 == CONFIG_ONDEMAND_YES || (do_icmp_mldv2 == CONFIG_ONDEMAND_ONDEMAND && (Icmp6InMLDv2Reports || Icmp6OutMLDv2Reports))) { - do_icmp_mldv2 = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpmldv2"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpmldv2", NULL, "icmp", NULL, "IPv6 ICMP MLDv2 Reports", "reports/s", 10600, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "sent", Icmp6InMLDv2Reports); - rrddim_set(st, "received", Icmp6OutMLDv2Reports); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_icmp_types == CONFIG_ONDEMAND_YES || (do_icmp_types == CONFIG_ONDEMAND_ONDEMAND - && ( - Icmp6InType1 - || Icmp6InType128 - || Icmp6InType129 - || Icmp6InType136 - || Icmp6OutType1 - || Icmp6OutType128 - || Icmp6OutType129 - || Icmp6OutType133 - || Icmp6OutType135 - || Icmp6OutType143 - ))) { - do_icmp_types = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmptypes"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmptypes", NULL, "icmp", NULL, "IPv6 ICMP Types", "messages/s", 10700, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "InType1", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InType128", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InType129", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InType136", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutType1", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutType128", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutType129", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutType133", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutType135", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "OutType143", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InType1", Icmp6InType1); - rrddim_set(st, "InType128", Icmp6InType128); - rrddim_set(st, "InType129", Icmp6InType129); - rrddim_set(st, "InType136", Icmp6InType136); - rrddim_set(st, "OutType1", Icmp6OutType1); - rrddim_set(st, "OutType128", Icmp6OutType128); - rrddim_set(st, "OutType129", Icmp6OutType129); - rrddim_set(st, "OutType133", Icmp6OutType133); - rrddim_set(st, "OutType135", Icmp6OutType135); - rrddim_set(st, "OutType143", Icmp6OutType143); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_ect == CONFIG_ONDEMAND_YES || (do_ect == CONFIG_ONDEMAND_ONDEMAND - && ( - Ip6InNoECTPkts - || Ip6InECT1Pkts - || Ip6InECT0Pkts - || Ip6InCEPkts - ))) { - do_ect = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_SNMP6 ".ect"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_SNMP6, "ect", NULL, "packets", NULL, "IPv6 ECT Packets", "packets/s", 10800, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "InNoECTPkts", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InECT1Pkts", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InECT0Pkts", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "InCEPkts", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "InNoECTPkts", Ip6InNoECTPkts); - rrddim_set(st, "InECT1Pkts", Ip6InECT1Pkts); - rrddim_set(st, "InECT0Pkts", Ip6InECT0Pkts); - rrddim_set(st, "InCEPkts", Ip6InCEPkts); - rrdset_done(st); - } - - return 0; + static procfile *ff = NULL; + static int gen_hashes = -1; + + static int do_ip_packets = -1, do_ip_fragsout = -1, do_ip_fragsin = -1, do_ip_errors = -1, + do_udplite_packets = -1, do_udplite_errors = -1, + do_udp_packets = -1, do_udp_errors = -1, + do_bandwidth = -1, do_mcast = -1, do_bcast = -1, do_mcast_p = -1, + do_icmp = -1, do_icmp_redir = -1, do_icmp_errors = -1, do_icmp_echos = -1, do_icmp_groupmemb = -1, + do_icmp_router = -1, do_icmp_neighbor = -1, do_icmp_mldv2 = -1, do_icmp_types = -1, do_ect = -1; + + static uint32_t hash_Ip6InReceives = -1; + + static uint32_t hash_Ip6InHdrErrors = -1; + static uint32_t hash_Ip6InTooBigErrors = -1; + static uint32_t hash_Ip6InNoRoutes = -1; + static uint32_t hash_Ip6InAddrErrors = -1; + static uint32_t hash_Ip6InUnknownProtos = -1; + static uint32_t hash_Ip6InTruncatedPkts = -1; + static uint32_t hash_Ip6InDiscards = -1; + static uint32_t hash_Ip6InDelivers = -1; + + static uint32_t hash_Ip6OutForwDatagrams = -1; + static uint32_t hash_Ip6OutRequests = -1; + static uint32_t hash_Ip6OutDiscards = -1; + static uint32_t hash_Ip6OutNoRoutes = -1; + + static uint32_t hash_Ip6ReasmTimeout = -1; + static uint32_t hash_Ip6ReasmReqds = -1; + static uint32_t hash_Ip6ReasmOKs = -1; + static uint32_t hash_Ip6ReasmFails = -1; + + static uint32_t hash_Ip6FragOKs = -1; + static uint32_t hash_Ip6FragFails = -1; + static uint32_t hash_Ip6FragCreates = -1; + + static uint32_t hash_Ip6InMcastPkts = -1; + static uint32_t hash_Ip6OutMcastPkts = -1; + + static uint32_t hash_Ip6InOctets = -1; + static uint32_t hash_Ip6OutOctets = -1; + + static uint32_t hash_Ip6InMcastOctets = -1; + static uint32_t hash_Ip6OutMcastOctets = -1; + static uint32_t hash_Ip6InBcastOctets = -1; + static uint32_t hash_Ip6OutBcastOctets = -1; + + static uint32_t hash_Ip6InNoECTPkts = -1; + static uint32_t hash_Ip6InECT1Pkts = -1; + static uint32_t hash_Ip6InECT0Pkts = -1; + static uint32_t hash_Ip6InCEPkts = -1; + + static uint32_t hash_Icmp6InMsgs = -1; + static uint32_t hash_Icmp6InErrors = -1; + static uint32_t hash_Icmp6OutMsgs = -1; + static uint32_t hash_Icmp6OutErrors = -1; + static uint32_t hash_Icmp6InCsumErrors = -1; + static uint32_t hash_Icmp6InDestUnreachs = -1; + static uint32_t hash_Icmp6InPktTooBigs = -1; + static uint32_t hash_Icmp6InTimeExcds = -1; + static uint32_t hash_Icmp6InParmProblems = -1; + static uint32_t hash_Icmp6InEchos = -1; + static uint32_t hash_Icmp6InEchoReplies = -1; + static uint32_t hash_Icmp6InGroupMembQueries = -1; + static uint32_t hash_Icmp6InGroupMembResponses = -1; + static uint32_t hash_Icmp6InGroupMembReductions = -1; + static uint32_t hash_Icmp6InRouterSolicits = -1; + static uint32_t hash_Icmp6InRouterAdvertisements = -1; + static uint32_t hash_Icmp6InNeighborSolicits = -1; + static uint32_t hash_Icmp6InNeighborAdvertisements = -1; + static uint32_t hash_Icmp6InRedirects = -1; + static uint32_t hash_Icmp6InMLDv2Reports = -1; + static uint32_t hash_Icmp6OutDestUnreachs = -1; + static uint32_t hash_Icmp6OutPktTooBigs = -1; + static uint32_t hash_Icmp6OutTimeExcds = -1; + static uint32_t hash_Icmp6OutParmProblems = -1; + static uint32_t hash_Icmp6OutEchos = -1; + static uint32_t hash_Icmp6OutEchoReplies = -1; + static uint32_t hash_Icmp6OutGroupMembQueries = -1; + static uint32_t hash_Icmp6OutGroupMembResponses = -1; + static uint32_t hash_Icmp6OutGroupMembReductions = -1; + static uint32_t hash_Icmp6OutRouterSolicits = -1; + static uint32_t hash_Icmp6OutRouterAdvertisements = -1; + static uint32_t hash_Icmp6OutNeighborSolicits = -1; + static uint32_t hash_Icmp6OutNeighborAdvertisements = -1; + static uint32_t hash_Icmp6OutRedirects = -1; + static uint32_t hash_Icmp6OutMLDv2Reports = -1; + static uint32_t hash_Icmp6InType1 = -1; + static uint32_t hash_Icmp6InType128 = -1; + static uint32_t hash_Icmp6InType129 = -1; + static uint32_t hash_Icmp6InType136 = -1; + static uint32_t hash_Icmp6OutType1 = -1; + static uint32_t hash_Icmp6OutType128 = -1; + static uint32_t hash_Icmp6OutType129 = -1; + static uint32_t hash_Icmp6OutType133 = -1; + static uint32_t hash_Icmp6OutType135 = -1; + static uint32_t hash_Icmp6OutType143 = -1; + + static uint32_t hash_Udp6InDatagrams = -1; + static uint32_t hash_Udp6NoPorts = -1; + static uint32_t hash_Udp6InErrors = -1; + static uint32_t hash_Udp6OutDatagrams = -1; + static uint32_t hash_Udp6RcvbufErrors = -1; + static uint32_t hash_Udp6SndbufErrors = -1; + static uint32_t hash_Udp6InCsumErrors = -1; + static uint32_t hash_Udp6IgnoredMulti = -1; + + static uint32_t hash_UdpLite6InDatagrams = -1; + static uint32_t hash_UdpLite6NoPorts = -1; + static uint32_t hash_UdpLite6InErrors = -1; + static uint32_t hash_UdpLite6OutDatagrams = -1; + static uint32_t hash_UdpLite6RcvbufErrors = -1; + static uint32_t hash_UdpLite6SndbufErrors = -1; + static uint32_t hash_UdpLite6InCsumErrors = -1; + + if(gen_hashes != 1) { + gen_hashes = 1; + hash_Ip6InReceives = simple_hash("Ip6InReceives"); + hash_Ip6InHdrErrors = simple_hash("Ip6InHdrErrors"); + hash_Ip6InTooBigErrors = simple_hash("Ip6InTooBigErrors"); + hash_Ip6InNoRoutes = simple_hash("Ip6InNoRoutes"); + hash_Ip6InAddrErrors = simple_hash("Ip6InAddrErrors"); + hash_Ip6InUnknownProtos = simple_hash("Ip6InUnknownProtos"); + hash_Ip6InTruncatedPkts = simple_hash("Ip6InTruncatedPkts"); + hash_Ip6InDiscards = simple_hash("Ip6InDiscards"); + hash_Ip6InDelivers = simple_hash("Ip6InDelivers"); + hash_Ip6OutForwDatagrams = simple_hash("Ip6OutForwDatagrams"); + hash_Ip6OutRequests = simple_hash("Ip6OutRequests"); + hash_Ip6OutDiscards = simple_hash("Ip6OutDiscards"); + hash_Ip6OutNoRoutes = simple_hash("Ip6OutNoRoutes"); + hash_Ip6ReasmTimeout = simple_hash("Ip6ReasmTimeout"); + hash_Ip6ReasmReqds = simple_hash("Ip6ReasmReqds"); + hash_Ip6ReasmOKs = simple_hash("Ip6ReasmOKs"); + hash_Ip6ReasmFails = simple_hash("Ip6ReasmFails"); + hash_Ip6FragOKs = simple_hash("Ip6FragOKs"); + hash_Ip6FragFails = simple_hash("Ip6FragFails"); + hash_Ip6FragCreates = simple_hash("Ip6FragCreates"); + hash_Ip6InMcastPkts = simple_hash("Ip6InMcastPkts"); + hash_Ip6OutMcastPkts = simple_hash("Ip6OutMcastPkts"); + hash_Ip6InOctets = simple_hash("Ip6InOctets"); + hash_Ip6OutOctets = simple_hash("Ip6OutOctets"); + hash_Ip6InMcastOctets = simple_hash("Ip6InMcastOctets"); + hash_Ip6OutMcastOctets = simple_hash("Ip6OutMcastOctets"); + hash_Ip6InBcastOctets = simple_hash("Ip6InBcastOctets"); + hash_Ip6OutBcastOctets = simple_hash("Ip6OutBcastOctets"); + hash_Ip6InNoECTPkts = simple_hash("Ip6InNoECTPkts"); + hash_Ip6InECT1Pkts = simple_hash("Ip6InECT1Pkts"); + hash_Ip6InECT0Pkts = simple_hash("Ip6InECT0Pkts"); + hash_Ip6InCEPkts = simple_hash("Ip6InCEPkts"); + hash_Icmp6InMsgs = simple_hash("Icmp6InMsgs"); + hash_Icmp6InErrors = simple_hash("Icmp6InErrors"); + hash_Icmp6OutMsgs = simple_hash("Icmp6OutMsgs"); + hash_Icmp6OutErrors = simple_hash("Icmp6OutErrors"); + hash_Icmp6InCsumErrors = simple_hash("Icmp6InCsumErrors"); + hash_Icmp6InDestUnreachs = simple_hash("Icmp6InDestUnreachs"); + hash_Icmp6InPktTooBigs = simple_hash("Icmp6InPktTooBigs"); + hash_Icmp6InTimeExcds = simple_hash("Icmp6InTimeExcds"); + hash_Icmp6InParmProblems = simple_hash("Icmp6InParmProblems"); + hash_Icmp6InEchos = simple_hash("Icmp6InEchos"); + hash_Icmp6InEchoReplies = simple_hash("Icmp6InEchoReplies"); + hash_Icmp6InGroupMembQueries = simple_hash("Icmp6InGroupMembQueries"); + hash_Icmp6InGroupMembResponses = simple_hash("Icmp6InGroupMembResponses"); + hash_Icmp6InGroupMembReductions = simple_hash("Icmp6InGroupMembReductions"); + hash_Icmp6InRouterSolicits = simple_hash("Icmp6InRouterSolicits"); + hash_Icmp6InRouterAdvertisements = simple_hash("Icmp6InRouterAdvertisements"); + hash_Icmp6InNeighborSolicits = simple_hash("Icmp6InNeighborSolicits"); + hash_Icmp6InNeighborAdvertisements = simple_hash("Icmp6InNeighborAdvertisements"); + hash_Icmp6InRedirects = simple_hash("Icmp6InRedirects"); + hash_Icmp6InMLDv2Reports = simple_hash("Icmp6InMLDv2Reports"); + hash_Icmp6OutDestUnreachs = simple_hash("Icmp6OutDestUnreachs"); + hash_Icmp6OutPktTooBigs = simple_hash("Icmp6OutPktTooBigs"); + hash_Icmp6OutTimeExcds = simple_hash("Icmp6OutTimeExcds"); + hash_Icmp6OutParmProblems = simple_hash("Icmp6OutParmProblems"); + hash_Icmp6OutEchos = simple_hash("Icmp6OutEchos"); + hash_Icmp6OutEchoReplies = simple_hash("Icmp6OutEchoReplies"); + hash_Icmp6OutGroupMembQueries = simple_hash("Icmp6OutGroupMembQueries"); + hash_Icmp6OutGroupMembResponses = simple_hash("Icmp6OutGroupMembResponses"); + hash_Icmp6OutGroupMembReductions = simple_hash("Icmp6OutGroupMembReductions"); + hash_Icmp6OutRouterSolicits = simple_hash("Icmp6OutRouterSolicits"); + hash_Icmp6OutRouterAdvertisements = simple_hash("Icmp6OutRouterAdvertisements"); + hash_Icmp6OutNeighborSolicits = simple_hash("Icmp6OutNeighborSolicits"); + hash_Icmp6OutNeighborAdvertisements = simple_hash("Icmp6OutNeighborAdvertisements"); + hash_Icmp6OutRedirects = simple_hash("Icmp6OutRedirects"); + hash_Icmp6OutMLDv2Reports = simple_hash("Icmp6OutMLDv2Reports"); + hash_Icmp6InType1 = simple_hash("Icmp6InType1"); + hash_Icmp6InType128 = simple_hash("Icmp6InType128"); + hash_Icmp6InType129 = simple_hash("Icmp6InType129"); + hash_Icmp6InType136 = simple_hash("Icmp6InType136"); + hash_Icmp6OutType1 = simple_hash("Icmp6OutType1"); + hash_Icmp6OutType128 = simple_hash("Icmp6OutType128"); + hash_Icmp6OutType129 = simple_hash("Icmp6OutType129"); + hash_Icmp6OutType133 = simple_hash("Icmp6OutType133"); + hash_Icmp6OutType135 = simple_hash("Icmp6OutType135"); + hash_Icmp6OutType143 = simple_hash("Icmp6OutType143"); + hash_Udp6InDatagrams = simple_hash("Udp6InDatagrams"); + hash_Udp6NoPorts = simple_hash("Udp6NoPorts"); + hash_Udp6InErrors = simple_hash("Udp6InErrors"); + hash_Udp6OutDatagrams = simple_hash("Udp6OutDatagrams"); + hash_Udp6RcvbufErrors = simple_hash("Udp6RcvbufErrors"); + hash_Udp6SndbufErrors = simple_hash("Udp6SndbufErrors"); + hash_Udp6InCsumErrors = simple_hash("Udp6InCsumErrors"); + hash_Udp6IgnoredMulti = simple_hash("Udp6IgnoredMulti"); + hash_UdpLite6InDatagrams = simple_hash("UdpLite6InDatagrams"); + hash_UdpLite6NoPorts = simple_hash("UdpLite6NoPorts"); + hash_UdpLite6InErrors = simple_hash("UdpLite6InErrors"); + hash_UdpLite6OutDatagrams = simple_hash("UdpLite6OutDatagrams"); + hash_UdpLite6RcvbufErrors = simple_hash("UdpLite6RcvbufErrors"); + hash_UdpLite6SndbufErrors = simple_hash("UdpLite6SndbufErrors"); + hash_UdpLite6InCsumErrors = simple_hash("UdpLite6InCsumErrors"); + } + + if(do_ip_packets == -1) do_ip_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 packets", CONFIG_ONDEMAND_ONDEMAND); + if(do_ip_fragsout == -1) do_ip_fragsout = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments sent", CONFIG_ONDEMAND_ONDEMAND); + if(do_ip_fragsin == -1) do_ip_fragsin = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments assembly", CONFIG_ONDEMAND_ONDEMAND); + if(do_ip_errors == -1) do_ip_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 errors", CONFIG_ONDEMAND_ONDEMAND); + if(do_udp_packets == -1) do_udp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP packets", CONFIG_ONDEMAND_ONDEMAND); + if(do_udp_errors == -1) do_udp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP errors", CONFIG_ONDEMAND_ONDEMAND); + if(do_udplite_packets == -1) do_udplite_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite packets", CONFIG_ONDEMAND_ONDEMAND); + if(do_udplite_errors == -1) do_udplite_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite errors", CONFIG_ONDEMAND_ONDEMAND); + if(do_bandwidth == -1) do_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "bandwidth", CONFIG_ONDEMAND_ONDEMAND); + if(do_mcast == -1) do_mcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast bandwidth", CONFIG_ONDEMAND_ONDEMAND); + if(do_bcast == -1) do_bcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "broadcast bandwidth", CONFIG_ONDEMAND_ONDEMAND); + if(do_mcast_p == -1) do_mcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast packets", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp == -1) do_icmp = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_redir == -1) do_icmp_redir = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp redirects", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_errors == -1) do_icmp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp errors", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_echos == -1) do_icmp_echos = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp echos", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_groupmemb == -1) do_icmp_groupmemb = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp group membership", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_router == -1) do_icmp_router = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp router", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_neighbor == -1) do_icmp_neighbor = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp neighbor", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_mldv2 == -1) do_icmp_mldv2 = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp mldv2", CONFIG_ONDEMAND_ONDEMAND); + if(do_icmp_types == -1) do_icmp_types = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp types", CONFIG_ONDEMAND_ONDEMAND); + if(do_ect == -1) do_ect = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ect", CONFIG_ONDEMAND_ONDEMAND); + + if(dt) {}; + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + unsigned long long Ip6InReceives = 0ULL; + unsigned long long Ip6InHdrErrors = 0ULL; + unsigned long long Ip6InTooBigErrors = 0ULL; + unsigned long long Ip6InNoRoutes = 0ULL; + unsigned long long Ip6InAddrErrors = 0ULL; + unsigned long long Ip6InUnknownProtos = 0ULL; + unsigned long long Ip6InTruncatedPkts = 0ULL; + unsigned long long Ip6InDiscards = 0ULL; + unsigned long long Ip6InDelivers = 0ULL; + unsigned long long Ip6OutForwDatagrams = 0ULL; + unsigned long long Ip6OutRequests = 0ULL; + unsigned long long Ip6OutDiscards = 0ULL; + unsigned long long Ip6OutNoRoutes = 0ULL; + unsigned long long Ip6ReasmTimeout = 0ULL; + unsigned long long Ip6ReasmReqds = 0ULL; + unsigned long long Ip6ReasmOKs = 0ULL; + unsigned long long Ip6ReasmFails = 0ULL; + unsigned long long Ip6FragOKs = 0ULL; + unsigned long long Ip6FragFails = 0ULL; + unsigned long long Ip6FragCreates = 0ULL; + unsigned long long Ip6InMcastPkts = 0ULL; + unsigned long long Ip6OutMcastPkts = 0ULL; + unsigned long long Ip6InOctets = 0ULL; + unsigned long long Ip6OutOctets = 0ULL; + unsigned long long Ip6InMcastOctets = 0ULL; + unsigned long long Ip6OutMcastOctets = 0ULL; + unsigned long long Ip6InBcastOctets = 0ULL; + unsigned long long Ip6OutBcastOctets = 0ULL; + unsigned long long Ip6InNoECTPkts = 0ULL; + unsigned long long Ip6InECT1Pkts = 0ULL; + unsigned long long Ip6InECT0Pkts = 0ULL; + unsigned long long Ip6InCEPkts = 0ULL; + unsigned long long Icmp6InMsgs = 0ULL; + unsigned long long Icmp6InErrors = 0ULL; + unsigned long long Icmp6OutMsgs = 0ULL; + unsigned long long Icmp6OutErrors = 0ULL; + unsigned long long Icmp6InCsumErrors = 0ULL; + unsigned long long Icmp6InDestUnreachs = 0ULL; + unsigned long long Icmp6InPktTooBigs = 0ULL; + unsigned long long Icmp6InTimeExcds = 0ULL; + unsigned long long Icmp6InParmProblems = 0ULL; + unsigned long long Icmp6InEchos = 0ULL; + unsigned long long Icmp6InEchoReplies = 0ULL; + unsigned long long Icmp6InGroupMembQueries = 0ULL; + unsigned long long Icmp6InGroupMembResponses = 0ULL; + unsigned long long Icmp6InGroupMembReductions = 0ULL; + unsigned long long Icmp6InRouterSolicits = 0ULL; + unsigned long long Icmp6InRouterAdvertisements = 0ULL; + unsigned long long Icmp6InNeighborSolicits = 0ULL; + unsigned long long Icmp6InNeighborAdvertisements = 0ULL; + unsigned long long Icmp6InRedirects = 0ULL; + unsigned long long Icmp6InMLDv2Reports = 0ULL; + unsigned long long Icmp6OutDestUnreachs = 0ULL; + unsigned long long Icmp6OutPktTooBigs = 0ULL; + unsigned long long Icmp6OutTimeExcds = 0ULL; + unsigned long long Icmp6OutParmProblems = 0ULL; + unsigned long long Icmp6OutEchos = 0ULL; + unsigned long long Icmp6OutEchoReplies = 0ULL; + unsigned long long Icmp6OutGroupMembQueries = 0ULL; + unsigned long long Icmp6OutGroupMembResponses = 0ULL; + unsigned long long Icmp6OutGroupMembReductions = 0ULL; + unsigned long long Icmp6OutRouterSolicits = 0ULL; + unsigned long long Icmp6OutRouterAdvertisements = 0ULL; + unsigned long long Icmp6OutNeighborSolicits = 0ULL; + unsigned long long Icmp6OutNeighborAdvertisements = 0ULL; + unsigned long long Icmp6OutRedirects = 0ULL; + unsigned long long Icmp6OutMLDv2Reports = 0ULL; + unsigned long long Icmp6InType1 = 0ULL; + unsigned long long Icmp6InType128 = 0ULL; + unsigned long long Icmp6InType129 = 0ULL; + unsigned long long Icmp6InType136 = 0ULL; + unsigned long long Icmp6OutType1 = 0ULL; + unsigned long long Icmp6OutType128 = 0ULL; + unsigned long long Icmp6OutType129 = 0ULL; + unsigned long long Icmp6OutType133 = 0ULL; + unsigned long long Icmp6OutType135 = 0ULL; + unsigned long long Icmp6OutType143 = 0ULL; + unsigned long long Udp6InDatagrams = 0ULL; + unsigned long long Udp6NoPorts = 0ULL; + unsigned long long Udp6InErrors = 0ULL; + unsigned long long Udp6OutDatagrams = 0ULL; + unsigned long long Udp6RcvbufErrors = 0ULL; + unsigned long long Udp6SndbufErrors = 0ULL; + unsigned long long Udp6InCsumErrors = 0ULL; + unsigned long long Udp6IgnoredMulti = 0ULL; + unsigned long long UdpLite6InDatagrams = 0ULL; + unsigned long long UdpLite6NoPorts = 0ULL; + unsigned long long UdpLite6InErrors = 0ULL; + unsigned long long UdpLite6OutDatagrams = 0ULL; + unsigned long long UdpLite6RcvbufErrors = 0ULL; + unsigned long long UdpLite6SndbufErrors = 0ULL; + unsigned long long UdpLite6InCsumErrors = 0ULL; + + for(l = 0; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(words < 2) { + if(words) error("Cannot read /proc/net/snmp6 line %u. Expected 2 params, read %u.", l, words); + continue; + } + + char *name = procfile_lineword(ff, l, 0); + char * value = procfile_lineword(ff, l, 1); + if(!name || !*name || !value || !*value) continue; + + uint32_t hash = simple_hash(name); + + if(0) ; + else if(hash == hash_Ip6InReceives && strcmp(name, "Ip6InReceives") == 0) Ip6InReceives = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InHdrErrors && strcmp(name, "Ip6InHdrErrors") == 0) Ip6InHdrErrors = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InTooBigErrors && strcmp(name, "Ip6InTooBigErrors") == 0) Ip6InTooBigErrors = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InNoRoutes && strcmp(name, "Ip6InNoRoutes") == 0) Ip6InNoRoutes = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InAddrErrors && strcmp(name, "Ip6InAddrErrors") == 0) Ip6InAddrErrors = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InUnknownProtos && strcmp(name, "Ip6InUnknownProtos") == 0) Ip6InUnknownProtos = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InTruncatedPkts && strcmp(name, "Ip6InTruncatedPkts") == 0) Ip6InTruncatedPkts = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InDiscards && strcmp(name, "Ip6InDiscards") == 0) Ip6InDiscards = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InDelivers && strcmp(name, "Ip6InDelivers") == 0) Ip6InDelivers = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutForwDatagrams && strcmp(name, "Ip6OutForwDatagrams") == 0) Ip6OutForwDatagrams = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutRequests && strcmp(name, "Ip6OutRequests") == 0) Ip6OutRequests = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutDiscards && strcmp(name, "Ip6OutDiscards") == 0) Ip6OutDiscards = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutNoRoutes && strcmp(name, "Ip6OutNoRoutes") == 0) Ip6OutNoRoutes = strtoull(value, NULL, 10); + else if(hash == hash_Ip6ReasmTimeout && strcmp(name, "Ip6ReasmTimeout") == 0) Ip6ReasmTimeout = strtoull(value, NULL, 10); + else if(hash == hash_Ip6ReasmReqds && strcmp(name, "Ip6ReasmReqds") == 0) Ip6ReasmReqds = strtoull(value, NULL, 10); + else if(hash == hash_Ip6ReasmOKs && strcmp(name, "Ip6ReasmOKs") == 0) Ip6ReasmOKs = strtoull(value, NULL, 10); + else if(hash == hash_Ip6ReasmFails && strcmp(name, "Ip6ReasmFails") == 0) Ip6ReasmFails = strtoull(value, NULL, 10); + else if(hash == hash_Ip6FragOKs && strcmp(name, "Ip6FragOKs") == 0) Ip6FragOKs = strtoull(value, NULL, 10); + else if(hash == hash_Ip6FragFails && strcmp(name, "Ip6FragFails") == 0) Ip6FragFails = strtoull(value, NULL, 10); + else if(hash == hash_Ip6FragCreates && strcmp(name, "Ip6FragCreates") == 0) Ip6FragCreates = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InMcastPkts && strcmp(name, "Ip6InMcastPkts") == 0) Ip6InMcastPkts = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutMcastPkts && strcmp(name, "Ip6OutMcastPkts") == 0) Ip6OutMcastPkts = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InOctets && strcmp(name, "Ip6InOctets") == 0) Ip6InOctets = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutOctets && strcmp(name, "Ip6OutOctets") == 0) Ip6OutOctets = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InMcastOctets && strcmp(name, "Ip6InMcastOctets") == 0) Ip6InMcastOctets = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutMcastOctets && strcmp(name, "Ip6OutMcastOctets") == 0) Ip6OutMcastOctets = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InBcastOctets && strcmp(name, "Ip6InBcastOctets") == 0) Ip6InBcastOctets = strtoull(value, NULL, 10); + else if(hash == hash_Ip6OutBcastOctets && strcmp(name, "Ip6OutBcastOctets") == 0) Ip6OutBcastOctets = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InNoECTPkts && strcmp(name, "Ip6InNoECTPkts") == 0) Ip6InNoECTPkts = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InECT1Pkts && strcmp(name, "Ip6InECT1Pkts") == 0) Ip6InECT1Pkts = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InECT0Pkts && strcmp(name, "Ip6InECT0Pkts") == 0) Ip6InECT0Pkts = strtoull(value, NULL, 10); + else if(hash == hash_Ip6InCEPkts && strcmp(name, "Ip6InCEPkts") == 0) Ip6InCEPkts = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InMsgs && strcmp(name, "Icmp6InMsgs") == 0) Icmp6InMsgs = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InErrors && strcmp(name, "Icmp6InErrors") == 0) Icmp6InErrors = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutMsgs && strcmp(name, "Icmp6OutMsgs") == 0) Icmp6OutMsgs = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutErrors && strcmp(name, "Icmp6OutErrors") == 0) Icmp6OutErrors = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InCsumErrors && strcmp(name, "Icmp6InCsumErrors") == 0) Icmp6InCsumErrors = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InDestUnreachs && strcmp(name, "Icmp6InDestUnreachs") == 0) Icmp6InDestUnreachs = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InPktTooBigs && strcmp(name, "Icmp6InPktTooBigs") == 0) Icmp6InPktTooBigs = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InTimeExcds && strcmp(name, "Icmp6InTimeExcds") == 0) Icmp6InTimeExcds = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InParmProblems && strcmp(name, "Icmp6InParmProblems") == 0) Icmp6InParmProblems = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InEchos && strcmp(name, "Icmp6InEchos") == 0) Icmp6InEchos = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InEchoReplies && strcmp(name, "Icmp6InEchoReplies") == 0) Icmp6InEchoReplies = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InGroupMembQueries && strcmp(name, "Icmp6InGroupMembQueries") == 0) Icmp6InGroupMembQueries = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InGroupMembResponses && strcmp(name, "Icmp6InGroupMembResponses") == 0) Icmp6InGroupMembResponses = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InGroupMembReductions && strcmp(name, "Icmp6InGroupMembReductions") == 0) Icmp6InGroupMembReductions = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InRouterSolicits && strcmp(name, "Icmp6InRouterSolicits") == 0) Icmp6InRouterSolicits = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InRouterAdvertisements && strcmp(name, "Icmp6InRouterAdvertisements") == 0) Icmp6InRouterAdvertisements = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InNeighborSolicits && strcmp(name, "Icmp6InNeighborSolicits") == 0) Icmp6InNeighborSolicits = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InNeighborAdvertisements && strcmp(name, "Icmp6InNeighborAdvertisements") == 0) Icmp6InNeighborAdvertisements = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InRedirects && strcmp(name, "Icmp6InRedirects") == 0) Icmp6InRedirects = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InMLDv2Reports && strcmp(name, "Icmp6InMLDv2Reports") == 0) Icmp6InMLDv2Reports = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutDestUnreachs && strcmp(name, "Icmp6OutDestUnreachs") == 0) Icmp6OutDestUnreachs = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutPktTooBigs && strcmp(name, "Icmp6OutPktTooBigs") == 0) Icmp6OutPktTooBigs = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutTimeExcds && strcmp(name, "Icmp6OutTimeExcds") == 0) Icmp6OutTimeExcds = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutParmProblems && strcmp(name, "Icmp6OutParmProblems") == 0) Icmp6OutParmProblems = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutEchos && strcmp(name, "Icmp6OutEchos") == 0) Icmp6OutEchos = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutEchoReplies && strcmp(name, "Icmp6OutEchoReplies") == 0) Icmp6OutEchoReplies = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutGroupMembQueries && strcmp(name, "Icmp6OutGroupMembQueries") == 0) Icmp6OutGroupMembQueries = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutGroupMembResponses && strcmp(name, "Icmp6OutGroupMembResponses") == 0) Icmp6OutGroupMembResponses = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutGroupMembReductions && strcmp(name, "Icmp6OutGroupMembReductions") == 0) Icmp6OutGroupMembReductions = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutRouterSolicits && strcmp(name, "Icmp6OutRouterSolicits") == 0) Icmp6OutRouterSolicits = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutRouterAdvertisements && strcmp(name, "Icmp6OutRouterAdvertisements") == 0) Icmp6OutRouterAdvertisements = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutNeighborSolicits && strcmp(name, "Icmp6OutNeighborSolicits") == 0) Icmp6OutNeighborSolicits = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutNeighborAdvertisements && strcmp(name, "Icmp6OutNeighborAdvertisements") == 0) Icmp6OutNeighborAdvertisements = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutRedirects && strcmp(name, "Icmp6OutRedirects") == 0) Icmp6OutRedirects = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutMLDv2Reports && strcmp(name, "Icmp6OutMLDv2Reports") == 0) Icmp6OutMLDv2Reports = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InType1 && strcmp(name, "Icmp6InType1") == 0) Icmp6InType1 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InType128 && strcmp(name, "Icmp6InType128") == 0) Icmp6InType128 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InType129 && strcmp(name, "Icmp6InType129") == 0) Icmp6InType129 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6InType136 && strcmp(name, "Icmp6InType136") == 0) Icmp6InType136 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutType1 && strcmp(name, "Icmp6OutType1") == 0) Icmp6OutType1 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutType128 && strcmp(name, "Icmp6OutType128") == 0) Icmp6OutType128 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutType129 && strcmp(name, "Icmp6OutType129") == 0) Icmp6OutType129 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutType133 && strcmp(name, "Icmp6OutType133") == 0) Icmp6OutType133 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutType135 && strcmp(name, "Icmp6OutType135") == 0) Icmp6OutType135 = strtoull(value, NULL, 10); + else if(hash == hash_Icmp6OutType143 && strcmp(name, "Icmp6OutType143") == 0) Icmp6OutType143 = strtoull(value, NULL, 10); + else if(hash == hash_Udp6InDatagrams && strcmp(name, "Udp6InDatagrams") == 0) Udp6InDatagrams = strtoull(value, NULL, 10); + else if(hash == hash_Udp6NoPorts && strcmp(name, "Udp6NoPorts") == 0) Udp6NoPorts = strtoull(value, NULL, 10); + else if(hash == hash_Udp6InErrors && strcmp(name, "Udp6InErrors") == 0) Udp6InErrors = strtoull(value, NULL, 10); + else if(hash == hash_Udp6OutDatagrams && strcmp(name, "Udp6OutDatagrams") == 0) Udp6OutDatagrams = strtoull(value, NULL, 10); + else if(hash == hash_Udp6RcvbufErrors && strcmp(name, "Udp6RcvbufErrors") == 0) Udp6RcvbufErrors = strtoull(value, NULL, 10); + else if(hash == hash_Udp6SndbufErrors && strcmp(name, "Udp6SndbufErrors") == 0) Udp6SndbufErrors = strtoull(value, NULL, 10); + else if(hash == hash_Udp6InCsumErrors && strcmp(name, "Udp6InCsumErrors") == 0) Udp6InCsumErrors = strtoull(value, NULL, 10); + else if(hash == hash_Udp6IgnoredMulti && strcmp(name, "Udp6IgnoredMulti") == 0) Udp6IgnoredMulti = strtoull(value, NULL, 10); + else if(hash == hash_UdpLite6InDatagrams && strcmp(name, "UdpLite6InDatagrams") == 0) UdpLite6InDatagrams = strtoull(value, NULL, 10); + else if(hash == hash_UdpLite6NoPorts && strcmp(name, "UdpLite6NoPorts") == 0) UdpLite6NoPorts = strtoull(value, NULL, 10); + else if(hash == hash_UdpLite6InErrors && strcmp(name, "UdpLite6InErrors") == 0) UdpLite6InErrors = strtoull(value, NULL, 10); + else if(hash == hash_UdpLite6OutDatagrams && strcmp(name, "UdpLite6OutDatagrams") == 0) UdpLite6OutDatagrams = strtoull(value, NULL, 10); + else if(hash == hash_UdpLite6RcvbufErrors && strcmp(name, "UdpLite6RcvbufErrors") == 0) UdpLite6RcvbufErrors = strtoull(value, NULL, 10); + else if(hash == hash_UdpLite6SndbufErrors && strcmp(name, "UdpLite6SndbufErrors") == 0) UdpLite6SndbufErrors = strtoull(value, NULL, 10); + else if(hash == hash_UdpLite6InCsumErrors && strcmp(name, "UdpLite6InCsumErrors") == 0) UdpLite6InCsumErrors = strtoull(value, NULL, 10); + } + + RRDSET *st; + + // -------------------------------------------------------------------- + + if(do_bandwidth == CONFIG_ONDEMAND_YES || (do_bandwidth == CONFIG_ONDEMAND_ONDEMAND && (Ip6InOctets || Ip6OutOctets))) { + do_bandwidth = CONFIG_ONDEMAND_YES; + st = rrdset_find("system.ipv6"); + if(!st) { + st = rrdset_create("system", "ipv6", NULL, "network", NULL, "IPv6 Bandwidth", "kilobits/s", 500, update_every, RRDSET_TYPE_AREA); + + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Ip6OutOctets); + rrddim_set(st, "received", Ip6InOctets); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ip_packets == CONFIG_ONDEMAND_YES || (do_ip_packets == CONFIG_ONDEMAND_ONDEMAND && (Ip6InReceives || Ip6OutRequests || Ip6InDelivers || Ip6OutForwDatagrams))) { + do_ip_packets = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".packets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "packets", NULL, "packets", NULL, "IPv6 Packets", "packets/s", 3000, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "forwarded", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "delivers", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Ip6OutRequests); + rrddim_set(st, "received", Ip6InReceives); + rrddim_set(st, "forwarded", Ip6InDelivers); + rrddim_set(st, "delivers", Ip6OutForwDatagrams); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ip_fragsout == CONFIG_ONDEMAND_YES || (do_ip_fragsout == CONFIG_ONDEMAND_ONDEMAND && (Ip6FragOKs || Ip6FragFails || Ip6FragCreates))) { + do_ip_fragsout = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".fragsout"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "fragsout", NULL, "fragments", NULL, "IPv6 Fragments Sent", "packets/s", 3010, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "ok", Ip6FragOKs); + rrddim_set(st, "failed", Ip6FragFails); + rrddim_set(st, "all", Ip6FragCreates); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ip_fragsin == CONFIG_ONDEMAND_YES || (do_ip_fragsin == CONFIG_ONDEMAND_ONDEMAND + && ( + Ip6ReasmOKs + || Ip6ReasmFails + || Ip6ReasmTimeout + || Ip6ReasmReqds + ))) { + do_ip_fragsin = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".fragsin"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "fragsin", NULL, "fragments", NULL, "IPv6 Fragments Reassembly", "packets/s", 3011, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "ok", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "failed", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "timeout", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "all", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "ok", Ip6ReasmOKs); + rrddim_set(st, "failed", Ip6ReasmFails); + rrddim_set(st, "timeout", Ip6ReasmTimeout); + rrddim_set(st, "all", Ip6ReasmReqds); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ip_errors == CONFIG_ONDEMAND_YES || (do_ip_errors == CONFIG_ONDEMAND_ONDEMAND + && ( + Ip6InDiscards + || Ip6OutDiscards + || Ip6InHdrErrors + || Ip6InAddrErrors + || Ip6InUnknownProtos + || Ip6InTooBigErrors + || Ip6InTruncatedPkts + || Ip6InNoRoutes + ))) { + do_ip_errors = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".errors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "errors", NULL, "errors", NULL, "IPv6 Errors", "packets/s", 3002, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "InDiscards", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutDiscards", NULL, -1, 1, RRDDIM_INCREMENTAL); + + rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InTooBigErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InTruncatedPkts", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InNoRoutes", NULL, 1, 1, RRDDIM_INCREMENTAL); + + rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InDiscards", Ip6InDiscards); + rrddim_set(st, "OutDiscards", Ip6OutDiscards); + + rrddim_set(st, "InHdrErrors", Ip6InHdrErrors); + rrddim_set(st, "InAddrErrors", Ip6InAddrErrors); + rrddim_set(st, "InUnknownProtos", Ip6InUnknownProtos); + rrddim_set(st, "InTooBigErrors", Ip6InTooBigErrors); + rrddim_set(st, "InTruncatedPkts", Ip6InTruncatedPkts); + rrddim_set(st, "InNoRoutes", Ip6InNoRoutes); + + rrddim_set(st, "OutNoRoutes", Ip6OutNoRoutes); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_udp_packets == CONFIG_ONDEMAND_YES || (do_udp_packets == CONFIG_ONDEMAND_ONDEMAND && (Udp6InDatagrams || Udp6OutDatagrams))) { + do_udp_packets = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udppackets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "udppackets", NULL, "udp", NULL, "IPv6 UDP Packets", "packets/s", 3601, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "received", Udp6InDatagrams); + rrddim_set(st, "sent", Udp6OutDatagrams); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_udp_errors == CONFIG_ONDEMAND_YES || (do_udp_errors == CONFIG_ONDEMAND_ONDEMAND + && ( + Udp6InErrors + || Udp6NoPorts + || Udp6RcvbufErrors + || Udp6SndbufErrors + || Udp6InCsumErrors + || Udp6IgnoredMulti + ))) { + do_udp_errors = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udperrors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "udperrors", NULL, "udp", NULL, "IPv6 UDP Errors", "events/s", 3701, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "NoPorts", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "IgnoredMulti", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InErrors", Udp6InErrors); + rrddim_set(st, "NoPorts", Udp6NoPorts); + rrddim_set(st, "RcvbufErrors", Udp6RcvbufErrors); + rrddim_set(st, "SndbufErrors", Udp6SndbufErrors); + rrddim_set(st, "InCsumErrors", Udp6InCsumErrors); + rrddim_set(st, "IgnoredMulti", Udp6IgnoredMulti); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_udplite_packets == CONFIG_ONDEMAND_YES || (do_udplite_packets == CONFIG_ONDEMAND_ONDEMAND && (UdpLite6InDatagrams || UdpLite6OutDatagrams))) { + do_udplite_packets = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udplitepackets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "udplitepackets", NULL, "udplite", NULL, "IPv6 UDPlite Packets", "packets/s", 3601, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "received", UdpLite6InDatagrams); + rrddim_set(st, "sent", UdpLite6OutDatagrams); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_udplite_errors == CONFIG_ONDEMAND_YES || (do_udplite_errors == CONFIG_ONDEMAND_ONDEMAND + && ( + UdpLite6InErrors + || UdpLite6NoPorts + || UdpLite6RcvbufErrors + || UdpLite6SndbufErrors + || Udp6InCsumErrors + || UdpLite6InCsumErrors + ))) { + do_udplite_errors = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".udpliteerrors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "udpliteerrors", NULL, "udplite", NULL, "IPv6 UDP Lite Errors", "events/s", 3701, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "NoPorts", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InErrors", UdpLite6InErrors); + rrddim_set(st, "NoPorts", UdpLite6NoPorts); + rrddim_set(st, "RcvbufErrors", UdpLite6RcvbufErrors); + rrddim_set(st, "SndbufErrors", UdpLite6SndbufErrors); + rrddim_set(st, "InCsumErrors", UdpLite6InCsumErrors); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_mcast == CONFIG_ONDEMAND_YES || (do_mcast == CONFIG_ONDEMAND_ONDEMAND && (Ip6OutMcastOctets || Ip6InMcastOctets))) { + do_mcast = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".mcast"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "mcast", NULL, "multicast", NULL, "IPv6 Multicast Bandwidth", "kilobits/s", 9000, update_every, RRDSET_TYPE_AREA); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Ip6OutMcastOctets); + rrddim_set(st, "received", Ip6InMcastOctets); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_bcast == CONFIG_ONDEMAND_YES || (do_bcast == CONFIG_ONDEMAND_ONDEMAND && (Ip6OutBcastOctets || Ip6InBcastOctets))) { + do_bcast = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".bcast"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "bcast", NULL, "broadcast", NULL, "IPv6 Broadcast Bandwidth", "kilobits/s", 8000, update_every, RRDSET_TYPE_AREA); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Ip6OutBcastOctets); + rrddim_set(st, "received", Ip6InBcastOctets); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_mcast_p == CONFIG_ONDEMAND_YES || (do_mcast_p == CONFIG_ONDEMAND_ONDEMAND && (Ip6OutMcastPkts || Ip6InMcastPkts))) { + do_mcast_p = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".mcastpkts"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "mcastpkts", NULL, "multicast", NULL, "IPv6 Multicast Packets", "packets/s", 9500, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Ip6OutMcastPkts); + rrddim_set(st, "received", Ip6InMcastPkts); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp == CONFIG_ONDEMAND_YES || (do_icmp == CONFIG_ONDEMAND_ONDEMAND && (Icmp6InMsgs || Icmp6OutMsgs))) { + do_icmp = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmp"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmp", NULL, "icmp", NULL, "IPv6 ICMP Messages", "messages/s", 10000, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Icmp6InMsgs); + rrddim_set(st, "received", Icmp6OutMsgs); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_redir == CONFIG_ONDEMAND_YES || (do_icmp_redir == CONFIG_ONDEMAND_ONDEMAND && (Icmp6InRedirects || Icmp6OutRedirects))) { + do_icmp_redir = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpredir"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpredir", NULL, "icmp", NULL, "IPv6 ICMP Redirects", "redirects/s", 10050, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Icmp6InRedirects); + rrddim_set(st, "received", Icmp6OutRedirects); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_errors == CONFIG_ONDEMAND_YES || (do_icmp_errors == CONFIG_ONDEMAND_ONDEMAND + && ( + Icmp6InErrors + || Icmp6OutErrors + || Icmp6InCsumErrors + || Icmp6InDestUnreachs + || Icmp6InPktTooBigs + || Icmp6InTimeExcds + || Icmp6InParmProblems + || Icmp6OutDestUnreachs + || Icmp6OutPktTooBigs + || Icmp6OutTimeExcds + || Icmp6OutParmProblems + ))) { + do_icmp_errors = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmperrors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmperrors", NULL, "icmp", NULL, "IPv6 ICMP Errors", "errors/s", 10100, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "InErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutErrors", NULL, -1, 1, RRDDIM_INCREMENTAL); + + rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InDestUnreachs", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InPktTooBigs", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InTimeExcds", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InParmProblems", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutDestUnreachs", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutPktTooBigs", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutTimeExcds", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutParmProblems", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InErrors", Icmp6InErrors); + rrddim_set(st, "OutErrors", Icmp6OutErrors); + rrddim_set(st, "InCsumErrors", Icmp6InCsumErrors); + rrddim_set(st, "InDestUnreachs", Icmp6InDestUnreachs); + rrddim_set(st, "InPktTooBigs", Icmp6InPktTooBigs); + rrddim_set(st, "InTimeExcds", Icmp6InTimeExcds); + rrddim_set(st, "InParmProblems", Icmp6InParmProblems); + rrddim_set(st, "OutDestUnreachs", Icmp6OutDestUnreachs); + rrddim_set(st, "OutPktTooBigs", Icmp6OutPktTooBigs); + rrddim_set(st, "OutTimeExcds", Icmp6OutTimeExcds); + rrddim_set(st, "OutParmProblems", Icmp6OutParmProblems); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_echos == CONFIG_ONDEMAND_YES || (do_icmp_echos == CONFIG_ONDEMAND_ONDEMAND + && ( + Icmp6InEchos + || Icmp6OutEchos + || Icmp6InEchoReplies + || Icmp6OutEchoReplies + ))) { + do_icmp_echos = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpechos"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpechos", NULL, "icmp", NULL, "IPv6 ICMP Echo", "messages/s", 10200, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "InEchos", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutEchos", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InEchoReplies", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutEchoReplies", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InEchos", Icmp6InEchos); + rrddim_set(st, "OutEchos", Icmp6OutEchos); + rrddim_set(st, "InEchoReplies", Icmp6InEchoReplies); + rrddim_set(st, "OutEchoReplies", Icmp6OutEchoReplies); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_groupmemb == CONFIG_ONDEMAND_YES || (do_icmp_groupmemb == CONFIG_ONDEMAND_ONDEMAND + && ( + Icmp6InGroupMembQueries + || Icmp6OutGroupMembQueries + || Icmp6InGroupMembResponses + || Icmp6OutGroupMembResponses + || Icmp6InGroupMembReductions + || Icmp6OutGroupMembReductions + ))) { + do_icmp_groupmemb = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".groupmemb"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "groupmemb", NULL, "icmp", NULL, "IPv6 ICMP Group Membership", "messages/s", 10300, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "InQueries", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutQueries", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InResponses", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutResponses", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InReductions", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutReductions", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InQueries", Icmp6InGroupMembQueries); + rrddim_set(st, "OutQueries", Icmp6OutGroupMembQueries); + rrddim_set(st, "InResponses", Icmp6InGroupMembResponses); + rrddim_set(st, "OutResponses", Icmp6OutGroupMembResponses); + rrddim_set(st, "InReductions", Icmp6InGroupMembReductions); + rrddim_set(st, "OutReductions", Icmp6OutGroupMembReductions); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_router == CONFIG_ONDEMAND_YES || (do_icmp_router == CONFIG_ONDEMAND_ONDEMAND + && ( + Icmp6InRouterSolicits + || Icmp6OutRouterSolicits + || Icmp6InRouterAdvertisements + || Icmp6OutRouterAdvertisements + ))) { + do_icmp_router = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmprouter"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmprouter", NULL, "icmp", NULL, "IPv6 Router Messages", "messages/s", 10400, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "InSolicits", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutSolicits", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InSolicits", Icmp6InRouterSolicits); + rrddim_set(st, "OutSolicits", Icmp6OutRouterSolicits); + rrddim_set(st, "InAdvertisements", Icmp6InRouterAdvertisements); + rrddim_set(st, "OutAdvertisements", Icmp6OutRouterAdvertisements); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_neighbor == CONFIG_ONDEMAND_YES || (do_icmp_neighbor == CONFIG_ONDEMAND_ONDEMAND + && ( + Icmp6InNeighborSolicits + || Icmp6OutNeighborSolicits + || Icmp6InNeighborAdvertisements + || Icmp6OutNeighborAdvertisements + ))) { + do_icmp_neighbor = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpneighbor"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpneighbor", NULL, "icmp", NULL, "IPv6 Neighbor Messages", "messages/s", 10500, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "InSolicits", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutSolicits", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InSolicits", Icmp6InNeighborSolicits); + rrddim_set(st, "OutSolicits", Icmp6OutNeighborSolicits); + rrddim_set(st, "InAdvertisements", Icmp6InNeighborAdvertisements); + rrddim_set(st, "OutAdvertisements", Icmp6OutNeighborAdvertisements); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_mldv2 == CONFIG_ONDEMAND_YES || (do_icmp_mldv2 == CONFIG_ONDEMAND_ONDEMAND && (Icmp6InMLDv2Reports || Icmp6OutMLDv2Reports))) { + do_icmp_mldv2 = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmpmldv2"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmpmldv2", NULL, "icmp", NULL, "IPv6 ICMP MLDv2 Reports", "reports/s", 10600, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "sent", Icmp6InMLDv2Reports); + rrddim_set(st, "received", Icmp6OutMLDv2Reports); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_icmp_types == CONFIG_ONDEMAND_YES || (do_icmp_types == CONFIG_ONDEMAND_ONDEMAND + && ( + Icmp6InType1 + || Icmp6InType128 + || Icmp6InType129 + || Icmp6InType136 + || Icmp6OutType1 + || Icmp6OutType128 + || Icmp6OutType129 + || Icmp6OutType133 + || Icmp6OutType135 + || Icmp6OutType143 + ))) { + do_icmp_types = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".icmptypes"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "icmptypes", NULL, "icmp", NULL, "IPv6 ICMP Types", "messages/s", 10700, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "InType1", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InType128", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InType129", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InType136", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutType1", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutType128", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutType129", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutType133", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutType135", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "OutType143", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InType1", Icmp6InType1); + rrddim_set(st, "InType128", Icmp6InType128); + rrddim_set(st, "InType129", Icmp6InType129); + rrddim_set(st, "InType136", Icmp6InType136); + rrddim_set(st, "OutType1", Icmp6OutType1); + rrddim_set(st, "OutType128", Icmp6OutType128); + rrddim_set(st, "OutType129", Icmp6OutType129); + rrddim_set(st, "OutType133", Icmp6OutType133); + rrddim_set(st, "OutType135", Icmp6OutType135); + rrddim_set(st, "OutType143", Icmp6OutType143); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_ect == CONFIG_ONDEMAND_YES || (do_ect == CONFIG_ONDEMAND_ONDEMAND + && ( + Ip6InNoECTPkts + || Ip6InECT1Pkts + || Ip6InECT0Pkts + || Ip6InCEPkts + ))) { + do_ect = CONFIG_ONDEMAND_YES; + st = rrdset_find(RRD_TYPE_NET_SNMP6 ".ect"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_SNMP6, "ect", NULL, "packets", NULL, "IPv6 ECT Packets", "packets/s", 10800, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "InNoECTPkts", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InECT1Pkts", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InECT0Pkts", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "InCEPkts", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "InNoECTPkts", Ip6InNoECTPkts); + rrddim_set(st, "InECT1Pkts", Ip6InECT1Pkts); + rrddim_set(st, "InECT0Pkts", Ip6InECT0Pkts); + rrddim_set(st, "InCEPkts", Ip6InCEPkts); + rrdset_done(st); + } + + return 0; } diff --git a/src/proc_net_stat_conntrack.c b/src/proc_net_stat_conntrack.c index 7d754a1d9..8234b20d2 100644 --- a/src/proc_net_stat_conntrack.c +++ b/src/proc_net_stat_conntrack.c @@ -1,215 +1,203 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -#define RRD_TYPE_NET_STAT_NETFILTER "netfilter" -#define RRD_TYPE_NET_STAT_CONNTRACK "conntrack" -#define RRD_TYPE_NET_STAT_CONNTRACK_LEN strlen(RRD_TYPE_NET_STAT_CONNTRACK) +#define RRD_TYPE_NET_STAT_NETFILTER "netfilter" +#define RRD_TYPE_NET_STAT_CONNTRACK "conntrack" +#define RRD_TYPE_NET_STAT_CONNTRACK_LEN strlen(RRD_TYPE_NET_STAT_CONNTRACK) int do_proc_net_stat_conntrack(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int do_sockets = -1, do_new = -1, do_changes = -1, do_expect = -1, do_search = -1, do_errors = -1; - - if(do_sockets == -1) do_sockets = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connections", 1); - if(do_new == -1) do_new = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter new connections", 1); - if(do_changes == -1) do_changes = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection changes", 1); - if(do_expect == -1) do_expect = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection expectations", 1); - if(do_search == -1) do_search = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection searches", 1); - if(do_errors == -1) do_errors = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter errors", 1); - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - unsigned long long aentries = 0, asearched = 0, afound = 0, anew = 0, ainvalid = 0, aignore = 0, adelete = 0, adelete_list = 0, - ainsert = 0, ainsert_failed = 0, adrop = 0, aearly_drop = 0, aicmp_error = 0, aexpect_new = 0, aexpect_create = 0, aexpect_delete = 0, asearch_restart = 0; - - for(l = 1; l < lines ;l++) { - words = procfile_linewords(ff, l); - if(words < 17) { - if(words) error("Cannot read /proc/net/stat/nf_conntrack line. Expected 17 params, read %d.", words); - continue; - } - - unsigned long long tentries = 0, tsearched = 0, tfound = 0, tnew = 0, tinvalid = 0, tignore = 0, tdelete = 0, tdelete_list = 0, tinsert = 0, tinsert_failed = 0, tdrop = 0, tearly_drop = 0, ticmp_error = 0, texpect_new = 0, texpect_create = 0, texpect_delete = 0, tsearch_restart = 0; - - tentries = strtoull(procfile_lineword(ff, l, 0), NULL, 16); - tsearched = strtoull(procfile_lineword(ff, l, 1), NULL, 16); - tfound = strtoull(procfile_lineword(ff, l, 2), NULL, 16); - tnew = strtoull(procfile_lineword(ff, l, 3), NULL, 16); - tinvalid = strtoull(procfile_lineword(ff, l, 4), NULL, 16); - tignore = strtoull(procfile_lineword(ff, l, 5), NULL, 16); - tdelete = strtoull(procfile_lineword(ff, l, 6), NULL, 16); - tdelete_list = strtoull(procfile_lineword(ff, l, 7), NULL, 16); - tinsert = strtoull(procfile_lineword(ff, l, 8), NULL, 16); - tinsert_failed = strtoull(procfile_lineword(ff, l, 9), NULL, 16); - tdrop = strtoull(procfile_lineword(ff, l, 10), NULL, 16); - tearly_drop = strtoull(procfile_lineword(ff, l, 11), NULL, 16); - ticmp_error = strtoull(procfile_lineword(ff, l, 12), NULL, 16); - texpect_new = strtoull(procfile_lineword(ff, l, 13), NULL, 16); - texpect_create = strtoull(procfile_lineword(ff, l, 14), NULL, 16); - texpect_delete = strtoull(procfile_lineword(ff, l, 15), NULL, 16); - tsearch_restart = strtoull(procfile_lineword(ff, l, 16), NULL, 16); - - if(!aentries) aentries = tentries; - - // sum all the cpus together - asearched += tsearched; // conntrack.search - afound += tfound; // conntrack.search - anew += tnew; // conntrack.new - ainvalid += tinvalid; // conntrack.new - aignore += tignore; // conntrack.new - adelete += tdelete; // conntrack.changes - adelete_list += tdelete_list; // conntrack.changes - ainsert += tinsert; // conntrack.changes - ainsert_failed += tinsert_failed; // conntrack.errors - adrop += tdrop; // conntrack.errors - aearly_drop += tearly_drop; // conntrack.errors - aicmp_error += ticmp_error; // conntrack.errors - aexpect_new += texpect_new; // conntrack.expect - aexpect_create += texpect_create; // conntrack.expect - aexpect_delete += texpect_delete; // conntrack.expect - asearch_restart += tsearch_restart; // conntrack.search - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - if(do_sockets) { - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_sockets"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_sockets", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Connections", "active connections", 1000, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "connections", NULL, 1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "connections", aentries); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_new) { - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_new"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_new", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker New Connections", "connections/s", 1001, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "new", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "ignore", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "invalid", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "new", anew); - rrddim_set(st, "ignore", aignore); - rrddim_set(st, "invalid", ainvalid); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_changes) { - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_changes"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_changes", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Changes", "changes/s", 1002, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "inserted", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "deleted", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "delete_list", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "inserted", ainsert); - rrddim_set(st, "deleted", adelete); - rrddim_set(st, "delete_list", adelete_list); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_expect) { - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_expect"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_expect", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Expectations", "expectations/s", 1003, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "created", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "deleted", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "new", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "created", aexpect_create); - rrddim_set(st, "deleted", aexpect_delete); - rrddim_set(st, "new", aexpect_new); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_search) { - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_search"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_search", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Searches", "searches/s", 1010, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "searched", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "restarted", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "found", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "searched", asearched); - rrddim_set(st, "restarted", asearch_restart); - rrddim_set(st, "found", afound); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_errors) { - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_errors"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_errors", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Errors", "events/s", 1005, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "icmp_error", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "insert_failed", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "drop", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "early_drop", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "icmp_error", aicmp_error); - rrddim_set(st, "insert_failed", ainsert_failed); - rrddim_set(st, "drop", adrop); - rrddim_set(st, "early_drop", aearly_drop); - rrdset_done(st); - } - - return 0; + static procfile *ff = NULL; + static int do_sockets = -1, do_new = -1, do_changes = -1, do_expect = -1, do_search = -1, do_errors = -1; + + if(do_sockets == -1) do_sockets = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connections", 1); + if(do_new == -1) do_new = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter new connections", 1); + if(do_changes == -1) do_changes = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection changes", 1); + if(do_expect == -1) do_expect = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection expectations", 1); + if(do_search == -1) do_search = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection searches", 1); + if(do_errors == -1) do_errors = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter errors", 1); + + if(dt) {}; + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + unsigned long long aentries = 0, asearched = 0, afound = 0, anew = 0, ainvalid = 0, aignore = 0, adelete = 0, adelete_list = 0, + ainsert = 0, ainsert_failed = 0, adrop = 0, aearly_drop = 0, aicmp_error = 0, aexpect_new = 0, aexpect_create = 0, aexpect_delete = 0, asearch_restart = 0; + + for(l = 1; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(words < 17) { + if(words) error("Cannot read /proc/net/stat/nf_conntrack line. Expected 17 params, read %u.", words); + continue; + } + + unsigned long long tentries = 0, tsearched = 0, tfound = 0, tnew = 0, tinvalid = 0, tignore = 0, tdelete = 0, tdelete_list = 0, tinsert = 0, tinsert_failed = 0, tdrop = 0, tearly_drop = 0, ticmp_error = 0, texpect_new = 0, texpect_create = 0, texpect_delete = 0, tsearch_restart = 0; + + tentries = strtoull(procfile_lineword(ff, l, 0), NULL, 16); + tsearched = strtoull(procfile_lineword(ff, l, 1), NULL, 16); + tfound = strtoull(procfile_lineword(ff, l, 2), NULL, 16); + tnew = strtoull(procfile_lineword(ff, l, 3), NULL, 16); + tinvalid = strtoull(procfile_lineword(ff, l, 4), NULL, 16); + tignore = strtoull(procfile_lineword(ff, l, 5), NULL, 16); + tdelete = strtoull(procfile_lineword(ff, l, 6), NULL, 16); + tdelete_list = strtoull(procfile_lineword(ff, l, 7), NULL, 16); + tinsert = strtoull(procfile_lineword(ff, l, 8), NULL, 16); + tinsert_failed = strtoull(procfile_lineword(ff, l, 9), NULL, 16); + tdrop = strtoull(procfile_lineword(ff, l, 10), NULL, 16); + tearly_drop = strtoull(procfile_lineword(ff, l, 11), NULL, 16); + ticmp_error = strtoull(procfile_lineword(ff, l, 12), NULL, 16); + texpect_new = strtoull(procfile_lineword(ff, l, 13), NULL, 16); + texpect_create = strtoull(procfile_lineword(ff, l, 14), NULL, 16); + texpect_delete = strtoull(procfile_lineword(ff, l, 15), NULL, 16); + tsearch_restart = strtoull(procfile_lineword(ff, l, 16), NULL, 16); + + if(!aentries) aentries = tentries; + + // sum all the cpus together + asearched += tsearched; // conntrack.search + afound += tfound; // conntrack.search + anew += tnew; // conntrack.new + ainvalid += tinvalid; // conntrack.new + aignore += tignore; // conntrack.new + adelete += tdelete; // conntrack.changes + adelete_list += tdelete_list; // conntrack.changes + ainsert += tinsert; // conntrack.changes + ainsert_failed += tinsert_failed; // conntrack.errors + adrop += tdrop; // conntrack.errors + aearly_drop += tearly_drop; // conntrack.errors + aicmp_error += ticmp_error; // conntrack.errors + aexpect_new += texpect_new; // conntrack.expect + aexpect_create += texpect_create; // conntrack.expect + aexpect_delete += texpect_delete; // conntrack.expect + asearch_restart += tsearch_restart; // conntrack.search + } + + RRDSET *st; + + // -------------------------------------------------------------------- + + if(do_sockets) { + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_sockets"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_sockets", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Connections", "active connections", 1000, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "connections", NULL, 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "connections", aentries); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_new) { + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_new"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_new", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker New Connections", "connections/s", 1001, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "new", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "ignore", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "invalid", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "new", anew); + rrddim_set(st, "ignore", aignore); + rrddim_set(st, "invalid", ainvalid); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_changes) { + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_changes"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_changes", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Changes", "changes/s", 1002, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "inserted", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "deleted", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "delete_list", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "inserted", ainsert); + rrddim_set(st, "deleted", adelete); + rrddim_set(st, "delete_list", adelete_list); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_expect) { + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_expect"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_expect", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Expectations", "expectations/s", 1003, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "created", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "deleted", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "new", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "created", aexpect_create); + rrddim_set(st, "deleted", aexpect_delete); + rrddim_set(st, "new", aexpect_new); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_search) { + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_search"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_search", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Searches", "searches/s", 1010, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "searched", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "restarted", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "found", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "searched", asearched); + rrddim_set(st, "restarted", asearch_restart); + rrddim_set(st, "found", afound); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(do_errors) { + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_CONNTRACK "_errors"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_CONNTRACK "_errors", NULL, RRD_TYPE_NET_STAT_CONNTRACK, NULL, "Connection Tracker Errors", "events/s", 1005, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "icmp_error", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "insert_failed", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "drop", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "early_drop", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "icmp_error", aicmp_error); + rrddim_set(st, "insert_failed", ainsert_failed); + rrddim_set(st, "drop", adrop); + rrddim_set(st, "early_drop", aearly_drop); + rrdset_done(st); + } + + return 0; } diff --git a/src/proc_net_stat_synproxy.c b/src/proc_net_stat_synproxy.c index 508b7d3b4..758c35dee 100644 --- a/src/proc_net_stat_synproxy.c +++ b/src/proc_net_stat_synproxy.c @@ -1,138 +1,127 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> - #include "common.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -#include "log.h" -#define RRD_TYPE_NET_STAT_NETFILTER "netfilter" -#define RRD_TYPE_NET_STAT_SYNPROXY "synproxy" -#define RRD_TYPE_NET_STAT_SYNPROXY_LEN strlen(RRD_TYPE_NET_STAT_SYNPROXY) +#define RRD_TYPE_NET_STAT_NETFILTER "netfilter" +#define RRD_TYPE_NET_STAT_SYNPROXY "synproxy" +#define RRD_TYPE_NET_STAT_SYNPROXY_LEN strlen(RRD_TYPE_NET_STAT_SYNPROXY) int do_proc_net_stat_synproxy(int update_every, unsigned long long dt) { - static int do_entries = -1, do_cookies = -1, do_syns = -1, do_reopened = -1; - static procfile *ff = NULL; + static int do_entries = -1, do_cookies = -1, do_syns = -1, do_reopened = -1; + static procfile *ff = NULL; - if(do_entries == -1) do_entries = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY entries", CONFIG_ONDEMAND_ONDEMAND); - if(do_cookies == -1) do_cookies = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY cookies", CONFIG_ONDEMAND_ONDEMAND); - if(do_syns == -1) do_syns = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY SYN received", CONFIG_ONDEMAND_ONDEMAND); - if(do_reopened == -1) do_reopened = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY connections reopened", CONFIG_ONDEMAND_ONDEMAND); + if(do_entries == -1) do_entries = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY entries", CONFIG_ONDEMAND_ONDEMAND); + if(do_cookies == -1) do_cookies = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY cookies", CONFIG_ONDEMAND_ONDEMAND); + if(do_syns == -1) do_syns = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY SYN received", CONFIG_ONDEMAND_ONDEMAND); + if(do_reopened == -1) do_reopened = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY connections reopened", CONFIG_ONDEMAND_ONDEMAND); - if(dt) {}; + if(dt) {}; - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time - // make sure we have 3 lines - unsigned long lines = procfile_lines(ff), l; - if(lines < 2) { - error("/proc/net/stat/synproxy has %d lines, expected no less than 2. Disabling it.", lines); - return 1; - } + // make sure we have 3 lines + size_t lines = procfile_lines(ff), l; + if(lines < 2) { + error("/proc/net/stat/synproxy has %zu lines, expected no less than 2. Disabling it.", lines); + return 1; + } - unsigned long long entries = 0, syn_received = 0, cookie_invalid = 0, cookie_valid = 0, cookie_retrans = 0, conn_reopened = 0; + unsigned long long entries = 0, syn_received = 0, cookie_invalid = 0, cookie_valid = 0, cookie_retrans = 0, conn_reopened = 0; - // synproxy gives its values per CPU - for(l = 1; l < lines ;l++) { - int words = procfile_linewords(ff, l); - if(words < 6) continue; + // synproxy gives its values per CPU + for(l = 1; l < lines ;l++) { + int words = procfile_linewords(ff, l); + if(words < 6) continue; - entries += strtoull(procfile_lineword(ff, l, 0), NULL, 16); - syn_received += strtoull(procfile_lineword(ff, l, 1), NULL, 16); - cookie_invalid += strtoull(procfile_lineword(ff, l, 2), NULL, 16); - cookie_valid += strtoull(procfile_lineword(ff, l, 3), NULL, 16); - cookie_retrans += strtoull(procfile_lineword(ff, l, 4), NULL, 16); - conn_reopened += strtoull(procfile_lineword(ff, l, 5), NULL, 16); - } + entries += strtoull(procfile_lineword(ff, l, 0), NULL, 16); + syn_received += strtoull(procfile_lineword(ff, l, 1), NULL, 16); + cookie_invalid += strtoull(procfile_lineword(ff, l, 2), NULL, 16); + cookie_valid += strtoull(procfile_lineword(ff, l, 3), NULL, 16); + cookie_retrans += strtoull(procfile_lineword(ff, l, 4), NULL, 16); + conn_reopened += strtoull(procfile_lineword(ff, l, 5), NULL, 16); + } - unsigned long long events = entries + syn_received + cookie_invalid + cookie_valid + cookie_retrans + conn_reopened; + unsigned long long events = entries + syn_received + cookie_invalid + cookie_valid + cookie_retrans + conn_reopened; - RRDSET *st; + RRDSET *st; - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if((do_entries == CONFIG_ONDEMAND_ONDEMAND && events) || do_entries == CONFIG_ONDEMAND_YES) { - do_entries = CONFIG_ONDEMAND_YES; + if((do_entries == CONFIG_ONDEMAND_ONDEMAND && events) || do_entries == CONFIG_ONDEMAND_YES) { + do_entries = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_entries"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_entries", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY Entries Used", "entries", 1004, update_every, RRDSET_TYPE_LINE); + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_entries"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_entries", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY Entries Used", "entries", 1004, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "entries", NULL, 1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); + rrddim_add(st, "entries", NULL, 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); - rrddim_set(st, "entries", entries); - rrdset_done(st); - } + rrddim_set(st, "entries", entries); + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if((do_syns == CONFIG_ONDEMAND_ONDEMAND && events) || do_syns == CONFIG_ONDEMAND_YES) { - do_syns = CONFIG_ONDEMAND_YES; + if((do_syns == CONFIG_ONDEMAND_ONDEMAND && events) || do_syns == CONFIG_ONDEMAND_YES) { + do_syns = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_syn_received"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_syn_received", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY SYN Packets received", "SYN/s", 1001, update_every, RRDSET_TYPE_LINE); + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_syn_received"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_syn_received", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY SYN Packets received", "SYN/s", 1001, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + rrddim_add(st, "received", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - rrddim_set(st, "received", syn_received); - rrdset_done(st); - } + rrddim_set(st, "received", syn_received); + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if((do_reopened == CONFIG_ONDEMAND_ONDEMAND && events) || do_reopened == CONFIG_ONDEMAND_YES) { - do_reopened = CONFIG_ONDEMAND_YES; + if((do_reopened == CONFIG_ONDEMAND_ONDEMAND && events) || do_reopened == CONFIG_ONDEMAND_YES) { + do_reopened = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_conn_reopened"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_conn_reopened", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY Connections Reopened", "connections/s", 1003, update_every, RRDSET_TYPE_LINE); + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_conn_reopened"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_conn_reopened", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY Connections Reopened", "connections/s", 1003, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "reopened", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + rrddim_add(st, "reopened", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - rrddim_set(st, "reopened", conn_reopened); - rrdset_done(st); - } + rrddim_set(st, "reopened", conn_reopened); + rrdset_done(st); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if((do_cookies == CONFIG_ONDEMAND_ONDEMAND && events) || do_cookies == CONFIG_ONDEMAND_YES) { - do_cookies = CONFIG_ONDEMAND_YES; + if((do_cookies == CONFIG_ONDEMAND_ONDEMAND && events) || do_cookies == CONFIG_ONDEMAND_YES) { + do_cookies = CONFIG_ONDEMAND_YES; - st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_cookies"); - if(!st) { - st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_cookies", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY TCP Cookies", "cookies/s", 1002, update_every, RRDSET_TYPE_LINE); + st = rrdset_find(RRD_TYPE_NET_STAT_NETFILTER "." RRD_TYPE_NET_STAT_SYNPROXY "_cookies"); + if(!st) { + st = rrdset_create(RRD_TYPE_NET_STAT_NETFILTER, RRD_TYPE_NET_STAT_SYNPROXY "_cookies", NULL, RRD_TYPE_NET_STAT_SYNPROXY, NULL, "SYNPROXY TCP Cookies", "cookies/s", 1002, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "valid", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "invalid", NULL, -1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st, "retransmits", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); + rrddim_add(st, "valid", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "invalid", NULL, -1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st, "retransmits", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); - rrddim_set(st, "valid", cookie_valid); - rrddim_set(st, "invalid", cookie_invalid); - rrddim_set(st, "retransmits", cookie_retrans); - rrdset_done(st); - } + rrddim_set(st, "valid", cookie_valid); + rrddim_set(st, "invalid", cookie_invalid); + rrddim_set(st, "retransmits", cookie_retrans); + rrdset_done(st); + } - return 0; + return 0; } diff --git a/src/proc_self_mountinfo.c b/src/proc_self_mountinfo.c index 45630b4c0..51aea7aee 100644 --- a/src/proc_self_mountinfo.c +++ b/src/proc_self_mountinfo.c @@ -1,231 +1,242 @@ -#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; + struct mountinfo *mi; - for(mi = root; mi ; mi = mi->next) - if(mi->major == major && mi->minor == minor) - return mi; + for(mi = root; mi ; mi = mi->next) + if(mi->major == major && mi->minor == minor) + return mi; - return NULL; + 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 *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); + struct mountinfo *mi; + uint32_t filesystem_hash = simple_hash(filesystem); - size_t solen = strlen(super_options); + 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)) { + 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++; + // super_options is a comma separated list + char *s = mi->super_options, *e; + while(*s) { + e = s + 1; + while(*e && *e != ',') e++; - size_t len = e - s; - if(len == solen && !strncmp(s, super_options, len)) - return mi; + size_t len = e - s; + if(len == solen && !strncmp(s, super_options, len)) + return mi; - if(*e == ',') s = ++e; - else s = e; - } - } + if(*e == ',') s = ++e; + else s = e; + } + } - return NULL; + return NULL; } // free a linked list of mountinfo structures void mountinfo_free(struct mountinfo *mi) { - if(unlikely(!mi)) - return; + if(unlikely(!mi)) + return; - if(likely(mi->next)) - mountinfo_free(mi->next); + 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); + freez(mi->root); + freez(mi->mount_point); + freez(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); + 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); + freez(mi->filesystem); + freez(mi->mount_source); + freez(mi->super_options); + freez(mi); +} + +static char *strdupz_decoding_octal(const char *string) { + char *buffer = strdupz(string); + + char *d = buffer; + const char *s = string; + + while(*s) { + if(unlikely(*s == '\\')) { + s++; + if(likely(isdigit(*s) && isdigit(s[1]) && isdigit(s[2]))) { + char c = *s++ - '0'; + c <<= 3; + c |= *s++ - '0'; + c <<= 3; + c |= *s++ - '0'; + *d++ = c; + } + else *d++ = '_'; + } + else *d++ = *s++; + } + *d = '\0'; + + return buffer; } // read the whole mountinfo into a linked list struct mountinfo *mountinfo_read() { - procfile *ff = NULL; + 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; - 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; - } + struct mountinfo *root = NULL, *last = NULL, *mi = NULL; - ff = procfile_readall(ff); - if(!ff) return NULL; + unsigned long l, lines = procfile_lines(ff); + for(l = 0; l < lines ;l++) { + if(procfile_linewords(ff, l) < 5) + continue; - struct mountinfo *root = NULL, *last = NULL, *mi = NULL; + mi = mallocz(sizeof(struct mountinfo)); - unsigned long l, lines = procfile_lines(ff); - for(l = 0; l < lines ;l++) { - if(procfile_linewords(ff, l) < 5) - continue; + if(unlikely(!root)) + root = last = mi; + else + last->next = mi; - mi = malloc(sizeof(struct mountinfo)); - if(unlikely(!mi)) fatal("Cannot allocate memory for mountinfo"); + last = mi; + mi->next = NULL; - if(unlikely(!root)) - root = last = mi; - else - last->next = mi; + 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++; - last = mi; - mi->next = NULL; + char *major = procfile_lineword(ff, l, w), *minor; w++; + for(minor = major; *minor && *minor != ':' ;minor++) ; - 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++; + if(!*minor) { + error("Cannot parse major:minor on '%s' at line %lu of '%s'", major, l + 1, filename); + continue; + } - char *major = procfile_lineword(ff, l, w), *minor; w++; - for(minor = major; *minor && *minor != ':' ;minor++) ; - *minor = '\0'; - minor++; + *minor = '\0'; + minor++; - mi->major = strtoul(major, NULL, 10); - mi->minor = strtoul(minor, NULL, 10); + 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->root = strdupz(procfile_lineword(ff, l, w)); w++; + 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_point = strdupz_decoding_octal(procfile_lineword(ff, l, w)); w++; + 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"); + mi->mount_options = strdupz(procfile_lineword(ff, l, w)); w++; - // count the optional fields + // count the optional fields /* - unsigned long wo = w; + 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++; - } + 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(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++; + 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->filesystem = strdupz(procfile_lineword(ff, l, w)); w++; + 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->mount_source = strdupz(procfile_lineword(ff, l, w)); w++; + 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; - } + mi->super_options = strdupz(procfile_lineword(ff, l, w)); w++; + } + 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 - ); + 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; + procfile_close(ff); + return root; } diff --git a/src/proc_self_mountinfo.h b/src/proc_self_mountinfo.h index 51712a58a..c2d9688c1 100644 --- a/src/proc_self_mountinfo.h +++ b/src/proc_self_mountinfo.h @@ -1,35 +1,33 @@ -#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; + 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 *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_point; // mount point: mount point relative to the process's root. + uint32_t mount_point_hash; - char *mount_options; // mount options: per-mount options. + char *mount_options; // mount options: per-mount options. - int optional_fields_count; + int optional_fields_count; /* - char ***optional_fields; // optional fields: zero or more fields of the form "tag[:value]". + 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 *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 *mount_source; // mount source: filesystem-specific information or "none". + uint32_t mount_source_hash; - char *super_options; // super options: per-superblock options. + char *super_options; // super options: per-superblock options. - struct mountinfo *next; + struct mountinfo *next; }; extern struct mountinfo *mountinfo_find(struct mountinfo *root, unsigned long major, unsigned long minor); diff --git a/src/proc_softirqs.c b/src/proc_softirqs.c index 96b5d3d30..a5165040a 100644 --- a/src/proc_softirqs.c +++ b/src/proc_softirqs.c @@ -1,26 +1,13 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <ctype.h> - #include "common.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -#include "log.h" #define MAX_INTERRUPT_NAME 50 struct interrupt { - int used; - char *id; - char name[MAX_INTERRUPT_NAME + 1]; - unsigned long long total; - unsigned long long value[]; + int used; + char *id; + char name[MAX_INTERRUPT_NAME + 1]; + unsigned long long total; + unsigned long long value[]; }; // since each interrupt is variable in size @@ -31,160 +18,157 @@ struct interrupt { #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 allocated = 0; - - if(lines < allocated) return irrs; - else { - irrs = (struct interrupt *)realloc(irrs, lines * recordsize(cpus)); - if(!irrs) - fatal("Cannot allocate memory for %d interrupts", lines); + static struct interrupt *irrs = NULL; + static int allocated = 0; - allocated = lines; - } + if(lines < allocated) return irrs; + else { + irrs = (struct interrupt *)reallocz(irrs, lines * recordsize(cpus)); + allocated = lines; + } - return irrs; + return irrs; } int do_proc_softirqs(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int cpus = -1, do_per_core = -1; + static procfile *ff = NULL; + static int cpus = -1, do_per_core = -1; - struct interrupt *irrs = NULL; - - if(dt) {}; + struct interrupt *irrs = NULL; + + if(dt) {}; - if(do_per_core == -1) do_per_core = config_get_boolean("plugin:proc:/proc/softirqs", "interrupts per core", 1); - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words = procfile_linewords(ff, 0), w; - - if(!lines) { - error("Cannot read /proc/softirqs, zero lines reported."); - return 1; - } - - // find how many CPUs are there - if(cpus == -1) { - cpus = 0; - for(w = 0; w < words ; w++) { - if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0) - cpus++; - } - } - - if(!cpus) { - error("PLUGIN: PROC_SOFTIRQS: Cannot find the number of CPUs in /proc/softirqs"); - return 1; - } - - // allocate the size we need; - irrs = get_interrupts_array(lines, cpus); - irrs[0].used = 0; - - // loop through all lines - for(l = 1; l < lines ;l++) { - struct interrupt *irr = irrindex(irrs, l, cpus); - irr->used = 0; - irr->total = 0; - - words = procfile_linewords(ff, l); - if(!words) continue; - - irr->id = procfile_lineword(ff, l, 0); - if(!irr->id || !irr->id[0]) continue; - - int idlen = strlen(irr->id); - if(irr->id[idlen - 1] == ':') - irr->id[idlen - 1] = '\0'; - - int c; - for(c = 0; c < cpus ;c++) { - if((c + 1) < (int)words) - irr->value[c] = strtoull(procfile_lineword(ff, l, (uint32_t)(c + 1)), NULL, 10); - else - irr->value[c] = 0; - - irr->total += irr->value[c]; - } - - strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME); - - irr->used = 1; - } - - RRDSET *st; - - // -------------------------------------------------------------------- - - st = rrdset_find_bytype("system", "softirqs"); - if(!st) { - st = rrdset_create("system", "softirqs", NULL, "softirqs", NULL, "System softirqs", "softirqs/s", 950, update_every, RRDSET_TYPE_STACKED); - - for(l = 0; l < lines ;l++) { - 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++) { - struct interrupt *irr = irrindex(irrs, l, cpus); - if(!irr->used) continue; - rrddim_set(st, irr->id, irr->total); - } - rrdset_done(st); - - if(do_per_core) { - int c; - - for(c = 0; c < cpus ; 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++) { - 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+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++) { - 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++) { - struct interrupt *irr = irrindex(irrs, l, cpus); - if(!irr->used) continue; - rrddim_set(st, irr->id, irr->value[c]); - } - rrdset_done(st); - } - } - - return 0; + if(do_per_core == -1) do_per_core = config_get_boolean("plugin:proc:/proc/softirqs", "interrupts per core", 1); + + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; + + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words = procfile_linewords(ff, 0), w; + + if(!lines) { + error("Cannot read /proc/softirqs, zero lines reported."); + return 1; + } + + // find how many CPUs are there + if(cpus == -1) { + cpus = 0; + for(w = 0; w < words ; w++) { + if(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0) + cpus++; + } + } + + if(!cpus) { + error("PLUGIN: PROC_SOFTIRQS: Cannot find the number of CPUs in /proc/softirqs"); + return 1; + } + + // allocate the size we need; + irrs = get_interrupts_array(lines, cpus); + irrs[0].used = 0; + + // loop through all lines + for(l = 1; l < lines ;l++) { + struct interrupt *irr = irrindex(irrs, l, cpus); + irr->used = 0; + irr->total = 0; + + words = procfile_linewords(ff, l); + if(!words) continue; + + irr->id = procfile_lineword(ff, l, 0); + if(!irr->id || !irr->id[0]) continue; + + int idlen = strlen(irr->id); + if(irr->id[idlen - 1] == ':') + irr->id[idlen - 1] = '\0'; + + int c; + for(c = 0; c < cpus ;c++) { + if((c + 1) < (int)words) + irr->value[c] = strtoull(procfile_lineword(ff, l, (uint32_t)(c + 1)), NULL, 10); + else + irr->value[c] = 0; + + irr->total += irr->value[c]; + } + + strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME); + + irr->used = 1; + } + + RRDSET *st; + + // -------------------------------------------------------------------- + + st = rrdset_find_bytype("system", "softirqs"); + if(!st) { + st = rrdset_create("system", "softirqs", NULL, "softirqs", NULL, "System softirqs", "softirqs/s", 950, update_every, RRDSET_TYPE_STACKED); + + for(l = 0; l < lines ;l++) { + 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++) { + struct interrupt *irr = irrindex(irrs, l, cpus); + if(!irr->used) continue; + rrddim_set(st, irr->id, irr->total); + } + rrdset_done(st); + + if(do_per_core) { + int c; + + for(c = 0; c < cpus ; 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++) { + 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+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++) { + 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++) { + struct interrupt *irr = irrindex(irrs, l, cpus); + if(!irr->used) continue; + rrddim_set(st, irr->id, irr->value[c]); + } + rrdset_done(st); + } + } + + return 0; } diff --git a/src/proc_stat.c b/src/proc_stat.c index 154ba167d..88cb820b3 100644 --- a/src/proc_stat.c +++ b/src/proc_stat.c @@ -1,204 +1,211 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" - -#define RRD_TYPE_STAT "cpu" -#define RRD_TYPE_STAT_LEN strlen(RRD_TYPE_STAT) int do_proc_stat(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int do_cpu = -1, do_cpu_cores = -1, do_interrupts = -1, do_context = -1, do_forks = -1, do_processes = -1; - - if(do_cpu == -1) do_cpu = config_get_boolean("plugin:proc:/proc/stat", "cpu utilization", 1); - if(do_cpu_cores == -1) do_cpu_cores = config_get_boolean("plugin:proc:/proc/stat", "per cpu core utilization", 1); - if(do_interrupts == -1) do_interrupts = config_get_boolean("plugin:proc:/proc/stat", "cpu interrupts", 1); - if(do_context == -1) do_context = config_get_boolean("plugin:proc:/proc/stat", "context switches", 1); - if(do_forks == -1) do_forks = config_get_boolean("plugin:proc:/proc/stat", "processes started", 1); - if(do_processes == -1) do_processes = config_get_boolean("plugin:proc:/proc/stat", "processes running", 1); - - if(dt) {}; - - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; - - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time - - uint32_t lines = procfile_lines(ff), l; - uint32_t words; - - unsigned long long processes = 0, running = 0 , blocked = 0; - RRDSET *st; - - for(l = 0; l < lines ;l++) { - if(strncmp(procfile_lineword(ff, l, 0), "cpu", 3) == 0) { - words = procfile_linewords(ff, l); - if(words < 9) { - error("Cannot read /proc/stat cpu line. Expected 9 params, read %d.", words); - continue; - } - - char *id; - unsigned long long user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0, steal = 0, guest = 0, guest_nice = 0; - - id = procfile_lineword(ff, l, 0); - user = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - nice = strtoull(procfile_lineword(ff, l, 2), NULL, 10); - system = strtoull(procfile_lineword(ff, l, 3), NULL, 10); - idle = strtoull(procfile_lineword(ff, l, 4), NULL, 10); - iowait = strtoull(procfile_lineword(ff, l, 5), NULL, 10); - irq = strtoull(procfile_lineword(ff, l, 6), NULL, 10); - softirq = strtoull(procfile_lineword(ff, l, 7), NULL, 10); - steal = strtoull(procfile_lineword(ff, l, 8), NULL, 10); - if(words >= 10) guest = strtoull(procfile_lineword(ff, l, 9), NULL, 10); - if(words >= 11) guest_nice = strtoull(procfile_lineword(ff, l, 10), NULL, 10); - - char *title = "Core utilization"; - char *type = RRD_TYPE_STAT; - char *context = "cpu.cpu"; - char *family = "utilization"; - long priority = 1000; - int isthistotal = 0; - - if(strcmp(id, "cpu") == 0) { - isthistotal = 1; - type = "system"; - title = "Total CPU utilization"; - context = "system.cpu"; - family = id; - priority = 100; - } - - if((isthistotal && do_cpu) || (!isthistotal && do_cpu_cores)) { - st = rrdset_find_bytype(type, id); - if(!st) { - st = rrdset_create(type, id, NULL, family, context, title, "percentage", priority, update_every, RRDSET_TYPE_STACKED); - - long multiplier = 1; - long divisor = 1; // sysconf(_SC_CLK_TCK); - - rrddim_add(st, "guest_nice", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "guest", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "steal", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "softirq", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "irq", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "user", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "system", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "nice", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_add(st, "iowait", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - - rrddim_add(st, "idle", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); - rrddim_hide(st, "idle"); - } - else rrdset_next(st); - - rrddim_set(st, "user", user); - rrddim_set(st, "nice", nice); - rrddim_set(st, "system", system); - rrddim_set(st, "idle", idle); - rrddim_set(st, "iowait", iowait); - rrddim_set(st, "irq", irq); - rrddim_set(st, "softirq", softirq); - rrddim_set(st, "steal", steal); - rrddim_set(st, "guest", guest); - rrddim_set(st, "guest_nice", guest_nice); - rrdset_done(st); - } - } - else if(strcmp(procfile_lineword(ff, l, 0), "intr") == 0) { - unsigned long long value = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - - // -------------------------------------------------------------------- - - if(do_interrupts) { - st = rrdset_find_bytype("system", "intr"); - if(!st) { - st = rrdset_create("system", "intr", NULL, "interrupts", NULL, "CPU Interrupts", "interrupts/s", 900, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "interrupts", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "interrupts", value); - rrdset_done(st); - } - } - else if(strcmp(procfile_lineword(ff, l, 0), "ctxt") == 0) { - unsigned long long value = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - - // -------------------------------------------------------------------- - - if(do_context) { - st = rrdset_find_bytype("system", "ctxt"); - if(!st) { - st = rrdset_create("system", "ctxt", NULL, "processes", NULL, "CPU Context Switches", "context switches/s", 800, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "switches", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "switches", value); - rrdset_done(st); - } - } - else if(!processes && strcmp(procfile_lineword(ff, l, 0), "processes") == 0) { - processes = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - } - else if(!running && strcmp(procfile_lineword(ff, l, 0), "procs_running") == 0) { - running = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - } - else if(!blocked && strcmp(procfile_lineword(ff, l, 0), "procs_blocked") == 0) { - blocked = strtoull(procfile_lineword(ff, l, 1), NULL, 10); - } - } - - // -------------------------------------------------------------------- - - if(do_forks) { - st = rrdset_find_bytype("system", "forks"); - if(!st) { - st = rrdset_create("system", "forks", NULL, "processes", NULL, "Started Processes", "processes/s", 700, update_every, RRDSET_TYPE_LINE); - st->isdetail = 1; - - rrddim_add(st, "started", NULL, 1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st); - - rrddim_set(st, "started", processes); - rrdset_done(st); - } - - // -------------------------------------------------------------------- - - if(do_processes) { - st = rrdset_find_bytype("system", "processes"); - if(!st) { - st = rrdset_create("system", "processes", NULL, "processes", NULL, "System Processes", "processes", 600, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "running", NULL, 1, 1, RRDDIM_ABSOLUTE); - rrddim_add(st, "blocked", NULL, -1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "running", running); - rrddim_set(st, "blocked", blocked); - rrdset_done(st); - } - - return 0; + (void)dt; + + static procfile *ff = NULL; + static int do_cpu = -1, do_cpu_cores = -1, do_interrupts = -1, do_context = -1, do_forks = -1, do_processes = -1; + static uint32_t hash_intr, hash_ctxt, hash_processes, hash_procs_running, hash_procs_blocked; + + if(unlikely(do_cpu == -1)) { + do_cpu = config_get_boolean("plugin:proc:/proc/stat", "cpu utilization", 1); + do_cpu_cores = config_get_boolean("plugin:proc:/proc/stat", "per cpu core utilization", 1); + do_interrupts = config_get_boolean("plugin:proc:/proc/stat", "cpu interrupts", 1); + do_context = config_get_boolean("plugin:proc:/proc/stat", "context switches", 1); + do_forks = config_get_boolean("plugin:proc:/proc/stat", "processes started", 1); + do_processes = config_get_boolean("plugin:proc:/proc/stat", "processes running", 1); + + hash_intr = simple_hash("intr"); + hash_ctxt = simple_hash("ctxt"); + hash_processes = simple_hash("processes"); + hash_procs_running = simple_hash("procs_running"); + hash_procs_blocked = simple_hash("procs_blocked"); + } + + if(unlikely(!ff)) { + char filename[FILENAME_MAX + 1]; + 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(unlikely(!ff)) return 1; + } + + ff = procfile_readall(ff); + if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time + + uint32_t lines = procfile_lines(ff), l; + uint32_t words; + + unsigned long long processes = 0, running = 0 , blocked = 0; + RRDSET *st; + + for(l = 0; l < lines ;l++) { + char *row_key = procfile_lineword(ff, l, 0); + uint32_t hash = simple_hash(row_key); + + // faster strncmp(row_key, "cpu", 3) == 0 + if(likely(row_key[0] == 'c' && row_key[1] == 'p' && row_key[2] == 'u')) { + words = procfile_linewords(ff, l); + if(unlikely(words < 9)) { + error("Cannot read /proc/stat cpu line. Expected 9 params, read %u.", words); + continue; + } + + char *id; + unsigned long long user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0, steal = 0, guest = 0, guest_nice = 0; + + id = row_key; + user = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + nice = strtoull(procfile_lineword(ff, l, 2), NULL, 10); + system = strtoull(procfile_lineword(ff, l, 3), NULL, 10); + idle = strtoull(procfile_lineword(ff, l, 4), NULL, 10); + iowait = strtoull(procfile_lineword(ff, l, 5), NULL, 10); + irq = strtoull(procfile_lineword(ff, l, 6), NULL, 10); + softirq = strtoull(procfile_lineword(ff, l, 7), NULL, 10); + steal = strtoull(procfile_lineword(ff, l, 8), NULL, 10); + + guest = strtoull(procfile_lineword(ff, l, 9), NULL, 10); + user -= guest; + + guest_nice = strtoull(procfile_lineword(ff, l, 10), NULL, 10); + nice -= guest_nice; + + char *title, *type, *context, *family; + long priority; + int isthistotal; + + if(unlikely(strcmp(id, "cpu")) == 0) { + title = "Total CPU utilization"; + type = "system"; + context = "system.cpu"; + family = id; + priority = 100; + isthistotal = 1; + } + else { + title = "Core utilization"; + type = "cpu"; + context = "cpu.cpu"; + family = "utilization"; + priority = 1000; + isthistotal = 0; + } + + if(likely((isthistotal && do_cpu) || (!isthistotal && do_cpu_cores))) { + st = rrdset_find_bytype(type, id); + if(unlikely(!st)) { + st = rrdset_create(type, id, NULL, family, context, title, "percentage", priority, update_every, RRDSET_TYPE_STACKED); + + long multiplier = 1; + long divisor = 1; // sysconf(_SC_CLK_TCK); + + rrddim_add(st, "guest_nice", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "guest", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "steal", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "softirq", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "irq", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "user", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "system", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "nice", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_add(st, "iowait", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + + rrddim_add(st, "idle", NULL, multiplier, divisor, RRDDIM_PCENT_OVER_DIFF_TOTAL); + rrddim_hide(st, "idle"); + } + else rrdset_next(st); + + rrddim_set(st, "user", user); + rrddim_set(st, "nice", nice); + rrddim_set(st, "system", system); + rrddim_set(st, "idle", idle); + rrddim_set(st, "iowait", iowait); + rrddim_set(st, "irq", irq); + rrddim_set(st, "softirq", softirq); + rrddim_set(st, "steal", steal); + rrddim_set(st, "guest", guest); + rrddim_set(st, "guest_nice", guest_nice); + rrdset_done(st); + } + } + else if(hash == hash_intr && strcmp(row_key, "intr") == 0) { + unsigned long long value = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + + // -------------------------------------------------------------------- + + if(likely(do_interrupts)) { + st = rrdset_find_bytype("system", "intr"); + if(unlikely(!st)) { + st = rrdset_create("system", "intr", NULL, "interrupts", NULL, "CPU Interrupts", "interrupts/s", 900, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "interrupts", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "interrupts", value); + rrdset_done(st); + } + } + else if(hash == hash_ctxt && strcmp(row_key, "ctxt") == 0) { + unsigned long long value = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + + // -------------------------------------------------------------------- + + if(likely(do_context)) { + st = rrdset_find_bytype("system", "ctxt"); + if(unlikely(!st)) { + st = rrdset_create("system", "ctxt", NULL, "processes", NULL, "CPU Context Switches", "context switches/s", 800, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "switches", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "switches", value); + rrdset_done(st); + } + } + else if(hash == hash_processes && !processes && strcmp(row_key, "processes") == 0) { + processes = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + } + else if(hash == hash_procs_running && !running && strcmp(row_key, "procs_running") == 0) { + running = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + } + else if(hash == hash_procs_blocked && !blocked && strcmp(row_key, "procs_blocked") == 0) { + blocked = strtoull(procfile_lineword(ff, l, 1), NULL, 10); + } + } + + // -------------------------------------------------------------------- + + if(likely(do_forks)) { + st = rrdset_find_bytype("system", "forks"); + if(unlikely(!st)) { + st = rrdset_create("system", "forks", NULL, "processes", NULL, "Started Processes", "processes/s", 700, update_every, RRDSET_TYPE_LINE); + st->isdetail = 1; + + rrddim_add(st, "started", NULL, 1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st); + + rrddim_set(st, "started", processes); + rrdset_done(st); + } + + // -------------------------------------------------------------------- + + if(likely(do_processes)) { + st = rrdset_find_bytype("system", "processes"); + if(unlikely(!st)) { + st = rrdset_create("system", "processes", NULL, "processes", NULL, "System Processes", "processes", 600, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "running", NULL, 1, 1, RRDDIM_ABSOLUTE); + rrddim_add(st, "blocked", NULL, -1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "running", running); + rrddim_set(st, "blocked", blocked); + rrdset_done(st); + } + + return 0; } diff --git a/src/proc_sys_kernel_random_entropy_avail.c b/src/proc_sys_kernel_random_entropy_avail.c index d7d1e8261..9515dad61 100644 --- a/src/proc_sys_kernel_random_entropy_avail.c +++ b/src/proc_sys_kernel_random_entropy_avail.c @@ -1,41 +1,31 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> - #include "common.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" int do_proc_sys_kernel_random_entropy_avail(int update_every, unsigned long long dt) { - static procfile *ff = NULL; + static procfile *ff = NULL; - if(dt) {} ; + if(dt) {} ; - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time - unsigned long long entropy = strtoull(procfile_lineword(ff, 0, 0), NULL, 10); + unsigned long long entropy = strtoull(procfile_lineword(ff, 0, 0), NULL, 10); - RRDSET *st = rrdset_find_bytype("system", "entropy"); - if(!st) { - st = rrdset_create("system", "entropy", NULL, "entropy", NULL, "Available Entropy", "entropy", 1000, update_every, RRDSET_TYPE_LINE); - rrddim_add(st, "entropy", NULL, 1, 1, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); + RRDSET *st = rrdset_find_bytype("system", "entropy"); + if(!st) { + st = rrdset_create("system", "entropy", NULL, "entropy", NULL, "Available Entropy", "entropy", 1000, update_every, RRDSET_TYPE_LINE); + rrddim_add(st, "entropy", NULL, 1, 1, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); - rrddim_set(st, "entropy", entropy); - rrdset_done(st); + rrddim_set(st, "entropy", entropy); + rrdset_done(st); - return 0; + return 0; } diff --git a/src/proc_vmstat.c b/src/proc_vmstat.c index 7b20ed8cf..f25d50c50 100644 --- a/src/proc_vmstat.c +++ b/src/proc_vmstat.c @@ -1,483 +1,470 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" int do_proc_vmstat(int update_every, unsigned long long dt) { - static procfile *ff = NULL; - static int do_swapio = -1, do_io = -1, do_pgfaults = -1, gen_hashes = -1; + static procfile *ff = NULL; + static int do_swapio = -1, do_io = -1, do_pgfaults = -1, gen_hashes = -1; - // static uint32_t hash_allocstall = -1; - // static uint32_t hash_compact_blocks_moved = -1; - // static uint32_t hash_compact_fail = -1; - // static uint32_t hash_compact_pagemigrate_failed = -1; - // static uint32_t hash_compact_pages_moved = -1; - // static uint32_t hash_compact_stall = -1; - // static uint32_t hash_compact_success = -1; - // static uint32_t hash_htlb_buddy_alloc_fail = -1; - // static uint32_t hash_htlb_buddy_alloc_success = -1; - // static uint32_t hash_kswapd_high_wmark_hit_quickly = -1; - // static uint32_t hash_kswapd_inodesteal = -1; - // static uint32_t hash_kswapd_low_wmark_hit_quickly = -1; - // static uint32_t hash_kswapd_skip_congestion_wait = -1; - // static uint32_t hash_nr_active_anon = -1; - // static uint32_t hash_nr_active_file = -1; - // static uint32_t hash_nr_anon_pages = -1; - // static uint32_t hash_nr_anon_transparent_hugepages = -1; - // static uint32_t hash_nr_bounce = -1; - // static uint32_t hash_nr_dirtied = -1; - // static uint32_t hash_nr_dirty = -1; - // static uint32_t hash_nr_dirty_background_threshold = -1; - // static uint32_t hash_nr_dirty_threshold = -1; - // static uint32_t hash_nr_file_pages = -1; - // static uint32_t hash_nr_free_pages = -1; - // static uint32_t hash_nr_inactive_anon = -1; - // static uint32_t hash_nr_inactive_file = -1; - // static uint32_t hash_nr_isolated_anon = -1; - // static uint32_t hash_nr_isolated_file = -1; - // static uint32_t hash_nr_kernel_stack = -1; - // static uint32_t hash_nr_mapped = -1; - // static uint32_t hash_nr_mlock = -1; - // static uint32_t hash_nr_page_table_pages = -1; - // static uint32_t hash_nr_shmem = -1; - // static uint32_t hash_nr_slab_reclaimable = -1; - // static uint32_t hash_nr_slab_unreclaimable = -1; - // static uint32_t hash_nr_unevictable = -1; - // static uint32_t hash_nr_unstable = -1; - // static uint32_t hash_nr_vmscan_immediate_reclaim = -1; - // static uint32_t hash_nr_vmscan_write = -1; - // static uint32_t hash_nr_writeback = -1; - // static uint32_t hash_nr_writeback_temp = -1; - // static uint32_t hash_nr_written = -1; - // static uint32_t hash_pageoutrun = -1; - // static uint32_t hash_pgactivate = -1; - // static uint32_t hash_pgalloc_dma = -1; - // static uint32_t hash_pgalloc_dma32 = -1; - // static uint32_t hash_pgalloc_movable = -1; - // static uint32_t hash_pgalloc_normal = -1; - // static uint32_t hash_pgdeactivate = -1; - static uint32_t hash_pgfault = -1; - // static uint32_t hash_pgfree = -1; - // static uint32_t hash_pginodesteal = -1; - static uint32_t hash_pgmajfault = -1; - static uint32_t hash_pgpgin = -1; - static uint32_t hash_pgpgout = -1; - // static uint32_t hash_pgrefill_dma = -1; - // static uint32_t hash_pgrefill_dma32 = -1; - // static uint32_t hash_pgrefill_movable = -1; - // static uint32_t hash_pgrefill_normal = -1; - // static uint32_t hash_pgrotated = -1; - // static uint32_t hash_pgscan_direct_dma = -1; - // static uint32_t hash_pgscan_direct_dma32 = -1; - // static uint32_t hash_pgscan_direct_movable = -1; - // static uint32_t hash_pgscan_direct_normal = -1; - // static uint32_t hash_pgscan_kswapd_dma = -1; - // static uint32_t hash_pgscan_kswapd_dma32 = -1; - // static uint32_t hash_pgscan_kswapd_movable = -1; - // static uint32_t hash_pgscan_kswapd_normal = -1; - // static uint32_t hash_pgsteal_direct_dma = -1; - // static uint32_t hash_pgsteal_direct_dma32 = -1; - // static uint32_t hash_pgsteal_direct_movable = -1; - // static uint32_t hash_pgsteal_direct_normal = -1; - // static uint32_t hash_pgsteal_kswapd_dma = -1; - // static uint32_t hash_pgsteal_kswapd_dma32 = -1; - // static uint32_t hash_pgsteal_kswapd_movable = -1; - // static uint32_t hash_pgsteal_kswapd_normal = -1; - static uint32_t hash_pswpin = -1; - static uint32_t hash_pswpout = -1; - // static uint32_t hash_slabs_scanned = -1; - // static uint32_t hash_thp_collapse_alloc = -1; - // static uint32_t hash_thp_collapse_alloc_failed = -1; - // static uint32_t hash_thp_fault_alloc = -1; - // static uint32_t hash_thp_fault_fallback = -1; - // static uint32_t hash_thp_split = -1; - // static uint32_t hash_unevictable_pgs_cleared = -1; - // static uint32_t hash_unevictable_pgs_culled = -1; - // static uint32_t hash_unevictable_pgs_mlocked = -1; - // static uint32_t hash_unevictable_pgs_mlockfreed = -1; - // static uint32_t hash_unevictable_pgs_munlocked = -1; - // static uint32_t hash_unevictable_pgs_rescued = -1; - // static uint32_t hash_unevictable_pgs_scanned = -1; - // static uint32_t hash_unevictable_pgs_stranded = -1; + // static uint32_t hash_allocstall = -1; + // static uint32_t hash_compact_blocks_moved = -1; + // static uint32_t hash_compact_fail = -1; + // static uint32_t hash_compact_pagemigrate_failed = -1; + // static uint32_t hash_compact_pages_moved = -1; + // static uint32_t hash_compact_stall = -1; + // static uint32_t hash_compact_success = -1; + // static uint32_t hash_htlb_buddy_alloc_fail = -1; + // static uint32_t hash_htlb_buddy_alloc_success = -1; + // static uint32_t hash_kswapd_high_wmark_hit_quickly = -1; + // static uint32_t hash_kswapd_inodesteal = -1; + // static uint32_t hash_kswapd_low_wmark_hit_quickly = -1; + // static uint32_t hash_kswapd_skip_congestion_wait = -1; + // static uint32_t hash_nr_active_anon = -1; + // static uint32_t hash_nr_active_file = -1; + // static uint32_t hash_nr_anon_pages = -1; + // static uint32_t hash_nr_anon_transparent_hugepages = -1; + // static uint32_t hash_nr_bounce = -1; + // static uint32_t hash_nr_dirtied = -1; + // static uint32_t hash_nr_dirty = -1; + // static uint32_t hash_nr_dirty_background_threshold = -1; + // static uint32_t hash_nr_dirty_threshold = -1; + // static uint32_t hash_nr_file_pages = -1; + // static uint32_t hash_nr_free_pages = -1; + // static uint32_t hash_nr_inactive_anon = -1; + // static uint32_t hash_nr_inactive_file = -1; + // static uint32_t hash_nr_isolated_anon = -1; + // static uint32_t hash_nr_isolated_file = -1; + // static uint32_t hash_nr_kernel_stack = -1; + // static uint32_t hash_nr_mapped = -1; + // static uint32_t hash_nr_mlock = -1; + // static uint32_t hash_nr_page_table_pages = -1; + // static uint32_t hash_nr_shmem = -1; + // static uint32_t hash_nr_slab_reclaimable = -1; + // static uint32_t hash_nr_slab_unreclaimable = -1; + // static uint32_t hash_nr_unevictable = -1; + // static uint32_t hash_nr_unstable = -1; + // static uint32_t hash_nr_vmscan_immediate_reclaim = -1; + // static uint32_t hash_nr_vmscan_write = -1; + // static uint32_t hash_nr_writeback = -1; + // static uint32_t hash_nr_writeback_temp = -1; + // static uint32_t hash_nr_written = -1; + // static uint32_t hash_pageoutrun = -1; + // static uint32_t hash_pgactivate = -1; + // static uint32_t hash_pgalloc_dma = -1; + // static uint32_t hash_pgalloc_dma32 = -1; + // static uint32_t hash_pgalloc_movable = -1; + // static uint32_t hash_pgalloc_normal = -1; + // static uint32_t hash_pgdeactivate = -1; + static uint32_t hash_pgfault = -1; + // static uint32_t hash_pgfree = -1; + // static uint32_t hash_pginodesteal = -1; + static uint32_t hash_pgmajfault = -1; + static uint32_t hash_pgpgin = -1; + static uint32_t hash_pgpgout = -1; + // static uint32_t hash_pgrefill_dma = -1; + // static uint32_t hash_pgrefill_dma32 = -1; + // static uint32_t hash_pgrefill_movable = -1; + // static uint32_t hash_pgrefill_normal = -1; + // static uint32_t hash_pgrotated = -1; + // static uint32_t hash_pgscan_direct_dma = -1; + // static uint32_t hash_pgscan_direct_dma32 = -1; + // static uint32_t hash_pgscan_direct_movable = -1; + // static uint32_t hash_pgscan_direct_normal = -1; + // static uint32_t hash_pgscan_kswapd_dma = -1; + // static uint32_t hash_pgscan_kswapd_dma32 = -1; + // static uint32_t hash_pgscan_kswapd_movable = -1; + // static uint32_t hash_pgscan_kswapd_normal = -1; + // static uint32_t hash_pgsteal_direct_dma = -1; + // static uint32_t hash_pgsteal_direct_dma32 = -1; + // static uint32_t hash_pgsteal_direct_movable = -1; + // static uint32_t hash_pgsteal_direct_normal = -1; + // static uint32_t hash_pgsteal_kswapd_dma = -1; + // static uint32_t hash_pgsteal_kswapd_dma32 = -1; + // static uint32_t hash_pgsteal_kswapd_movable = -1; + // static uint32_t hash_pgsteal_kswapd_normal = -1; + static uint32_t hash_pswpin = -1; + static uint32_t hash_pswpout = -1; + // static uint32_t hash_slabs_scanned = -1; + // static uint32_t hash_thp_collapse_alloc = -1; + // static uint32_t hash_thp_collapse_alloc_failed = -1; + // static uint32_t hash_thp_fault_alloc = -1; + // static uint32_t hash_thp_fault_fallback = -1; + // static uint32_t hash_thp_split = -1; + // static uint32_t hash_unevictable_pgs_cleared = -1; + // static uint32_t hash_unevictable_pgs_culled = -1; + // static uint32_t hash_unevictable_pgs_mlocked = -1; + // static uint32_t hash_unevictable_pgs_mlockfreed = -1; + // static uint32_t hash_unevictable_pgs_munlocked = -1; + // static uint32_t hash_unevictable_pgs_rescued = -1; + // static uint32_t hash_unevictable_pgs_scanned = -1; + // static uint32_t hash_unevictable_pgs_stranded = -1; - if(gen_hashes != 1) { - gen_hashes = 1; - // hash_allocstall = simple_hash("allocstall"); - // hash_compact_blocks_moved = simple_hash("compact_blocks_moved"); - // hash_compact_fail = simple_hash("compact_fail"); - // hash_compact_pagemigrate_failed = simple_hash("compact_pagemigrate_failed"); - // hash_compact_pages_moved = simple_hash("compact_pages_moved"); - // hash_compact_stall = simple_hash("compact_stall"); - // hash_compact_success = simple_hash("compact_success"); - // hash_htlb_buddy_alloc_fail = simple_hash("htlb_buddy_alloc_fail"); - // hash_htlb_buddy_alloc_success = simple_hash("htlb_buddy_alloc_success"); - // hash_kswapd_high_wmark_hit_quickly = simple_hash("kswapd_high_wmark_hit_quickly"); - // hash_kswapd_inodesteal = simple_hash("kswapd_inodesteal"); - // hash_kswapd_low_wmark_hit_quickly = simple_hash("kswapd_low_wmark_hit_quickly"); - // hash_kswapd_skip_congestion_wait = simple_hash("kswapd_skip_congestion_wait"); - // hash_nr_active_anon = simple_hash("nr_active_anon"); - // hash_nr_active_file = simple_hash("nr_active_file"); - // hash_nr_anon_pages = simple_hash("nr_anon_pages"); - // hash_nr_anon_transparent_hugepages = simple_hash("nr_anon_transparent_hugepages"); - // hash_nr_bounce = simple_hash("nr_bounce"); - // hash_nr_dirtied = simple_hash("nr_dirtied"); - // hash_nr_dirty = simple_hash("nr_dirty"); - // hash_nr_dirty_background_threshold = simple_hash("nr_dirty_background_threshold"); - // hash_nr_dirty_threshold = simple_hash("nr_dirty_threshold"); - // hash_nr_file_pages = simple_hash("nr_file_pages"); - // hash_nr_free_pages = simple_hash("nr_free_pages"); - // hash_nr_inactive_anon = simple_hash("nr_inactive_anon"); - // hash_nr_inactive_file = simple_hash("nr_inactive_file"); - // hash_nr_isolated_anon = simple_hash("nr_isolated_anon"); - // hash_nr_isolated_file = simple_hash("nr_isolated_file"); - // hash_nr_kernel_stack = simple_hash("nr_kernel_stack"); - // hash_nr_mapped = simple_hash("nr_mapped"); - // hash_nr_mlock = simple_hash("nr_mlock"); - // hash_nr_page_table_pages = simple_hash("nr_page_table_pages"); - // hash_nr_shmem = simple_hash("nr_shmem"); - // hash_nr_slab_reclaimable = simple_hash("nr_slab_reclaimable"); - // hash_nr_slab_unreclaimable = simple_hash("nr_slab_unreclaimable"); - // hash_nr_unevictable = simple_hash("nr_unevictable"); - // hash_nr_unstable = simple_hash("nr_unstable"); - // hash_nr_vmscan_immediate_reclaim = simple_hash("nr_vmscan_immediate_reclaim"); - // hash_nr_vmscan_write = simple_hash("nr_vmscan_write"); - // hash_nr_writeback = simple_hash("nr_writeback"); - // hash_nr_writeback_temp = simple_hash("nr_writeback_temp"); - // hash_nr_written = simple_hash("nr_written"); - // hash_pageoutrun = simple_hash("pageoutrun"); - // hash_pgactivate = simple_hash("pgactivate"); - // hash_pgalloc_dma = simple_hash("pgalloc_dma"); - // hash_pgalloc_dma32 = simple_hash("pgalloc_dma32"); - // hash_pgalloc_movable = simple_hash("pgalloc_movable"); - // hash_pgalloc_normal = simple_hash("pgalloc_normal"); - // hash_pgdeactivate = simple_hash("pgdeactivate"); - hash_pgfault = simple_hash("pgfault"); - // hash_pgfree = simple_hash("pgfree"); - // hash_pginodesteal = simple_hash("pginodesteal"); - hash_pgmajfault = simple_hash("pgmajfault"); - hash_pgpgin = simple_hash("pgpgin"); - hash_pgpgout = simple_hash("pgpgout"); - // hash_pgrefill_dma = simple_hash("pgrefill_dma"); - // hash_pgrefill_dma32 = simple_hash("pgrefill_dma32"); - // hash_pgrefill_movable = simple_hash("pgrefill_movable"); - // hash_pgrefill_normal = simple_hash("pgrefill_normal"); - // hash_pgrotated = simple_hash("pgrotated"); - // hash_pgscan_direct_dma = simple_hash("pgscan_direct_dma"); - // hash_pgscan_direct_dma32 = simple_hash("pgscan_direct_dma32"); - // hash_pgscan_direct_movable = simple_hash("pgscan_direct_movable"); - // hash_pgscan_direct_normal = simple_hash("pgscan_direct_normal"); - // hash_pgscan_kswapd_dma = simple_hash("pgscan_kswapd_dma"); - // hash_pgscan_kswapd_dma32 = simple_hash("pgscan_kswapd_dma32"); - // hash_pgscan_kswapd_movable = simple_hash("pgscan_kswapd_movable"); - // hash_pgscan_kswapd_normal = simple_hash("pgscan_kswapd_normal"); - // hash_pgsteal_direct_dma = simple_hash("pgsteal_direct_dma"); - // hash_pgsteal_direct_dma32 = simple_hash("pgsteal_direct_dma32"); - // hash_pgsteal_direct_movable = simple_hash("pgsteal_direct_movable"); - // hash_pgsteal_direct_normal = simple_hash("pgsteal_direct_normal"); - // hash_pgsteal_kswapd_dma = simple_hash("pgsteal_kswapd_dma"); - // hash_pgsteal_kswapd_dma32 = simple_hash("pgsteal_kswapd_dma32"); - // hash_pgsteal_kswapd_movable = simple_hash("pgsteal_kswapd_movable"); - // hash_pgsteal_kswapd_normal = simple_hash("pgsteal_kswapd_normal"); - hash_pswpin = simple_hash("pswpin"); - hash_pswpout = simple_hash("pswpout"); - // hash_slabs_scanned = simple_hash("slabs_scanned"); - // hash_thp_collapse_alloc = simple_hash("thp_collapse_alloc"); - // hash_thp_collapse_alloc_failed = simple_hash("thp_collapse_alloc_failed"); - // hash_thp_fault_alloc = simple_hash("thp_fault_alloc"); - // hash_thp_fault_fallback = simple_hash("thp_fault_fallback"); - // hash_thp_split = simple_hash("thp_split"); - // hash_unevictable_pgs_cleared = simple_hash("unevictable_pgs_cleared"); - // hash_unevictable_pgs_culled = simple_hash("unevictable_pgs_culled"); - // hash_unevictable_pgs_mlocked = simple_hash("unevictable_pgs_mlocked"); - // hash_unevictable_pgs_mlockfreed = simple_hash("unevictable_pgs_mlockfreed"); - // hash_unevictable_pgs_munlocked = simple_hash("unevictable_pgs_munlocked"); - // hash_unevictable_pgs_rescued = simple_hash("unevictable_pgs_rescued"); - // hash_unevictable_pgs_scanned = simple_hash("unevictable_pgs_scanned"); - // hash_unevictable_pgs_stranded = simple_hash("unevictable_pgs_stranded"); - } + if(gen_hashes != 1) { + gen_hashes = 1; + // hash_allocstall = simple_hash("allocstall"); + // hash_compact_blocks_moved = simple_hash("compact_blocks_moved"); + // hash_compact_fail = simple_hash("compact_fail"); + // hash_compact_pagemigrate_failed = simple_hash("compact_pagemigrate_failed"); + // hash_compact_pages_moved = simple_hash("compact_pages_moved"); + // hash_compact_stall = simple_hash("compact_stall"); + // hash_compact_success = simple_hash("compact_success"); + // hash_htlb_buddy_alloc_fail = simple_hash("htlb_buddy_alloc_fail"); + // hash_htlb_buddy_alloc_success = simple_hash("htlb_buddy_alloc_success"); + // hash_kswapd_high_wmark_hit_quickly = simple_hash("kswapd_high_wmark_hit_quickly"); + // hash_kswapd_inodesteal = simple_hash("kswapd_inodesteal"); + // hash_kswapd_low_wmark_hit_quickly = simple_hash("kswapd_low_wmark_hit_quickly"); + // hash_kswapd_skip_congestion_wait = simple_hash("kswapd_skip_congestion_wait"); + // hash_nr_active_anon = simple_hash("nr_active_anon"); + // hash_nr_active_file = simple_hash("nr_active_file"); + // hash_nr_anon_pages = simple_hash("nr_anon_pages"); + // hash_nr_anon_transparent_hugepages = simple_hash("nr_anon_transparent_hugepages"); + // hash_nr_bounce = simple_hash("nr_bounce"); + // hash_nr_dirtied = simple_hash("nr_dirtied"); + // hash_nr_dirty = simple_hash("nr_dirty"); + // hash_nr_dirty_background_threshold = simple_hash("nr_dirty_background_threshold"); + // hash_nr_dirty_threshold = simple_hash("nr_dirty_threshold"); + // hash_nr_file_pages = simple_hash("nr_file_pages"); + // hash_nr_free_pages = simple_hash("nr_free_pages"); + // hash_nr_inactive_anon = simple_hash("nr_inactive_anon"); + // hash_nr_inactive_file = simple_hash("nr_inactive_file"); + // hash_nr_isolated_anon = simple_hash("nr_isolated_anon"); + // hash_nr_isolated_file = simple_hash("nr_isolated_file"); + // hash_nr_kernel_stack = simple_hash("nr_kernel_stack"); + // hash_nr_mapped = simple_hash("nr_mapped"); + // hash_nr_mlock = simple_hash("nr_mlock"); + // hash_nr_page_table_pages = simple_hash("nr_page_table_pages"); + // hash_nr_shmem = simple_hash("nr_shmem"); + // hash_nr_slab_reclaimable = simple_hash("nr_slab_reclaimable"); + // hash_nr_slab_unreclaimable = simple_hash("nr_slab_unreclaimable"); + // hash_nr_unevictable = simple_hash("nr_unevictable"); + // hash_nr_unstable = simple_hash("nr_unstable"); + // hash_nr_vmscan_immediate_reclaim = simple_hash("nr_vmscan_immediate_reclaim"); + // hash_nr_vmscan_write = simple_hash("nr_vmscan_write"); + // hash_nr_writeback = simple_hash("nr_writeback"); + // hash_nr_writeback_temp = simple_hash("nr_writeback_temp"); + // hash_nr_written = simple_hash("nr_written"); + // hash_pageoutrun = simple_hash("pageoutrun"); + // hash_pgactivate = simple_hash("pgactivate"); + // hash_pgalloc_dma = simple_hash("pgalloc_dma"); + // hash_pgalloc_dma32 = simple_hash("pgalloc_dma32"); + // hash_pgalloc_movable = simple_hash("pgalloc_movable"); + // hash_pgalloc_normal = simple_hash("pgalloc_normal"); + // hash_pgdeactivate = simple_hash("pgdeactivate"); + hash_pgfault = simple_hash("pgfault"); + // hash_pgfree = simple_hash("pgfree"); + // hash_pginodesteal = simple_hash("pginodesteal"); + hash_pgmajfault = simple_hash("pgmajfault"); + hash_pgpgin = simple_hash("pgpgin"); + hash_pgpgout = simple_hash("pgpgout"); + // hash_pgrefill_dma = simple_hash("pgrefill_dma"); + // hash_pgrefill_dma32 = simple_hash("pgrefill_dma32"); + // hash_pgrefill_movable = simple_hash("pgrefill_movable"); + // hash_pgrefill_normal = simple_hash("pgrefill_normal"); + // hash_pgrotated = simple_hash("pgrotated"); + // hash_pgscan_direct_dma = simple_hash("pgscan_direct_dma"); + // hash_pgscan_direct_dma32 = simple_hash("pgscan_direct_dma32"); + // hash_pgscan_direct_movable = simple_hash("pgscan_direct_movable"); + // hash_pgscan_direct_normal = simple_hash("pgscan_direct_normal"); + // hash_pgscan_kswapd_dma = simple_hash("pgscan_kswapd_dma"); + // hash_pgscan_kswapd_dma32 = simple_hash("pgscan_kswapd_dma32"); + // hash_pgscan_kswapd_movable = simple_hash("pgscan_kswapd_movable"); + // hash_pgscan_kswapd_normal = simple_hash("pgscan_kswapd_normal"); + // hash_pgsteal_direct_dma = simple_hash("pgsteal_direct_dma"); + // hash_pgsteal_direct_dma32 = simple_hash("pgsteal_direct_dma32"); + // hash_pgsteal_direct_movable = simple_hash("pgsteal_direct_movable"); + // hash_pgsteal_direct_normal = simple_hash("pgsteal_direct_normal"); + // hash_pgsteal_kswapd_dma = simple_hash("pgsteal_kswapd_dma"); + // hash_pgsteal_kswapd_dma32 = simple_hash("pgsteal_kswapd_dma32"); + // hash_pgsteal_kswapd_movable = simple_hash("pgsteal_kswapd_movable"); + // hash_pgsteal_kswapd_normal = simple_hash("pgsteal_kswapd_normal"); + hash_pswpin = simple_hash("pswpin"); + hash_pswpout = simple_hash("pswpout"); + // hash_slabs_scanned = simple_hash("slabs_scanned"); + // hash_thp_collapse_alloc = simple_hash("thp_collapse_alloc"); + // hash_thp_collapse_alloc_failed = simple_hash("thp_collapse_alloc_failed"); + // hash_thp_fault_alloc = simple_hash("thp_fault_alloc"); + // hash_thp_fault_fallback = simple_hash("thp_fault_fallback"); + // hash_thp_split = simple_hash("thp_split"); + // hash_unevictable_pgs_cleared = simple_hash("unevictable_pgs_cleared"); + // hash_unevictable_pgs_culled = simple_hash("unevictable_pgs_culled"); + // hash_unevictable_pgs_mlocked = simple_hash("unevictable_pgs_mlocked"); + // hash_unevictable_pgs_mlockfreed = simple_hash("unevictable_pgs_mlockfreed"); + // hash_unevictable_pgs_munlocked = simple_hash("unevictable_pgs_munlocked"); + // hash_unevictable_pgs_rescued = simple_hash("unevictable_pgs_rescued"); + // hash_unevictable_pgs_scanned = simple_hash("unevictable_pgs_scanned"); + // hash_unevictable_pgs_stranded = simple_hash("unevictable_pgs_stranded"); + } - if(do_swapio == -1) do_swapio = config_get_boolean("plugin:proc:/proc/vmstat", "swap i/o", 1); - if(do_io == -1) do_io = config_get_boolean("plugin:proc:/proc/vmstat", "disk i/o", 1); - if(do_pgfaults == -1) do_pgfaults = config_get_boolean("plugin:proc:/proc/vmstat", "memory page faults", 1); + if(do_swapio == -1) do_swapio = config_get_boolean("plugin:proc:/proc/vmstat", "swap i/o", 1); + if(do_io == -1) do_io = config_get_boolean("plugin:proc:/proc/vmstat", "disk i/o", 1); + if(do_pgfaults == -1) do_pgfaults = config_get_boolean("plugin:proc:/proc/vmstat", "memory page faults", 1); - if(dt) {}; + if(dt) {}; - if(!ff) { - char filename[FILENAME_MAX + 1]; - 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; + if(!ff) { + char filename[FILENAME_MAX + 1]; + 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; - ff = procfile_readall(ff); - if(!ff) return 0; // we return 0, so that we will retry to open it next time + ff = procfile_readall(ff); + if(!ff) return 0; // we return 0, so that we will retry to open it next time - uint32_t lines = procfile_lines(ff), l; - uint32_t words; + uint32_t lines = procfile_lines(ff), l; + uint32_t words; - // unsigned long long allocstall = 0ULL; - // unsigned long long compact_blocks_moved = 0ULL; - // unsigned long long compact_fail = 0ULL; - // unsigned long long compact_pagemigrate_failed = 0ULL; - // unsigned long long compact_pages_moved = 0ULL; - // unsigned long long compact_stall = 0ULL; - // unsigned long long compact_success = 0ULL; - // unsigned long long htlb_buddy_alloc_fail = 0ULL; - // unsigned long long htlb_buddy_alloc_success = 0ULL; - // unsigned long long kswapd_high_wmark_hit_quickly = 0ULL; - // unsigned long long kswapd_inodesteal = 0ULL; - // unsigned long long kswapd_low_wmark_hit_quickly = 0ULL; - // unsigned long long kswapd_skip_congestion_wait = 0ULL; - // unsigned long long nr_active_anon = 0ULL; - // unsigned long long nr_active_file = 0ULL; - // unsigned long long nr_anon_pages = 0ULL; - // unsigned long long nr_anon_transparent_hugepages = 0ULL; - // unsigned long long nr_bounce = 0ULL; - // unsigned long long nr_dirtied = 0ULL; - // unsigned long long nr_dirty = 0ULL; - // unsigned long long nr_dirty_background_threshold = 0ULL; - // unsigned long long nr_dirty_threshold = 0ULL; - // unsigned long long nr_file_pages = 0ULL; - // unsigned long long nr_free_pages = 0ULL; - // unsigned long long nr_inactive_anon = 0ULL; - // unsigned long long nr_inactive_file = 0ULL; - // unsigned long long nr_isolated_anon = 0ULL; - // unsigned long long nr_isolated_file = 0ULL; - // unsigned long long nr_kernel_stack = 0ULL; - // unsigned long long nr_mapped = 0ULL; - // unsigned long long nr_mlock = 0ULL; - // unsigned long long nr_page_table_pages = 0ULL; - // unsigned long long nr_shmem = 0ULL; - // unsigned long long nr_slab_reclaimable = 0ULL; - // unsigned long long nr_slab_unreclaimable = 0ULL; - // unsigned long long nr_unevictable = 0ULL; - // unsigned long long nr_unstable = 0ULL; - // unsigned long long nr_vmscan_immediate_reclaim = 0ULL; - // unsigned long long nr_vmscan_write = 0ULL; - // unsigned long long nr_writeback = 0ULL; - // unsigned long long nr_writeback_temp = 0ULL; - // unsigned long long nr_written = 0ULL; - // unsigned long long pageoutrun = 0ULL; - // unsigned long long pgactivate = 0ULL; - // unsigned long long pgalloc_dma = 0ULL; - // unsigned long long pgalloc_dma32 = 0ULL; - // unsigned long long pgalloc_movable = 0ULL; - // unsigned long long pgalloc_normal = 0ULL; - // unsigned long long pgdeactivate = 0ULL; - unsigned long long pgfault = 0ULL; - // unsigned long long pgfree = 0ULL; - // unsigned long long pginodesteal = 0ULL; - unsigned long long pgmajfault = 0ULL; - unsigned long long pgpgin = 0ULL; - unsigned long long pgpgout = 0ULL; - // unsigned long long pgrefill_dma = 0ULL; - // unsigned long long pgrefill_dma32 = 0ULL; - // unsigned long long pgrefill_movable = 0ULL; - // unsigned long long pgrefill_normal = 0ULL; - // unsigned long long pgrotated = 0ULL; - // unsigned long long pgscan_direct_dma = 0ULL; - // unsigned long long pgscan_direct_dma32 = 0ULL; - // unsigned long long pgscan_direct_movable = 0ULL; - // unsigned long long pgscan_direct_normal = 0ULL; - // unsigned long long pgscan_kswapd_dma = 0ULL; - // unsigned long long pgscan_kswapd_dma32 = 0ULL; - // unsigned long long pgscan_kswapd_movable = 0ULL; - // unsigned long long pgscan_kswapd_normal = 0ULL; - // unsigned long long pgsteal_direct_dma = 0ULL; - // unsigned long long pgsteal_direct_dma32 = 0ULL; - // unsigned long long pgsteal_direct_movable = 0ULL; - // unsigned long long pgsteal_direct_normal = 0ULL; - // unsigned long long pgsteal_kswapd_dma = 0ULL; - // unsigned long long pgsteal_kswapd_dma32 = 0ULL; - // unsigned long long pgsteal_kswapd_movable = 0ULL; - // unsigned long long pgsteal_kswapd_normal = 0ULL; - unsigned long long pswpin = 0ULL; - unsigned long long pswpout = 0ULL; - // unsigned long long slabs_scanned = 0ULL; - // unsigned long long thp_collapse_alloc = 0ULL; - // unsigned long long thp_collapse_alloc_failed = 0ULL; - // unsigned long long thp_fault_alloc = 0ULL; - // unsigned long long thp_fault_fallback = 0ULL; - // unsigned long long thp_split = 0ULL; - // unsigned long long unevictable_pgs_cleared = 0ULL; - // unsigned long long unevictable_pgs_culled = 0ULL; - // unsigned long long unevictable_pgs_mlocked = 0ULL; - // unsigned long long unevictable_pgs_mlockfreed = 0ULL; - // unsigned long long unevictable_pgs_munlocked = 0ULL; - // unsigned long long unevictable_pgs_rescued = 0ULL; - // unsigned long long unevictable_pgs_scanned = 0ULL; - // unsigned long long unevictable_pgs_stranded = 0ULL; + // unsigned long long allocstall = 0ULL; + // unsigned long long compact_blocks_moved = 0ULL; + // unsigned long long compact_fail = 0ULL; + // unsigned long long compact_pagemigrate_failed = 0ULL; + // unsigned long long compact_pages_moved = 0ULL; + // unsigned long long compact_stall = 0ULL; + // unsigned long long compact_success = 0ULL; + // unsigned long long htlb_buddy_alloc_fail = 0ULL; + // unsigned long long htlb_buddy_alloc_success = 0ULL; + // unsigned long long kswapd_high_wmark_hit_quickly = 0ULL; + // unsigned long long kswapd_inodesteal = 0ULL; + // unsigned long long kswapd_low_wmark_hit_quickly = 0ULL; + // unsigned long long kswapd_skip_congestion_wait = 0ULL; + // unsigned long long nr_active_anon = 0ULL; + // unsigned long long nr_active_file = 0ULL; + // unsigned long long nr_anon_pages = 0ULL; + // unsigned long long nr_anon_transparent_hugepages = 0ULL; + // unsigned long long nr_bounce = 0ULL; + // unsigned long long nr_dirtied = 0ULL; + // unsigned long long nr_dirty = 0ULL; + // unsigned long long nr_dirty_background_threshold = 0ULL; + // unsigned long long nr_dirty_threshold = 0ULL; + // unsigned long long nr_file_pages = 0ULL; + // unsigned long long nr_free_pages = 0ULL; + // unsigned long long nr_inactive_anon = 0ULL; + // unsigned long long nr_inactive_file = 0ULL; + // unsigned long long nr_isolated_anon = 0ULL; + // unsigned long long nr_isolated_file = 0ULL; + // unsigned long long nr_kernel_stack = 0ULL; + // unsigned long long nr_mapped = 0ULL; + // unsigned long long nr_mlock = 0ULL; + // unsigned long long nr_page_table_pages = 0ULL; + // unsigned long long nr_shmem = 0ULL; + // unsigned long long nr_slab_reclaimable = 0ULL; + // unsigned long long nr_slab_unreclaimable = 0ULL; + // unsigned long long nr_unevictable = 0ULL; + // unsigned long long nr_unstable = 0ULL; + // unsigned long long nr_vmscan_immediate_reclaim = 0ULL; + // unsigned long long nr_vmscan_write = 0ULL; + // unsigned long long nr_writeback = 0ULL; + // unsigned long long nr_writeback_temp = 0ULL; + // unsigned long long nr_written = 0ULL; + // unsigned long long pageoutrun = 0ULL; + // unsigned long long pgactivate = 0ULL; + // unsigned long long pgalloc_dma = 0ULL; + // unsigned long long pgalloc_dma32 = 0ULL; + // unsigned long long pgalloc_movable = 0ULL; + // unsigned long long pgalloc_normal = 0ULL; + // unsigned long long pgdeactivate = 0ULL; + unsigned long long pgfault = 0ULL; + // unsigned long long pgfree = 0ULL; + // unsigned long long pginodesteal = 0ULL; + unsigned long long pgmajfault = 0ULL; + unsigned long long pgpgin = 0ULL; + unsigned long long pgpgout = 0ULL; + // unsigned long long pgrefill_dma = 0ULL; + // unsigned long long pgrefill_dma32 = 0ULL; + // unsigned long long pgrefill_movable = 0ULL; + // unsigned long long pgrefill_normal = 0ULL; + // unsigned long long pgrotated = 0ULL; + // unsigned long long pgscan_direct_dma = 0ULL; + // unsigned long long pgscan_direct_dma32 = 0ULL; + // unsigned long long pgscan_direct_movable = 0ULL; + // unsigned long long pgscan_direct_normal = 0ULL; + // unsigned long long pgscan_kswapd_dma = 0ULL; + // unsigned long long pgscan_kswapd_dma32 = 0ULL; + // unsigned long long pgscan_kswapd_movable = 0ULL; + // unsigned long long pgscan_kswapd_normal = 0ULL; + // unsigned long long pgsteal_direct_dma = 0ULL; + // unsigned long long pgsteal_direct_dma32 = 0ULL; + // unsigned long long pgsteal_direct_movable = 0ULL; + // unsigned long long pgsteal_direct_normal = 0ULL; + // unsigned long long pgsteal_kswapd_dma = 0ULL; + // unsigned long long pgsteal_kswapd_dma32 = 0ULL; + // unsigned long long pgsteal_kswapd_movable = 0ULL; + // unsigned long long pgsteal_kswapd_normal = 0ULL; + unsigned long long pswpin = 0ULL; + unsigned long long pswpout = 0ULL; + // unsigned long long slabs_scanned = 0ULL; + // unsigned long long thp_collapse_alloc = 0ULL; + // unsigned long long thp_collapse_alloc_failed = 0ULL; + // unsigned long long thp_fault_alloc = 0ULL; + // unsigned long long thp_fault_fallback = 0ULL; + // unsigned long long thp_split = 0ULL; + // unsigned long long unevictable_pgs_cleared = 0ULL; + // unsigned long long unevictable_pgs_culled = 0ULL; + // unsigned long long unevictable_pgs_mlocked = 0ULL; + // unsigned long long unevictable_pgs_mlockfreed = 0ULL; + // unsigned long long unevictable_pgs_munlocked = 0ULL; + // unsigned long long unevictable_pgs_rescued = 0ULL; + // unsigned long long unevictable_pgs_scanned = 0ULL; + // unsigned long long unevictable_pgs_stranded = 0ULL; - for(l = 0; l < lines ;l++) { - words = procfile_linewords(ff, l); - if(words < 2) { - if(words) error("Cannot read /proc/vmstat line %d. Expected 2 params, read %d.", l, words); - continue; - } + for(l = 0; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(words < 2) { + if(words) error("Cannot read /proc/vmstat line %u. Expected 2 params, read %u.", l, words); + continue; + } - char *name = procfile_lineword(ff, l, 0); - char * value = procfile_lineword(ff, l, 1); - if(!name || !*name || !value || !*value) continue; + char *name = procfile_lineword(ff, l, 0); + char * value = procfile_lineword(ff, l, 1); + if(!name || !*name || !value || !*value) continue; - uint32_t hash = simple_hash(name); + uint32_t hash = simple_hash(name); - if(0) ; - // else if(hash == hash_allocstall && strcmp(name, "allocstall") == 0) allocstall = strtoull(value, NULL, 10); - // else if(hash == hash_compact_blocks_moved && strcmp(name, "compact_blocks_moved") == 0) compact_blocks_moved = strtoull(value, NULL, 10); - // else if(hash == hash_compact_fail && strcmp(name, "compact_fail") == 0) compact_fail = strtoull(value, NULL, 10); - // else if(hash == hash_compact_pagemigrate_failed && strcmp(name, "compact_pagemigrate_failed") == 0) compact_pagemigrate_failed = strtoull(value, NULL, 10); - // else if(hash == hash_compact_pages_moved && strcmp(name, "compact_pages_moved") == 0) compact_pages_moved = strtoull(value, NULL, 10); - // else if(hash == hash_compact_stall && strcmp(name, "compact_stall") == 0) compact_stall = strtoull(value, NULL, 10); - // else if(hash == hash_compact_success && strcmp(name, "compact_success") == 0) compact_success = strtoull(value, NULL, 10); - // else if(hash == hash_htlb_buddy_alloc_fail && strcmp(name, "htlb_buddy_alloc_fail") == 0) htlb_buddy_alloc_fail = strtoull(value, NULL, 10); - // else if(hash == hash_htlb_buddy_alloc_success && strcmp(name, "htlb_buddy_alloc_success") == 0) htlb_buddy_alloc_success = strtoull(value, NULL, 10); - // else if(hash == hash_kswapd_high_wmark_hit_quickly && strcmp(name, "kswapd_high_wmark_hit_quickly") == 0) kswapd_high_wmark_hit_quickly = strtoull(value, NULL, 10); - // else if(hash == hash_kswapd_inodesteal && strcmp(name, "kswapd_inodesteal") == 0) kswapd_inodesteal = strtoull(value, NULL, 10); - // else if(hash == hash_kswapd_low_wmark_hit_quickly && strcmp(name, "kswapd_low_wmark_hit_quickly") == 0) kswapd_low_wmark_hit_quickly = strtoull(value, NULL, 10); - // else if(hash == hash_kswapd_skip_congestion_wait && strcmp(name, "kswapd_skip_congestion_wait") == 0) kswapd_skip_congestion_wait = strtoull(value, NULL, 10); - // else if(hash == hash_nr_active_anon && strcmp(name, "nr_active_anon") == 0) nr_active_anon = strtoull(value, NULL, 10); - // else if(hash == hash_nr_active_file && strcmp(name, "nr_active_file") == 0) nr_active_file = strtoull(value, NULL, 10); - // else if(hash == hash_nr_anon_pages && strcmp(name, "nr_anon_pages") == 0) nr_anon_pages = strtoull(value, NULL, 10); - // else if(hash == hash_nr_anon_transparent_hugepages && strcmp(name, "nr_anon_transparent_hugepages") == 0) nr_anon_transparent_hugepages = strtoull(value, NULL, 10); - // else if(hash == hash_nr_bounce && strcmp(name, "nr_bounce") == 0) nr_bounce = strtoull(value, NULL, 10); - // else if(hash == hash_nr_dirtied && strcmp(name, "nr_dirtied") == 0) nr_dirtied = strtoull(value, NULL, 10); - // else if(hash == hash_nr_dirty && strcmp(name, "nr_dirty") == 0) nr_dirty = strtoull(value, NULL, 10); - // else if(hash == hash_nr_dirty_background_threshold && strcmp(name, "nr_dirty_background_threshold") == 0) nr_dirty_background_threshold = strtoull(value, NULL, 10); - // else if(hash == hash_nr_dirty_threshold && strcmp(name, "nr_dirty_threshold") == 0) nr_dirty_threshold = strtoull(value, NULL, 10); - // else if(hash == hash_nr_file_pages && strcmp(name, "nr_file_pages") == 0) nr_file_pages = strtoull(value, NULL, 10); - // else if(hash == hash_nr_free_pages && strcmp(name, "nr_free_pages") == 0) nr_free_pages = strtoull(value, NULL, 10); - // else if(hash == hash_nr_inactive_anon && strcmp(name, "nr_inactive_anon") == 0) nr_inactive_anon = strtoull(value, NULL, 10); - // else if(hash == hash_nr_inactive_file && strcmp(name, "nr_inactive_file") == 0) nr_inactive_file = strtoull(value, NULL, 10); - // else if(hash == hash_nr_isolated_anon && strcmp(name, "nr_isolated_anon") == 0) nr_isolated_anon = strtoull(value, NULL, 10); - // else if(hash == hash_nr_isolated_file && strcmp(name, "nr_isolated_file") == 0) nr_isolated_file = strtoull(value, NULL, 10); - // else if(hash == hash_nr_kernel_stack && strcmp(name, "nr_kernel_stack") == 0) nr_kernel_stack = strtoull(value, NULL, 10); - // else if(hash == hash_nr_mapped && strcmp(name, "nr_mapped") == 0) nr_mapped = strtoull(value, NULL, 10); - // else if(hash == hash_nr_mlock && strcmp(name, "nr_mlock") == 0) nr_mlock = strtoull(value, NULL, 10); - // else if(hash == hash_nr_page_table_pages && strcmp(name, "nr_page_table_pages") == 0) nr_page_table_pages = strtoull(value, NULL, 10); - // else if(hash == hash_nr_shmem && strcmp(name, "nr_shmem") == 0) nr_shmem = strtoull(value, NULL, 10); - // else if(hash == hash_nr_slab_reclaimable && strcmp(name, "nr_slab_reclaimable") == 0) nr_slab_reclaimable = strtoull(value, NULL, 10); - // else if(hash == hash_nr_slab_unreclaimable && strcmp(name, "nr_slab_unreclaimable") == 0) nr_slab_unreclaimable = strtoull(value, NULL, 10); - // else if(hash == hash_nr_unevictable && strcmp(name, "nr_unevictable") == 0) nr_unevictable = strtoull(value, NULL, 10); - // else if(hash == hash_nr_unstable && strcmp(name, "nr_unstable") == 0) nr_unstable = strtoull(value, NULL, 10); - // else if(hash == hash_nr_vmscan_immediate_reclaim && strcmp(name, "nr_vmscan_immediate_reclaim") == 0) nr_vmscan_immediate_reclaim = strtoull(value, NULL, 10); - // else if(hash == hash_nr_vmscan_write && strcmp(name, "nr_vmscan_write") == 0) nr_vmscan_write = strtoull(value, NULL, 10); - // else if(hash == hash_nr_writeback && strcmp(name, "nr_writeback") == 0) nr_writeback = strtoull(value, NULL, 10); - // else if(hash == hash_nr_writeback_temp && strcmp(name, "nr_writeback_temp") == 0) nr_writeback_temp = strtoull(value, NULL, 10); - // else if(hash == hash_nr_written && strcmp(name, "nr_written") == 0) nr_written = strtoull(value, NULL, 10); - // else if(hash == hash_pageoutrun && strcmp(name, "pageoutrun") == 0) pageoutrun = strtoull(value, NULL, 10); - // else if(hash == hash_pgactivate && strcmp(name, "pgactivate") == 0) pgactivate = strtoull(value, NULL, 10); - // else if(hash == hash_pgalloc_dma && strcmp(name, "pgalloc_dma") == 0) pgalloc_dma = strtoull(value, NULL, 10); - // else if(hash == hash_pgalloc_dma32 && strcmp(name, "pgalloc_dma32") == 0) pgalloc_dma32 = strtoull(value, NULL, 10); - // else if(hash == hash_pgalloc_movable && strcmp(name, "pgalloc_movable") == 0) pgalloc_movable = strtoull(value, NULL, 10); - // else if(hash == hash_pgalloc_normal && strcmp(name, "pgalloc_normal") == 0) pgalloc_normal = strtoull(value, NULL, 10); - // else if(hash == hash_pgdeactivate && strcmp(name, "pgdeactivate") == 0) pgdeactivate = strtoull(value, NULL, 10); - else if(hash == hash_pgfault && strcmp(name, "pgfault") == 0) pgfault = strtoull(value, NULL, 10); - // else if(hash == hash_pgfree && strcmp(name, "pgfree") == 0) pgfree = strtoull(value, NULL, 10); - // else if(hash == hash_pginodesteal && strcmp(name, "pginodesteal") == 0) pginodesteal = strtoull(value, NULL, 10); - else if(hash == hash_pgmajfault && strcmp(name, "pgmajfault") == 0) pgmajfault = strtoull(value, NULL, 10); - else if(hash == hash_pgpgin && strcmp(name, "pgpgin") == 0) pgpgin = strtoull(value, NULL, 10); - else if(hash == hash_pgpgout && strcmp(name, "pgpgout") == 0) pgpgout = strtoull(value, NULL, 10); - // else if(hash == hash_pgrefill_dma && strcmp(name, "pgrefill_dma") == 0) pgrefill_dma = strtoull(value, NULL, 10); - // else if(hash == hash_pgrefill_dma32 && strcmp(name, "pgrefill_dma32") == 0) pgrefill_dma32 = strtoull(value, NULL, 10); - // else if(hash == hash_pgrefill_movable && strcmp(name, "pgrefill_movable") == 0) pgrefill_movable = strtoull(value, NULL, 10); - // else if(hash == hash_pgrefill_normal && strcmp(name, "pgrefill_normal") == 0) pgrefill_normal = strtoull(value, NULL, 10); - // else if(hash == hash_pgrotated && strcmp(name, "pgrotated") == 0) pgrotated = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_direct_dma && strcmp(name, "pgscan_direct_dma") == 0) pgscan_direct_dma = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_direct_dma32 && strcmp(name, "pgscan_direct_dma32") == 0) pgscan_direct_dma32 = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_direct_movable && strcmp(name, "pgscan_direct_movable") == 0) pgscan_direct_movable = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_direct_normal && strcmp(name, "pgscan_direct_normal") == 0) pgscan_direct_normal = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_kswapd_dma && strcmp(name, "pgscan_kswapd_dma") == 0) pgscan_kswapd_dma = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_kswapd_dma32 && strcmp(name, "pgscan_kswapd_dma32") == 0) pgscan_kswapd_dma32 = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_kswapd_movable && strcmp(name, "pgscan_kswapd_movable") == 0) pgscan_kswapd_movable = strtoull(value, NULL, 10); - // else if(hash == hash_pgscan_kswapd_normal && strcmp(name, "pgscan_kswapd_normal") == 0) pgscan_kswapd_normal = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_direct_dma && strcmp(name, "pgsteal_direct_dma") == 0) pgsteal_direct_dma = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_direct_dma32 && strcmp(name, "pgsteal_direct_dma32") == 0) pgsteal_direct_dma32 = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_direct_movable && strcmp(name, "pgsteal_direct_movable") == 0) pgsteal_direct_movable = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_direct_normal && strcmp(name, "pgsteal_direct_normal") == 0) pgsteal_direct_normal = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_kswapd_dma && strcmp(name, "pgsteal_kswapd_dma") == 0) pgsteal_kswapd_dma = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_kswapd_dma32 && strcmp(name, "pgsteal_kswapd_dma32") == 0) pgsteal_kswapd_dma32 = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_kswapd_movable && strcmp(name, "pgsteal_kswapd_movable") == 0) pgsteal_kswapd_movable = strtoull(value, NULL, 10); - // else if(hash == hash_pgsteal_kswapd_normal && strcmp(name, "pgsteal_kswapd_normal") == 0) pgsteal_kswapd_normal = strtoull(value, NULL, 10); - else if(hash == hash_pswpin && strcmp(name, "pswpin") == 0) pswpin = strtoull(value, NULL, 10); - else if(hash == hash_pswpout && strcmp(name, "pswpout") == 0) pswpout = strtoull(value, NULL, 10); - // else if(hash == hash_slabs_scanned && strcmp(name, "slabs_scanned") == 0) slabs_scanned = strtoull(value, NULL, 10); - // else if(hash == hash_thp_collapse_alloc && strcmp(name, "thp_collapse_alloc") == 0) thp_collapse_alloc = strtoull(value, NULL, 10); - // else if(hash == hash_thp_collapse_alloc_failed && strcmp(name, "thp_collapse_alloc_failed") == 0) thp_collapse_alloc_failed = strtoull(value, NULL, 10); - // else if(hash == hash_thp_fault_alloc && strcmp(name, "thp_fault_alloc") == 0) thp_fault_alloc = strtoull(value, NULL, 10); - // else if(hash == hash_thp_fault_fallback && strcmp(name, "thp_fault_fallback") == 0) thp_fault_fallback = strtoull(value, NULL, 10); - // else if(hash == hash_thp_split && strcmp(name, "thp_split") == 0) thp_split = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_cleared && strcmp(name, "unevictable_pgs_cleared") == 0) unevictable_pgs_cleared = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_culled && strcmp(name, "unevictable_pgs_culled") == 0) unevictable_pgs_culled = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_mlocked && strcmp(name, "unevictable_pgs_mlocked") == 0) unevictable_pgs_mlocked = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_mlockfreed && strcmp(name, "unevictable_pgs_mlockfreed") == 0) unevictable_pgs_mlockfreed = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_munlocked && strcmp(name, "unevictable_pgs_munlocked") == 0) unevictable_pgs_munlocked = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_rescued && strcmp(name, "unevictable_pgs_rescued") == 0) unevictable_pgs_rescued = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_scanned && strcmp(name, "unevictable_pgs_scanned") == 0) unevictable_pgs_scanned = strtoull(value, NULL, 10); - // else if(hash == hash_unevictable_pgs_stranded && strcmp(name, "unevictable_pgs_stranded") == 0) unevictable_pgs_stranded = strtoull(value, NULL, 10); - } + if(0) ; + // else if(hash == hash_allocstall && strcmp(name, "allocstall") == 0) allocstall = strtoull(value, NULL, 10); + // else if(hash == hash_compact_blocks_moved && strcmp(name, "compact_blocks_moved") == 0) compact_blocks_moved = strtoull(value, NULL, 10); + // else if(hash == hash_compact_fail && strcmp(name, "compact_fail") == 0) compact_fail = strtoull(value, NULL, 10); + // else if(hash == hash_compact_pagemigrate_failed && strcmp(name, "compact_pagemigrate_failed") == 0) compact_pagemigrate_failed = strtoull(value, NULL, 10); + // else if(hash == hash_compact_pages_moved && strcmp(name, "compact_pages_moved") == 0) compact_pages_moved = strtoull(value, NULL, 10); + // else if(hash == hash_compact_stall && strcmp(name, "compact_stall") == 0) compact_stall = strtoull(value, NULL, 10); + // else if(hash == hash_compact_success && strcmp(name, "compact_success") == 0) compact_success = strtoull(value, NULL, 10); + // else if(hash == hash_htlb_buddy_alloc_fail && strcmp(name, "htlb_buddy_alloc_fail") == 0) htlb_buddy_alloc_fail = strtoull(value, NULL, 10); + // else if(hash == hash_htlb_buddy_alloc_success && strcmp(name, "htlb_buddy_alloc_success") == 0) htlb_buddy_alloc_success = strtoull(value, NULL, 10); + // else if(hash == hash_kswapd_high_wmark_hit_quickly && strcmp(name, "kswapd_high_wmark_hit_quickly") == 0) kswapd_high_wmark_hit_quickly = strtoull(value, NULL, 10); + // else if(hash == hash_kswapd_inodesteal && strcmp(name, "kswapd_inodesteal") == 0) kswapd_inodesteal = strtoull(value, NULL, 10); + // else if(hash == hash_kswapd_low_wmark_hit_quickly && strcmp(name, "kswapd_low_wmark_hit_quickly") == 0) kswapd_low_wmark_hit_quickly = strtoull(value, NULL, 10); + // else if(hash == hash_kswapd_skip_congestion_wait && strcmp(name, "kswapd_skip_congestion_wait") == 0) kswapd_skip_congestion_wait = strtoull(value, NULL, 10); + // else if(hash == hash_nr_active_anon && strcmp(name, "nr_active_anon") == 0) nr_active_anon = strtoull(value, NULL, 10); + // else if(hash == hash_nr_active_file && strcmp(name, "nr_active_file") == 0) nr_active_file = strtoull(value, NULL, 10); + // else if(hash == hash_nr_anon_pages && strcmp(name, "nr_anon_pages") == 0) nr_anon_pages = strtoull(value, NULL, 10); + // else if(hash == hash_nr_anon_transparent_hugepages && strcmp(name, "nr_anon_transparent_hugepages") == 0) nr_anon_transparent_hugepages = strtoull(value, NULL, 10); + // else if(hash == hash_nr_bounce && strcmp(name, "nr_bounce") == 0) nr_bounce = strtoull(value, NULL, 10); + // else if(hash == hash_nr_dirtied && strcmp(name, "nr_dirtied") == 0) nr_dirtied = strtoull(value, NULL, 10); + // else if(hash == hash_nr_dirty && strcmp(name, "nr_dirty") == 0) nr_dirty = strtoull(value, NULL, 10); + // else if(hash == hash_nr_dirty_background_threshold && strcmp(name, "nr_dirty_background_threshold") == 0) nr_dirty_background_threshold = strtoull(value, NULL, 10); + // else if(hash == hash_nr_dirty_threshold && strcmp(name, "nr_dirty_threshold") == 0) nr_dirty_threshold = strtoull(value, NULL, 10); + // else if(hash == hash_nr_file_pages && strcmp(name, "nr_file_pages") == 0) nr_file_pages = strtoull(value, NULL, 10); + // else if(hash == hash_nr_free_pages && strcmp(name, "nr_free_pages") == 0) nr_free_pages = strtoull(value, NULL, 10); + // else if(hash == hash_nr_inactive_anon && strcmp(name, "nr_inactive_anon") == 0) nr_inactive_anon = strtoull(value, NULL, 10); + // else if(hash == hash_nr_inactive_file && strcmp(name, "nr_inactive_file") == 0) nr_inactive_file = strtoull(value, NULL, 10); + // else if(hash == hash_nr_isolated_anon && strcmp(name, "nr_isolated_anon") == 0) nr_isolated_anon = strtoull(value, NULL, 10); + // else if(hash == hash_nr_isolated_file && strcmp(name, "nr_isolated_file") == 0) nr_isolated_file = strtoull(value, NULL, 10); + // else if(hash == hash_nr_kernel_stack && strcmp(name, "nr_kernel_stack") == 0) nr_kernel_stack = strtoull(value, NULL, 10); + // else if(hash == hash_nr_mapped && strcmp(name, "nr_mapped") == 0) nr_mapped = strtoull(value, NULL, 10); + // else if(hash == hash_nr_mlock && strcmp(name, "nr_mlock") == 0) nr_mlock = strtoull(value, NULL, 10); + // else if(hash == hash_nr_page_table_pages && strcmp(name, "nr_page_table_pages") == 0) nr_page_table_pages = strtoull(value, NULL, 10); + // else if(hash == hash_nr_shmem && strcmp(name, "nr_shmem") == 0) nr_shmem = strtoull(value, NULL, 10); + // else if(hash == hash_nr_slab_reclaimable && strcmp(name, "nr_slab_reclaimable") == 0) nr_slab_reclaimable = strtoull(value, NULL, 10); + // else if(hash == hash_nr_slab_unreclaimable && strcmp(name, "nr_slab_unreclaimable") == 0) nr_slab_unreclaimable = strtoull(value, NULL, 10); + // else if(hash == hash_nr_unevictable && strcmp(name, "nr_unevictable") == 0) nr_unevictable = strtoull(value, NULL, 10); + // else if(hash == hash_nr_unstable && strcmp(name, "nr_unstable") == 0) nr_unstable = strtoull(value, NULL, 10); + // else if(hash == hash_nr_vmscan_immediate_reclaim && strcmp(name, "nr_vmscan_immediate_reclaim") == 0) nr_vmscan_immediate_reclaim = strtoull(value, NULL, 10); + // else if(hash == hash_nr_vmscan_write && strcmp(name, "nr_vmscan_write") == 0) nr_vmscan_write = strtoull(value, NULL, 10); + // else if(hash == hash_nr_writeback && strcmp(name, "nr_writeback") == 0) nr_writeback = strtoull(value, NULL, 10); + // else if(hash == hash_nr_writeback_temp && strcmp(name, "nr_writeback_temp") == 0) nr_writeback_temp = strtoull(value, NULL, 10); + // else if(hash == hash_nr_written && strcmp(name, "nr_written") == 0) nr_written = strtoull(value, NULL, 10); + // else if(hash == hash_pageoutrun && strcmp(name, "pageoutrun") == 0) pageoutrun = strtoull(value, NULL, 10); + // else if(hash == hash_pgactivate && strcmp(name, "pgactivate") == 0) pgactivate = strtoull(value, NULL, 10); + // else if(hash == hash_pgalloc_dma && strcmp(name, "pgalloc_dma") == 0) pgalloc_dma = strtoull(value, NULL, 10); + // else if(hash == hash_pgalloc_dma32 && strcmp(name, "pgalloc_dma32") == 0) pgalloc_dma32 = strtoull(value, NULL, 10); + // else if(hash == hash_pgalloc_movable && strcmp(name, "pgalloc_movable") == 0) pgalloc_movable = strtoull(value, NULL, 10); + // else if(hash == hash_pgalloc_normal && strcmp(name, "pgalloc_normal") == 0) pgalloc_normal = strtoull(value, NULL, 10); + // else if(hash == hash_pgdeactivate && strcmp(name, "pgdeactivate") == 0) pgdeactivate = strtoull(value, NULL, 10); + else if(hash == hash_pgfault && strcmp(name, "pgfault") == 0) pgfault = strtoull(value, NULL, 10); + // else if(hash == hash_pgfree && strcmp(name, "pgfree") == 0) pgfree = strtoull(value, NULL, 10); + // else if(hash == hash_pginodesteal && strcmp(name, "pginodesteal") == 0) pginodesteal = strtoull(value, NULL, 10); + else if(hash == hash_pgmajfault && strcmp(name, "pgmajfault") == 0) pgmajfault = strtoull(value, NULL, 10); + else if(hash == hash_pgpgin && strcmp(name, "pgpgin") == 0) pgpgin = strtoull(value, NULL, 10); + else if(hash == hash_pgpgout && strcmp(name, "pgpgout") == 0) pgpgout = strtoull(value, NULL, 10); + // else if(hash == hash_pgrefill_dma && strcmp(name, "pgrefill_dma") == 0) pgrefill_dma = strtoull(value, NULL, 10); + // else if(hash == hash_pgrefill_dma32 && strcmp(name, "pgrefill_dma32") == 0) pgrefill_dma32 = strtoull(value, NULL, 10); + // else if(hash == hash_pgrefill_movable && strcmp(name, "pgrefill_movable") == 0) pgrefill_movable = strtoull(value, NULL, 10); + // else if(hash == hash_pgrefill_normal && strcmp(name, "pgrefill_normal") == 0) pgrefill_normal = strtoull(value, NULL, 10); + // else if(hash == hash_pgrotated && strcmp(name, "pgrotated") == 0) pgrotated = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_direct_dma && strcmp(name, "pgscan_direct_dma") == 0) pgscan_direct_dma = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_direct_dma32 && strcmp(name, "pgscan_direct_dma32") == 0) pgscan_direct_dma32 = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_direct_movable && strcmp(name, "pgscan_direct_movable") == 0) pgscan_direct_movable = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_direct_normal && strcmp(name, "pgscan_direct_normal") == 0) pgscan_direct_normal = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_kswapd_dma && strcmp(name, "pgscan_kswapd_dma") == 0) pgscan_kswapd_dma = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_kswapd_dma32 && strcmp(name, "pgscan_kswapd_dma32") == 0) pgscan_kswapd_dma32 = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_kswapd_movable && strcmp(name, "pgscan_kswapd_movable") == 0) pgscan_kswapd_movable = strtoull(value, NULL, 10); + // else if(hash == hash_pgscan_kswapd_normal && strcmp(name, "pgscan_kswapd_normal") == 0) pgscan_kswapd_normal = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_direct_dma && strcmp(name, "pgsteal_direct_dma") == 0) pgsteal_direct_dma = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_direct_dma32 && strcmp(name, "pgsteal_direct_dma32") == 0) pgsteal_direct_dma32 = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_direct_movable && strcmp(name, "pgsteal_direct_movable") == 0) pgsteal_direct_movable = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_direct_normal && strcmp(name, "pgsteal_direct_normal") == 0) pgsteal_direct_normal = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_kswapd_dma && strcmp(name, "pgsteal_kswapd_dma") == 0) pgsteal_kswapd_dma = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_kswapd_dma32 && strcmp(name, "pgsteal_kswapd_dma32") == 0) pgsteal_kswapd_dma32 = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_kswapd_movable && strcmp(name, "pgsteal_kswapd_movable") == 0) pgsteal_kswapd_movable = strtoull(value, NULL, 10); + // else if(hash == hash_pgsteal_kswapd_normal && strcmp(name, "pgsteal_kswapd_normal") == 0) pgsteal_kswapd_normal = strtoull(value, NULL, 10); + else if(hash == hash_pswpin && strcmp(name, "pswpin") == 0) pswpin = strtoull(value, NULL, 10); + else if(hash == hash_pswpout && strcmp(name, "pswpout") == 0) pswpout = strtoull(value, NULL, 10); + // else if(hash == hash_slabs_scanned && strcmp(name, "slabs_scanned") == 0) slabs_scanned = strtoull(value, NULL, 10); + // else if(hash == hash_thp_collapse_alloc && strcmp(name, "thp_collapse_alloc") == 0) thp_collapse_alloc = strtoull(value, NULL, 10); + // else if(hash == hash_thp_collapse_alloc_failed && strcmp(name, "thp_collapse_alloc_failed") == 0) thp_collapse_alloc_failed = strtoull(value, NULL, 10); + // else if(hash == hash_thp_fault_alloc && strcmp(name, "thp_fault_alloc") == 0) thp_fault_alloc = strtoull(value, NULL, 10); + // else if(hash == hash_thp_fault_fallback && strcmp(name, "thp_fault_fallback") == 0) thp_fault_fallback = strtoull(value, NULL, 10); + // else if(hash == hash_thp_split && strcmp(name, "thp_split") == 0) thp_split = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_cleared && strcmp(name, "unevictable_pgs_cleared") == 0) unevictable_pgs_cleared = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_culled && strcmp(name, "unevictable_pgs_culled") == 0) unevictable_pgs_culled = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_mlocked && strcmp(name, "unevictable_pgs_mlocked") == 0) unevictable_pgs_mlocked = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_mlockfreed && strcmp(name, "unevictable_pgs_mlockfreed") == 0) unevictable_pgs_mlockfreed = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_munlocked && strcmp(name, "unevictable_pgs_munlocked") == 0) unevictable_pgs_munlocked = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_rescued && strcmp(name, "unevictable_pgs_rescued") == 0) unevictable_pgs_rescued = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_scanned && strcmp(name, "unevictable_pgs_scanned") == 0) unevictable_pgs_scanned = strtoull(value, NULL, 10); + // else if(hash == hash_unevictable_pgs_stranded && strcmp(name, "unevictable_pgs_stranded") == 0) unevictable_pgs_stranded = strtoull(value, NULL, 10); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_swapio) { - static RRDSET *st_swapio = NULL; - if(!st_swapio) { - st_swapio = rrdset_create("system", "swapio", NULL, "swap", NULL, "Swap I/O", "kilobytes/s", 250, update_every, RRDSET_TYPE_AREA); + if(do_swapio) { + static RRDSET *st_swapio = NULL; + if(!st_swapio) { + st_swapio = rrdset_create("system", "swapio", NULL, "swap", NULL, "Swap I/O", "kilobytes/s", 250, update_every, RRDSET_TYPE_AREA); - rrddim_add(st_swapio, "in", NULL, sysconf(_SC_PAGESIZE), 1024, RRDDIM_INCREMENTAL); - rrddim_add(st_swapio, "out", NULL, -sysconf(_SC_PAGESIZE), 1024, RRDDIM_INCREMENTAL); - } - else rrdset_next(st_swapio); + rrddim_add(st_swapio, "in", NULL, sysconf(_SC_PAGESIZE), 1024, RRDDIM_INCREMENTAL); + rrddim_add(st_swapio, "out", NULL, -sysconf(_SC_PAGESIZE), 1024, RRDDIM_INCREMENTAL); + } + else rrdset_next(st_swapio); - rrddim_set(st_swapio, "in", pswpin); - rrddim_set(st_swapio, "out", pswpout); - rrdset_done(st_swapio); - } + rrddim_set(st_swapio, "in", pswpin); + rrddim_set(st_swapio, "out", pswpout); + rrdset_done(st_swapio); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_io) { - static RRDSET *st_io = NULL; - if(!st_io) { - st_io = rrdset_create("system", "io", NULL, "disk", NULL, "Disk I/O", "kilobytes/s", 150, update_every, RRDSET_TYPE_AREA); + if(do_io) { + static RRDSET *st_io = NULL; + if(!st_io) { + st_io = rrdset_create("system", "io", NULL, "disk", NULL, "Disk I/O", "kilobytes/s", 150, update_every, RRDSET_TYPE_AREA); - rrddim_add(st_io, "in", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st_io, "out", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st_io); + rrddim_add(st_io, "in", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st_io, "out", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st_io); - rrddim_set(st_io, "in", pgpgin); - rrddim_set(st_io, "out", pgpgout); - rrdset_done(st_io); - } + rrddim_set(st_io, "in", pgpgin); + rrddim_set(st_io, "out", pgpgout); + rrdset_done(st_io); + } - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(do_pgfaults) { - static RRDSET *st_pgfaults = NULL; - if(!st_pgfaults) { - st_pgfaults = rrdset_create("mem", "pgfaults", NULL, "system", NULL, "Memory Page Faults", "page faults/s", 500, update_every, RRDSET_TYPE_LINE); - st_pgfaults->isdetail = 1; + if(do_pgfaults) { + static RRDSET *st_pgfaults = NULL; + if(!st_pgfaults) { + st_pgfaults = rrdset_create("mem", "pgfaults", NULL, "system", NULL, "Memory Page Faults", "page faults/s", 500, update_every, RRDSET_TYPE_LINE); + st_pgfaults->isdetail = 1; - rrddim_add(st_pgfaults, "minor", NULL, 1, 1, RRDDIM_INCREMENTAL); - rrddim_add(st_pgfaults, "major", NULL, -1, 1, RRDDIM_INCREMENTAL); - } - else rrdset_next(st_pgfaults); + rrddim_add(st_pgfaults, "minor", NULL, 1, 1, RRDDIM_INCREMENTAL); + rrddim_add(st_pgfaults, "major", NULL, -1, 1, RRDDIM_INCREMENTAL); + } + else rrdset_next(st_pgfaults); - rrddim_set(st_pgfaults, "minor", pgfault); - rrddim_set(st_pgfaults, "major", pgmajfault); - rrdset_done(st_pgfaults); - } + rrddim_set(st_pgfaults, "minor", pgfault); + rrddim_set(st_pgfaults, "major", pgmajfault); + rrdset_done(st_pgfaults); + } - return 0; + return 0; } diff --git a/src/procfile.c b/src/procfile.c index 291f14519..e2aa60582 100644 --- a/src/procfile.c +++ b/src/procfile.c @@ -1,26 +1,4 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <errno.h> -#include <fcntl.h> -#include <string.h> -#include <malloc.h> -#include <ctype.h> -#include <time.h> -#include <sys/time.h> -#include <sys/wait.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/mman.h> - #include "common.h" -#include "log.h" -#include "procfile.h" -#include "../config.h" #define PF_PREFIX "PROCFILE" @@ -41,48 +19,40 @@ size_t procfile_max_allocation = PROCFILE_INCREMENT_BUFFER; pfwords *pfwords_add(pfwords *fw, char *str) { - // debug(D_PROCFILE, PF_PREFIX ": adding word No %d: '%s'", fw->len, str); + // debug(D_PROCFILE, PF_PREFIX ": adding word No %d: '%s'", fw->len, str); - if(unlikely(fw->len == fw->size)) { - // debug(D_PROCFILE, PF_PREFIX ": expanding words"); + if(unlikely(fw->len == fw->size)) { + // debug(D_PROCFILE, PF_PREFIX ": expanding words"); - pfwords *new = realloc(fw, sizeof(pfwords) + (fw->size + PFWORDS_INCREASE_STEP) * sizeof(char *)); - if(unlikely(!new)) { - error(PF_PREFIX ": failed to expand words"); - free(fw); - return NULL; - } - fw = new; - fw->size += PFWORDS_INCREASE_STEP; - } + fw = reallocz(fw, sizeof(pfwords) + (fw->size + PFWORDS_INCREASE_STEP) * sizeof(char *)); + fw->size += PFWORDS_INCREASE_STEP; + } - fw->words[fw->len++] = str; + fw->words[fw->len++] = str; - return fw; + return fw; } pfwords *pfwords_new(void) { - // debug(D_PROCFILE, PF_PREFIX ": initializing words"); - - uint32_t size = (procfile_adaptive_initial_allocation) ? procfile_max_words : PFWORDS_INCREASE_STEP; + // debug(D_PROCFILE, PF_PREFIX ": initializing words"); - pfwords *new = malloc(sizeof(pfwords) + size * sizeof(char *)); - if(unlikely(!new)) return NULL; + uint32_t size = (procfile_adaptive_initial_allocation) ? procfile_max_words : PFWORDS_INCREASE_STEP; - new->len = 0; - new->size = size; - return new; + pfwords *new = mallocz(sizeof(pfwords) + size * sizeof(char *)); + new->len = 0; + new->size = size; + return new; } void pfwords_reset(pfwords *fw) { - // debug(D_PROCFILE, PF_PREFIX ": reseting words"); - fw->len = 0; + // debug(D_PROCFILE, PF_PREFIX ": reseting words"); + fw->len = 0; } void pfwords_free(pfwords *fw) { - // debug(D_PROCFILE, PF_PREFIX ": freeing words"); + // debug(D_PROCFILE, PF_PREFIX ": freeing words"); - free(fw); + freez(fw); } @@ -90,422 +60,402 @@ void pfwords_free(pfwords *fw) { // An array of lines pflines *pflines_add(pflines *fl, uint32_t first_word) { - // debug(D_PROCFILE, PF_PREFIX ": adding line %d at word %d", fl->len, first_word); + // debug(D_PROCFILE, PF_PREFIX ": adding line %d at word %d", fl->len, first_word); - if(unlikely(fl->len == fl->size)) { - // debug(D_PROCFILE, PF_PREFIX ": expanding lines"); + if(unlikely(fl->len == fl->size)) { + // debug(D_PROCFILE, PF_PREFIX ": expanding lines"); - pflines *new = realloc(fl, sizeof(pflines) + (fl->size + PFLINES_INCREASE_STEP) * sizeof(ffline)); - if(unlikely(!new)) { - error(PF_PREFIX ": failed to expand lines"); - free(fl); - return NULL; - } - fl = new; - fl->size += PFLINES_INCREASE_STEP; - } + fl = reallocz(fl, sizeof(pflines) + (fl->size + PFLINES_INCREASE_STEP) * sizeof(ffline)); + fl->size += PFLINES_INCREASE_STEP; + } - fl->lines[fl->len].words = 0; - fl->lines[fl->len++].first = first_word; + fl->lines[fl->len].words = 0; + fl->lines[fl->len++].first = first_word; - return fl; + return fl; } pflines *pflines_new(void) { - // debug(D_PROCFILE, PF_PREFIX ": initializing lines"); - - uint32_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_words : PFLINES_INCREASE_STEP; + // debug(D_PROCFILE, PF_PREFIX ": initializing lines"); - pflines *new = malloc(sizeof(pflines) + size * sizeof(ffline)); - if(unlikely(!new)) return NULL; + uint32_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_words : PFLINES_INCREASE_STEP; - new->len = 0; - new->size = size; - return new; + pflines *new = mallocz(sizeof(pflines) + size * sizeof(ffline)); + new->len = 0; + new->size = size; + return new; } void pflines_reset(pflines *fl) { - // debug(D_PROCFILE, PF_PREFIX ": reseting lines"); + // debug(D_PROCFILE, PF_PREFIX ": reseting lines"); - fl->len = 0; + fl->len = 0; } void pflines_free(pflines *fl) { - // debug(D_PROCFILE, PF_PREFIX ": freeing lines"); + // debug(D_PROCFILE, PF_PREFIX ": freeing lines"); - free(fl); + freez(fl); } // ---------------------------------------------------------------------------- // The procfile -#define PF_CHAR_IS_SEPARATOR ' ' -#define PF_CHAR_IS_NEWLINE 'N' -#define PF_CHAR_IS_WORD 'W' +#define PF_CHAR_IS_SEPARATOR ' ' +#define PF_CHAR_IS_NEWLINE 'N' +#define PF_CHAR_IS_WORD 'W' #define PF_CHAR_IS_QUOTE 'Q' #define PF_CHAR_IS_OPEN 'O' #define PF_CHAR_IS_CLOSE 'C' void procfile_close(procfile *ff) { - debug(D_PROCFILE, PF_PREFIX ": Closing file '%s'", ff->filename); + debug(D_PROCFILE, PF_PREFIX ": Closing file '%s'", ff->filename); - if(likely(ff->lines)) pflines_free(ff->lines); - if(likely(ff->words)) pfwords_free(ff->words); + if(likely(ff->lines)) pflines_free(ff->lines); + if(likely(ff->words)) pfwords_free(ff->words); - if(likely(ff->fd != -1)) close(ff->fd); - free(ff); + if(likely(ff->fd != -1)) close(ff->fd); + freez(ff); } procfile *procfile_parser(procfile *ff) { - debug(D_PROCFILE, PF_PREFIX ": Parsing file '%s'", ff->filename); - - char *s = ff->data, *e = &ff->data[ff->len], *t = ff->data, quote = 0; - uint32_t l = 0, w = 0; - int opened = 0; - - ff->lines = pflines_add(ff->lines, w); - if(unlikely(!ff->lines)) goto cleanup; - - while(likely(s < e)) { - // we are not at the end - - switch(ff->separators[(int)(*s)]) { - case PF_CHAR_IS_OPEN: - if(s == t) { - opened++; - t = ++s; - } - else if(opened) { - opened++; - s++; - } - else - s++; - continue; - - case PF_CHAR_IS_CLOSE: - if(opened) { - opened--; - - if(!opened) { - *s = '\0'; - ff->words = pfwords_add(ff->words, t); - if(unlikely(!ff->words)) goto cleanup; - - ff->lines->lines[l].words++; - w++; - - t = ++s; - } - else - s++; - } - else - s++; - continue; - - case PF_CHAR_IS_QUOTE: - if(unlikely(!quote && s == t)) { - // quote opened at the beginning - quote = *s; - t = ++s; - } - else if(unlikely(quote && quote == *s)) { - // quote closed - quote = 0; - - *s = '\0'; - ff->words = pfwords_add(ff->words, t); - if(unlikely(!ff->words)) goto cleanup; - - ff->lines->lines[l].words++; - w++; - - t = ++s; - } - else - s++; - continue; - - case PF_CHAR_IS_SEPARATOR: - if(unlikely(quote || opened)) { - // we are inside a quote - s++; - continue; - } - - if(unlikely(s == t)) { - // skip all leading white spaces - t = ++s; - continue; - } - - // end of word - *s = '\0'; - - ff->words = pfwords_add(ff->words, t); - if(unlikely(!ff->words)) goto cleanup; - - ff->lines->lines[l].words++; - w++; - - t = ++s; - continue; - - case PF_CHAR_IS_NEWLINE: - // end of line - *s = '\0'; - - ff->words = pfwords_add(ff->words, t); - if(unlikely(!ff->words)) goto cleanup; - - ff->lines->lines[l].words++; - w++; - - // debug(D_PROCFILE, PF_PREFIX ": ended line %d with %d words", l, ff->lines->lines[l].words); - - ff->lines = pflines_add(ff->lines, w); - if(unlikely(!ff->lines)) goto cleanup; - l++; - - t = ++s; - continue; - - default: - s++; - continue; - } - } - - if(likely(s > t && t < e)) { - // the last word - if(likely(ff->len < ff->size)) - *s = '\0'; - else { - // we are going to loose the last byte - ff->data[ff->size - 1] = '\0'; - } - - ff->words = pfwords_add(ff->words, t); - if(unlikely(!ff->words)) goto cleanup; - - ff->lines->lines[l].words++; - w++; - } - - return ff; + debug(D_PROCFILE, PF_PREFIX ": Parsing file '%s'", ff->filename); + + char *s = ff->data, *e = &ff->data[ff->len], *t = ff->data, quote = 0; + uint32_t l = 0, w = 0; + int opened = 0; + + ff->lines = pflines_add(ff->lines, w); + if(unlikely(!ff->lines)) goto cleanup; + + while(likely(s < e)) { + // we are not at the end + + switch(ff->separators[(uint8_t)(*s)]) { + case PF_CHAR_IS_OPEN: + if(s == t) { + opened++; + t = ++s; + } + else if(opened) { + opened++; + s++; + } + else + s++; + continue; + + case PF_CHAR_IS_CLOSE: + if(opened) { + opened--; + + if(!opened) { + *s = '\0'; + ff->words = pfwords_add(ff->words, t); + if(unlikely(!ff->words)) goto cleanup; + + ff->lines->lines[l].words++; + w++; + + t = ++s; + } + else + s++; + } + else + s++; + continue; + + case PF_CHAR_IS_QUOTE: + if(unlikely(!quote && s == t)) { + // quote opened at the beginning + quote = *s; + t = ++s; + } + else if(unlikely(quote && quote == *s)) { + // quote closed + quote = 0; + + *s = '\0'; + ff->words = pfwords_add(ff->words, t); + if(unlikely(!ff->words)) goto cleanup; + + ff->lines->lines[l].words++; + w++; + + t = ++s; + } + else + s++; + continue; + + case PF_CHAR_IS_SEPARATOR: + if(unlikely(quote || opened)) { + // we are inside a quote + s++; + continue; + } + + if(unlikely(s == t)) { + // skip all leading white spaces + t = ++s; + continue; + } + + // end of word + *s = '\0'; + + ff->words = pfwords_add(ff->words, t); + if(unlikely(!ff->words)) goto cleanup; + + ff->lines->lines[l].words++; + w++; + + t = ++s; + continue; + + case PF_CHAR_IS_NEWLINE: + // end of line + *s = '\0'; + + ff->words = pfwords_add(ff->words, t); + if(unlikely(!ff->words)) goto cleanup; + + ff->lines->lines[l].words++; + w++; + + // debug(D_PROCFILE, PF_PREFIX ": ended line %d with %d words", l, ff->lines->lines[l].words); + + ff->lines = pflines_add(ff->lines, w); + if(unlikely(!ff->lines)) goto cleanup; + l++; + + t = ++s; + continue; + + default: + s++; + continue; + } + } + + if(likely(s > t && t < e)) { + // the last word + if(likely(ff->len < ff->size)) + *s = '\0'; + else { + // we are going to loose the last byte + ff->data[ff->size - 1] = '\0'; + } + + ff->words = pfwords_add(ff->words, t); + if(unlikely(!ff->words)) goto cleanup; + + ff->lines->lines[l].words++; + w++; + } + + return ff; cleanup: - error(PF_PREFIX ": Failed to parse file '%s'", ff->filename); - procfile_close(ff); - return NULL; + error(PF_PREFIX ": Failed to parse file '%s'", ff->filename); + procfile_close(ff); + return NULL; } procfile *procfile_readall(procfile *ff) { - debug(D_PROCFILE, PF_PREFIX ": Reading file '%s'.", ff->filename); - - ssize_t s, r = 1, x; - ff->len = 0; - - while(likely(r > 0)) { - s = ff->len; - x = ff->size - s; - - if(!x) { - debug(D_PROCFILE, PF_PREFIX ": Expanding data buffer for file '%s'.", ff->filename); - - procfile *new = realloc(ff, sizeof(procfile) + ff->size + PROCFILE_INCREMENT_BUFFER); - if(unlikely(!new)) { - error(PF_PREFIX ": Cannot allocate memory for file '%s'", ff->filename); - procfile_close(ff); - return NULL; - } - ff = new; - ff->size += PROCFILE_INCREMENT_BUFFER; - } - - debug(D_PROCFILE, "Reading file '%s', from position %ld with length %ld", ff->filename, s, ff->size - s); - r = read(ff->fd, &ff->data[s], ff->size - s); - if(unlikely(r == -1)) { - if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot read from file '%s'", ff->filename); - procfile_close(ff); - return NULL; - } - - ff->len += r; - } - - debug(D_PROCFILE, "Rewinding file '%s'", ff->filename); - if(unlikely(lseek(ff->fd, 0, SEEK_SET) == -1)) { - if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot rewind on file '%s'.", ff->filename); - procfile_close(ff); - return NULL; - } - - pflines_reset(ff->lines); - pfwords_reset(ff->words); - - ff = procfile_parser(ff); - - if(unlikely(procfile_adaptive_initial_allocation)) { - if(unlikely(ff->len > procfile_max_allocation)) procfile_max_allocation = ff->len; - if(unlikely(ff->lines->len > procfile_max_lines)) procfile_max_lines = ff->lines->len; - if(unlikely(ff->words->len > procfile_max_words)) procfile_max_words = ff->words->len; - } - - debug(D_PROCFILE, "File '%s' updated.", ff->filename); - return ff; + debug(D_PROCFILE, PF_PREFIX ": Reading file '%s'.", ff->filename); + + ssize_t s, r = 1, x; + ff->len = 0; + + while(likely(r > 0)) { + s = ff->len; + x = ff->size - s; + + if(!x) { + debug(D_PROCFILE, PF_PREFIX ": Expanding data buffer for file '%s'.", ff->filename); + + ff = reallocz(ff, sizeof(procfile) + ff->size + PROCFILE_INCREMENT_BUFFER); + ff->size += PROCFILE_INCREMENT_BUFFER; + } + + debug(D_PROCFILE, "Reading file '%s', from position %ld with length %lu", ff->filename, s, ff->size - s); + r = read(ff->fd, &ff->data[s], ff->size - s); + if(unlikely(r == -1)) { + if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot read from file '%s'", ff->filename); + procfile_close(ff); + return NULL; + } + + ff->len += r; + } + + debug(D_PROCFILE, "Rewinding file '%s'", ff->filename); + if(unlikely(lseek(ff->fd, 0, SEEK_SET) == -1)) { + if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot rewind on file '%s'.", ff->filename); + procfile_close(ff); + return NULL; + } + + pflines_reset(ff->lines); + pfwords_reset(ff->words); + + ff = procfile_parser(ff); + + if(unlikely(procfile_adaptive_initial_allocation)) { + if(unlikely(ff->len > procfile_max_allocation)) procfile_max_allocation = ff->len; + if(unlikely(ff->lines->len > procfile_max_lines)) procfile_max_lines = ff->lines->len; + if(unlikely(ff->words->len > procfile_max_words)) procfile_max_words = ff->words->len; + } + + debug(D_PROCFILE, "File '%s' updated.", ff->filename); + return ff; } static void procfile_set_separators(procfile *ff, const char *separators) { - static char def[256] = { [0 ... 255] = 0 }; - int i; - - if(unlikely(!def[255])) { - // this is thread safe - // we check that the last byte is non-zero - // if it is zero, multiple threads may be executing this at the same time - // setting in def[] the exact same values - for(i = 0; likely(i < 256) ;i++) { - if(unlikely(i == '\n' || i == '\r')) def[i] = PF_CHAR_IS_NEWLINE; - else if(unlikely(isspace(i) || !isprint(i))) def[i] = PF_CHAR_IS_SEPARATOR; - else def[i] = PF_CHAR_IS_WORD; - } - } - - // copy the default - char *ffs = ff->separators, *ffd = def, *ffe = &def[256]; - while(likely(ffd != ffe)) *ffs++ = *ffd++; - - // set the separators - if(unlikely(!separators)) - separators = " \t=|"; - - ffs = ff->separators; - const char *s = separators; - while(likely(*s)) - ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR; + static char def[256] = { [0 ... 255] = 0 }; + int i; + + if(unlikely(!def[255])) { + // this is thread safe + // we check that the last byte is non-zero + // if it is zero, multiple threads may be executing this at the same time + // setting in def[] the exact same values + for(i = 0; likely(i < 256) ;i++) { + if(unlikely(i == '\n' || i == '\r')) def[i] = PF_CHAR_IS_NEWLINE; + else if(unlikely(isspace(i) || !isprint(i))) def[i] = PF_CHAR_IS_SEPARATOR; + else def[i] = PF_CHAR_IS_WORD; + } + } + + // copy the default + char *ffs = ff->separators, *ffd = def, *ffe = &def[256]; + while(likely(ffd != ffe)) *ffs++ = *ffd++; + + // set the separators + if(unlikely(!separators)) + separators = " \t=|"; + + ffs = ff->separators; + const char *s = separators; + while(likely(*s)) + ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR; } void procfile_set_quotes(procfile *ff, const char *quotes) { - // remove all quotes - int i; - for(i = 0; i < 256 ; i++) - if(ff->separators[i] == PF_CHAR_IS_QUOTE) - ff->separators[i] = PF_CHAR_IS_WORD; - - // if nothing given, return - if(unlikely(!quotes || !*quotes)) - return; - - // set the quotes - char *ffs = ff->separators; - const char *s = quotes; - while(likely(*s)) - ffs[(int)*s++] = PF_CHAR_IS_QUOTE; + // remove all quotes + int i; + for(i = 0; i < 256 ; i++) + if(ff->separators[i] == PF_CHAR_IS_QUOTE) + ff->separators[i] = PF_CHAR_IS_WORD; + + // if nothing given, return + if(unlikely(!quotes || !*quotes)) + return; + + // set the quotes + char *ffs = ff->separators; + const char *s = quotes; + while(likely(*s)) + ffs[(int)*s++] = PF_CHAR_IS_QUOTE; } void procfile_set_open_close(procfile *ff, const char *open, const char *close) { - // remove all open/close - int i; - for(i = 0; i < 256 ; i++) - if(ff->separators[i] == PF_CHAR_IS_OPEN || ff->separators[i] == PF_CHAR_IS_CLOSE) - ff->separators[i] = PF_CHAR_IS_WORD; - - // if nothing given, return - if(unlikely(!open || !*open || !close || !*close)) - return; - - // set the openings - char *ffs = ff->separators; - const char *s = open; - while(likely(*s)) - ffs[(int)*s++] = PF_CHAR_IS_OPEN; - - s = close; - while(likely(*s)) - ffs[(int)*s++] = PF_CHAR_IS_CLOSE; + // remove all open/close + int i; + for(i = 0; i < 256 ; i++) + if(ff->separators[i] == PF_CHAR_IS_OPEN || ff->separators[i] == PF_CHAR_IS_CLOSE) + ff->separators[i] = PF_CHAR_IS_WORD; + + // if nothing given, return + if(unlikely(!open || !*open || !close || !*close)) + return; + + // set the openings + char *ffs = ff->separators; + const char *s = open; + while(likely(*s)) + ffs[(int)*s++] = PF_CHAR_IS_OPEN; + + s = close; + while(likely(*s)) + ffs[(int)*s++] = PF_CHAR_IS_CLOSE; } procfile *procfile_open(const char *filename, const char *separators, uint32_t flags) { - debug(D_PROCFILE, PF_PREFIX ": Opening file '%s'", filename); - - int fd = open(filename, O_RDONLY, 0666); - if(unlikely(fd == -1)) { - if(unlikely(!(flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot open file '%s'", filename); - return NULL; - } - - size_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_allocation : PROCFILE_INCREMENT_BUFFER; - procfile *ff = malloc(sizeof(procfile) + size); - if(unlikely(!ff)) { - error(PF_PREFIX ": Cannot allocate memory for file '%s'", filename); - close(fd); - return NULL; - } - - strncpyz(ff->filename, filename, FILENAME_MAX); - - ff->fd = fd; - ff->size = size; - ff->len = 0; - ff->flags = flags; - - ff->lines = pflines_new(); - ff->words = pfwords_new(); - - if(unlikely(!ff->lines || !ff->words)) { - error(PF_PREFIX ": Cannot initialize parser for file '%s'", filename); - procfile_close(ff); - return NULL; - } - - procfile_set_separators(ff, separators); - - debug(D_PROCFILE, "File '%s' opened.", filename); - return ff; + debug(D_PROCFILE, PF_PREFIX ": Opening file '%s'", filename); + + int fd = open(filename, O_RDONLY, 0666); + if(unlikely(fd == -1)) { + if(unlikely(!(flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot open file '%s'", filename); + return NULL; + } + + size_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_allocation : PROCFILE_INCREMENT_BUFFER; + procfile *ff = mallocz(sizeof(procfile) + size); + strncpyz(ff->filename, filename, FILENAME_MAX); + + ff->fd = fd; + ff->size = size; + ff->len = 0; + ff->flags = flags; + + ff->lines = pflines_new(); + ff->words = pfwords_new(); + + if(unlikely(!ff->lines || !ff->words)) { + error(PF_PREFIX ": Cannot initialize parser for file '%s'", filename); + procfile_close(ff); + return NULL; + } + + procfile_set_separators(ff, separators); + + debug(D_PROCFILE, "File '%s' opened.", filename); + return ff; } procfile *procfile_reopen(procfile *ff, const char *filename, const char *separators, uint32_t flags) { - if(unlikely(!ff)) return procfile_open(filename, separators, flags); + if(unlikely(!ff)) return procfile_open(filename, separators, flags); - if(likely(ff->fd != -1)) close(ff->fd); + if(likely(ff->fd != -1)) close(ff->fd); - ff->fd = open(filename, O_RDONLY, 0666); - if(unlikely(ff->fd == -1)) { - procfile_close(ff); - return NULL; - } + ff->fd = open(filename, O_RDONLY, 0666); + if(unlikely(ff->fd == -1)) { + procfile_close(ff); + return NULL; + } - strncpyz(ff->filename, filename, FILENAME_MAX); + strncpyz(ff->filename, filename, FILENAME_MAX); - ff->flags = flags; + ff->flags = flags; - // do not do the separators again if NULL is given - if(likely(separators)) procfile_set_separators(ff, separators); + // do not do the separators again if NULL is given + if(likely(separators)) procfile_set_separators(ff, separators); - return ff; + return ff; } // ---------------------------------------------------------------------------- // example parsing of procfile data void procfile_print(procfile *ff) { - uint32_t lines = procfile_lines(ff), l; - uint32_t words, w; - char *s; + uint32_t lines = procfile_lines(ff), l; + uint32_t words, w; + char *s; - debug(D_PROCFILE, "File '%s' with %d lines and %d words", ff->filename, ff->lines->len, ff->words->len); + debug(D_PROCFILE, "File '%s' with %u lines and %u words", ff->filename, ff->lines->len, ff->words->len); - for(l = 0; likely(l < lines) ;l++) { - words = procfile_linewords(ff, l); + for(l = 0; likely(l < lines) ;l++) { + words = procfile_linewords(ff, l); - debug(D_PROCFILE, " line %d starts at word %d and has %d words", l, ff->lines->lines[l].first, ff->lines->lines[l].words); + debug(D_PROCFILE, " line %u starts at word %u and has %u words", l, ff->lines->lines[l].first, ff->lines->lines[l].words); - for(w = 0; likely(w < words) ;w++) { - s = procfile_lineword(ff, l, w); - debug(D_PROCFILE, " [%d.%d] '%s'", l, w, s); - } - } + for(w = 0; likely(w < words) ;w++) { + s = procfile_lineword(ff, l, w); + debug(D_PROCFILE, " [%u.%u] '%s'", l, w, s); + } + } } diff --git a/src/procfile.h b/src/procfile.h index 122e153f1..5e00b2584 100644 --- a/src/procfile.h +++ b/src/procfile.h @@ -30,9 +30,9 @@ // An array of words typedef struct { - uint32_t len; // used entries - uint32_t size; // capacity - char *words[]; // array of pointers + uint32_t len; // used entries + uint32_t size; // capacity + char *words[]; // array of pointers } pfwords; @@ -40,15 +40,15 @@ typedef struct { // An array of lines typedef struct { - uint32_t words; // how many words this line has - uint32_t first; // the id of the first word of this line - // in the words array + uint32_t words; // how many words this line has + uint32_t first; // the id of the first word of this line + // in the words array } ffline; typedef struct { - uint32_t len; // used entries - uint32_t size; // capacity - ffline lines[]; // array of lines + uint32_t len; // used entries + uint32_t size; // capacity + ffline lines[]; // array of lines } pflines; @@ -59,15 +59,15 @@ typedef struct { #define PROCFILE_FLAG_NO_ERROR_ON_FILE_IO 0x00000001 typedef struct { - char filename[FILENAME_MAX + 1]; - uint32_t flags; - int fd; // the file desriptor - size_t len; // the bytes we have placed into data - size_t size; // the bytes we have allocated for data - pflines *lines; - pfwords *words; - char separators[256]; - char data[]; // allocated buffer to keep file contents + char filename[FILENAME_MAX + 1]; + uint32_t flags; + int fd; // the file desriptor + size_t len; // the bytes we have placed into data + size_t size; // the bytes we have allocated for data + pflines *lines; + pfwords *words; + char separators[256]; + char data[]; // allocated buffer to keep file contents } procfile; // close the proc file and free all related memory diff --git a/src/registry.c b/src/registry.c index f39ce3e2e..f2319c478 100644 --- a/src/registry.c +++ b/src/registry.c @@ -1,28 +1,4 @@ -#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 @@ -63,59 +39,60 @@ // 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; + 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 + int verify_cookies_redirects; + + 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; @@ -126,9 +103,9 @@ struct registry { // 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 + 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; @@ -138,27 +115,27 @@ typedef struct url URL; // For each MACHINE-URL pair we keep this struct machine_url { - URL *url; // de-duplicated URL -// DICTIONARY *persons; // dictionary of PERSON * + 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 + 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 + char guid[36 + 1]; // the GUID - uint32_t links; // the number of PERSON_URLs linked to this machine + uint32_t links; // the number of PERSON_URLs linked to this machine - DICTIONARY *urls; // MACHINE_URL * + 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 + 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; @@ -168,28 +145,28 @@ typedef struct machine MACHINE; // for each PERSON-URL pair we keep this struct person_url { - URL *url; // de-duplicated URL - MACHINE *machine; // link the MACHINE of this 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 + 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 + 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 + char guid[36 + 1]; // the person GUID - DICTIONARY *urls; // dictionary of PERSON_URL * + 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 + 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; @@ -198,27 +175,27 @@ typedef struct person PERSON; // REGISTRY concurrency locking static inline void registry_persons_lock(void) { - pthread_mutex_lock(®istry.persons_lock); + pthread_mutex_lock(®istry.persons_lock); } static inline void registry_persons_unlock(void) { - pthread_mutex_unlock(®istry.persons_lock); + pthread_mutex_unlock(®istry.persons_lock); } static inline void registry_machines_lock(void) { - pthread_mutex_lock(®istry.machines_lock); + pthread_mutex_lock(®istry.machines_lock); } static inline void registry_machines_unlock(void) { - pthread_mutex_unlock(®istry.machines_lock); + pthread_mutex_unlock(®istry.machines_lock); } static inline void registry_urls_lock(void) { - pthread_mutex_lock(®istry.urls_lock); + pthread_mutex_lock(®istry.urls_lock); } static inline void registry_urls_unlock(void) { - pthread_mutex_unlock(®istry.urls_lock); + pthread_mutex_unlock(®istry.urls_lock); } // ideally, we should not lock the whole registry for @@ -226,13 +203,13 @@ static inline void registry_urls_unlock(void) { // however, to save the memory required for keeping a // mutex (40 bytes) per person, we do... static inline void registry_person_urls_lock(PERSON *p) { - (void)p; - pthread_mutex_lock(®istry.person_urls_lock); + (void)p; + pthread_mutex_lock(®istry.person_urls_lock); } static inline void registry_person_urls_unlock(PERSON *p) { - (void)p; - pthread_mutex_unlock(®istry.person_urls_lock); + (void)p; + pthread_mutex_unlock(®istry.person_urls_lock); } // ideally, we should not lock the whole registry for @@ -240,21 +217,21 @@ static inline void registry_person_urls_unlock(PERSON *p) { // however, to save the memory required for keeping a // mutex (40 bytes) per machine, we do... static inline void registry_machine_urls_lock(MACHINE *m) { - (void)m; - pthread_mutex_lock(®istry.machine_urls_lock); + (void)m; + pthread_mutex_lock(®istry.machine_urls_lock); } static inline void registry_machine_urls_unlock(MACHINE *m) { - (void)m; - pthread_mutex_unlock(®istry.machine_urls_lock); + (void)m; + pthread_mutex_unlock(®istry.machine_urls_lock); } static inline void registry_log_lock(void) { - pthread_mutex_lock(®istry.log_lock); + pthread_mutex_lock(®istry.log_lock); } static inline void registry_log_unlock(void) { - pthread_mutex_unlock(®istry.log_lock); + pthread_mutex_unlock(®istry.log_lock); } @@ -264,58 +241,58 @@ static inline void registry_log_unlock(void) { // 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); + 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); + if(strcmp(guid, result)) + info("Registry: source GUID '%s' and re-generated GUID '%s' differ!", guid, result); #endif /* NETDATA_INTERNAL_CHECKS */ - } + } - return 0; + 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:""; + char *s = name?name:""; - // skip leading spaces - while(*s && isspace(*s)) s++; + // 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 = ' '; + // make sure all spaces are a SPACE + char *t = s; + while(*t) { + if(unlikely(isspace(*t))) + *t = ' '; - t++; - } + t++; + } - // remove trailing spaces - while(--t >= s) { - if(*t == ' ') - *t = '\0'; - else - break; - } - t++; + // remove trailing spaces + while(--t >= s) { + if(*t == ' ') + *t = '\0'; + else + break; + } + t++; - if(likely(len)) - *len = (t - s); + if(likely(len)) + *len = (t - s); - return s; + return s; } static inline char *registry_fix_url(char *url, size_t *len) { - return registry_fix_machine_name(url, len); + return registry_fix_machine_name(url, len); } @@ -330,59 +307,57 @@ extern PERSON *registry_request_delete(char *person_guid, char *machine_guid, ch // 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); + // protection from too big URLs + if(urllen > registry.max_url_length) + urllen = registry.max_url_length; - // a simple strcpy() should do the job - // but I prefer to be safe, since the caller specified urllen - strncpyz(u->url, url, urllen); + debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): allocating %zu bytes", url, sizeof(URL) + urllen); + URL *u = mallocz(sizeof(URL) + urllen); - u->len = urllen; - u->links = 0; + // a simple strcpy() should do the job + // but I prefer to be safe, since the caller specified urllen + u->len = (uint16_t)urllen; + strncpyz(u->url, url, u->len); + u->links = 0; - registry.urls_memory += sizeof(URL) + urllen; + 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)); + debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): indexing it", url); + dictionary_set(registry.urls, u->url, u, sizeof(URL)); - return u; + return u; } static inline URL *registry_url_get(const char *url, size_t urllen) { - debug(D_REGISTRY, "Registry: registry_url_get('%s')", url); + debug(D_REGISTRY, "Registry: registry_url_get('%s')", url); - registry_urls_lock(); + registry_urls_lock(); - URL *u = dictionary_get(registry.urls, url); - if(!u) { - u = registry_url_allocate_nolock(url, urllen); - registry.urls_count++; - } + URL *u = dictionary_get(registry.urls, url); + if(!u) { + u = registry_url_allocate_nolock(url, urllen); + registry.urls_count++; + } - registry_urls_unlock(); + registry_urls_unlock(); - return u; + 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); + 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); + 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); + freez(u); + } + else + debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): URL has %u links left", u->url, u->links); } @@ -390,78 +365,76 @@ static inline void registry_url_unlink_nolock(URL *u) { // 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); + 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)); + 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)); + MACHINE_URL *mu = mallocz(sizeof(MACHINE_URL)); - // mu->persons = dictionary_create(DICTIONARY_FLAGS); - // dictionary_set(mu->persons, p->guid, p, sizeof(PERSON)); + // 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; + mu->first_t = mu->last_t = (uint32_t)when; + mu->usages = 1; + mu->url = u; + mu->flags = REGISTRY_URL_FLAGS_DEFAULT; - registry.machines_urls_memory += sizeof(MACHINE_URL); + 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); + 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; + 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)); + 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); + MACHINE *m = mallocz(sizeof(MACHINE)); - strncpyz(m->guid, machine_guid, 36); + 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); + 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; + m->first_t = m->last_t = (uint32_t)when; + m->usages = 0; - registry.machines_memory += sizeof(MACHINE); + registry.machines_memory += sizeof(MACHINE); - registry.machines_count++; - dictionary_set(registry.machines, m->guid, m, sizeof(MACHINE)); + registry.machines_count++; + dictionary_set(registry.machines, m->guid, m, sizeof(MACHINE)); - return m; + 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; + MACHINE *m = NULL; - registry_machines_lock(); + 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); - } - } + 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(); + registry_machines_unlock(); - return m; + return m; } @@ -469,101 +442,99 @@ static inline MACHINE *registry_machine_get(const char *machine_guid, time_t whe // 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); + 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; + // 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); + 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); + PERSON_URL *pu = mallocz(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); + // 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++; + 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; + 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); + 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; + 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 + // 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); + 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; + 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); + // 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); + freez(pu); - return tpu; + return tpu; } static inline PERSON *registry_person_allocate(const char *person_guid, time_t when) { - PERSON *p = NULL; + PERSON *p = NULL; - debug(D_REGISTRY, "Registry: registry_person_allocate('%s'): allocating new person, sizeof(PERSON)=%zu", (person_guid)?person_guid:"", sizeof(PERSON)); + 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."); + p = mallocz(sizeof(PERSON)); - if(!person_guid) { - for (; ;) { - uuid_t uuid; - uuid_generate(uuid); - uuid_unparse_lower(uuid, p->guid); + 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: 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); + 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; + p->first_t = p->last_t = when; + p->usages = 0; - registry.persons_memory += sizeof(PERSON); + registry.persons_memory += sizeof(PERSON); - registry.persons_count++; - dictionary_set(registry.persons, p->guid, p, sizeof(PERSON)); + registry.persons_count++; + dictionary_set(registry.persons, p->guid, p, sizeof(PERSON)); - return p; + return p; } @@ -572,258 +543,260 @@ static inline PERSON *registry_person_allocate(const char *person_guid, time_t w // 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; + PERSON *p = NULL; - registry_persons_lock(); + 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(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); + if(!p) p = registry_person_allocate(NULL, when); - registry_persons_unlock(); + registry_persons_unlock(); - return p; + 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; + 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 < (uint32_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 < (uint32_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); + 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); + 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; - } + 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 < (uint32_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)); + //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; + m->usages++; + if(likely(m->last_t < (uint32_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; - } + 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); + registry_machine_urls_unlock(m); - return mu; + 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; + 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(); - } + 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); + if(registry.log_fp) + fclose(registry.log_fp); - registry.log_fp = fopen(registry.log_filename, "a"); + 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; - } + 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; + 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; - } + 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(); + 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); + // 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_fp = NULL; - registry_log_open_nolock(); - } + 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; + 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 %zu 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 %zu 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; + } + } + + fclose(fp); + } + + // open the log again + registry_log_open_nolock(); + + return line; } @@ -831,176 +804,176 @@ int registry_log_load(void) { // 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); + 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; + 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); + // 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); + size_t urllen; + url = registry_fix_url(url, &urllen); - URL *u = registry_url_get(url, urllen); - PERSON *p = registry_person_get(person_guid, when); + 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_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_log('A', p, m, u, name); - registry.usages_count++; - return p; + 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; + 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; + (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; + 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); + // 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; - } + // 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); + 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; - } + 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); + 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); + dictionary_del(p->urls, dpu->url->url); + registry_url_unlink_nolock(dpu->url); + freez(dpu); - registry_person_urls_unlock(p); - return p; + 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; + 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; + 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 - } + if(mypu->machine == myrdata->find_this_machine) { + myrdata->result = mypu; + return -1; // this will also stop the walk through + } - return 0; // continue + return 0; // continue } MACHINE *registry_request_machine(char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) { - (void)when; + (void)when; - char mbuf[36 + 1]; + 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; + 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 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; - } + // 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 + // 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 }; + // 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); + // 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; + if(rdata.result) + return m; - return NULL; + return NULL; } @@ -1011,231 +984,243 @@ MACHINE *registry_request_machine(char *person_guid, char *machine_guid, char *u #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); +int registry_verify_cookies_redirects(void) { + return registry.verify_cookies_redirects; +} - snprintfz(w->cookie1, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s", p->guid, edate); +const char *registry_to_announce(void) { + return registry.registry_to_announce; +} - 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); +void registry_set_cookie(struct web_client *w, const char *guid) { + 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", guid, edate); + + if(registry.registry_domain && registry.registry_domain[0]) + snprintfz(w->cookie2, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Domain=%s; Expires=%s", guid, registry.registry_domain, edate); +} + +static inline void registry_set_person_cookie(struct web_client *w, PERSON *p) { + registry_set_cookie(w, p->guid); } 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); + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; + 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"); + buffer_strcat(w->response.data, "\n}\n"); } int registry_request_hello_json(struct web_client *w) { - registry_json_header(w, "hello", REGISTRY_STATUS_OK); + registry_json_header(w, "hello", REGISTRY_STATUS_OK); - buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"", - registry.registry_to_announce); + buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"", + registry.registry_to_announce); - registry_json_footer(w); - return 200; + 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); + registry_json_header(w, action, REGISTRY_STATUS_DISABLED); - buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"", - registry.registry_to_announce); + buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"", + registry.registry_to_announce); - registry_json_footer(w); - return 200; + 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; + 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; + 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, ","); + 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); + 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; + 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; + 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, ","); + 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); + buffer_sprintf(w->response.data, "\n\t\t[ \"%s\", \"%s\", %u000, %u ]", + m->guid, mu->url->url, mu->last_t, mu->usages); - return 1; + 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"); + 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; - } + 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); + // set the cookie + registry_set_person_cookie(w, p); - // generate the response - registry_json_header(w, "access", REGISTRY_STATUS_OK); + // 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"); + 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; + 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; + 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"); + 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; - } + 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); + 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"); + 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; + 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; + 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; + 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++; + if(pu->machine == m) + d->count++; - return 0; + 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; + (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; } @@ -1243,47 +1228,49 @@ int registry_request_switch_json(struct web_client *w, char *person_guid, char * // 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; + 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); + } + + setenv("NETDATA_REGISTRY_UNIQUE_ID", registry.machine_guid, 1); + + return registry.machine_guid; } @@ -1291,548 +1278,557 @@ char *registry_get_this_machine_guid(void) { // REGISTRY LOAD/SAVE int registry_machine_save_url(void *entry, void *file) { - MACHINE_URL *mu = entry; - FILE *fp = file; + MACHINE_URL *mu = entry; + FILE *fp = file; - debug(D_REGISTRY, "Registry: registry_machine_save_url('%s')", mu->url->url); + 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 - ); + 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() + // error handling is done at registry_save() - return ret; + return ret; } int registry_machine_save(void *entry, void *file) { - MACHINE *m = entry; - FILE *fp = file; + MACHINE *m = entry; + FILE *fp = file; - debug(D_REGISTRY, "Registry: registry_machine_save('%s')", m->guid); + 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 - ); + 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; - } + 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() + // error handling is done at registry_save() - return ret; + return ret; } static inline int registry_person_save_url(void *entry, void *file) { - PERSON_URL *pu = entry; - FILE *fp = file; + PERSON_URL *pu = entry; + FILE *fp = file; - debug(D_REGISTRY, "Registry: registry_person_save_url('%s')", pu->url->url); + 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 - ); + 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() + // error handling is done at registry_save() - return ret; + return ret; } static inline int registry_person_save(void *entry, void *file) { - PERSON *p = entry; - FILE *fp = file; + PERSON *p = entry; + FILE *fp = file; - debug(D_REGISTRY, "Registry: registry_person_save('%s')", p->guid); + 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 - ); + 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; - } + 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() + // error handling is done at registry_save() - return ret; + 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; + 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; + 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 %zu 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 %zu 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 %zu 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 %zu 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 %zu 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 %zu 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(®istry.persons_lock, NULL); - pthread_mutex_init(®istry.machines_lock, NULL); - pthread_mutex_init(®istry.urls_lock, NULL); - pthread_mutex_init(®istry.person_urls_lock, NULL); - pthread_mutex_init(®istry.machine_urls_lock, NULL); - - // create dictionaries - registry.persons = dictionary_create(DICTIONARY_FLAGS); - registry.machines = dictionary_create(DICTIONARY_FLAGS); - registry.urls = dictionary_create(DICTIONARY_FLAGS); - - // load the registry database - if(registry.enabled) { - registry_log_open_nolock(); - registry_load(); - registry_log_load(); - } - - return 0; + 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, 0770) == -1 && errno != EEXIST) + fatal("Cannot create directory '%s'.", registry.pathname); + + // 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.verify_cookies_redirects = config_get_boolean("registry", "verify browser cookies support", 1); + + setenv("NETDATA_REGISTRY_HOSTNAME", registry.hostname, 1); + setenv("NETDATA_REGISTRY_URL", registry.registry_to_announce, 1); + + registry.max_url_length = config_get_number("registry", "max URL length", 1024); + if(registry.max_url_length < 10) { + registry.max_url_length = 10; + config_set_number("registry", "max URL length", registry.max_url_length); + } + + registry.max_name_length = config_get_number("registry", "max URL name length", 50); + if(registry.max_name_length < 10) { + registry.max_name_length = 10; + config_set_number("registry", "max URL name length", registry.max_name_length); + } + + // initialize entries counters + registry.persons_count = 0; + registry.machines_count = 0; + registry.usages_count = 0; + registry.urls_count = 0; + registry.persons_urls_count = 0; + registry.machines_urls_count = 0; + + // initialize memory counters + registry.persons_memory = 0; + registry.machines_memory = 0; + registry.urls_memory = 0; + registry.persons_urls_memory = 0; + registry.machines_urls_memory = 0; + + // initialize locks + pthread_mutex_init(®istry.persons_lock, NULL); + pthread_mutex_init(®istry.machines_lock, NULL); + pthread_mutex_init(®istry.urls_lock, NULL); + pthread_mutex_init(®istry.person_urls_lock, NULL); + pthread_mutex_init(®istry.machine_urls_lock, NULL); + + // create dictionaries + registry.persons = dictionary_create(DICTIONARY_FLAGS); + registry.machines = dictionary_create(DICTIONARY_FLAGS); + registry.urls = dictionary_create(DICTIONARY_FLAGS); + + // load the registry database + if(registry.enabled) { + registry_log_open_nolock(); + registry_load(); + registry_log_load(); + } + + return 0; } void registry_free(void) { - if(!registry.enabled) return; + if(!registry.enabled) return; - // we need to destroy the dictionaries ourselves - // since the dictionaries use memory we allocated + // 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; + 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); + // 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; + 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); + // 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: 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: 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: freeing person url"); + freez(pu); + } - debug(D_REGISTRY, "Registry: deleting person '%s' from persons registry", p->guid); - dictionary_del(registry.persons, p->guid); + 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: destroying URL dictionary of person '%s'", p->guid); + dictionary_destroy(p->urls); - debug(D_REGISTRY, "Registry: freeing person '%s'", p->guid); - free(p); - } + debug(D_REGISTRY, "Registry: freeing person '%s'", p->guid); + freez(p); + } - while(registry.machines->values_index.root) { - MACHINE *m = ((NAME_VALUE *)registry.machines->values_index.root)->value; + 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); + // 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; + 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); + // 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: 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: 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: 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: freeing machine url"); + freez(mu); + } - debug(D_REGISTRY, "Registry: deleting machine '%s' from machines registry", m->guid); - dictionary_del(registry.machines, m->guid); + 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: destroying URL dictionary of machine '%s'", m->guid); + dictionary_destroy(m->urls); - debug(D_REGISTRY, "Registry: freeing machine '%s'", m->guid); - free(m); - } + debug(D_REGISTRY, "Registry: freeing machine '%s'", m->guid); + freez(m); + } - // and free the memory of remaining dictionary structures + // and free the memory of remaining dictionary structures - debug(D_REGISTRY, "Registry: destroying persons dictionary"); - dictionary_destroy(registry.persons); + 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 machines dictionary"); + dictionary_destroy(registry.machines); - debug(D_REGISTRY, "Registry: destroying urls dictionary"); - dictionary_destroy(registry.urls); + 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); + 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 index d95383b5d..c2b57a23d 100644 --- a/src/registry.h +++ b/src/registry.h @@ -1,10 +1,12 @@ -#include "web_client.h" - #ifndef NETDATA_REGISTRY_H #define NETDATA_REGISTRY_H 1 #define NETDATA_REGISTRY_COOKIE_NAME "netdata_registry_id" +extern void registry_set_cookie(struct web_client *w, const char *guid); +extern const char *registry_to_announce(void); +extern int registry_verify_cookies_redirects(void); + 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); @@ -1,24 +1,4 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stddef.h> -#include <string.h> -#include <unistd.h> -#include <time.h> -#include <sys/time.h> -#include <sys/mman.h> -#include <pthread.h> -#include <errno.h> -#include <ctype.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <stdlib.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" - -#include "rrd.h" #define RRD_DEFAULT_GAP_INTERPOLATIONS 1 @@ -32,36 +12,143 @@ int rrd_delete_unupdated_dimensions = 0; int rrd_update_every = UPDATE_EVERY; int rrd_default_history_entries = RRD_DEFAULT_HISTORY_ENTRIES; +int rrd_memory_mode = RRD_MEMORY_MODE_SAVE; -RRDSET *rrdset_root = NULL; -pthread_rwlock_t rrdset_root_rwlock = PTHREAD_RWLOCK_INITIALIZER; +static int rrdset_compare(void* a, void* b); +static int rrdset_compare_name(void* a, void* b); +static int rrdfamily_compare(void *a, void *b); -int rrd_memory_mode = RRD_MEMORY_MODE_SAVE; +// ---------------------------------------------------------------------------- +// RRDHOST + +RRDHOST localhost = { + .hostname = "localhost", + .rrdset_root = NULL, + .rrdset_root_rwlock = PTHREAD_RWLOCK_INITIALIZER, + .rrdset_root_index = { + { NULL, rrdset_compare }, + AVL_LOCK_INITIALIZER + }, + .rrdset_root_index_name = { + { NULL, rrdset_compare_name }, + AVL_LOCK_INITIALIZER + }, + .rrdfamily_root_index = { + { NULL, rrdfamily_compare }, + AVL_LOCK_INITIALIZER + }, + .variables_root_index = { + { NULL, rrdvar_compare }, + AVL_LOCK_INITIALIZER + }, + .health_log = { + .nextid = 1, + .count = 0, + .max = 1000, + .alarms = NULL, + .alarm_log_rwlock = PTHREAD_RWLOCK_INITIALIZER + } +}; + +void rrdhost_rwlock(RRDHOST *host) { + pthread_rwlock_wrlock(&host->rrdset_root_rwlock); +} + +void rrdhost_rdlock(RRDHOST *host) { + pthread_rwlock_rdlock(&host->rrdset_root_rwlock); +} + +void rrdhost_unlock(RRDHOST *host) { + pthread_rwlock_unlock(&host->rrdset_root_rwlock); +} + +void rrdhost_check_rdlock_int(RRDHOST *host, const char *file, const char *function, const unsigned long line) { + int ret = pthread_rwlock_trywrlock(&host->rrdset_root_rwlock); + + if(ret == 0) + fatal("RRDHOST '%s' should be read-locked, but it is not, at function %s() at line %lu of file '%s'", host->hostname, function, line, file); +} + +void rrdhost_check_wrlock_int(RRDHOST *host, const char *file, const char *function, const unsigned long line) { + int ret = pthread_rwlock_tryrdlock(&host->rrdset_root_rwlock); + + if(ret == 0) + fatal("RRDHOST '%s' should be write-locked, but it is not, at function %s() at line %lu of file '%s'", host->hostname, function, line, file); +} + +// ---------------------------------------------------------------------------- +// RRDFAMILY index + +static int rrdfamily_compare(void *a, void *b) { + if(((RRDFAMILY *)a)->hash_family < ((RRDFAMILY *)b)->hash_family) return -1; + else if(((RRDFAMILY *)a)->hash_family > ((RRDFAMILY *)b)->hash_family) return 1; + else return strcmp(((RRDFAMILY *)a)->family, ((RRDFAMILY *)b)->family); +} + +#define rrdfamily_index_add(host, rc) (RRDFAMILY *)avl_insert_lock(&((host)->rrdfamily_root_index), (avl *)(rc)) +#define rrdfamily_index_del(host, rc) (RRDFAMILY *)avl_remove_lock(&((host)->rrdfamily_root_index), (avl *)(rc)) + +static RRDFAMILY *rrdfamily_index_find(RRDHOST *host, const char *id, uint32_t hash) { + RRDFAMILY tmp; + tmp.family = id; + tmp.hash_family = (hash)?hash:simple_hash(tmp.family); + + return (RRDFAMILY *)avl_search_lock(&(host->rrdfamily_root_index), (avl *) &tmp); +} + +RRDFAMILY *rrdfamily_create(const char *id) { + RRDFAMILY *rc = rrdfamily_index_find(&localhost, id, 0); + if(!rc) { + rc = callocz(1, sizeof(RRDFAMILY)); + + rc->family = strdupz(id); + rc->hash_family = simple_hash(rc->family); + + // initialize the variables index + avl_init_lock(&rc->variables_root_index, rrdvar_compare); + RRDFAMILY *ret = rrdfamily_index_add(&localhost, rc); + if(ret != rc) + fatal("INTERNAL ERROR: Expected to INSERT RRDFAMILY '%s' into index, but inserted '%s'.", rc->family, (ret)?ret->family:"NONE"); + } + + rc->use_count++; + return rc; +} + +void rrdfamily_free(RRDFAMILY *rc) { + rc->use_count--; + if(!rc->use_count) { + RRDFAMILY *ret = rrdfamily_index_del(&localhost, rc); + if(ret != rc) + fatal("INTERNAL ERROR: Expected to DELETE RRDFAMILY '%s' from index, but deleted '%s'.", rc->family, (ret)?ret->family:"NONE"); + + if(rc->variables_root_index.avl_tree.root != NULL) + fatal("INTERNAL ERROR: Variables index of RRDFAMILY '%s' that is freed, is not empty.", rc->family); + + freez((void *)rc->family); + freez(rc); + } +} // ---------------------------------------------------------------------------- // RRDSET index 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); + 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_lock rrdset_root_index = { - { NULL, rrdset_compare }, - AVL_LOCK_INITIALIZER -}; - -#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)) +#define rrdset_index_add(host, st) (RRDSET *)avl_insert_lock(&((host)->rrdset_root_index), (avl *)(st)) +#define rrdset_index_del(host, st) (RRDSET *)avl_remove_lock(&((host)->rrdset_root_index), (avl *)(st)) -static RRDSET *rrdset_index_find(const char *id, uint32_t hash) { - RRDSET tmp; - strncpyz(tmp.id, id, RRD_ID_LENGTH_MAX); - tmp.hash = (hash)?hash:simple_hash(tmp.id); +static RRDSET *rrdset_index_find(RRDHOST *host, const char *id, uint32_t hash) { + RRDSET tmp; + strncpyz(tmp.id, id, RRD_ID_LENGTH_MAX); + tmp.hash = (hash)?hash:simple_hash(tmp.id); - return (RRDSET *)avl_search_lock(&(rrdset_root_index), (avl *) &tmp); + return (RRDSET *)avl_search_lock(&(host->rrdset_root_index), (avl *) &tmp); } // ---------------------------------------------------------------------------- @@ -70,46 +157,50 @@ 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_compare_name(void* a, void* b) { - RRDSET *A = rrdset_from_avlname(a); - RRDSET *B = rrdset_from_avlname(b); + RRDSET *A = rrdset_from_avlname(a); + RRDSET *B = rrdset_from_avlname(b); - // fprintf(stderr, "COMPARING: %s with %s\n", A->name, B->name); + // fprintf(stderr, "COMPARING: %s with %s\n", A->name, B->name); - if(A->hash_name < B->hash_name) return -1; - else if(A->hash_name > B->hash_name) return 1; - else return strcmp(A->name, B->name); + if(A->hash_name < B->hash_name) return -1; + else if(A->hash_name > B->hash_name) return 1; + else return strcmp(A->name, B->name); } -avl_tree_lock rrdset_root_index_name = { - { NULL, rrdset_compare_name }, - AVL_LOCK_INITIALIZER -}; +RRDSET *rrdset_index_add_name(RRDHOST *host, RRDSET *st) { + void *result; + // fprintf(stderr, "ADDING: %s (name: %s)\n", st->id, st->name); + result = avl_insert_lock(&host->rrdset_root_index_name, (avl *) (&st->avlname)); + if(result) return rrdset_from_avlname(result); + return NULL; +} -int rrdset_index_add_name(RRDSET *st) { - // fprintf(stderr, "ADDING: %s (name: %s)\n", st->id, st->name); - return avl_insert_lock(&rrdset_root_index_name, (avl *) (&st->avlname)); +RRDSET *rrdset_index_del_name(RRDHOST *host, RRDSET *st) { + void *result; + // fprintf(stderr, "DELETING: %s (name: %s)\n", st->id, st->name); + result = (RRDSET *)avl_remove_lock(&((host)->rrdset_root_index_name), (avl *)(&st->avlname)); + if(result) return rrdset_from_avlname(result); + return NULL; } -#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; - RRDSET tmp; - tmp.name = name; - tmp.hash_name = (hash)?hash:simple_hash(tmp.name); - - // fprintf(stderr, "SEARCHING: %s\n", name); - 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)) - error("Search for RRDSET %s returned an invalid RRDSET %s (name %s)", name, st->id, st->name); - - // fprintf(stderr, "FOUND: %s\n", name); - return rrdset_from_avlname(result); - } - // fprintf(stderr, "NOT FOUND: %s\n", name); - return NULL; +static RRDSET *rrdset_index_find_name(RRDHOST *host, const char *name, uint32_t hash) { + void *result = NULL; + RRDSET tmp; + tmp.name = name; + tmp.hash_name = (hash)?hash:simple_hash(tmp.name); + + // fprintf(stderr, "SEARCHING: %s\n", name); + result = avl_search_lock(&host->rrdset_root_index_name, (avl *) (&(tmp.avlname))); + if(result) { + RRDSET *st = rrdset_from_avlname(result); + if(strcmp(st->magic, RRDSET_MAGIC)) + error("Search for RRDSET %s returned an invalid RRDSET %s (name %s)", name, st->id, st->name); + + // fprintf(stderr, "FOUND: %s\n", name); + return rrdset_from_avlname(result); + } + // fprintf(stderr, "NOT FOUND: %s\n", name); + return NULL; } @@ -117,20 +208,20 @@ static RRDSET *rrdset_index_find_name(const char *name, uint32_t hash) { // RRDDIM index 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); + 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_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 tmp; - strncpyz(tmp.id, id, RRD_ID_LENGTH_MAX); - tmp.hash = (hash)?hash:simple_hash(tmp.id); + RRDDIM tmp; + strncpyz(tmp.id, id, RRD_ID_LENGTH_MAX); + tmp.hash = (hash)?hash:simple_hash(tmp.id); - return (RRDDIM *)avl_search_lock(&(st->dimensions_index), (avl *) &tmp); + return (RRDDIM *)avl_search_lock(&(st->dimensions_index), (avl *) &tmp); } // ---------------------------------------------------------------------------- @@ -138,29 +229,29 @@ static RRDDIM *rrddim_index_find(RRDSET *st, const char *id, uint32_t hash) { int rrdset_type_id(const char *name) { - if(unlikely(strcmp(name, RRDSET_TYPE_AREA_NAME) == 0)) return RRDSET_TYPE_AREA; - else if(unlikely(strcmp(name, RRDSET_TYPE_STACKED_NAME) == 0)) return RRDSET_TYPE_STACKED; - else if(unlikely(strcmp(name, RRDSET_TYPE_LINE_NAME) == 0)) return RRDSET_TYPE_LINE; - return RRDSET_TYPE_LINE; + if(unlikely(strcmp(name, RRDSET_TYPE_AREA_NAME) == 0)) return RRDSET_TYPE_AREA; + else if(unlikely(strcmp(name, RRDSET_TYPE_STACKED_NAME) == 0)) return RRDSET_TYPE_STACKED; + else if(unlikely(strcmp(name, RRDSET_TYPE_LINE_NAME) == 0)) return RRDSET_TYPE_LINE; + return RRDSET_TYPE_LINE; } const char *rrdset_type_name(int chart_type) { - static char line[] = RRDSET_TYPE_LINE_NAME; - static char area[] = RRDSET_TYPE_AREA_NAME; - static char stacked[] = RRDSET_TYPE_STACKED_NAME; + static char line[] = RRDSET_TYPE_LINE_NAME; + static char area[] = RRDSET_TYPE_AREA_NAME; + static char stacked[] = RRDSET_TYPE_STACKED_NAME; - switch(chart_type) { - case RRDSET_TYPE_LINE: - return line; + switch(chart_type) { + case RRDSET_TYPE_LINE: + return line; - case RRDSET_TYPE_AREA: - return area; + case RRDSET_TYPE_AREA: + return area; - case RRDSET_TYPE_STACKED: - return stacked; - } - return line; + case RRDSET_TYPE_STACKED: + return stacked; + } + return line; } // ---------------------------------------------------------------------------- @@ -168,33 +259,33 @@ const char *rrdset_type_name(int chart_type) const char *rrd_memory_mode_name(int id) { - static const char ram[] = RRD_MEMORY_MODE_RAM_NAME; - static const char map[] = RRD_MEMORY_MODE_MAP_NAME; - static const char save[] = RRD_MEMORY_MODE_SAVE_NAME; + static const char ram[] = RRD_MEMORY_MODE_RAM_NAME; + static const char map[] = RRD_MEMORY_MODE_MAP_NAME; + static const char save[] = RRD_MEMORY_MODE_SAVE_NAME; - switch(id) { - case RRD_MEMORY_MODE_RAM: - return ram; + switch(id) { + case RRD_MEMORY_MODE_RAM: + return ram; - case RRD_MEMORY_MODE_MAP: - return map; + case RRD_MEMORY_MODE_MAP: + return map; - case RRD_MEMORY_MODE_SAVE: - default: - return save; - } + case RRD_MEMORY_MODE_SAVE: + default: + return save; + } - return save; + return save; } int rrd_memory_mode_id(const char *name) { - if(unlikely(!strcmp(name, RRD_MEMORY_MODE_RAM_NAME))) - return RRD_MEMORY_MODE_RAM; - else if(unlikely(!strcmp(name, RRD_MEMORY_MODE_MAP_NAME))) - return RRD_MEMORY_MODE_MAP; + if(unlikely(!strcmp(name, RRD_MEMORY_MODE_RAM_NAME))) + return RRD_MEMORY_MODE_RAM; + else if(unlikely(!strcmp(name, RRD_MEMORY_MODE_MAP_NAME))) + return RRD_MEMORY_MODE_MAP; - return RRD_MEMORY_MODE_SAVE; + return RRD_MEMORY_MODE_SAVE; } // ---------------------------------------------------------------------------- @@ -202,67 +293,73 @@ int rrd_memory_mode_id(const char *name) int rrddim_algorithm_id(const char *name) { - 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; + 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; } const char *rrddim_algorithm_name(int chart_type) { - static char absolute[] = RRDDIM_ABSOLUTE_NAME; - static char incremental[] = RRDDIM_INCREMENTAL_NAME; - static char percentage_of_absolute_row[] = RRDDIM_PCENT_OVER_ROW_TOTAL_NAME; - static char percentage_of_incremental_row[] = RRDDIM_PCENT_OVER_DIFF_TOTAL_NAME; + static char absolute[] = RRDDIM_ABSOLUTE_NAME; + static char incremental[] = RRDDIM_INCREMENTAL_NAME; + static char percentage_of_absolute_row[] = RRDDIM_PCENT_OVER_ROW_TOTAL_NAME; + static char percentage_of_incremental_row[] = RRDDIM_PCENT_OVER_DIFF_TOTAL_NAME; - switch(chart_type) { - case RRDDIM_ABSOLUTE: - return absolute; + switch(chart_type) { + case RRDDIM_ABSOLUTE: + return absolute; - case RRDDIM_INCREMENTAL: - return incremental; + case RRDDIM_INCREMENTAL: + return incremental; - case RRDDIM_PCENT_OVER_ROW_TOTAL: - return percentage_of_absolute_row; + case RRDDIM_PCENT_OVER_ROW_TOTAL: + return percentage_of_absolute_row; - case RRDDIM_PCENT_OVER_DIFF_TOTAL: - return percentage_of_incremental_row; - } - return absolute; + case RRDDIM_PCENT_OVER_DIFF_TOTAL: + return percentage_of_incremental_row; + } + return absolute; } // ---------------------------------------------------------------------------- // chart names -char *rrdset_strncpy_name(char *to, const char *from, int length) +char *rrdset_strncpyz_name(char *to, const char *from, size_t length) { - int i; - for(i = 0; i < length && from[i] ;i++) { - if(from[i] == '.' || isalpha(from[i]) || isdigit(from[i])) to[i] = from[i]; - else to[i] = '_'; - } - if(i < length) to[i] = '\0'; - to[length - 1] = '\0'; - - return to; + char c, *p = to; + + while (length-- && (c = *from++)) { + if(c != '.' && !isalnum(c)) + c = '_'; + + *p++ = c; + } + + *p = '\0'; + + return to; } void rrdset_set_name(RRDSET *st, const char *name) { - debug(D_RRD_CALLS, "rrdset_set_name() old: %s, new: %s", st->name, name); + debug(D_RRD_CALLS, "rrdset_set_name() old: %s, new: %s", st->name, name); - if(st->name) rrdset_index_del_name(st); + if(st->name) { + rrdset_index_del_name(&localhost, st); + rrdsetvar_rename_all(st); + } - char b[CONFIG_MAX_VALUE + 1]; - char n[RRD_ID_LENGTH_MAX + 1]; + char b[CONFIG_MAX_VALUE + 1]; + char n[RRD_ID_LENGTH_MAX + 1]; - 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); + snprintfz(n, RRD_ID_LENGTH_MAX, "%s.%s", st->type, name); + rrdset_strncpyz_name(b, n, CONFIG_MAX_VALUE); + st->name = config_get(st->id, "name", b); + st->hash_name = simple_hash(st->name); - rrdset_index_add_name(st); + rrdset_index_add_name(&localhost, st); } // ---------------------------------------------------------------------------- @@ -270,25 +367,30 @@ void rrdset_set_name(RRDSET *st, const char *name) char *rrdset_cache_dir(const char *id) { - char *ret = NULL; - - static char *cache_dir = NULL; - if(!cache_dir) cache_dir = config_get("global", "cache directory", CACHE_DIR); - - char b[FILENAME_MAX + 1]; - char n[FILENAME_MAX + 1]; - rrdset_strncpy_name(b, id, FILENAME_MAX); - - 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) { - int r = mkdir(ret, 0775); - if(r != 0 && errno != EEXIST) - error("Cannot create directory '%s'", ret); - } - - return ret; + char *ret = NULL; + + static char *cache_dir = NULL; + if(!cache_dir) { + cache_dir = config_get("global", "cache directory", CACHE_DIR); + int r = mkdir(cache_dir, 0755); + if(r != 0 && errno != EEXIST) + error("Cannot create directory '%s'", cache_dir); + } + + char b[FILENAME_MAX + 1]; + char n[FILENAME_MAX + 1]; + rrdset_strncpyz_name(b, id, FILENAME_MAX); + + 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) { + int r = mkdir(ret, 0775); + if(r != 0 && errno != EEXIST) + error("Cannot create directory '%s'", ret); + } + + return ret; } // ---------------------------------------------------------------------------- @@ -296,1025 +398,1110 @@ char *rrdset_cache_dir(const char *id) void rrdset_reset(RRDSET *st) { - debug(D_RRD_CALLS, "rrdset_reset() %s", st->name); - - st->last_collected_time.tv_sec = 0; - st->last_collected_time.tv_usec = 0; - st->last_updated.tv_sec = 0; - st->last_updated.tv_usec = 0; - st->current_entry = 0; - st->counter = 0; - st->counter_done = 0; - - RRDDIM *rd; - for(rd = st->dimensions; rd ; rd = rd->next) { - rd->last_collected_time.tv_sec = 0; - rd->last_collected_time.tv_usec = 0; - rd->counter = 0; - bzero(rd->values, rd->entries * sizeof(storage_number)); - } + debug(D_RRD_CALLS, "rrdset_reset() %s", st->name); + + st->last_collected_time.tv_sec = 0; + st->last_collected_time.tv_usec = 0; + st->last_updated.tv_sec = 0; + st->last_updated.tv_usec = 0; + st->current_entry = 0; + st->counter = 0; + st->counter_done = 0; + + RRDDIM *rd; + for(rd = st->dimensions; rd ; rd = rd->next) { + rd->last_collected_time.tv_sec = 0; + rd->last_collected_time.tv_usec = 0; + rd->counter = 0; + bzero(rd->values, rd->entries * sizeof(storage_number)); + } } RRDSET *rrdset_create(const char *type, const char *id, const char *name, const char *family, const char *context, const char *title, const char *units, long priority, int update_every, int chart_type) { - if(!type || !type[0]) { - fatal("Cannot create rrd stats without a type."); - return NULL; - } - - if(!id || !id[0]) { - fatal("Cannot create rrd stats without an id."); - return NULL; - } - - char fullid[RRD_ID_LENGTH_MAX + 1]; - char fullfilename[FILENAME_MAX + 1]; - RRDSET *st = NULL; - - snprintfz(fullid, RRD_ID_LENGTH_MAX, "%s.%s", type, id); - - st = rrdset_find(fullid); - if(st) { - error("Cannot create rrd stats for '%s', it already exists.", fullid); - return st; - } - - long entries = config_get_number(fullid, "history", rrd_default_history_entries); - if(entries < 5) entries = config_set_number(fullid, "history", 5); - if(entries > RRD_HISTORY_ENTRIES_MAX) entries = config_set_number(fullid, "history", RRD_HISTORY_ENTRIES_MAX); - - int enabled = config_get_boolean(fullid, "enabled", 1); - if(!enabled) entries = 5; - - unsigned long size = sizeof(RRDSET); - char *cache_dir = rrdset_cache_dir(fullid); - - debug(D_RRD_CALLS, "Creating RRD_STATS for '%s.%s'.", type, id); - - 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) { - errno = 0; - info("Initializing file %s.", fullfilename); - bzero(st, size); - } - else if(strcmp(st->id, fullid) != 0) { - errno = 0; - error("File %s contents are not for chart %s. Clearing it.", fullfilename, fullid); - // munmap(st, size); - // st = NULL; - bzero(st, size); - } - else if(st->memsize != size || st->entries != entries) { - errno = 0; - error("File %s does not have the desired size. Clearing it.", fullfilename); - bzero(st, size); - } - else if(st->update_every != update_every) { - errno = 0; - error("File %s does not have the desired update frequency. Clearing it.", fullfilename); - bzero(st, size); - } - else if((time(NULL) - st->last_updated.tv_sec) > update_every * entries) { - errno = 0; - error("File %s is too old. Clearing it.", fullfilename); - bzero(st, size); - } - } - - if(st) { - st->name = NULL; - st->type = NULL; - st->family = NULL; - st->context = NULL; - st->title = NULL; - st->units = NULL; - st->dimensions = NULL; - st->next = NULL; - st->mapped = rrd_memory_mode; - } - else { - st = calloc(1, size); - if(!st) { - fatal("Cannot allocate memory for RRD_STATS %s.%s", type, id); - return NULL; - } - st->mapped = RRD_MEMORY_MODE_RAM; - } - st->memsize = size; - st->entries = entries; - st->update_every = update_every; - - strcpy(st->cache_filename, fullfilename); - strcpy(st->magic, RRDSET_MAGIC); - - strcpy(st->id, fullid); - st->hash = simple_hash(st->id); - - st->cache_dir = cache_dir; - - st->chart_type = rrdset_type_id(config_get(st->id, "chart type", rrdset_type_name(chart_type))); - st->type = config_get(st->id, "type", type); - st->family = config_get(st->id, "family", family?family:st->type); - st->context = config_get(st->id, "context", context?context:st->id); - st->units = config_get(st->id, "units", units?units:""); - - st->priority = config_get_number(st->id, "priority", priority); - st->enabled = enabled; - - st->isdetail = 0; - st->debug = 0; - - st->last_collected_time.tv_sec = 0; - st->last_collected_time.tv_usec = 0; - st->counter_done = 0; - - st->gap_when_lost_iterations_above = (int) ( - config_get_number(st->id, "gap when lost iterations above", RRD_DEFAULT_GAP_INTERPOLATIONS) + 2); - - avl_init_lock(&st->dimensions_index, rrddim_compare); - - pthread_rwlock_init(&st->rwlock, NULL); - pthread_rwlock_wrlock(&rrdset_root_rwlock); - - if(name && *name) rrdset_set_name(st, name); - else rrdset_set_name(st, id); - - { - char varvalue[CONFIG_MAX_VALUE + 1]; - snprintfz(varvalue, CONFIG_MAX_VALUE, "%s (%s)", title?title:"", st->name); - st->title = config_get(st->id, "title", varvalue); - } - - st->next = rrdset_root; - rrdset_root = st; - - rrdset_index_add(st); - - pthread_rwlock_unlock(&rrdset_root_rwlock); - - return(st); + if(!type || !type[0]) { + fatal("Cannot create rrd stats without a type."); + return NULL; + } + + if(!id || !id[0]) { + fatal("Cannot create rrd stats without an id."); + return NULL; + } + + char fullid[RRD_ID_LENGTH_MAX + 1]; + char fullfilename[FILENAME_MAX + 1]; + RRDSET *st = NULL; + + snprintfz(fullid, RRD_ID_LENGTH_MAX, "%s.%s", type, id); + + st = rrdset_find(fullid); + if(st) { + error("Cannot create rrd stats for '%s', it already exists.", fullid); + return st; + } + + long entries = config_get_number(fullid, "history", rrd_default_history_entries); + if(entries < 5) entries = config_set_number(fullid, "history", 5); + if(entries > RRD_HISTORY_ENTRIES_MAX) entries = config_set_number(fullid, "history", RRD_HISTORY_ENTRIES_MAX); + + int enabled = config_get_boolean(fullid, "enabled", 1); + if(!enabled) entries = 5; + + unsigned long size = sizeof(RRDSET); + char *cache_dir = rrdset_cache_dir(fullid); + + debug(D_RRD_CALLS, "Creating RRD_STATS for '%s.%s'.", type, id); + + 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) { + errno = 0; + info("Initializing file %s.", fullfilename); + bzero(st, size); + } + else if(strcmp(st->id, fullid) != 0) { + errno = 0; + error("File %s contents are not for chart %s. Clearing it.", fullfilename, fullid); + // munmap(st, size); + // st = NULL; + bzero(st, size); + } + else if(st->memsize != size || st->entries != entries) { + errno = 0; + error("File %s does not have the desired size. Clearing it.", fullfilename); + bzero(st, size); + } + else if(st->update_every != update_every) { + errno = 0; + error("File %s does not have the desired update frequency. Clearing it.", fullfilename); + bzero(st, size); + } + else if((time(NULL) - st->last_updated.tv_sec) > update_every * entries) { + errno = 0; + error("File %s is too old. Clearing it.", fullfilename); + bzero(st, size); + } + } + + if(st) { + st->name = NULL; + st->type = NULL; + st->family = NULL; + st->context = NULL; + st->title = NULL; + st->units = NULL; + st->dimensions = NULL; + st->next = NULL; + st->mapped = rrd_memory_mode; + st->variables = NULL; + } + else { + st = callocz(1, size); + st->mapped = RRD_MEMORY_MODE_RAM; + } + + st->memsize = size; + st->entries = entries; + st->update_every = update_every; + + if(st->current_entry >= st->entries) st->current_entry = 0; + + strcpy(st->cache_filename, fullfilename); + strcpy(st->magic, RRDSET_MAGIC); + + strcpy(st->id, fullid); + st->hash = simple_hash(st->id); + + st->cache_dir = cache_dir; + + st->chart_type = rrdset_type_id(config_get(st->id, "chart type", rrdset_type_name(chart_type))); + st->type = config_get(st->id, "type", type); + st->family = config_get(st->id, "family", family?family:st->type); + st->units = config_get(st->id, "units", units?units:""); + + st->context = config_get(st->id, "context", context?context:st->id); + st->hash_context = simple_hash(st->context); + + st->priority = config_get_number(st->id, "priority", priority); + st->enabled = enabled; + + st->isdetail = 0; + st->debug = 0; + + // if(!strcmp(st->id, "disk_util.dm-0")) { + // st->debug = 1; + // error("enabled debugging for '%s'", st->id); + // } + // else error("not enabled debugging for '%s'", st->id); + + st->green = NAN; + st->red = NAN; + + st->last_collected_time.tv_sec = 0; + st->last_collected_time.tv_usec = 0; + st->counter_done = 0; + + st->gap_when_lost_iterations_above = (int) ( + config_get_number(st->id, "gap when lost iterations above", RRD_DEFAULT_GAP_INTERPOLATIONS) + 2); + + avl_init_lock(&st->dimensions_index, rrddim_compare); + avl_init_lock(&st->variables_root_index, rrdvar_compare); + + pthread_rwlock_init(&st->rwlock, NULL); + rrdhost_rwlock(&localhost); + + if(name && *name) rrdset_set_name(st, name); + else rrdset_set_name(st, id); + + { + char varvalue[CONFIG_MAX_VALUE + 1]; + snprintfz(varvalue, CONFIG_MAX_VALUE, "%s (%s)", title?title:"", st->name); + st->title = config_get(st->id, "title", varvalue); + } + + st->rrdfamily = rrdfamily_create(st->family); + st->rrdhost = &localhost; + + st->next = localhost.rrdset_root; + localhost.rrdset_root = st; + + if(health_enabled) { + rrdsetvar_create(st, "last_collected_t", RRDVAR_TYPE_TIME_T, &st->last_collected_time.tv_sec, 0); + rrdsetvar_create(st, "collected_total_raw", RRDVAR_TYPE_TOTAL, &st->last_collected_total, 0); + rrdsetvar_create(st, "green", RRDVAR_TYPE_CALCULATED, &st->green, 0); + rrdsetvar_create(st, "red", RRDVAR_TYPE_CALCULATED, &st->red, 0); + rrdsetvar_create(st, "update_every", RRDVAR_TYPE_INT, &st->update_every, 0); + } + + rrdset_index_add(&localhost, st); + + rrdsetcalc_link_matching(st); + rrdcalctemplate_link_matching(st); + + rrdhost_unlock(&localhost); + + return(st); } RRDDIM *rrddim_add(RRDSET *st, const char *id, const char *name, long multiplier, long divisor, int algorithm) { - char filename[FILENAME_MAX + 1]; - char fullfilename[FILENAME_MAX + 1]; - - char varname[CONFIG_MAX_NAME + 1]; - RRDDIM *rd = NULL; - unsigned long size = sizeof(RRDDIM) + (st->entries * sizeof(storage_number)); - - debug(D_RRD_CALLS, "Adding dimension '%s/%s'.", st->id, id); - - rrdset_strncpy_name(filename, id, FILENAME_MAX); - 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; - gettimeofday(&now, NULL); - - if(strcmp(rd->magic, RRDDIMENSION_MAGIC) != 0) { - errno = 0; - info("Initializing file %s.", fullfilename); - bzero(rd, size); - } - else if(rd->memsize != size) { - errno = 0; - error("File %s does not have the desired size. Clearing it.", fullfilename); - bzero(rd, size); - } - else if(rd->multiplier != multiplier) { - errno = 0; - error("File %s does not have the same multiplier. Clearing it.", fullfilename); - bzero(rd, size); - } - else if(rd->divisor != divisor) { - errno = 0; - error("File %s does not have the same divisor. Clearing it.", fullfilename); - bzero(rd, size); - } - else if(rd->algorithm != algorithm) { - errno = 0; - error("File %s does not have the same algorithm. Clearing it.", fullfilename); - bzero(rd, size); - } - else if(rd->update_every != st->update_every) { - errno = 0; - error("File %s does not have the same refresh frequency. Clearing it.", fullfilename); - bzero(rd, size); - } - else if(usecdiff(&now, &rd->last_collected_time) > (rd->entries * rd->update_every * 1000000ULL)) { - errno = 0; - error("File %s is too old. Clearing it.", fullfilename); - bzero(rd, size); - } - else if(strcmp(rd->id, id) != 0) { - errno = 0; - error("File %s contents are not for dimension %s. Clearing it.", fullfilename, id); - // munmap(rd, size); - // rd = NULL; - bzero(rd, size); - } - } - - if(rd) { - // we have a file mapped for rd - rd->mapped = rrd_memory_mode; - rd->flags = 0x00000000; - rd->next = NULL; - rd->name = NULL; - } - else { - // if we didn't manage to get a mmap'd dimension, just create one - - rd = calloc(1, size); - if(!rd) { - fatal("Cannot allocate RRD_DIMENSION %s/%s.", st->id, id); - return NULL; - } - - rd->mapped = RRD_MEMORY_MODE_RAM; - } - rd->memsize = size; - - strcpy(rd->magic, RRDDIMENSION_MAGIC); - strcpy(rd->cache_filename, fullfilename); - strncpyz(rd->id, id, RRD_ID_LENGTH_MAX); - rd->hash = simple_hash(rd->id); - - snprintfz(varname, CONFIG_MAX_NAME, "dim %s name", rd->id); - rd->name = config_get(st->id, varname, (name && *name)?name: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))); - - snprintfz(varname, CONFIG_MAX_NAME, "dim %s multiplier", rd->id); - rd->multiplier = config_get_number(st->id, varname, multiplier); - - 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; - - rd->entries = st->entries; - rd->update_every = st->update_every; - - // prevent incremental calculation spikes - rd->counter = 0; - - // append this dimension - pthread_rwlock_wrlock(&st->rwlock); - if(!st->dimensions) - st->dimensions = rd; - else { - RRDDIM *td = st->dimensions; - for(; td->next; td = td->next) ; - td->next = rd; - } - pthread_rwlock_unlock(&st->rwlock); - - rrddim_index_add(st, rd); - - return(rd); + char filename[FILENAME_MAX + 1]; + char fullfilename[FILENAME_MAX + 1]; + + char varname[CONFIG_MAX_NAME + 1]; + RRDDIM *rd = NULL; + unsigned long size = sizeof(RRDDIM) + (st->entries * sizeof(storage_number)); + + debug(D_RRD_CALLS, "Adding dimension '%s/%s'.", st->id, id); + + rrdset_strncpyz_name(filename, id, FILENAME_MAX); + 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; + gettimeofday(&now, NULL); + + if(strcmp(rd->magic, RRDDIMENSION_MAGIC) != 0) { + errno = 0; + info("Initializing file %s.", fullfilename); + bzero(rd, size); + } + else if(rd->memsize != size) { + errno = 0; + error("File %s does not have the desired size. Clearing it.", fullfilename); + bzero(rd, size); + } + else if(rd->multiplier != multiplier) { + errno = 0; + error("File %s does not have the same multiplier. Clearing it.", fullfilename); + bzero(rd, size); + } + else if(rd->divisor != divisor) { + errno = 0; + error("File %s does not have the same divisor. Clearing it.", fullfilename); + bzero(rd, size); + } + else if(rd->algorithm != algorithm) { + errno = 0; + error("File %s does not have the same algorithm. Clearing it.", fullfilename); + bzero(rd, size); + } + else if(rd->update_every != st->update_every) { + errno = 0; + error("File %s does not have the same refresh frequency. Clearing it.", fullfilename); + bzero(rd, size); + } + else if(usec_dt(&now, &rd->last_collected_time) > (rd->entries * rd->update_every * 1000000ULL)) { + errno = 0; + error("File %s is too old. Clearing it.", fullfilename); + bzero(rd, size); + } + else if(strcmp(rd->id, id) != 0) { + errno = 0; + error("File %s contents are not for dimension %s. Clearing it.", fullfilename, id); + // munmap(rd, size); + // rd = NULL; + bzero(rd, size); + } + } + + if(rd) { + // we have a file mapped for rd + rd->mapped = rrd_memory_mode; + rd->flags = 0x00000000; + rd->variables = NULL; + rd->next = NULL; + rd->name = NULL; + } + else { + // if we didn't manage to get a mmap'd dimension, just create one + + rd = callocz(1, size); + rd->mapped = RRD_MEMORY_MODE_RAM; + } + rd->memsize = size; + + strcpy(rd->magic, RRDDIMENSION_MAGIC); + strcpy(rd->cache_filename, fullfilename); + strncpyz(rd->id, id, RRD_ID_LENGTH_MAX); + rd->hash = simple_hash(rd->id); + + snprintfz(varname, CONFIG_MAX_NAME, "dim %s name", rd->id); + rd->name = config_get(st->id, varname, (name && *name)?name: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))); + + snprintfz(varname, CONFIG_MAX_NAME, "dim %s multiplier", rd->id); + rd->multiplier = config_get_number(st->id, varname, multiplier); + + 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; + + rd->entries = st->entries; + rd->update_every = st->update_every; + + // prevent incremental calculation spikes + rd->counter = 0; + rd->updated = 0; + rd->calculated_value = 0; + rd->last_calculated_value = 0; + rd->collected_value = 0; + rd->last_collected_value = 0; + rd->collected_volume = 0; + rd->stored_volume = 0; + rd->last_stored_value = 0; + rd->values[st->current_entry] = pack_storage_number(0, SN_NOT_EXISTS); + rd->last_collected_time.tv_sec = 0; + rd->last_collected_time.tv_usec = 0; + rd->rrdset = st; + + // append this dimension + pthread_rwlock_wrlock(&st->rwlock); + if(!st->dimensions) + st->dimensions = rd; + else { + RRDDIM *td = st->dimensions; + for(; td->next; td = td->next) ; + td->next = rd; + } + + if(health_enabled) { + rrddimvar_create(rd, RRDVAR_TYPE_CALCULATED, NULL, NULL, &rd->last_stored_value, 0); + rrddimvar_create(rd, RRDVAR_TYPE_COLLECTED, NULL, "_raw", &rd->last_collected_value, 0); + rrddimvar_create(rd, RRDVAR_TYPE_TIME_T, NULL, "_last_collected_t", &rd->last_collected_time.tv_sec, 0); + } + + pthread_rwlock_unlock(&st->rwlock); + + rrddim_index_add(st, rd); + + return(rd); } void rrddim_set_name(RRDSET *st, RRDDIM *rd, const char *name) { - debug(D_RRD_CALLS, "rrddim_set_name() %s.%s", st->name, rd->name); + debug(D_RRD_CALLS, "rrddim_set_name() %s.%s", st->name, rd->name); + + char varname[CONFIG_MAX_NAME + 1]; + snprintfz(varname, CONFIG_MAX_NAME, "dim %s name", rd->id); + config_set_default(st->id, varname, name); - char varname[CONFIG_MAX_NAME + 1]; - snprintfz(varname, CONFIG_MAX_NAME, "dim %s name", rd->id); - config_set_default(st->id, varname, name); + rrddimvar_rename_all(rd); } void rrddim_free(RRDSET *st, RRDDIM *rd) { - debug(D_RRD_CALLS, "rrddim_free() %s.%s", st->name, rd->name); - - RRDDIM *i, *last = NULL; - for(i = st->dimensions; i && i != rd ; i = i->next) last = i; - - if(!i) { - error("Request to free dimension %s.%s but it is not linked.", st->id, rd->name); - return; - } - - if(last) last->next = rd->next; - else st->dimensions = rd->next; - rd->next = NULL; - - rrddim_index_del(st, rd); - - // free(rd->annotations); - if(rd->mapped == RRD_MEMORY_MODE_SAVE) { - debug(D_RRD_CALLS, "Saving dimension '%s' to '%s'.", rd->name, rd->cache_filename); - savememory(rd->cache_filename, rd, rd->memsize); - - debug(D_RRD_CALLS, "Unmapping dimension '%s'.", rd->name); - munmap(rd, rd->memsize); - } - else if(rd->mapped == RRD_MEMORY_MODE_MAP) { - debug(D_RRD_CALLS, "Unmapping dimension '%s'.", rd->name); - munmap(rd, rd->memsize); - } - else { - debug(D_RRD_CALLS, "Removing dimension '%s'.", rd->name); - free(rd); - } + debug(D_RRD_CALLS, "rrddim_free() %s.%s", st->name, rd->name); + + if(rd == st->dimensions) + st->dimensions = rd->next; + else { + RRDDIM *i; + for (i = st->dimensions; i && i->next != rd; i = i->next) ; + + if (i && i->next == rd) + i->next = rd->next; + else + error("Request to free dimension '%s.%s' but it is not linked.", st->id, rd->name); + } + rd->next = NULL; + + while(rd->variables) + rrddimvar_free(rd->variables); + + rrddim_index_del(st, rd); + + // free(rd->annotations); + if(rd->mapped == RRD_MEMORY_MODE_SAVE) { + debug(D_RRD_CALLS, "Saving dimension '%s' to '%s'.", rd->name, rd->cache_filename); + savememory(rd->cache_filename, rd, rd->memsize); + + debug(D_RRD_CALLS, "Unmapping dimension '%s'.", rd->name); + munmap(rd, rd->memsize); + } + else if(rd->mapped == RRD_MEMORY_MODE_MAP) { + debug(D_RRD_CALLS, "Unmapping dimension '%s'.", rd->name); + munmap(rd, rd->memsize); + } + else { + debug(D_RRD_CALLS, "Removing dimension '%s'.", rd->name); + freez(rd); + } } void rrdset_free_all(void) { - info("Freeing all memory..."); + info("Freeing all memory..."); - RRDSET *st; - for(st = rrdset_root; st ;) { - RRDSET *next = st->next; + rrdhost_rwlock(&localhost); - while(st->dimensions) - rrddim_free(st, st->dimensions); + RRDSET *st; + for(st = localhost.rrdset_root; st ;) { + RRDSET *next = st->next; - rrdset_index_del(st); + pthread_rwlock_wrlock(&st->rwlock); - if(st->mapped == RRD_MEMORY_MODE_SAVE) { - debug(D_RRD_CALLS, "Saving stats '%s' to '%s'.", st->name, st->cache_filename); - savememory(st->cache_filename, st, st->memsize); + while(st->variables) + rrdsetvar_free(st->variables); - debug(D_RRD_CALLS, "Unmapping stats '%s'.", st->name); - munmap(st, st->memsize); - } - else if(st->mapped == RRD_MEMORY_MODE_MAP) { - debug(D_RRD_CALLS, "Unmapping stats '%s'.", st->name); - munmap(st, st->memsize); - } - else - free(st); + while(st->alarms) + rrdsetcalc_unlink(st->alarms); - st = next; - } - rrdset_root = NULL; + while(st->dimensions) + rrddim_free(st, st->dimensions); - info("Memory cleanup completed..."); -} + rrdset_index_del(&localhost, st); -void rrdset_save_all(void) -{ - debug(D_RRD_CALLS, "rrdset_save_all()"); + st->rrdfamily->use_count--; + if(!st->rrdfamily->use_count) + rrdfamily_free(st->rrdfamily); + + pthread_rwlock_unlock(&st->rwlock); + + if(st->mapped == RRD_MEMORY_MODE_SAVE) { + debug(D_RRD_CALLS, "Saving stats '%s' to '%s'.", st->name, st->cache_filename); + savememory(st->cache_filename, st, st->memsize); + + debug(D_RRD_CALLS, "Unmapping stats '%s'.", st->name); + munmap(st, st->memsize); + } + else if(st->mapped == RRD_MEMORY_MODE_MAP) { + debug(D_RRD_CALLS, "Unmapping stats '%s'.", st->name); + munmap(st, st->memsize); + } + else + freez(st); + + st = next; + } + localhost.rrdset_root = NULL; + + rrdhost_unlock(&localhost); + + info("Memory cleanup completed..."); +} - // let it log a few error messages - error_log_limit_reset(); +void rrdset_save_all(void) { + info("Saving database..."); - RRDSET *st; - RRDDIM *rd; + RRDSET *st; + RRDDIM *rd; - pthread_rwlock_wrlock(&rrdset_root_rwlock); - for(st = rrdset_root; st ; st = st->next) { - pthread_rwlock_wrlock(&st->rwlock); + rrdhost_rwlock(&localhost); + for(st = localhost.rrdset_root; st ; st = st->next) { + pthread_rwlock_wrlock(&st->rwlock); - if(st->mapped == RRD_MEMORY_MODE_SAVE) { - debug(D_RRD_CALLS, "Saving stats '%s' to '%s'.", st->name, st->cache_filename); - savememory(st->cache_filename, st, st->memsize); - } + if(st->mapped == RRD_MEMORY_MODE_SAVE) { + debug(D_RRD_CALLS, "Saving stats '%s' to '%s'.", st->name, st->cache_filename); + savememory(st->cache_filename, st, st->memsize); + } - for(rd = st->dimensions; rd ; rd = rd->next) { - if(likely(rd->mapped == RRD_MEMORY_MODE_SAVE)) { - debug(D_RRD_CALLS, "Saving dimension '%s' to '%s'.", rd->name, rd->cache_filename); - savememory(rd->cache_filename, rd, rd->memsize); - } - } + for(rd = st->dimensions; rd ; rd = rd->next) { + if(likely(rd->mapped == RRD_MEMORY_MODE_SAVE)) { + debug(D_RRD_CALLS, "Saving dimension '%s' to '%s'.", rd->name, rd->cache_filename); + savememory(rd->cache_filename, rd, rd->memsize); + } + } - pthread_rwlock_unlock(&st->rwlock); - } - pthread_rwlock_unlock(&rrdset_root_rwlock); + pthread_rwlock_unlock(&st->rwlock); + } + rrdhost_unlock(&localhost); } RRDSET *rrdset_find(const char *id) { - debug(D_RRD_CALLS, "rrdset_find() for chart %s", id); + debug(D_RRD_CALLS, "rrdset_find() for chart %s", id); - RRDSET *st = rrdset_index_find(id, 0); - return(st); + RRDSET *st = rrdset_index_find(&localhost, id, 0); + return(st); } RRDSET *rrdset_find_bytype(const char *type, const char *id) { - debug(D_RRD_CALLS, "rrdset_find_bytype() for chart %s.%s", type, id); + debug(D_RRD_CALLS, "rrdset_find_bytype() for chart %s.%s", type, id); - char buf[RRD_ID_LENGTH_MAX + 1]; + char buf[RRD_ID_LENGTH_MAX + 1]; - strncpyz(buf, type, RRD_ID_LENGTH_MAX - 1); - strcat(buf, "."); - int len = (int) strlen(buf); - strncpyz(&buf[len], id, (size_t) (RRD_ID_LENGTH_MAX - len)); + strncpyz(buf, type, RRD_ID_LENGTH_MAX - 1); + strcat(buf, "."); + int len = (int) strlen(buf); + strncpyz(&buf[len], id, (size_t) (RRD_ID_LENGTH_MAX - len)); - return(rrdset_find(buf)); + return(rrdset_find(buf)); } RRDSET *rrdset_find_byname(const char *name) { - debug(D_RRD_CALLS, "rrdset_find_byname() for chart %s", name); + debug(D_RRD_CALLS, "rrdset_find_byname() for chart %s", name); - RRDSET *st = rrdset_index_find_name(name, 0); - return(st); + RRDSET *st = rrdset_index_find_name(&localhost, name, 0); + return(st); } RRDDIM *rrddim_find(RRDSET *st, const char *id) { - debug(D_RRD_CALLS, "rrddim_find() for chart %s, dimension %s", st->name, id); + debug(D_RRD_CALLS, "rrddim_find() for chart %s, dimension %s", st->name, id); - return rrddim_index_find(st, id, 0); + return rrddim_index_find(st, id, 0); } int rrddim_hide(RRDSET *st, const char *id) { - debug(D_RRD_CALLS, "rrddim_hide() for chart %s, dimension %s", st->name, id); + debug(D_RRD_CALLS, "rrddim_hide() for chart %s, dimension %s", st->name, id); - RRDDIM *rd = rrddim_find(st, id); - if(unlikely(!rd)) { - error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id); - return 1; - } + RRDDIM *rd = rrddim_find(st, id); + if(unlikely(!rd)) { + error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id); + return 1; + } - rd->flags |= RRDDIM_FLAG_HIDDEN; - return 0; + rd->flags |= RRDDIM_FLAG_HIDDEN; + return 0; } int rrddim_unhide(RRDSET *st, const char *id) { - debug(D_RRD_CALLS, "rrddim_unhide() for chart %s, dimension %s", st->name, id); + debug(D_RRD_CALLS, "rrddim_unhide() for chart %s, dimension %s", st->name, id); - RRDDIM *rd = rrddim_find(st, id); - if(unlikely(!rd)) { - error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id); - return 1; - } + RRDDIM *rd = rrddim_find(st, id); + if(unlikely(!rd)) { + error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id); + return 1; + } - if(rd->flags & RRDDIM_FLAG_HIDDEN) rd->flags ^= RRDDIM_FLAG_HIDDEN; - return 0; + if(rd->flags & RRDDIM_FLAG_HIDDEN) rd->flags ^= RRDDIM_FLAG_HIDDEN; + return 0; } collected_number rrddim_set_by_pointer(RRDSET *st, RRDDIM *rd, collected_number value) { - debug(D_RRD_CALLS, "rrddim_set_by_pointer() for chart %s, dimension %s, value " COLLECTED_NUMBER_FORMAT, st->name, rd->name, value); + debug(D_RRD_CALLS, "rrddim_set_by_pointer() for chart %s, dimension %s, value " COLLECTED_NUMBER_FORMAT, st->name, rd->name, value); - gettimeofday(&rd->last_collected_time, NULL); - rd->collected_value = value; - rd->updated = 1; - rd->counter++; + gettimeofday(&rd->last_collected_time, NULL); + rd->collected_value = value; + rd->updated = 1; + rd->counter++; - return rd->last_collected_value; + return rd->last_collected_value; } collected_number rrddim_set(RRDSET *st, const char *id, collected_number value) { - RRDDIM *rd = rrddim_find(st, id); - if(unlikely(!rd)) { - error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id); - return 0; - } + RRDDIM *rd = rrddim_find(st, id); + if(unlikely(!rd)) { + error("Cannot find dimension with id '%s' on stats '%s' (%s).", id, st->name, st->id); + return 0; + } - return rrddim_set_by_pointer(st, rd, value); + return rrddim_set_by_pointer(st, rd, value); } void rrdset_next_usec(RRDSET *st, unsigned long long microseconds) { - if(!microseconds) rrdset_next(st); - else { - debug(D_RRD_CALLS, "rrdset_next_usec() for chart %s with microseconds %llu", st->name, microseconds); + if(!microseconds) rrdset_next(st); + else { + debug(D_RRD_CALLS, "rrdset_next_usec() for chart %s with microseconds %llu", st->name, microseconds); - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: NEXT: %llu microseconds", st->name, microseconds); - st->usec_since_last_update = microseconds; - } + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: NEXT: %llu microseconds", st->name, microseconds); + st->usec_since_last_update = microseconds; + } } void rrdset_next(RRDSET *st) { - unsigned long long microseconds = 0; + unsigned long long microseconds = 0; - if(likely(st->last_collected_time.tv_sec)) { - struct timeval now; - gettimeofday(&now, NULL); - microseconds = usecdiff(&now, &st->last_collected_time); - } - // prevent infinite loop - else microseconds = st->update_every * 1000000ULL; + if(likely(st->last_collected_time.tv_sec)) { + struct timeval now; + gettimeofday(&now, NULL); + microseconds = usec_dt(&now, &st->last_collected_time); + } + // prevent infinite loop + else microseconds = st->update_every * 1000000ULL; - rrdset_next_usec(st, microseconds); + rrdset_next_usec(st, microseconds); } void rrdset_next_plugins(RRDSET *st) { - rrdset_next(st); + rrdset_next(st); } unsigned long long rrdset_done(RRDSET *st) { - debug(D_RRD_CALLS, "rrdset_done() for chart %s", st->name); - - RRDDIM *rd, *last; - int oldstate, store_this_entry = 1, first_entry = 0; - unsigned long long last_ut, now_ut, next_ut, stored_entries = 0; - - if(unlikely(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate) != 0)) - error("Cannot set pthread cancel state to DISABLE."); - - // a read lock is OK here - pthread_rwlock_rdlock(&st->rwlock); - - // enable the chart, if it was disabled - 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)) { - info("%s: took too long to be updated (%0.3Lf secs). Reseting it.", st->name, (long double)(st->usec_since_last_update / 1000000.0)); - rrdset_reset(st); - st->usec_since_last_update = st->update_every * 1000000ULL; - first_entry = 1; - } - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: microseconds since last update: %llu", st->name, st->usec_since_last_update); - - // set last_collected_time - if(unlikely(!st->last_collected_time.tv_sec)) { - // it is the first entry - // set the last_collected_time to now - gettimeofday(&st->last_collected_time, NULL); - - // the first entry should not be stored - store_this_entry = 0; - first_entry = 1; - - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: has not set last_collected_time. Setting it now. Will not store the next entry.", st->name); - } - else { - // it is not the first entry - // calculate the proper last_collected_time, using usec_since_last_update - unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec + st->usec_since_last_update; - st->last_collected_time.tv_sec = (time_t) (ut / 1000000ULL); - st->last_collected_time.tv_usec = (useconds_t) (ut % 1000000ULL); - } - - // if this set has not been updated in the past - // we fake the last_update time to be = now - usec_since_last_update - if(unlikely(!st->last_updated.tv_sec)) { - // it has never been updated before - // set a fake last_updated, in the past using usec_since_last_update - unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec - st->usec_since_last_update; - st->last_updated.tv_sec = (time_t) (ut / 1000000ULL); - st->last_updated.tv_usec = (useconds_t) (ut % 1000000ULL); - - // the first entry should not be stored - store_this_entry = 0; - first_entry = 1; - - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: initializing last_updated to now - %llu microseconds (%0.3Lf). Will not store the next entry.", st->name, st->usec_since_last_update, (long double)ut/1000000.0); - } - - // check if we will re-write the entire data set - if(unlikely(usecdiff(&st->last_collected_time, &st->last_updated) > st->update_every * st->entries * 1000000ULL)) { - info("%s: too old data (last updated at %u.%u, last collected at %u.%u). Reseting it. Will not store the next entry.", st->name, st->last_updated.tv_sec, st->last_updated.tv_usec, st->last_collected_time.tv_sec, st->last_collected_time.tv_usec); - rrdset_reset(st); - - st->usec_since_last_update = st->update_every * 1000000ULL; - - gettimeofday(&st->last_collected_time, NULL); - - unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec - st->usec_since_last_update; - st->last_updated.tv_sec = (time_t) (ut / 1000000ULL); - st->last_updated.tv_usec = (useconds_t) (ut % 1000000ULL); - - // the first entry should not be stored - store_this_entry = 0; - first_entry = 1; - } - - // these are the 3 variables that will help us in interpolation - // last_ut = the last time we added a value to the storage - // now_ut = the time the current value is taken at - // next_ut = the time of the next interpolation point - last_ut = st->last_updated.tv_sec * 1000000ULL + st->last_updated.tv_usec; - now_ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec; - next_ut = (st->last_updated.tv_sec + st->update_every) * 1000000ULL; - - if(unlikely(!first_entry && now_ut < next_ut)) { - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: THIS IS IN THE SAME INTERPOLATION POINT", st->name); - } - - if(unlikely(st->debug)) { - debug(D_RRD_STATS, "%s: last ut = %0.3Lf (last updated time)", st->name, (long double)last_ut/1000000.0); - debug(D_RRD_STATS, "%s: now ut = %0.3Lf (current update time)", st->name, (long double)now_ut/1000000.0); - debug(D_RRD_STATS, "%s: next ut = %0.3Lf (next interpolation point)", st->name, (long double)next_ut/1000000.0); - } - - if(unlikely(!st->counter_done)) { - store_this_entry = 0; - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: Will not store the next entry.", st->name); - } - st->counter_done++; - - // calculate totals and count the dimensions - int dimensions; - st->collected_total = 0; - for( rd = st->dimensions, dimensions = 0 ; likely(rd) ; rd = rd->next, dimensions++ ) - st->collected_total += rd->collected_value; - - uint32_t storage_flags = SN_EXISTS; - - // process all dimensions to calculate their values - // based on the collected figures only - // at this stage we do not interpolate anything - for( rd = st->dimensions ; likely(rd) ; rd = rd->next ) { - - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: START " - " last_collected_value = " COLLECTED_NUMBER_FORMAT - " collected_value = " COLLECTED_NUMBER_FORMAT - " last_calculated_value = " CALCULATED_NUMBER_FORMAT - " calculated_value = " CALCULATED_NUMBER_FORMAT - , st->id, rd->name - , rd->last_collected_value - , rd->collected_value - , rd->last_calculated_value - , rd->calculated_value - ); - - switch(rd->algorithm) { - case RRDDIM_ABSOLUTE: - rd->calculated_value = (calculated_number)rd->collected_value - * (calculated_number)rd->multiplier - / (calculated_number)rd->divisor; - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: CALC ABS/ABS-NO-IN " - CALCULATED_NUMBER_FORMAT " = " - COLLECTED_NUMBER_FORMAT - " * " CALCULATED_NUMBER_FORMAT - " / " CALCULATED_NUMBER_FORMAT - , st->id, rd->name - , rd->calculated_value - , rd->collected_value - , (calculated_number)rd->multiplier - , (calculated_number)rd->divisor - ); - break; - - case RRDDIM_PCENT_OVER_ROW_TOTAL: - if(unlikely(!st->collected_total)) rd->calculated_value = 0; - else - // the percentage of the current value - // over the total of all dimensions - rd->calculated_value = - (calculated_number)100 - * (calculated_number)rd->collected_value - / (calculated_number)st->collected_total; - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: CALC PCENT-ROW " - CALCULATED_NUMBER_FORMAT " = 100" - " * " COLLECTED_NUMBER_FORMAT - " / " COLLECTED_NUMBER_FORMAT - , st->id, rd->name - , rd->calculated_value - , rd->collected_value - , st->collected_total - ); - break; - - case RRDDIM_INCREMENTAL: - if(unlikely(!rd->updated || rd->counter <= 1)) { - rd->calculated_value = 0; - continue; - } - - // if the new is smaller than the old (an overflow, or reset), set the old equal to the new - // to reset the calculation (it will give zero as the calculation for this second) - if(unlikely(rd->last_collected_value > rd->collected_value)) { - debug(D_RRD_STATS, "%s.%s: RESET or OVERFLOW. Last collected value = " COLLECTED_NUMBER_FORMAT ", current = " COLLECTED_NUMBER_FORMAT - , st->name, rd->name - , rd->last_collected_value - , rd->collected_value); - if(!(rd->flags & RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS)) storage_flags = SN_EXISTS_RESET; - rd->last_collected_value = rd->collected_value; - } - - rd->calculated_value = (calculated_number)(rd->collected_value - rd->last_collected_value) - * (calculated_number)rd->multiplier - / (calculated_number)rd->divisor; - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: CALC INC PRE " - CALCULATED_NUMBER_FORMAT " = (" - COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT - ")" - " * " CALCULATED_NUMBER_FORMAT - " / " CALCULATED_NUMBER_FORMAT - , st->id, rd->name - , rd->calculated_value - , rd->collected_value, rd->last_collected_value - , (calculated_number)rd->multiplier - , (calculated_number)rd->divisor - ); - break; - - case RRDDIM_PCENT_OVER_DIFF_TOTAL: - if(unlikely(!rd->updated || rd->counter <= 1)) { - rd->calculated_value = 0; - continue; - } - - // the percentage of the current increment - // over the increment of all dimensions together - if(unlikely(st->collected_total == st->last_collected_total)) rd->calculated_value = rd->last_calculated_value; - else rd->calculated_value = - (calculated_number)100 - * (calculated_number)(rd->collected_value - rd->last_collected_value) - / (calculated_number)(st->collected_total - st->last_collected_total); - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: CALC PCENT-DIFF " - CALCULATED_NUMBER_FORMAT " = 100" - " * (" COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT ")" - " / (" COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT ")" - , st->id, rd->name - , rd->calculated_value - , rd->collected_value, rd->last_collected_value - , st->collected_total, st->last_collected_total - ); - break; - - default: - // make the default zero, to make sure - // it gets noticed when we add new types - rd->calculated_value = 0; - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: CALC " - CALCULATED_NUMBER_FORMAT " = 0" - , st->id, rd->name - , rd->calculated_value - ); - break; - } - - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: PHASE2 " - " last_collected_value = " COLLECTED_NUMBER_FORMAT - " collected_value = " COLLECTED_NUMBER_FORMAT - " last_calculated_value = " CALCULATED_NUMBER_FORMAT - " calculated_value = " CALCULATED_NUMBER_FORMAT - , st->id, rd->name - , rd->last_collected_value - , rd->collected_value - , rd->last_calculated_value - , rd->calculated_value - ); - - } - - // at this point we have all the calculated values ready - // it is now time to interpolate values on a second boundary - - unsigned long long first_ut = last_ut; - long long iterations = (now_ut - last_ut) / (st->update_every * 1000000ULL); - if((now_ut % (st->update_every * 1000000ULL)) == 0) iterations++; - - for( ; likely(next_ut <= now_ut) ; next_ut += st->update_every * 1000000ULL, iterations-- ) { + if(unlikely(netdata_exit)) return 0; + + debug(D_RRD_CALLS, "rrdset_done() for chart %s", st->name); + + RRDDIM *rd, *last; + int oldstate, store_this_entry = 1, first_entry = 0; + unsigned long long last_ut, now_ut, next_ut, stored_entries = 0; + + if(unlikely(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate) != 0)) + error("Cannot set pthread cancel state to DISABLE."); + + // a read lock is OK here + pthread_rwlock_rdlock(&st->rwlock); + + // enable the chart, if it was disabled + 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)) { + info("%s: took too long to be updated (%0.3Lf secs). Reseting it.", st->name, (long double)(st->usec_since_last_update / 1000000.0)); + rrdset_reset(st); + st->usec_since_last_update = st->update_every * 1000000ULL; + first_entry = 1; + } + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: microseconds since last update: %llu", st->name, st->usec_since_last_update); + + // set last_collected_time + if(unlikely(!st->last_collected_time.tv_sec)) { + // it is the first entry + // set the last_collected_time to now + gettimeofday(&st->last_collected_time, NULL); + + // the first entry should not be stored + store_this_entry = 0; + first_entry = 1; + + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: has not set last_collected_time. Setting it now. Will not store the next entry.", st->name); + } + else { + // it is not the first entry + // calculate the proper last_collected_time, using usec_since_last_update + unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec + st->usec_since_last_update; + st->last_collected_time.tv_sec = (time_t) (ut / 1000000ULL); + st->last_collected_time.tv_usec = (suseconds_t) (ut % 1000000ULL); + } + + // if this set has not been updated in the past + // we fake the last_update time to be = now - usec_since_last_update + if(unlikely(!st->last_updated.tv_sec)) { + // it has never been updated before + // set a fake last_updated, in the past using usec_since_last_update + unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec - st->usec_since_last_update; + st->last_updated.tv_sec = (time_t) (ut / 1000000ULL); + st->last_updated.tv_usec = (suseconds_t) (ut % 1000000ULL); + + // the first entry should not be stored + store_this_entry = 0; + first_entry = 1; + + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: initializing last_updated to now - %llu microseconds (%0.3Lf). Will not store the next entry.", st->name, st->usec_since_last_update, (long double)ut/1000000.0); + } + + // check if we will re-write the entire data set + if(unlikely(usec_dt(&st->last_collected_time, &st->last_updated) > st->update_every * st->entries * 1000000ULL)) { + info("%s: too old data (last updated at %ld.%ld, last collected at %ld.%ld). Reseting it. Will not store the next entry.", st->name, st->last_updated.tv_sec, st->last_updated.tv_usec, st->last_collected_time.tv_sec, st->last_collected_time.tv_usec); + rrdset_reset(st); + + st->usec_since_last_update = st->update_every * 1000000ULL; + + gettimeofday(&st->last_collected_time, NULL); + + unsigned long long ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec - st->usec_since_last_update; + st->last_updated.tv_sec = (time_t) (ut / 1000000ULL); + st->last_updated.tv_usec = (suseconds_t) (ut % 1000000ULL); + + // the first entry should not be stored + store_this_entry = 0; + first_entry = 1; + } + + // these are the 3 variables that will help us in interpolation + // last_ut = the last time we added a value to the storage + // now_ut = the time the current value is taken at + // next_ut = the time of the next interpolation point + last_ut = st->last_updated.tv_sec * 1000000ULL + st->last_updated.tv_usec; + now_ut = st->last_collected_time.tv_sec * 1000000ULL + st->last_collected_time.tv_usec; + next_ut = (st->last_updated.tv_sec + st->update_every) * 1000000ULL; + + if(unlikely(!first_entry && now_ut < next_ut)) { + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: THIS IS IN THE SAME INTERPOLATION POINT", st->name); + } + + if(unlikely(st->debug)) { + debug(D_RRD_STATS, "%s: last ut = %0.3Lf (last updated time)", st->name, (long double)last_ut/1000000.0); + debug(D_RRD_STATS, "%s: now ut = %0.3Lf (current update time)", st->name, (long double)now_ut/1000000.0); + debug(D_RRD_STATS, "%s: next ut = %0.3Lf (next interpolation point)", st->name, (long double)next_ut/1000000.0); + } + + if(unlikely(!st->counter_done)) { + store_this_entry = 0; + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s: Will not store the next entry.", st->name); + } + st->counter_done++; + + // calculate totals and count the dimensions + int dimensions; + st->collected_total = 0; + for( rd = st->dimensions, dimensions = 0 ; likely(rd) ; rd = rd->next, dimensions++ ) + if(likely(rd->updated)) st->collected_total += rd->collected_value; + + uint32_t storage_flags = SN_EXISTS; + + // process all dimensions to calculate their values + // based on the collected figures only + // at this stage we do not interpolate anything + for( rd = st->dimensions ; likely(rd) ; rd = rd->next ) { + + if(unlikely(!rd->updated)) { + rd->calculated_value = 0; + continue; + } + + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: START " + " last_collected_value = " COLLECTED_NUMBER_FORMAT + " collected_value = " COLLECTED_NUMBER_FORMAT + " last_calculated_value = " CALCULATED_NUMBER_FORMAT + " calculated_value = " CALCULATED_NUMBER_FORMAT + , st->id, rd->name + , rd->last_collected_value + , rd->collected_value + , rd->last_calculated_value + , rd->calculated_value + ); + + switch(rd->algorithm) { + case RRDDIM_ABSOLUTE: + rd->calculated_value = (calculated_number)rd->collected_value + * (calculated_number)rd->multiplier + / (calculated_number)rd->divisor; + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: CALC ABS/ABS-NO-IN " + CALCULATED_NUMBER_FORMAT " = " + COLLECTED_NUMBER_FORMAT + " * " CALCULATED_NUMBER_FORMAT + " / " CALCULATED_NUMBER_FORMAT + , st->id, rd->name + , rd->calculated_value + , rd->collected_value + , (calculated_number)rd->multiplier + , (calculated_number)rd->divisor + ); + break; + + case RRDDIM_PCENT_OVER_ROW_TOTAL: + if(unlikely(!st->collected_total)) + rd->calculated_value = 0; + else + // the percentage of the current value + // over the total of all dimensions + rd->calculated_value = + (calculated_number)100 + * (calculated_number)rd->collected_value + / (calculated_number)st->collected_total; + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: CALC PCENT-ROW " + CALCULATED_NUMBER_FORMAT " = 100" + " * " COLLECTED_NUMBER_FORMAT + " / " COLLECTED_NUMBER_FORMAT + , st->id, rd->name + , rd->calculated_value + , rd->collected_value + , st->collected_total + ); + break; + + case RRDDIM_INCREMENTAL: + if(unlikely(rd->counter <= 1)) { + rd->calculated_value = 0; + continue; + } + + // if the new is smaller than the old (an overflow, or reset), set the old equal to the new + // to reset the calculation (it will give zero as the calculation for this second) + if(unlikely(rd->last_collected_value > rd->collected_value)) { + debug(D_RRD_STATS, "%s.%s: RESET or OVERFLOW. Last collected value = " COLLECTED_NUMBER_FORMAT ", current = " COLLECTED_NUMBER_FORMAT + , st->name, rd->name + , rd->last_collected_value + , rd->collected_value); + if(!(rd->flags & RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS)) storage_flags = SN_EXISTS_RESET; + rd->last_collected_value = rd->collected_value; + } + + rd->calculated_value = + (calculated_number)(rd->collected_value - rd->last_collected_value) + * (calculated_number)rd->multiplier + / (calculated_number)rd->divisor; + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: CALC INC PRE " + CALCULATED_NUMBER_FORMAT " = (" + COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT + ")" + " * " CALCULATED_NUMBER_FORMAT + " / " CALCULATED_NUMBER_FORMAT + , st->id, rd->name + , rd->calculated_value + , rd->collected_value, rd->last_collected_value + , (calculated_number)rd->multiplier + , (calculated_number)rd->divisor + ); + break; + + case RRDDIM_PCENT_OVER_DIFF_TOTAL: + if(unlikely(rd->counter <= 1)) { + rd->calculated_value = 0; + continue; + } + + // if the new is smaller than the old (an overflow, or reset), set the old equal to the new + // to reset the calculation (it will give zero as the calculation for this second) + if(unlikely(rd->last_collected_value > rd->collected_value)) { + debug(D_RRD_STATS, "%s.%s: RESET or OVERFLOW. Last collected value = " COLLECTED_NUMBER_FORMAT ", current = " COLLECTED_NUMBER_FORMAT + , st->name, rd->name + , rd->last_collected_value + , rd->collected_value); + if(!(rd->flags & RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS)) storage_flags = SN_EXISTS_RESET; + rd->last_collected_value = rd->collected_value; + } + + // the percentage of the current increment + // over the increment of all dimensions together + if(unlikely(st->collected_total == st->last_collected_total)) + rd->calculated_value = 0; + else + rd->calculated_value = + (calculated_number)100 + * (calculated_number)(rd->collected_value - rd->last_collected_value) + / (calculated_number)(st->collected_total - st->last_collected_total); + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: CALC PCENT-DIFF " + CALCULATED_NUMBER_FORMAT " = 100" + " * (" COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT ")" + " / (" COLLECTED_NUMBER_FORMAT " - " COLLECTED_NUMBER_FORMAT ")" + , st->id, rd->name + , rd->calculated_value + , rd->collected_value, rd->last_collected_value + , st->collected_total, st->last_collected_total + ); + break; + + default: + // make the default zero, to make sure + // it gets noticed when we add new types + rd->calculated_value = 0; + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: CALC " + CALCULATED_NUMBER_FORMAT " = 0" + , st->id, rd->name + , rd->calculated_value + ); + break; + } + + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: PHASE2 " + " last_collected_value = " COLLECTED_NUMBER_FORMAT + " collected_value = " COLLECTED_NUMBER_FORMAT + " last_calculated_value = " CALCULATED_NUMBER_FORMAT + " calculated_value = " CALCULATED_NUMBER_FORMAT + , st->id, rd->name + , rd->last_collected_value + , rd->collected_value + , rd->last_calculated_value + , rd->calculated_value + ); + + } + + // at this point we have all the calculated values ready + // it is now time to interpolate values on a second boundary + + unsigned long long first_ut = last_ut; + long long iterations = (now_ut - last_ut) / (st->update_every * 1000000ULL); + if((now_ut % (st->update_every * 1000000ULL)) == 0) iterations++; + + for( ; likely(next_ut <= now_ut) ; next_ut += st->update_every * 1000000ULL, iterations-- ) { #ifdef NETDATA_INTERNAL_CHECKS - if(iterations < 0) { error("%s: iterations calculation wrapped! first_ut = %llu, last_ut = %llu, next_ut = %llu, now_ut = %llu", st->name, first_ut, last_ut, next_ut, now_ut); } + if(iterations < 0) { error("%s: iterations calculation wrapped! first_ut = %llu, last_ut = %llu, next_ut = %llu, now_ut = %llu", st->name, first_ut, last_ut, next_ut, now_ut); } #endif - if(unlikely(st->debug)) { - debug(D_RRD_STATS, "%s: last ut = %0.3Lf (last updated time)", st->name, (long double)last_ut/1000000.0); - debug(D_RRD_STATS, "%s: next ut = %0.3Lf (next interpolation point)", st->name, (long double)next_ut/1000000.0); - } - - st->last_updated.tv_sec = (time_t) (next_ut / 1000000ULL); - st->last_updated.tv_usec = 0; - - for( rd = st->dimensions ; likely(rd) ; rd = rd->next ) { - calculated_number new_value; - - switch(rd->algorithm) { - case RRDDIM_INCREMENTAL: - new_value = (calculated_number) - ( rd->calculated_value - * (calculated_number)(next_ut - last_ut) - / (calculated_number)(now_ut - last_ut) - ); - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: CALC2 INC " - CALCULATED_NUMBER_FORMAT " = " - CALCULATED_NUMBER_FORMAT - " * %llu" - " / %llu" - , st->id, rd->name - , new_value - , rd->calculated_value - , (next_ut - last_ut) - , (now_ut - last_ut) - ); - - rd->calculated_value -= new_value; - new_value += rd->last_calculated_value; - rd->last_calculated_value = 0; - new_value /= (calculated_number)st->update_every; - break; - - case RRDDIM_ABSOLUTE: - case RRDDIM_PCENT_OVER_ROW_TOTAL: - case RRDDIM_PCENT_OVER_DIFF_TOTAL: - default: - if(iterations == 1) { - // this is the last iteration - // do not interpolate - // just show the calculated value - - new_value = rd->calculated_value; - } - else { - // we have missed an update - // interpolate in the middle values - - new_value = (calculated_number) - ( ( (rd->calculated_value - rd->last_calculated_value) - * (calculated_number)(next_ut - first_ut) - / (calculated_number)(now_ut - first_ut) - ) - + rd->last_calculated_value - ); - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: CALC2 DEF " - CALCULATED_NUMBER_FORMAT " = (((" - "(" CALCULATED_NUMBER_FORMAT " - " CALCULATED_NUMBER_FORMAT ")" - " * %llu" - " / %llu) + " CALCULATED_NUMBER_FORMAT - , st->id, rd->name - , new_value - , rd->calculated_value, rd->last_calculated_value - , (next_ut - first_ut) - , (now_ut - first_ut), rd->last_calculated_value - ); - - // this is wrong - // it fades the value towards the target - // while we know the calculated value is different - // if(likely(next_ut + st->update_every * 1000000ULL > now_ut)) rd->calculated_value = new_value; - } - break; - } - - if(unlikely(!store_this_entry)) { - store_this_entry = 1; - continue; - } - - if(likely(rd->updated && rd->counter > 1 && iterations < st->gap_when_lost_iterations_above)) { - rd->values[st->current_entry] = pack_storage_number(new_value, storage_flags ); - - if(unlikely(st->debug)) - debug(D_RRD_STATS, "%s/%s: STORE[%ld] " - CALCULATED_NUMBER_FORMAT " = " CALCULATED_NUMBER_FORMAT - , st->id, rd->name - , st->current_entry - , unpack_storage_number(rd->values[st->current_entry]), new_value - ); - } - else { - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: STORE[%ld] = NON EXISTING " - , st->id, rd->name - , st->current_entry - ); - rd->values[st->current_entry] = pack_storage_number(0, SN_NOT_EXISTS); - } - - stored_entries++; - - if(unlikely(st->debug)) { - calculated_number t1 = new_value * (calculated_number)rd->multiplier / (calculated_number)rd->divisor; - calculated_number t2 = unpack_storage_number(rd->values[st->current_entry]); - calculated_number accuracy = accuracy_loss(t1, t2); - debug(D_RRD_STATS, "%s/%s: UNPACK[%ld] = " CALCULATED_NUMBER_FORMAT " FLAGS=0x%08x (original = " CALCULATED_NUMBER_FORMAT ", accuracy loss = " CALCULATED_NUMBER_FORMAT "%%%s)" - , st->id, rd->name - , st->current_entry - , t2 - , get_storage_number_flags(rd->values[st->current_entry]) - , t1 - , accuracy - , (accuracy > ACCURACY_LOSS) ? " **TOO BIG** " : "" - ); - - rd->collected_volume += t1; - rd->stored_volume += t2; - accuracy = accuracy_loss(rd->collected_volume, rd->stored_volume); - debug(D_RRD_STATS, "%s/%s: VOLUME[%ld] = " CALCULATED_NUMBER_FORMAT ", calculated = " CALCULATED_NUMBER_FORMAT ", accuracy loss = " CALCULATED_NUMBER_FORMAT "%%%s" - , st->id, rd->name - , st->current_entry - , rd->stored_volume - , rd->collected_volume - , accuracy - , (accuracy > ACCURACY_LOSS) ? " **TOO BIG** " : "" - ); - - } - } - // reset the storage flags for the next point, if any; - storage_flags = SN_EXISTS; - - st->counter++; - st->current_entry = ((st->current_entry + 1) >= st->entries) ? 0 : st->current_entry + 1; - last_ut = next_ut; - } - - // align next interpolation to last collection point - if(likely(stored_entries || !store_this_entry)) { - st->last_updated.tv_sec = st->last_collected_time.tv_sec; - st->last_updated.tv_usec = st->last_collected_time.tv_usec; - } - - for( rd = st->dimensions; likely(rd) ; rd = rd->next ) { - if(unlikely(!rd->updated)) continue; - - if(likely(stored_entries || !store_this_entry)) { - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: setting last_collected_value (old: " COLLECTED_NUMBER_FORMAT ") to last_collected_value (new: " COLLECTED_NUMBER_FORMAT ")", st->id, rd->name, rd->last_collected_value, rd->collected_value); - rd->last_collected_value = rd->collected_value; - - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: setting last_calculated_value (old: " CALCULATED_NUMBER_FORMAT ") to last_calculated_value (new: " CALCULATED_NUMBER_FORMAT ")", st->id, rd->name, rd->last_calculated_value, rd->calculated_value); - rd->last_calculated_value = rd->calculated_value; - } - - rd->calculated_value = 0; - rd->collected_value = 0; - rd->updated = 0; - - if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: END " - " last_collected_value = " COLLECTED_NUMBER_FORMAT - " collected_value = " COLLECTED_NUMBER_FORMAT - " last_calculated_value = " CALCULATED_NUMBER_FORMAT - " calculated_value = " CALCULATED_NUMBER_FORMAT - , st->id, rd->name - , rd->last_collected_value - , rd->collected_value - , rd->last_calculated_value - , rd->calculated_value - ); - } - st->last_collected_total = st->collected_total; - - // ALL DONE ABOUT THE DATA UPDATE - // -------------------------------------------------------------------- - - // find if there are any obsolete dimensions (not updated recently) - if(unlikely(rrd_delete_unupdated_dimensions)) { - - for( rd = st->dimensions; likely(rd) ; rd = rd->next ) - if((rd->last_collected_time.tv_sec + (rrd_delete_unupdated_dimensions * st->update_every)) < st->last_collected_time.tv_sec) - break; - - if(unlikely(rd)) { - // there is dimension to free - // upgrade our read lock to a write lock - pthread_rwlock_unlock(&st->rwlock); - pthread_rwlock_wrlock(&st->rwlock); - - for( rd = st->dimensions, last = NULL ; likely(rd) ; ) { - // remove it only it is not updated in rrd_delete_unupdated_dimensions seconds - - if(unlikely((rd->last_collected_time.tv_sec + (rrd_delete_unupdated_dimensions * st->update_every)) < st->last_collected_time.tv_sec)) { - info("Removing obsolete dimension '%s' (%s) of '%s' (%s).", rd->name, rd->id, st->name, st->id); - - if(unlikely(!last)) { - st->dimensions = rd->next; - rd->next = NULL; - rrddim_free(st, rd); - rd = st->dimensions; - continue; - } - else { - last->next = rd->next; - rd->next = NULL; - rrddim_free(st, rd); - rd = last->next; - continue; - } - } - - last = rd; - rd = rd->next; - } - - if(unlikely(!st->dimensions)) { - info("Disabling chart %s (%s) since it does not have any dimensions", st->name, st->id); - st->enabled = 0; - } - } - } - - pthread_rwlock_unlock(&st->rwlock); - - if(unlikely(pthread_setcancelstate(oldstate, NULL) != 0)) - error("Cannot set pthread cancel state to RESTORE (%d).", oldstate); - - return(st->usec_since_last_update); + if(unlikely(st->debug)) { + debug(D_RRD_STATS, "%s: last ut = %0.3Lf (last updated time)", st->name, (long double)last_ut/1000000.0); + debug(D_RRD_STATS, "%s: next ut = %0.3Lf (next interpolation point)", st->name, (long double)next_ut/1000000.0); + } + + st->last_updated.tv_sec = (time_t) (next_ut / 1000000ULL); + st->last_updated.tv_usec = 0; + + for( rd = st->dimensions ; likely(rd) ; rd = rd->next ) { + calculated_number new_value; + + switch(rd->algorithm) { + case RRDDIM_INCREMENTAL: + new_value = (calculated_number) + ( rd->calculated_value + * (calculated_number)(next_ut - last_ut) + / (calculated_number)(now_ut - last_ut) + ); + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: CALC2 INC " + CALCULATED_NUMBER_FORMAT " = " + CALCULATED_NUMBER_FORMAT + " * %llu" + " / %llu" + , st->id, rd->name + , new_value + , rd->calculated_value + , (next_ut - last_ut) + , (now_ut - last_ut) + ); + + rd->calculated_value -= new_value; + new_value += rd->last_calculated_value; + rd->last_calculated_value = 0; + new_value /= (calculated_number)st->update_every; + break; + + case RRDDIM_ABSOLUTE: + case RRDDIM_PCENT_OVER_ROW_TOTAL: + case RRDDIM_PCENT_OVER_DIFF_TOTAL: + default: + if(iterations == 1) { + // this is the last iteration + // do not interpolate + // just show the calculated value + + new_value = rd->calculated_value; + } + else { + // we have missed an update + // interpolate in the middle values + + new_value = (calculated_number) + ( ( (rd->calculated_value - rd->last_calculated_value) + * (calculated_number)(next_ut - first_ut) + / (calculated_number)(now_ut - first_ut) + ) + + rd->last_calculated_value + ); + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: CALC2 DEF " + CALCULATED_NUMBER_FORMAT " = (((" + "(" CALCULATED_NUMBER_FORMAT " - " CALCULATED_NUMBER_FORMAT ")" + " * %llu" + " / %llu) + " CALCULATED_NUMBER_FORMAT + , st->id, rd->name + , new_value + , rd->calculated_value, rd->last_calculated_value + , (next_ut - first_ut) + , (now_ut - first_ut), rd->last_calculated_value + ); + + // this is wrong + // it fades the value towards the target + // while we know the calculated value is different + // if(likely(next_ut + st->update_every * 1000000ULL > now_ut)) rd->calculated_value = new_value; + } + break; + } + + if(unlikely(!store_this_entry)) { + // store_this_entry = 1; + continue; + } + + if(likely(rd->updated && rd->counter > 1 && iterations < st->gap_when_lost_iterations_above)) { + rd->values[st->current_entry] = pack_storage_number(new_value, storage_flags ); + rd->last_stored_value = new_value; + + if(unlikely(st->debug)) + debug(D_RRD_STATS, "%s/%s: STORE[%ld] " + CALCULATED_NUMBER_FORMAT " = " CALCULATED_NUMBER_FORMAT + , st->id, rd->name + , st->current_entry + , unpack_storage_number(rd->values[st->current_entry]), new_value + ); + } + else { + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: STORE[%ld] = NON EXISTING " + , st->id, rd->name + , st->current_entry + ); + rd->values[st->current_entry] = pack_storage_number(0, SN_NOT_EXISTS); + rd->last_stored_value = NAN; + } + + stored_entries++; + + if(unlikely(st->debug)) { + calculated_number t1 = new_value * (calculated_number)rd->multiplier / (calculated_number)rd->divisor; + calculated_number t2 = unpack_storage_number(rd->values[st->current_entry]); + calculated_number accuracy = accuracy_loss(t1, t2); + debug(D_RRD_STATS, "%s/%s: UNPACK[%ld] = " CALCULATED_NUMBER_FORMAT " FLAGS=0x%08x (original = " CALCULATED_NUMBER_FORMAT ", accuracy loss = " CALCULATED_NUMBER_FORMAT "%%%s)" + , st->id, rd->name + , st->current_entry + , t2 + , get_storage_number_flags(rd->values[st->current_entry]) + , t1 + , accuracy + , (accuracy > ACCURACY_LOSS) ? " **TOO BIG** " : "" + ); + + rd->collected_volume += t1; + rd->stored_volume += t2; + accuracy = accuracy_loss(rd->collected_volume, rd->stored_volume); + debug(D_RRD_STATS, "%s/%s: VOLUME[%ld] = " CALCULATED_NUMBER_FORMAT ", calculated = " CALCULATED_NUMBER_FORMAT ", accuracy loss = " CALCULATED_NUMBER_FORMAT "%%%s" + , st->id, rd->name + , st->current_entry + , rd->stored_volume + , rd->collected_volume + , accuracy + , (accuracy > ACCURACY_LOSS) ? " **TOO BIG** " : "" + ); + + } + } + // reset the storage flags for the next point, if any; + storage_flags = SN_EXISTS; + + st->counter++; + st->current_entry = ((st->current_entry + 1) >= st->entries) ? 0 : st->current_entry + 1; + last_ut = next_ut; + } + + // align next interpolation to last collection point + if(likely(stored_entries || !store_this_entry)) { + st->last_updated.tv_sec = st->last_collected_time.tv_sec; + st->last_updated.tv_usec = st->last_collected_time.tv_usec; + st->last_collected_total = st->collected_total; + } + + for( rd = st->dimensions; likely(rd) ; rd = rd->next ) { + if(unlikely(!rd->updated)) continue; + + if(likely(stored_entries || !store_this_entry)) { + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: setting last_collected_value (old: " COLLECTED_NUMBER_FORMAT ") to last_collected_value (new: " COLLECTED_NUMBER_FORMAT ")", st->id, rd->name, rd->last_collected_value, rd->collected_value); + rd->last_collected_value = rd->collected_value; + + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: setting last_calculated_value (old: " CALCULATED_NUMBER_FORMAT ") to last_calculated_value (new: " CALCULATED_NUMBER_FORMAT ")", st->id, rd->name, rd->last_calculated_value, rd->calculated_value); + rd->last_calculated_value = rd->calculated_value; + } + + rd->calculated_value = 0; + rd->collected_value = 0; + rd->updated = 0; + + if(unlikely(st->debug)) debug(D_RRD_STATS, "%s/%s: END " + " last_collected_value = " COLLECTED_NUMBER_FORMAT + " collected_value = " COLLECTED_NUMBER_FORMAT + " last_calculated_value = " CALCULATED_NUMBER_FORMAT + " calculated_value = " CALCULATED_NUMBER_FORMAT + , st->id, rd->name + , rd->last_collected_value + , rd->collected_value + , rd->last_calculated_value + , rd->calculated_value + ); + } + + // ALL DONE ABOUT THE DATA UPDATE + // -------------------------------------------------------------------- + + // find if there are any obsolete dimensions (not updated recently) + if(unlikely(rrd_delete_unupdated_dimensions)) { + + for( rd = st->dimensions; likely(rd) ; rd = rd->next ) + if((rd->last_collected_time.tv_sec + (rrd_delete_unupdated_dimensions * st->update_every)) < st->last_collected_time.tv_sec) + break; + + if(unlikely(rd)) { + // there is dimension to free + // upgrade our read lock to a write lock + pthread_rwlock_unlock(&st->rwlock); + pthread_rwlock_wrlock(&st->rwlock); + + for( rd = st->dimensions, last = NULL ; likely(rd) ; ) { + // remove it only it is not updated in rrd_delete_unupdated_dimensions seconds + + if(unlikely((rd->last_collected_time.tv_sec + (rrd_delete_unupdated_dimensions * st->update_every)) < st->last_collected_time.tv_sec)) { + info("Removing obsolete dimension '%s' (%s) of '%s' (%s).", rd->name, rd->id, st->name, st->id); + + if(unlikely(!last)) { + st->dimensions = rd->next; + rd->next = NULL; + rrddim_free(st, rd); + rd = st->dimensions; + continue; + } + else { + last->next = rd->next; + rd->next = NULL; + rrddim_free(st, rd); + rd = last->next; + continue; + } + } + + last = rd; + rd = rd->next; + } + + if(unlikely(!st->dimensions)) { + info("Disabling chart %s (%s) since it does not have any dimensions", st->name, st->id); + st->enabled = 0; + } + } + } + + pthread_rwlock_unlock(&st->rwlock); + + if(unlikely(pthread_setcancelstate(oldstate, NULL) != 0)) + error("Cannot set pthread cancel state to RESTORE (%d).", oldstate); + + return(st->usec_since_last_update); } @@ -1,10 +1,3 @@ -#include <sys/time.h> -#include <pthread.h> -#include <stdint.h> - -#include "avl.h" -#include "storage_number.h" - #ifndef NETDATA_RRD_H #define NETDATA_RRD_H 1 @@ -20,10 +13,10 @@ extern int rrd_default_history_entries; // set to zero to disable this feature extern int rrd_delete_unupdated_dimensions; -#define RRD_ID_LENGTH_MAX 1024 +#define RRD_ID_LENGTH_MAX 400 -#define RRDSET_MAGIC "NETDATA RRD SET FILE V017" -#define RRDDIMENSION_MAGIC "NETDATA RRD DIMENSION FILE V017" +#define RRDSET_MAGIC "NETDATA RRD SET FILE V018" +#define RRDDIMENSION_MAGIC "NETDATA RRD DIMENSION FILE V018" typedef long long total_number; #define TOTAL_NUMBER_FORMAT "%lld" @@ -35,8 +28,8 @@ typedef long long total_number; #define RRDSET_TYPE_AREA_NAME "area" #define RRDSET_TYPE_STACKED_NAME "stacked" -#define RRDSET_TYPE_LINE 0 -#define RRDSET_TYPE_AREA 1 +#define RRDSET_TYPE_LINE 0 +#define RRDSET_TYPE_AREA 1 #define RRDSET_TYPE_STACKED 2 int rrdset_type_id(const char *name); @@ -63,15 +56,15 @@ extern int rrd_memory_mode_id(const char *name); // ---------------------------------------------------------------------------- // algorithms types -#define RRDDIM_ABSOLUTE_NAME "absolute" -#define RRDDIM_INCREMENTAL_NAME "incremental" -#define RRDDIM_PCENT_OVER_DIFF_TOTAL_NAME "percentage-of-incremental-row" -#define RRDDIM_PCENT_OVER_ROW_TOTAL_NAME "percentage-of-absolute-row" +#define RRDDIM_ABSOLUTE_NAME "absolute" +#define RRDDIM_INCREMENTAL_NAME "incremental" +#define RRDDIM_PCENT_OVER_DIFF_TOTAL_NAME "percentage-of-incremental-row" +#define RRDDIM_PCENT_OVER_ROW_TOTAL_NAME "percentage-of-absolute-row" -#define RRDDIM_ABSOLUTE 0 -#define RRDDIM_INCREMENTAL 1 -#define RRDDIM_PCENT_OVER_DIFF_TOTAL 2 -#define RRDDIM_PCENT_OVER_ROW_TOTAL 3 +#define RRDDIM_ABSOLUTE 0 +#define RRDDIM_INCREMENTAL 1 +#define RRDDIM_PCENT_OVER_DIFF_TOTAL 2 +#define RRDDIM_PCENT_OVER_ROW_TOTAL 3 extern int rrddim_algorithm_id(const char *name); extern const char *rrddim_algorithm_name(int chart_type); @@ -83,80 +76,103 @@ extern const char *rrddim_algorithm_name(int chart_type); #define RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS 0x00000002 // do not offer RESET or OVERFLOW info to callers // ---------------------------------------------------------------------------- +// RRD CONTEXT + +struct rrdfamily { + avl avl; + + const char *family; + uint32_t hash_family; + + size_t use_count; + + avl_tree_lock variables_root_index; +}; +typedef struct rrdfamily RRDFAMILY; + +// ---------------------------------------------------------------------------- // RRD DIMENSION struct rrddim { - // ------------------------------------------------------------------------ - // binary indexing structures + // ------------------------------------------------------------------------ + // binary indexing structures + + avl avl; // the binary index - this has to be first member! + + // ------------------------------------------------------------------------ + // the dimension definition + + char id[RRD_ID_LENGTH_MAX + 1]; // the id of this dimension (for internal identification) - avl avl; // the binary index - this has to be first member! + const char *name; // the name of this dimension (as presented to user) + // this is a pointer to the config structure + // since the config always has a higher priority + // (the user overwrites the name of the charts) - // ------------------------------------------------------------------------ - // the dimension definition + int algorithm; // the algorithm that is applied to add new collected values + long multiplier; // the multiplier of the collected values + long divisor; // the divider of the collected values - char id[RRD_ID_LENGTH_MAX + 1]; // the id of this dimension (for internal identification) + int mapped; // if set to non zero, this dimension is mapped to a file - const char *name; // the name of this dimension (as presented to user) - // this is a pointer to the config structure - // since the config always has a higher priority - // (the user overwrites the name of the charts) + // ------------------------------------------------------------------------ + // members for temporary data we need for calculations - int algorithm; // the algorithm that is applied to add new collected values - long multiplier; // the multiplier of the collected values - long divisor; // the divider of the collected values + uint32_t hash; // a simple hash of the id, to speed up searching / indexing + // instead of strcmp() every item in the binary index + // we first compare the hashes - int mapped; // if set to non zero, this dimension is mapped to a file + // FIXME + // we need the hash_name too! - // ------------------------------------------------------------------------ - // members for temporary data we need for calculations + uint32_t flags; - uint32_t hash; // a simple hash of the id, to speed up searching / indexing - // instead of strcmp() every item in the binary index - // we first compare the hashes + char cache_filename[FILENAME_MAX+1]; // the filename we load/save from/to this set - uint32_t flags; + unsigned long counter; // the number of times we added values to this rrdim - char cache_filename[FILENAME_MAX+1]; // the filename we load/save from/to this set + int updated; // set to 0 after each calculation, to 1 after each collected value + // we use this to detect that a dimension is not updated - unsigned long counter; // the number of times we added values to this rrdim + struct timeval last_collected_time; // when was this dimension last updated + // this is actual date time we updated the last_collected_value + // THIS IS DIFFERENT FROM THE SAME MEMBER OF RRDSET - int updated; // set to 0 after each calculation, to 1 after each collected value - // we use this to detect that a dimension is not updated + calculated_number calculated_value; // the current calculated value, after applying the algorithm - resets to zero after being used + calculated_number last_calculated_value; // the last calculated value processed - struct timeval last_collected_time; // when was this dimension last updated - // this is actual date time we updated the last_collected_value - // THIS IS DIFFERENT FROM THE SAME MEMBER OF RRDSET + calculated_number last_stored_value; // the last value as stored in the database (after interpolation) - calculated_number calculated_value; // the current calculated value, after applying the algorithm - calculated_number last_calculated_value; // the last calculated value + collected_number collected_value; // the current value, as collected - resets to 0 after being used + collected_number last_collected_value; // the last value that was collected, after being processed - collected_number collected_value; // the current value, as collected - collected_number last_collected_value; // the last value that was collected + // the *_volume members are used to calculate the accuracy of the rounding done by the + // storage number - they are printed to debug.log when debug is enabled for a set. + calculated_number collected_volume; // the sum of all collected values so far + calculated_number stored_volume; // the sum of all stored values so far - // the *_volume members are used to calculate the accuracy of the rounding done by the - // storage number - they are printed to debug.log when debug is enabled for a set. - calculated_number collected_volume; // the sum of all collected values so far - calculated_number stored_volume; // the sum of all stored values so far + struct rrddim *next; // linking of dimensions within the same data set + struct rrdset *rrdset; - struct rrddim *next; // linking of dimensions within the same data set + // ------------------------------------------------------------------------ + // members for checking the data when loading from disk - // ------------------------------------------------------------------------ - // members for checking the data when loading from disk + long entries; // how many entries this dimension has in ram + // this is the same to the entries of the data set + // we set it here, to check the data when we load it from disk. - long entries; // how many entries this dimension has in ram - // this is the same to the entries of the data set - // we set it here, to check the data when we load it from disk. + int update_every; // every how many seconds is this updated - int update_every; // every how many seconds is this updated + unsigned long memsize; // the memory allocated for this dimension - unsigned long memsize; // the memory allocated for this dimension + char magic[sizeof(RRDDIMENSION_MAGIC) + 1]; // a string to be saved, used to identify our data file - char magic[sizeof(RRDDIMENSION_MAGIC) + 1]; // a string to be saved, used to identify our data file + struct rrddimvar *variables; - // ------------------------------------------------------------------------ - // the values stored in this dimension, using our floating point numbers + // ------------------------------------------------------------------------ + // the values stored in this dimension, using our floating point numbers - storage_number values[]; // the array of values - THIS HAS TO BE THE LAST MEMBER + storage_number values[]; // the array of values - THIS HAS TO BE THE LAST MEMBER }; typedef struct rrddim RRDDIM; @@ -165,99 +181,155 @@ typedef struct rrddim RRDDIM; // RRDSET struct rrdset { - // ------------------------------------------------------------------------ - // binary indexing structures + // ------------------------------------------------------------------------ + // binary indexing structures + + avl avl; // the index, with key the id - this has to be first! + avl avlname; // the index, with key the name + + // ------------------------------------------------------------------------ + // the set configuration + + char id[RRD_ID_LENGTH_MAX + 1]; // id of the data set - avl avl; // the index, with key the id - this has to be first! - avl avlname; // the index, with key the name + const char *name; // the name of this dimension (as presented to user) + // this is a pointer to the config structure + // since the config always has a higher priority + // (the user overwrites the name of the charts) - // ------------------------------------------------------------------------ - // the set configuration + char *type; // the type of graph RRD_TYPE_* (a category, for determining graphing options) + char *family; // grouping sets under the same family + char *title; // title shown to user + char *units; // units of measurement - char id[RRD_ID_LENGTH_MAX + 1]; // id of the data set + char *context; // the template of this data set + uint32_t hash_context; - const char *name; // the name of this dimension (as presented to user) - // this is a pointer to the config structure - // since the config always has a higher priority - // (the user overwrites the name of the charts) + int chart_type; - char *type; // the type of graph RRD_TYPE_* (a category, for determining graphing options) - char *family; // grouping sets under the same family - char *context; // the template of this data set - char *title; // title shown to user - char *units; // units of measurement + int update_every; // every how many seconds is this updated? - int chart_type; + long entries; // total number of entries in the data set - int update_every; // every how many seconds is this updated? + long current_entry; // the entry that is currently being updated + // it goes around in a round-robin fashion - long entries; // total number of entries in the data set + int enabled; - long current_entry; // the entry that is currently being updated - // it goes around in a round-robin fashion + int gap_when_lost_iterations_above; // after how many lost iterations a gap should be stored + // netdata will interpolate values for gaps lower than this - int enabled; + long priority; - int gap_when_lost_iterations_above; // after how many lost iterations a gap should be stored - // netdata will interpolate values for gaps lower than this + int isdetail; // if set, the data set should be considered as a detail of another + // (the master data set should be the one that has the same family and is not detail) - long priority; + // ------------------------------------------------------------------------ + // members for temporary data we need for calculations - int isdetail; // if set, the data set should be considered as a detail of another - // (the master data set should be the one that has the same family and is not detail) + int mapped; // if set to 1, this is memory mapped - // ------------------------------------------------------------------------ - // members for temporary data we need for calculations + int debug; - int mapped; // if set to 1, this is memory mapped + char *cache_dir; // the directory to store dimensions + char cache_filename[FILENAME_MAX+1]; // the filename to store this set - int debug; + pthread_rwlock_t rwlock; - char *cache_dir; // the directory to store dimensions - char cache_filename[FILENAME_MAX+1]; // the filename to store this set + unsigned long counter; // the number of times we added values to this rrd + unsigned long counter_done; // the number of times we added values to this rrd - pthread_rwlock_t rwlock; + uint32_t hash; // a simple hash on the id, to speed up searching + // we first compare hashes, and only if the hashes are equal we do string comparisons - unsigned long counter; // the number of times we added values to this rrd - unsigned long counter_done; // the number of times we added values to this rrd + uint32_t hash_name; // a simple hash on the name - uint32_t hash; // a simple hash on the id, to speed up searching - // we first compare hashes, and only if the hashes are equal we do string comparisons + unsigned long long usec_since_last_update; // the time in microseconds since the last collection of data - uint32_t hash_name; // a simple hash on the name + struct timeval last_updated; // when this data set was last updated (updated every time the rrd_stats_done() function) + struct timeval last_collected_time; // when did this data set last collected values - unsigned long long usec_since_last_update; // the time in microseconds since the last collection of data + total_number collected_total; // used internally to calculate percentages + total_number last_collected_total; // used internally to calculate percentages - struct timeval last_updated; // when this data set was last updated (updated every time the rrd_stats_done() function) - struct timeval last_collected_time; // when did this data set last collected values + RRDFAMILY *rrdfamily; + struct rrdhost *rrdhost; - total_number collected_total; // used internally to calculate percentages - total_number last_collected_total; // used internally to calculate percentages + struct rrdset *next; // linking of rrdsets - struct rrdset *next; // linking of rrdsets + // ------------------------------------------------------------------------ + // local variables - // ------------------------------------------------------------------------ - // members for checking the data when loading from disk + calculated_number green; + calculated_number red; - unsigned long memsize; // how much mem we have allocated for this (without dimensions) + avl_tree_lock variables_root_index; + RRDSETVAR *variables; + RRDCALC *alarms; - char magic[sizeof(RRDSET_MAGIC) + 1]; // our magic + // ------------------------------------------------------------------------ + // members for checking the data when loading from disk - // ------------------------------------------------------------------------ - // the dimensions + unsigned long memsize; // how much mem we have allocated for this (without dimensions) + + char magic[sizeof(RRDSET_MAGIC) + 1]; // our magic + + // ------------------------------------------------------------------------ + // the dimensions + + avl_tree_lock dimensions_index; // the root of the dimensions index + RRDDIM *dimensions; // the actual data for every dimension - avl_tree_lock dimensions_index; // the root of the dimensions index - RRDDIM *dimensions; // the actual data for every dimension }; typedef struct rrdset RRDSET; -extern RRDSET *rrdset_root; -extern pthread_rwlock_t rrdset_root_rwlock; +// ---------------------------------------------------------------------------- +// RRD HOST + +struct rrdhost { + avl avl; + + char *hostname; + + RRDSET *rrdset_root; + pthread_rwlock_t rrdset_root_rwlock; + + avl_tree_lock rrdset_root_index; + avl_tree_lock rrdset_root_index_name; + + avl_tree_lock rrdfamily_root_index; + avl_tree_lock variables_root_index; + + // all RRDCALCs are primarily allocated and linked here + // RRDCALCs may be linked to charts at any point + // (charts may or may not exist when these are loaded) + RRDCALC *alarms; + ALARM_LOG health_log; + + RRDCALCTEMPLATE *templates; +}; +typedef struct rrdhost RRDHOST; +extern RRDHOST localhost; + +#ifdef NETDATA_INTERNAL_CHECKS +#define rrdhost_check_wrlock(host) rrdhost_check_wrlock_int(host, __FILE__, __FUNCTION__, __LINE__) +#define rrdhost_check_rdlock(host) rrdhost_check_rdlock_int(host, __FILE__, __FUNCTION__, __LINE__) +#else +#define rrdhost_check_rdlock(host) (void)0 +#define rrdhost_check_wrlock(host) (void)0 +#endif + +extern void rrdhost_check_wrlock_int(RRDHOST *host, const char *file, const char *function, const unsigned long line); +extern void rrdhost_check_rdlock_int(RRDHOST *host, const char *file, const char *function, const unsigned long line); + +extern void rrdhost_rwlock(RRDHOST *host); +extern void rrdhost_rdlock(RRDHOST *host); +extern void rrdhost_unlock(RRDHOST *host); // ---------------------------------------------------------------------------- // RRD SET functions -extern char *rrdset_strncpy_name(char *to, const char *from, int length); +extern char *rrdset_strncpyz_name(char *to, const char *from, size_t length); extern void rrdset_set_name(RRDSET *st, const char *name); extern char *rrdset_cache_dir(const char *id); @@ -265,15 +337,15 @@ extern char *rrdset_cache_dir(const char *id); extern void rrdset_reset(RRDSET *st); extern RRDSET *rrdset_create(const char *type - , const char *id - , const char *name - , const char *family - , const char *context - , const char *title - , const char *units - , long priority - , int update_every - , int chart_type); + , const char *id + , const char *name + , const char *family + , const char *context + , const char *title + , const char *units + , long priority + , int update_every + , int chart_type); extern void rrdset_free_all(void); extern void rrdset_save_all(void); @@ -306,20 +378,20 @@ extern unsigned long long rrdset_done(RRDSET *st); // get the slot of the round robin database, for the given timestamp (t) // it always returns a valid slot, although may not be for the time requested if the time is outside the round robin database #define rrdset_time2slot(st, t) ( \ - ( (time_t)(t) >= rrdset_last_entry_t(st)) ? ( rrdset_last_slot(st) ) : \ - ( ((time_t)(t) <= rrdset_first_entry_t(st)) ? rrdset_first_slot(st) : \ - ( (rrdset_last_slot(st) >= (unsigned long)((rrdset_last_entry_t(st) - (time_t)(t)) / (unsigned long)((st)->update_every)) ) ? \ - (rrdset_last_slot(st) - (unsigned long)((rrdset_last_entry_t(st) - (time_t)(t)) / (unsigned long)((st)->update_every)) ) : \ - (rrdset_last_slot(st) - (unsigned long)((rrdset_last_entry_t(st) - (time_t)(t)) / (unsigned long)((st)->update_every)) + (unsigned long)(st)->entries ) \ - ))) + ( (time_t)(t) >= rrdset_last_entry_t(st)) ? ( rrdset_last_slot(st) ) : \ + ( ((time_t)(t) <= rrdset_first_entry_t(st)) ? rrdset_first_slot(st) : \ + ( (rrdset_last_slot(st) >= (unsigned long)((rrdset_last_entry_t(st) - (time_t)(t)) / (unsigned long)((st)->update_every)) ) ? \ + (rrdset_last_slot(st) - (unsigned long)((rrdset_last_entry_t(st) - (time_t)(t)) / (unsigned long)((st)->update_every)) ) : \ + (rrdset_last_slot(st) - (unsigned long)((rrdset_last_entry_t(st) - (time_t)(t)) / (unsigned long)((st)->update_every)) + (unsigned long)(st)->entries ) \ + ))) // get the timestamp of a specific slot in the round robin database #define rrdset_slot2time(st, slot) ( rrdset_last_entry_t(st) - \ - ((unsigned long)(st)->update_every * ( \ - ( (unsigned long)(slot) > rrdset_last_slot(st)) ? \ - ( (rrdset_last_slot(st) - (unsigned long)(slot) + (unsigned long)(st)->entries) ) : \ - ( (rrdset_last_slot(st) - (unsigned long)(slot)) )) \ - )) + ((unsigned long)(st)->update_every * ( \ + ( (unsigned long)(slot) > rrdset_last_slot(st)) ? \ + ( (rrdset_last_slot(st) - (unsigned long)(slot) + (unsigned long)(st)->entries) ) : \ + ( (rrdset_last_slot(st) - (unsigned long)(slot)) )) \ + )) // ---------------------------------------------------------------------------- // RRD DIMENSION functions diff --git a/src/rrd2json.c b/src/rrd2json.c index e0bd06670..9009a8b1d 100644 --- a/src/rrd2json.c +++ b/src/rrd2json.c @@ -1,222 +1,215 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <pthread.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> -#include <math.h> - -#include "log.h" #include "common.h" -#include "rrd2json.h" #define HOSTNAME_MAX 1024 char *hostname = "unknown"; void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb) { - pthread_rwlock_rdlock(&st->rwlock); - - buffer_sprintf(wb, - "\t\t{\n" - "\t\t\t\"id\": \"%s\",\n" - "\t\t\t\"name\": \"%s\",\n" - "\t\t\t\"type\": \"%s\",\n" - "\t\t\t\"family\": \"%s\",\n" - "\t\t\t\"context\": \"%s\",\n" - "\t\t\t\"title\": \"%s\",\n" - "\t\t\t\"priority\": %ld,\n" - "\t\t\t\"enabled\": %s,\n" - "\t\t\t\"units\": \"%s\",\n" - "\t\t\t\"data_url\": \"/api/v1/data?chart=%s\",\n" - "\t\t\t\"chart_type\": \"%s\",\n" - "\t\t\t\"duration\": %ld,\n" - "\t\t\t\"first_entry\": %lu,\n" - "\t\t\t\"last_entry\": %lu,\n" - "\t\t\t\"update_every\": %d,\n" - "\t\t\t\"dimensions\": {\n" - , st->id - , st->name - , st->type - , st->family - , st->context - , st->title - , st->priority - , st->enabled?"true":"false" - , st->units - , st->name - , rrdset_type_name(st->chart_type) - , st->entries * st->update_every - , rrdset_first_entry_t(st) - , rrdset_last_entry_t(st) - , st->update_every - ); - - unsigned long memory = st->memsize; - - int c = 0; - RRDDIM *rd; - for(rd = st->dimensions; rd ; rd = rd->next) { - if(rd->flags & RRDDIM_FLAG_HIDDEN) continue; - - memory += rd->memsize; - - buffer_sprintf(wb, - "%s" - "\t\t\t\t\"%s\": { \"name\": \"%s\" }" - , c?",\n":"" - , rd->id - , rd->name - ); - - c++; - } - - buffer_sprintf(wb, - "\n\t\t\t}\n" - "\t\t}" - ); - - pthread_rwlock_unlock(&st->rwlock); + pthread_rwlock_rdlock(&st->rwlock); + + buffer_sprintf(wb, + "\t\t{\n" + "\t\t\t\"id\": \"%s\",\n" + "\t\t\t\"name\": \"%s\",\n" + "\t\t\t\"type\": \"%s\",\n" + "\t\t\t\"family\": \"%s\",\n" + "\t\t\t\"context\": \"%s\",\n" + "\t\t\t\"title\": \"%s\",\n" + "\t\t\t\"priority\": %ld,\n" + "\t\t\t\"enabled\": %s,\n" + "\t\t\t\"units\": \"%s\",\n" + "\t\t\t\"data_url\": \"/api/v1/data?chart=%s\",\n" + "\t\t\t\"chart_type\": \"%s\",\n" + "\t\t\t\"duration\": %ld,\n" + "\t\t\t\"first_entry\": %ld,\n" + "\t\t\t\"last_entry\": %ld,\n" + "\t\t\t\"update_every\": %d,\n" + "\t\t\t\"dimensions\": {\n" + , st->id + , st->name + , st->type + , st->family + , st->context + , st->title + , st->priority + , st->enabled?"true":"false" + , st->units + , st->name + , rrdset_type_name(st->chart_type) + , st->entries * st->update_every + , rrdset_first_entry_t(st) + , rrdset_last_entry_t(st) + , st->update_every + ); + + unsigned long memory = st->memsize; + + int c = 0; + RRDDIM *rd; + for(rd = st->dimensions; rd ; rd = rd->next) { + if(rd->flags & RRDDIM_FLAG_HIDDEN) continue; + + memory += rd->memsize; + + buffer_sprintf(wb, + "%s" + "\t\t\t\t\"%s\": { \"name\": \"%s\" }" + , c?",\n":"" + , rd->id + , rd->name + ); + + c++; + } + + buffer_strcat(wb, "\n\t\t\t},\n\t\t\t\"green\": "); + buffer_rrd_value(wb, st->green); + buffer_strcat(wb, ",\n\t\t\t\"red\": "); + buffer_rrd_value(wb, st->red); + + buffer_sprintf(wb, + "\n\t\t}" + ); + + pthread_rwlock_unlock(&st->rwlock); } void rrd_stats_api_v1_charts(BUFFER *wb) { - long c; - RRDSET *st; - - buffer_sprintf(wb, "{\n" - "\t\"hostname\": \"%s\"" - ",\n\t\"update_every\": %d" - ",\n\t\"history\": %d" - ",\n\t\"charts\": {" - , hostname - , rrd_update_every - , rrd_default_history_entries - ); - - pthread_rwlock_rdlock(&rrdset_root_rwlock); - for(st = rrdset_root, c = 0; st ; st = st->next) { - if(st->enabled) { - if(c) buffer_strcat(wb, ","); - buffer_strcat(wb, "\n\t\t\""); - buffer_strcat(wb, st->id); - buffer_strcat(wb, "\": "); - rrd_stats_api_v1_chart(st, wb); - c++; - } - } - pthread_rwlock_unlock(&rrdset_root_rwlock); - - buffer_strcat(wb, "\n\t}\n}\n"); + long c; + RRDSET *st; + + buffer_sprintf(wb, "{\n" + "\t\"hostname\": \"%s\"" + ",\n\t\"update_every\": %d" + ",\n\t\"history\": %d" + ",\n\t\"charts\": {" + , hostname + , rrd_update_every + , rrd_default_history_entries + ); + + pthread_rwlock_rdlock(&localhost.rrdset_root_rwlock); + for(st = localhost.rrdset_root, c = 0; st ; st = st->next) { + if(st->enabled && st->dimensions) { + if(c) buffer_strcat(wb, ","); + buffer_strcat(wb, "\n\t\t\""); + buffer_strcat(wb, st->id); + buffer_strcat(wb, "\": "); + rrd_stats_api_v1_chart(st, wb); + c++; + } + } + pthread_rwlock_unlock(&localhost.rrdset_root_rwlock); + + buffer_strcat(wb, "\n\t}\n}\n"); } unsigned long rrd_stats_one_json(RRDSET *st, char *options, BUFFER *wb) { - time_t now = time(NULL); - - pthread_rwlock_rdlock(&st->rwlock); - - buffer_sprintf(wb, - "\t\t{\n" - "\t\t\t\"id\": \"%s\",\n" - "\t\t\t\"name\": \"%s\",\n" - "\t\t\t\"type\": \"%s\",\n" - "\t\t\t\"family\": \"%s\",\n" - "\t\t\t\"context\": \"%s\",\n" - "\t\t\t\"title\": \"%s\",\n" - "\t\t\t\"priority\": %ld,\n" - "\t\t\t\"enabled\": %d,\n" - "\t\t\t\"units\": \"%s\",\n" - "\t\t\t\"url\": \"/data/%s/%s\",\n" - "\t\t\t\"chart_type\": \"%s\",\n" - "\t\t\t\"counter\": %ld,\n" - "\t\t\t\"entries\": %ld,\n" - "\t\t\t\"first_entry_t\": %lu,\n" - "\t\t\t\"last_entry\": %ld,\n" - "\t\t\t\"last_entry_t\": %lu,\n" - "\t\t\t\"last_entry_secs_ago\": %lu,\n" - "\t\t\t\"update_every\": %d,\n" - "\t\t\t\"isdetail\": %d,\n" - "\t\t\t\"usec_since_last_update\": %llu,\n" - "\t\t\t\"collected_total\": " TOTAL_NUMBER_FORMAT ",\n" - "\t\t\t\"last_collected_total\": " TOTAL_NUMBER_FORMAT ",\n" - "\t\t\t\"dimensions\": [\n" - , st->id - , st->name - , st->type - , st->family - , st->context - , st->title - , st->priority - , st->enabled - , st->units - , st->name, options?options:"" - , rrdset_type_name(st->chart_type) - , st->counter - , st->entries - , rrdset_first_entry_t(st) - , rrdset_last_slot(st) - , rrdset_last_entry_t(st) - , (now < rrdset_last_entry_t(st)) ? (time_t)0 : now - rrdset_last_entry_t(st) - , st->update_every - , st->isdetail - , st->usec_since_last_update - , st->collected_total - , st->last_collected_total - ); - - unsigned long memory = st->memsize; - - RRDDIM *rd; - for(rd = st->dimensions; rd ; rd = rd->next) { - - memory += rd->memsize; - - buffer_sprintf(wb, - "\t\t\t\t{\n" - "\t\t\t\t\t\"id\": \"%s\",\n" - "\t\t\t\t\t\"name\": \"%s\",\n" - "\t\t\t\t\t\"entries\": %ld,\n" - "\t\t\t\t\t\"isHidden\": %d,\n" - "\t\t\t\t\t\"algorithm\": \"%s\",\n" - "\t\t\t\t\t\"multiplier\": %ld,\n" - "\t\t\t\t\t\"divisor\": %ld,\n" - "\t\t\t\t\t\"last_entry_t\": %lu,\n" - "\t\t\t\t\t\"collected_value\": " COLLECTED_NUMBER_FORMAT ",\n" - "\t\t\t\t\t\"calculated_value\": " CALCULATED_NUMBER_FORMAT ",\n" - "\t\t\t\t\t\"last_collected_value\": " COLLECTED_NUMBER_FORMAT ",\n" - "\t\t\t\t\t\"last_calculated_value\": " CALCULATED_NUMBER_FORMAT ",\n" - "\t\t\t\t\t\"memory\": %lu\n" - "\t\t\t\t}%s\n" - , rd->id - , rd->name - , rd->entries - , (rd->flags & RRDDIM_FLAG_HIDDEN)?1:0 - , rrddim_algorithm_name(rd->algorithm) - , rd->multiplier - , rd->divisor - , rd->last_collected_time.tv_sec - , rd->collected_value - , rd->calculated_value - , rd->last_collected_value - , rd->last_calculated_value - , rd->memsize - , rd->next?",":"" - ); - } - - buffer_sprintf(wb, - "\t\t\t],\n" - "\t\t\t\"memory\" : %lu\n" - "\t\t}" - , memory - ); - - pthread_rwlock_unlock(&st->rwlock); - return memory; + time_t now = time(NULL); + + pthread_rwlock_rdlock(&st->rwlock); + + buffer_sprintf(wb, + "\t\t{\n" + "\t\t\t\"id\": \"%s\",\n" + "\t\t\t\"name\": \"%s\",\n" + "\t\t\t\"type\": \"%s\",\n" + "\t\t\t\"family\": \"%s\",\n" + "\t\t\t\"context\": \"%s\",\n" + "\t\t\t\"title\": \"%s\",\n" + "\t\t\t\"priority\": %ld,\n" + "\t\t\t\"enabled\": %d,\n" + "\t\t\t\"units\": \"%s\",\n" + "\t\t\t\"url\": \"/data/%s/%s\",\n" + "\t\t\t\"chart_type\": \"%s\",\n" + "\t\t\t\"counter\": %lu,\n" + "\t\t\t\"entries\": %ld,\n" + "\t\t\t\"first_entry_t\": %ld,\n" + "\t\t\t\"last_entry\": %lu,\n" + "\t\t\t\"last_entry_t\": %ld,\n" + "\t\t\t\"last_entry_secs_ago\": %ld,\n" + "\t\t\t\"update_every\": %d,\n" + "\t\t\t\"isdetail\": %d,\n" + "\t\t\t\"usec_since_last_update\": %llu,\n" + "\t\t\t\"collected_total\": " TOTAL_NUMBER_FORMAT ",\n" + "\t\t\t\"last_collected_total\": " TOTAL_NUMBER_FORMAT ",\n" + "\t\t\t\"dimensions\": [\n" + , st->id + , st->name + , st->type + , st->family + , st->context + , st->title + , st->priority + , st->enabled + , st->units + , st->name, options?options:"" + , rrdset_type_name(st->chart_type) + , st->counter + , st->entries + , rrdset_first_entry_t(st) + , rrdset_last_slot(st) + , rrdset_last_entry_t(st) + , (now < rrdset_last_entry_t(st)) ? (time_t)0 : now - rrdset_last_entry_t(st) + , st->update_every + , st->isdetail + , st->usec_since_last_update + , st->collected_total + , st->last_collected_total + ); + + unsigned long memory = st->memsize; + + RRDDIM *rd; + for(rd = st->dimensions; rd ; rd = rd->next) { + + memory += rd->memsize; + + buffer_sprintf(wb, + "\t\t\t\t{\n" + "\t\t\t\t\t\"id\": \"%s\",\n" + "\t\t\t\t\t\"name\": \"%s\",\n" + "\t\t\t\t\t\"entries\": %ld,\n" + "\t\t\t\t\t\"isHidden\": %d,\n" + "\t\t\t\t\t\"algorithm\": \"%s\",\n" + "\t\t\t\t\t\"multiplier\": %ld,\n" + "\t\t\t\t\t\"divisor\": %ld,\n" + "\t\t\t\t\t\"last_entry_t\": %ld,\n" + "\t\t\t\t\t\"collected_value\": " COLLECTED_NUMBER_FORMAT ",\n" + "\t\t\t\t\t\"calculated_value\": " CALCULATED_NUMBER_FORMAT ",\n" + "\t\t\t\t\t\"last_collected_value\": " COLLECTED_NUMBER_FORMAT ",\n" + "\t\t\t\t\t\"last_calculated_value\": " CALCULATED_NUMBER_FORMAT ",\n" + "\t\t\t\t\t\"memory\": %lu\n" + "\t\t\t\t}%s\n" + , rd->id + , rd->name + , rd->entries + , (rd->flags & RRDDIM_FLAG_HIDDEN)?1:0 + , rrddim_algorithm_name(rd->algorithm) + , rd->multiplier + , rd->divisor + , rd->last_collected_time.tv_sec + , rd->collected_value + , rd->calculated_value + , rd->last_collected_value + , rd->last_calculated_value + , rd->memsize + , rd->next?",":"" + ); + } + + buffer_sprintf(wb, + "\t\t\t],\n" + "\t\t\t\"memory\" : %lu\n" + "\t\t}" + , memory + ); + + pthread_rwlock_unlock(&st->rwlock); + return memory; } #define RRD_GRAPH_JSON_HEADER "{\n\t\"charts\": [\n" @@ -224,40 +217,40 @@ unsigned long rrd_stats_one_json(RRDSET *st, char *options, BUFFER *wb) void rrd_stats_graph_json(RRDSET *st, char *options, BUFFER *wb) { - buffer_strcat(wb, RRD_GRAPH_JSON_HEADER); - rrd_stats_one_json(st, options, wb); - buffer_strcat(wb, RRD_GRAPH_JSON_FOOTER); + buffer_strcat(wb, RRD_GRAPH_JSON_HEADER); + rrd_stats_one_json(st, options, wb); + buffer_strcat(wb, RRD_GRAPH_JSON_FOOTER); } void rrd_stats_all_json(BUFFER *wb) { - unsigned long memory = 0; - long c; - RRDSET *st; - - buffer_strcat(wb, RRD_GRAPH_JSON_HEADER); - - pthread_rwlock_rdlock(&rrdset_root_rwlock); - for(st = rrdset_root, c = 0; st ; st = st->next) { - if(st->enabled) { - if(c) buffer_strcat(wb, ",\n"); - memory += rrd_stats_one_json(st, NULL, wb); - c++; - } - } - pthread_rwlock_unlock(&rrdset_root_rwlock); - - buffer_sprintf(wb, "\n\t],\n" - "\t\"hostname\": \"%s\",\n" - "\t\"update_every\": %d,\n" - "\t\"history\": %d,\n" - "\t\"memory\": %lu\n" - "}\n" - , hostname - , rrd_update_every - , rrd_default_history_entries - , memory - ); + unsigned long memory = 0; + long c; + RRDSET *st; + + buffer_strcat(wb, RRD_GRAPH_JSON_HEADER); + + pthread_rwlock_rdlock(&localhost.rrdset_root_rwlock); + for(st = localhost.rrdset_root, c = 0; st ; st = st->next) { + if(st->enabled && st->dimensions) { + if(c) buffer_strcat(wb, ",\n"); + memory += rrd_stats_one_json(st, NULL, wb); + c++; + } + } + pthread_rwlock_unlock(&localhost.rrdset_root_rwlock); + + buffer_sprintf(wb, "\n\t],\n" + "\t\"hostname\": \"%s\",\n" + "\t\"update_every\": %d,\n" + "\t\"history\": %d,\n" + "\t\"memory\": %lu\n" + "}\n" + , hostname + , rrd_update_every + , rrd_default_history_entries + , memory + ); } @@ -265,42 +258,42 @@ void rrd_stats_all_json(BUFFER *wb) // ---------------------------------------------------------------------------- // RRDR dimension options -#define RRDR_EMPTY 0x01 // the dimension contains / the value is empty (null) -#define RRDR_RESET 0x02 // the dimension contains / the value is reset -#define RRDR_HIDDEN 0x04 // the dimension contains / the value is hidden -#define RRDR_NONZERO 0x08 // the dimension contains / the value is non-zero +#define RRDR_EMPTY 0x01 // the dimension contains / the value is empty (null) +#define RRDR_RESET 0x02 // the dimension contains / the value is reset +#define RRDR_HIDDEN 0x04 // the dimension contains / the value is hidden +#define RRDR_NONZERO 0x08 // the dimension contains / the value is non-zero // RRDR result options #define RRDR_RESULT_OPTION_ABSOLUTE 0x00000001 #define RRDR_RESULT_OPTION_RELATIVE 0x00000002 typedef struct rrdresult { - RRDSET *st; // the chart this result refers to + RRDSET *st; // the chart this result refers to - uint32_t result_options; // RRDR_RESULT_OPTION_* + uint32_t result_options; // RRDR_RESULT_OPTION_* - int d; // the number of dimensions - long n; // the number of values in the arrays - long rows; // the number of rows used + int d; // the number of dimensions + long n; // the number of values in the arrays + long rows; // the number of rows used - uint8_t *od; // the options for the dimensions + uint8_t *od; // the options for the dimensions - time_t *t; // array of n timestamps - calculated_number *v; // array n x d values - uint8_t *o; // array n x d options + time_t *t; // array of n timestamps + calculated_number *v; // array n x d values + uint8_t *o; // array n x d options - long c; // current line ( -1 ~ n ), ( -1 = none, use rrdr_rows() to get number of rows ) + long c; // current line ( -1 ~ n ), ( -1 = none, use rrdr_rows() to get number of rows ) - long group; // how many collected values were grouped for each row - long update_every; // what is the suggested update frequency in seconds + long group; // how many collected values were grouped for each row + int update_every; // what is the suggested update frequency in seconds - calculated_number min; - calculated_number max; + calculated_number min; + calculated_number max; - time_t before; - time_t after; + time_t before; + time_t after; - int has_st_lock; // if st is read locked by us + int has_st_lock; // if st is read locked by us } RRDR; #define rrdr_rows(r) ((r)->rows) @@ -308,346 +301,347 @@ typedef struct rrdresult { /* static void rrdr_dump(RRDR *r) { - long c, i; - RRDDIM *d; - - fprintf(stderr, "\nCHART %s (%s)\n", r->st->id, r->st->name); - - for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { - fprintf(stderr, "DIMENSION %s (%s), %s%s%s%s\n" - , d->id - , d->name - , (r->od[c] & RRDR_EMPTY)?"EMPTY ":"" - , (r->od[c] & RRDR_RESET)?"RESET ":"" - , (r->od[c] & RRDR_HIDDEN)?"HIDDEN ":"" - , (r->od[c] & RRDR_NONZERO)?"NONZERO ":"" - ); - } - - if(r->rows <= 0) { - fprintf(stderr, "RRDR does not have any values in it.\n"); - return; - } - - fprintf(stderr, "RRDR includes %d values in it:\n", r->rows); - - // for each line in the array - for(i = 0; i < r->rows ;i++) { - calculated_number *cn = &r->v[ i * r->d ]; - uint8_t *co = &r->o[ i * r->d ]; - - // print the id and the timestamp of the line - fprintf(stderr, "%ld %ld ", i + 1, r->t[i]); - - // for each dimension - for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_NONZERO))) continue; - - if(co[c] & RRDR_EMPTY) - fprintf(stderr, "null "); - else - fprintf(stderr, CALCULATED_NUMBER_FORMAT " %s%s%s%s " - , cn[c] - , (co[c] & RRDR_EMPTY)?"E":" " - , (co[c] & RRDR_RESET)?"R":" " - , (co[c] & RRDR_HIDDEN)?"H":" " - , (co[c] & RRDR_NONZERO)?"N":" " - ); - } - - fprintf(stderr, "\n"); - } + long c, i; + RRDDIM *d; + + fprintf(stderr, "\nCHART %s (%s)\n", r->st->id, r->st->name); + + for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { + fprintf(stderr, "DIMENSION %s (%s), %s%s%s%s\n" + , d->id + , d->name + , (r->od[c] & RRDR_EMPTY)?"EMPTY ":"" + , (r->od[c] & RRDR_RESET)?"RESET ":"" + , (r->od[c] & RRDR_HIDDEN)?"HIDDEN ":"" + , (r->od[c] & RRDR_NONZERO)?"NONZERO ":"" + ); + } + + if(r->rows <= 0) { + fprintf(stderr, "RRDR does not have any values in it.\n"); + return; + } + + fprintf(stderr, "RRDR includes %d values in it:\n", r->rows); + + // for each line in the array + for(i = 0; i < r->rows ;i++) { + calculated_number *cn = &r->v[ i * r->d ]; + uint8_t *co = &r->o[ i * r->d ]; + + // print the id and the timestamp of the line + fprintf(stderr, "%ld %ld ", i + 1, r->t[i]); + + // for each dimension + for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely(!(r->od[c] & RRDR_NONZERO))) continue; + + if(co[c] & RRDR_EMPTY) + fprintf(stderr, "null "); + else + fprintf(stderr, CALCULATED_NUMBER_FORMAT " %s%s%s%s " + , cn[c] + , (co[c] & RRDR_EMPTY)?"E":" " + , (co[c] & RRDR_RESET)?"R":" " + , (co[c] & RRDR_HIDDEN)?"H":" " + , (co[c] & RRDR_NONZERO)?"N":" " + ); + } + + fprintf(stderr, "\n"); + } } */ void rrdr_disable_not_selected_dimensions(RRDR *r, const char *dims) { - char b[strlen(dims) + 1]; - char *o = b, *tok; - strcpy(o, dims); - - long c; - RRDDIM *d; - - // disable all of them - for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) - r->od[c] |= RRDR_HIDDEN; - - while(o && *o && (tok = mystrsep(&o, ", |"))) { - if(!*tok) continue; - - // find it and enable it - for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { - if(!strcmp(d->name, tok)) { - r->od[c] &= ~RRDR_HIDDEN; - - // since the user needs this dimension - // make it appear as NONZERO, to return it - // even if the dimension has only zeros - r->od[c] |= RRDR_NONZERO; - } - } - } + char b[strlen(dims) + 1]; + char *o = b, *tok; + strcpy(o, dims); + + long c; + RRDDIM *d; + + // disable all of them + for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) + r->od[c] |= RRDR_HIDDEN; + + while(o && *o && (tok = mystrsep(&o, ",|"))) { + if(!*tok) continue; + + uint32_t hash = simple_hash(tok); + + // find it and enable it + for(c = 0, d = r->st->dimensions; d ;c++, d = d->next) { + if(unlikely((hash == d->hash && !strcmp(d->id, tok)) || !strcmp(d->name, tok))) { + r->od[c] &= ~RRDR_HIDDEN; + + // since the user needs this dimension + // make it appear as NONZERO, to return it + // even if the dimension has only zeros + r->od[c] |= RRDR_NONZERO; + } + } + } } void rrdr_buffer_print_format(BUFFER *wb, uint32_t format) { - switch(format) { - case DATASOURCE_JSON: - buffer_strcat(wb, DATASOURCE_FORMAT_JSON); - break; - - case DATASOURCE_DATATABLE_JSON: - buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSON); - break; - - case DATASOURCE_DATATABLE_JSONP: - buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSONP); - break; - - case DATASOURCE_JSONP: - buffer_strcat(wb, DATASOURCE_FORMAT_JSONP); - break; - - case DATASOURCE_SSV: - buffer_strcat(wb, DATASOURCE_FORMAT_SSV); - break; - - case DATASOURCE_CSV: - buffer_strcat(wb, DATASOURCE_FORMAT_CSV); - break; - - case DATASOURCE_TSV: - buffer_strcat(wb, DATASOURCE_FORMAT_TSV); - break; - - case DATASOURCE_HTML: - buffer_strcat(wb, DATASOURCE_FORMAT_HTML); - break; - - case DATASOURCE_JS_ARRAY: - buffer_strcat(wb, DATASOURCE_FORMAT_JS_ARRAY); - break; - - case DATASOURCE_SSV_COMMA: - buffer_strcat(wb, DATASOURCE_FORMAT_SSV_COMMA); - break; - - default: - buffer_strcat(wb, "unknown"); - break; - } + switch(format) { + case DATASOURCE_JSON: + buffer_strcat(wb, DATASOURCE_FORMAT_JSON); + break; + + case DATASOURCE_DATATABLE_JSON: + buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSON); + break; + + case DATASOURCE_DATATABLE_JSONP: + buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSONP); + break; + + case DATASOURCE_JSONP: + buffer_strcat(wb, DATASOURCE_FORMAT_JSONP); + break; + + case DATASOURCE_SSV: + buffer_strcat(wb, DATASOURCE_FORMAT_SSV); + break; + + case DATASOURCE_CSV: + buffer_strcat(wb, DATASOURCE_FORMAT_CSV); + break; + + case DATASOURCE_TSV: + buffer_strcat(wb, DATASOURCE_FORMAT_TSV); + break; + + case DATASOURCE_HTML: + buffer_strcat(wb, DATASOURCE_FORMAT_HTML); + break; + + case DATASOURCE_JS_ARRAY: + buffer_strcat(wb, DATASOURCE_FORMAT_JS_ARRAY); + break; + + case DATASOURCE_SSV_COMMA: + buffer_strcat(wb, DATASOURCE_FORMAT_SSV_COMMA); + break; + + default: + buffer_strcat(wb, "unknown"); + break; + } } uint32_t rrdr_check_options(RRDR *r, uint32_t options, const char *dims) { - if(options & RRDR_OPTION_NONZERO) { - long i; - - if(dims && *dims) { - // the caller wants specific dimensions - // disable NONZERO option - // to make sure we don't accidentally prevent - // the specific dimensions from being returned - i = 0; - } - else { - // find how many dimensions are not zero - long c; - RRDDIM *rd; - for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ; c++, rd = rd->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_NONZERO))) continue; - i++; - } - } - - // if with nonzero we get i = 0 (no dimensions will be returned) - // disable nonzero to show all dimensions - if(!i) options &= ~RRDR_OPTION_NONZERO; - } - - return options; + if(options & RRDR_OPTION_NONZERO) { + long i; + + if(dims && *dims) { + // the caller wants specific dimensions + // disable NONZERO option + // to make sure we don't accidentally prevent + // the specific dimensions from being returned + i = 0; + } + else { + // find how many dimensions are not zero + long c; + RRDDIM *rd; + for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ; c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely(!(r->od[c] & RRDR_NONZERO))) continue; + i++; + } + } + + // if with nonzero we get i = 0 (no dimensions will be returned) + // disable nonzero to show all dimensions + if(!i) options &= ~RRDR_OPTION_NONZERO; + } + + return options; } void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) { - long rows = rrdr_rows(r); - long c, i; - RRDDIM *rd; - - //info("JSONWRAPPER(): %s: BEGIN", r->st->id); - char kq[2] = "", // key quote - sq[2] = ""; // string quote - - if( options & RRDR_OPTION_GOOGLE_JSON ) { - kq[0] = '\0'; - sq[0] = '\''; - } - else { - kq[0] = '"'; - sq[0] = '"'; - } - - buffer_sprintf(wb, "{\n" - " %sapi%s: 1,\n" - " %sid%s: %s%s%s,\n" - " %sname%s: %s%s%s,\n" - " %sview_update_every%s: %d,\n" - " %supdate_every%s: %d,\n" - " %sfirst_entry%s: %u,\n" - " %slast_entry%s: %u,\n" - " %sbefore%s: %u,\n" - " %safter%s: %u,\n" - " %sdimension_names%s: [" - , kq, kq - , kq, kq, sq, r->st->id, sq - , kq, kq, sq, r->st->name, sq - , kq, kq, r->update_every - , kq, kq, r->st->update_every - , kq, kq, rrdset_first_entry_t(r->st) - , kq, kq, rrdset_last_entry_t(r->st) - , kq, kq, r->before - , kq, kq, r->after - , kq, kq); - - for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - if(i) buffer_strcat(wb, ", "); - buffer_strcat(wb, sq); - buffer_strcat(wb, rd->name); - buffer_strcat(wb, sq); - i++; - } - if(!i) { + long rows = rrdr_rows(r); + long c, i; + RRDDIM *rd; + + //info("JSONWRAPPER(): %s: BEGIN", r->st->id); + char kq[2] = "", // key quote + sq[2] = ""; // string quote + + if( options & RRDR_OPTION_GOOGLE_JSON ) { + kq[0] = '\0'; + sq[0] = '\''; + } + else { + kq[0] = '"'; + sq[0] = '"'; + } + + buffer_sprintf(wb, "{\n" + " %sapi%s: 1,\n" + " %sid%s: %s%s%s,\n" + " %sname%s: %s%s%s,\n" + " %sview_update_every%s: %d,\n" + " %supdate_every%s: %d,\n" + " %sfirst_entry%s: %u,\n" + " %slast_entry%s: %u,\n" + " %sbefore%s: %u,\n" + " %safter%s: %u,\n" + " %sdimension_names%s: [" + , kq, kq + , kq, kq, sq, r->st->id, sq + , kq, kq, sq, r->st->name, sq + , kq, kq, r->update_every + , kq, kq, r->st->update_every + , kq, kq, (uint32_t)rrdset_first_entry_t(r->st) + , kq, kq, (uint32_t)rrdset_last_entry_t(r->st) + , kq, kq, (uint32_t)r->before + , kq, kq, (uint32_t)r->after + , kq, kq); + + for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + buffer_strcat(wb, sq); + buffer_strcat(wb, rd->name); + buffer_strcat(wb, sq); + i++; + } + if(!i) { #ifdef NETDATA_INTERNAL_CHECKS - error("RRDR is empty for %s (RRDR has %d dimensions, options is 0x%08x)", r->st->id, r->d, options); + error("RRDR is empty for %s (RRDR has %d dimensions, options is 0x%08x)", r->st->id, r->d, options); #endif - rows = 0; - buffer_strcat(wb, sq); - buffer_strcat(wb, "no data"); - buffer_strcat(wb, sq); - } - - buffer_sprintf(wb, "],\n" - " %sdimension_ids%s: [" - , kq, kq); - - for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - if(i) buffer_strcat(wb, ", "); - buffer_strcat(wb, sq); - buffer_strcat(wb, rd->id); - buffer_strcat(wb, sq); - i++; - } - if(!i) { - rows = 0; - buffer_strcat(wb, sq); - buffer_strcat(wb, "no data"); - buffer_strcat(wb, sq); - } - - buffer_sprintf(wb, "],\n" - " %slatest_values%s: [" - , kq, kq); - - for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - if(i) buffer_strcat(wb, ", "); - i++; - - storage_number n = rd->values[rrdset_last_slot(r->st)]; - - if(!does_storage_number_exist(n)) - buffer_strcat(wb, "null"); - else - buffer_rrd_value(wb, unpack_storage_number(n)); - } - if(!i) { - rows = 0; - buffer_strcat(wb, "null"); - } - - buffer_sprintf(wb, "],\n" - " %sview_latest_values%s: [" - , kq, kq); - - i = 0; - if(rows) { - for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - if(i) buffer_strcat(wb, ", "); - i++; - - calculated_number *cn = &r->v[ (0) * r->d ]; - uint8_t *co = &r->o[ (0) * r->d ]; - - if(co[c] & RRDR_EMPTY) - buffer_strcat(wb, "null"); - else - buffer_rrd_value(wb, cn[c]); - } - } - if(!i) { - rows = 0; - buffer_strcat(wb, "null"); - } - - buffer_sprintf(wb, "],\n" - " %sdimensions%s: %d,\n" - " %spoints%s: %d,\n" - " %sformat%s: %s" - , kq, kq, i - , kq, kq, rows - , kq, kq, sq - ); - - rrdr_buffer_print_format(wb, format); - - buffer_sprintf(wb, "%s,\n" - " %sresult%s: " - , sq - , kq, kq - ); - - if(string_value) buffer_strcat(wb, sq); - //info("JSONWRAPPER(): %s: END", r->st->id); + rows = 0; + buffer_strcat(wb, sq); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, sq); + } + + buffer_sprintf(wb, "],\n" + " %sdimension_ids%s: [" + , kq, kq); + + for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + buffer_strcat(wb, sq); + buffer_strcat(wb, rd->id); + buffer_strcat(wb, sq); + i++; + } + if(!i) { + rows = 0; + buffer_strcat(wb, sq); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, sq); + } + + buffer_sprintf(wb, "],\n" + " %slatest_values%s: [" + , kq, kq); + + for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + i++; + + storage_number n = rd->values[rrdset_last_slot(r->st)]; + + if(!does_storage_number_exist(n)) + buffer_strcat(wb, "null"); + else + buffer_rrd_value(wb, unpack_storage_number(n)); + } + if(!i) { + rows = 0; + buffer_strcat(wb, "null"); + } + + buffer_sprintf(wb, "],\n" + " %sview_latest_values%s: [" + , kq, kq); + + i = 0; + if(rows) { + for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + if(i) buffer_strcat(wb, ", "); + i++; + + calculated_number *cn = &r->v[ (0) * r->d ]; + uint8_t *co = &r->o[ (0) * r->d ]; + + if(co[c] & RRDR_EMPTY) + buffer_strcat(wb, "null"); + else + buffer_rrd_value(wb, cn[c]); + } + } + if(!i) { + rows = 0; + buffer_strcat(wb, "null"); + } + + buffer_sprintf(wb, "],\n" + " %sdimensions%s: %ld,\n" + " %spoints%s: %ld,\n" + " %sformat%s: %s" + , kq, kq, i + , kq, kq, rows + , kq, kq, sq + ); + + rrdr_buffer_print_format(wb, format); + + buffer_sprintf(wb, "%s,\n" + " %sresult%s: " + , sq + , kq, kq + ); + + if(string_value) buffer_strcat(wb, sq); + //info("JSONWRAPPER(): %s: END", r->st->id); } void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) { - if(r) {;} - if(format) {;} - - char kq[2] = "", // key quote - sq[2] = ""; // string quote - - if( options & RRDR_OPTION_GOOGLE_JSON ) { - kq[0] = '\0'; - sq[0] = '\''; - } - else { - kq[0] = '"'; - sq[0] = '"'; - } - - if(string_value) buffer_strcat(wb, sq); - - buffer_sprintf(wb, ",\n %smin%s: ", kq, kq); - buffer_rrd_value(wb, r->min); - buffer_sprintf(wb, ",\n %smax%s: ", kq, kq); - buffer_rrd_value(wb, r->max); - buffer_strcat(wb, "\n}\n"); + (void)format; + + char kq[2] = "", // key quote + sq[2] = ""; // string quote + + if( options & RRDR_OPTION_GOOGLE_JSON ) { + kq[0] = '\0'; + sq[0] = '\''; + } + else { + kq[0] = '"'; + sq[0] = '"'; + } + + if(string_value) buffer_strcat(wb, sq); + + buffer_sprintf(wb, ",\n %smin%s: ", kq, kq); + buffer_rrd_value(wb, r->min); + buffer_sprintf(wb, ",\n %smax%s: ", kq, kq); + buffer_rrd_value(wb, r->max); + buffer_strcat(wb, "\n}\n"); } #define JSON_DATES_JS 1 @@ -655,1356 +649,1435 @@ void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t option static void rrdr2json(RRDR *r, BUFFER *wb, uint32_t options, int datatable) { - //info("RRD2JSON(): %s: BEGIN", r->st->id); - int row_annotations = 0, dates, dates_with_new = 0; - char kq[2] = "", // key quote - sq[2] = "", // string quote - pre_label[101] = "", // before each label - post_label[101] = "", // after each label - pre_date[101] = "", // the beginning of line, to the date - post_date[101] = "", // closing the date - pre_value[101] = "", // before each value - post_value[101] = "", // after each value - post_line[101] = "", // at the end of each row - normal_annotation[201] = "", // default row annotation - overflow_annotation[201] = "", // overflow row annotation - data_begin[101] = "", // between labels and values - finish[101] = ""; // at the end of everything - - if(datatable) { - dates = JSON_DATES_JS; - if( options & RRDR_OPTION_GOOGLE_JSON ) { - kq[0] = '\0'; - sq[0] = '\''; - } - else { - kq[0] = '"'; - sq[0] = '"'; - } - row_annotations = 1; - 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); - buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotation%s}},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); - buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotationText%s}}", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); - - // remove the valueobjects flag - // google wants its own keys - if(options & RRDR_OPTION_OBJECTSROWS) - options &= ~RRDR_OPTION_OBJECTSROWS; - } - else { - kq[0] = '"'; - sq[0] = '"'; - if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) { - dates = JSON_DATES_TIMESTAMP; - dates_with_new = 0; - } - else { - dates = JSON_DATES_JS; - dates_with_new = 1; - } - if( options & RRDR_OPTION_OBJECTSROWS ) - strcpy(pre_date, " { "); - else - strcpy(pre_date, " [ "); - strcpy(pre_label, ", \""); - strcpy(post_label, "\""); - strcpy(pre_value, ", "); - if( options & RRDR_OPTION_OBJECTSROWS ) - strcpy(post_line, "}"); - else - 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); - } - - // ------------------------------------------------------------------------- - // print the JSON header - - long c, i; - RRDDIM *rd; - - // print the header lines - for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - buffer_strcat(wb, pre_label); - buffer_strcat(wb, rd->name); - buffer_strcat(wb, post_label); - i++; - } - if(!i) { - buffer_strcat(wb, pre_label); - buffer_strcat(wb, "no data"); - buffer_strcat(wb, post_label); - } - - // print the begin of row data - buffer_strcat(wb, data_begin); - - // if all dimensions are hidden, print a null - if(!i) { - buffer_strcat(wb, finish); - return; - } - - long start = 0, end = rrdr_rows(r), step = 1; - if((options & RRDR_OPTION_REVERSED)) { - start = rrdr_rows(r) - 1; - end = -1; - step = -1; - } - - // for each line in the array - calculated_number total = 1; - for(i = start; i != end ;i += step) { - calculated_number *cn = &r->v[ i * r->d ]; - uint8_t *co = &r->o[ i * r->d ]; - - time_t now = r->t[i]; - - if(dates == JSON_DATES_JS) { - // generate the local date time - struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); - if(!tm) { error("localtime_r() failed."); continue; } - - if(likely(i != start)) buffer_strcat(wb, ",\n"); - buffer_strcat(wb, pre_date); - - if( options & RRDR_OPTION_OBJECTSROWS ) - buffer_sprintf(wb, "%stime%s: ", kq, kq); - - if(dates_with_new) - buffer_strcat(wb, "new "); - - buffer_jsdate(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - - buffer_strcat(wb, post_date); - - if(row_annotations) { - // google supports one annotation per row - int annotation_found = 0; - for(c = 0, rd = r->st->dimensions; rd ;c++, rd = rd->next) { - if(co[c] & RRDR_RESET) { - buffer_strcat(wb, overflow_annotation); - annotation_found = 1; - break; - } - } - if(!annotation_found) - buffer_strcat(wb, normal_annotation); - } - } - else { - // print the timestamp of the line - if(likely(i != start)) buffer_strcat(wb, ",\n"); - buffer_strcat(wb, pre_date); - - if( options & RRDR_OPTION_OBJECTSROWS ) - buffer_sprintf(wb, "%stime%s: ", kq, kq); - - buffer_rrd_value(wb, (calculated_number)r->t[i]); - // in ms - if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000"); - - buffer_strcat(wb, post_date); - } - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - total = 0; - for(c = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { - calculated_number n = cn[c]; - - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - total += n; - } - // prevent a division by zero - if(total == 0) total = 1; - } - - // for each dimension - for(c = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - calculated_number n = cn[c]; - - buffer_strcat(wb, pre_value); - - if( options & RRDR_OPTION_OBJECTSROWS ) - buffer_sprintf(wb, "%s%s%s: ", kq, rd->name, kq); - - if(co[c] & RRDR_EMPTY) { - if(options & RRDR_OPTION_NULL2ZERO) - buffer_strcat(wb, "0"); - else - buffer_strcat(wb, "null"); - } - else { - if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) - n = n * 100 / total; - - buffer_rrd_value(wb, n); - } - - buffer_strcat(wb, post_value); - } - - buffer_strcat(wb, post_line); - } - - buffer_strcat(wb, finish); - //info("RRD2JSON(): %s: END", r->st->id); + //info("RRD2JSON(): %s: BEGIN", r->st->id); + int row_annotations = 0, dates, dates_with_new = 0; + char kq[2] = "", // key quote + sq[2] = "", // string quote + pre_label[101] = "", // before each label + post_label[101] = "", // after each label + pre_date[101] = "", // the beginning of line, to the date + post_date[101] = "", // closing the date + pre_value[101] = "", // before each value + post_value[101] = "", // after each value + post_line[101] = "", // at the end of each row + normal_annotation[201] = "", // default row annotation + overflow_annotation[201] = "", // overflow row annotation + data_begin[101] = "", // between labels and values + finish[101] = ""; // at the end of everything + + if(datatable) { + dates = JSON_DATES_JS; + if( options & RRDR_OPTION_GOOGLE_JSON ) { + kq[0] = '\0'; + sq[0] = '\''; + } + else { + kq[0] = '"'; + sq[0] = '"'; + } + row_annotations = 1; + 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); + 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); + buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotation%s}},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); + buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotationText%s}}", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); + + // remove the valueobjects flag + // google wants its own keys + if(options & RRDR_OPTION_OBJECTSROWS) + options &= ~RRDR_OPTION_OBJECTSROWS; + } + else { + kq[0] = '"'; + sq[0] = '"'; + if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) { + dates = JSON_DATES_TIMESTAMP; + dates_with_new = 0; + } + else { + dates = JSON_DATES_JS; + dates_with_new = 1; + } + if( options & RRDR_OPTION_OBJECTSROWS ) + strcpy(pre_date, " { "); + else + strcpy(pre_date, " [ "); + strcpy(pre_label, ", \""); + strcpy(post_label, "\""); + strcpy(pre_value, ", "); + if( options & RRDR_OPTION_OBJECTSROWS ) + strcpy(post_line, "}"); + else + 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); + } + + // ------------------------------------------------------------------------- + // print the JSON header + + long c, i; + RRDDIM *rd; + + // print the header lines + for(c = 0, i = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + buffer_strcat(wb, pre_label); + buffer_strcat(wb, rd->name); + buffer_strcat(wb, post_label); + i++; + } + if(!i) { + buffer_strcat(wb, pre_label); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, post_label); + } + + // print the begin of row data + buffer_strcat(wb, data_begin); + + // if all dimensions are hidden, print a null + if(!i) { + buffer_strcat(wb, finish); + return; + } + + long start = 0, end = rrdr_rows(r), step = 1; + if((options & RRDR_OPTION_REVERSED)) { + start = rrdr_rows(r) - 1; + end = -1; + step = -1; + } + + // for each line in the array + calculated_number total = 1; + for(i = start; i != end ;i += step) { + calculated_number *cn = &r->v[ i * r->d ]; + uint8_t *co = &r->o[ i * r->d ]; + + time_t now = r->t[i]; + + if(dates == JSON_DATES_JS) { + // generate the local date time + struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); + if(!tm) { error("localtime_r() failed."); continue; } + + if(likely(i != start)) buffer_strcat(wb, ",\n"); + buffer_strcat(wb, pre_date); + + if( options & RRDR_OPTION_OBJECTSROWS ) + buffer_sprintf(wb, "%stime%s: ", kq, kq); + + if(dates_with_new) + buffer_strcat(wb, "new "); + + buffer_jsdate(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + buffer_strcat(wb, post_date); + + if(row_annotations) { + // google supports one annotation per row + int annotation_found = 0; + for(c = 0, rd = r->st->dimensions; rd ;c++, rd = rd->next) { + if(co[c] & RRDR_RESET) { + buffer_strcat(wb, overflow_annotation); + annotation_found = 1; + break; + } + } + if(!annotation_found) + buffer_strcat(wb, normal_annotation); + } + } + else { + // print the timestamp of the line + if(likely(i != start)) buffer_strcat(wb, ",\n"); + buffer_strcat(wb, pre_date); + + if( options & RRDR_OPTION_OBJECTSROWS ) + buffer_sprintf(wb, "%stime%s: ", kq, kq); + + buffer_rrd_value(wb, (calculated_number)r->t[i]); + // in ms + if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000"); + + buffer_strcat(wb, post_date); + } + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + total = 0; + for(c = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + total += n; + } + // prevent a division by zero + if(total == 0) total = 1; + } + + // for each dimension + for(c = 0, rd = r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + calculated_number n = cn[c]; + + buffer_strcat(wb, pre_value); + + if( options & RRDR_OPTION_OBJECTSROWS ) + buffer_sprintf(wb, "%s%s%s: ", kq, rd->name, kq); + + if(co[c] & RRDR_EMPTY) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_strcat(wb, "0"); + else + buffer_strcat(wb, "null"); + } + else { + if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) + n = n * 100 / total; + + buffer_rrd_value(wb, n); + } + + buffer_strcat(wb, post_value); + } + + buffer_strcat(wb, post_line); + } + + buffer_strcat(wb, finish); + //info("RRD2JSON(): %s: END", r->st->id); } static void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t options, const char *startline, const char *separator, const char *endline, const char *betweenlines) { - //info("RRD2CSV(): %s: BEGIN", r->st->id); - long c, i; - RRDDIM *d; - - // print the csv header - for(c = 0, i = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - if(!i) { - buffer_strcat(wb, startline); - if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); - buffer_strcat(wb, "time"); - if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); - } - buffer_strcat(wb, separator); - if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); - buffer_strcat(wb, d->name); - if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); - i++; - } - buffer_strcat(wb, endline); - - if(!i) { - // no dimensions present - return; - } - - long start = 0, end = rrdr_rows(r), step = 1; - if((options & RRDR_OPTION_REVERSED)) { - start = rrdr_rows(r) - 1; - end = -1; - step = -1; - } - - // for each line in the array - calculated_number total = 1; - for(i = start; i != end ;i += step) { - calculated_number *cn = &r->v[ i * r->d ]; - uint8_t *co = &r->o[ i * r->d ]; - - buffer_strcat(wb, betweenlines); - buffer_strcat(wb, startline); - - time_t now = r->t[i]; - - if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) { - // print the timestamp of the line - buffer_rrd_value(wb, (calculated_number)now); - // in ms - if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000"); - } - else { - // generate the local date time - struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); - if(!tm) { error("localtime() failed."); continue; } - buffer_date(wb, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - } - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - total = 0; - for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { - calculated_number n = cn[c]; - - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - total += n; - } - // prevent a division by zero - if(total == 0) total = 1; - } - - // for each dimension - for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - buffer_strcat(wb, separator); - - calculated_number n = cn[c]; - - if(co[c] & RRDR_EMPTY) { - if(options & RRDR_OPTION_NULL2ZERO) - buffer_strcat(wb, "0"); - else - buffer_strcat(wb, "null"); - } - else { - if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) - n = n * 100 / total; - - buffer_rrd_value(wb, n); - } - } - - buffer_strcat(wb, endline); - } - //info("RRD2CSV(): %s: END", r->st->id); + //info("RRD2CSV(): %s: BEGIN", r->st->id); + long c, i; + RRDDIM *d; + + // print the csv header + for(c = 0, i = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + if(!i) { + buffer_strcat(wb, startline); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + buffer_strcat(wb, "time"); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + } + buffer_strcat(wb, separator); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + buffer_strcat(wb, d->name); + if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); + i++; + } + buffer_strcat(wb, endline); + + if(!i) { + // no dimensions present + return; + } + + long start = 0, end = rrdr_rows(r), step = 1; + if((options & RRDR_OPTION_REVERSED)) { + start = rrdr_rows(r) - 1; + end = -1; + step = -1; + } + + // for each line in the array + calculated_number total = 1; + for(i = start; i != end ;i += step) { + calculated_number *cn = &r->v[ i * r->d ]; + uint8_t *co = &r->o[ i * r->d ]; + + buffer_strcat(wb, betweenlines); + buffer_strcat(wb, startline); + + time_t now = r->t[i]; + + if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) { + // print the timestamp of the line + buffer_rrd_value(wb, (calculated_number)now); + // in ms + if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000"); + } + else { + // generate the local date time + struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); + if(!tm) { error("localtime() failed."); continue; } + buffer_date(wb, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + } + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + total = 0; + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + total += n; + } + // prevent a division by zero + if(total == 0) total = 1; + } + + // for each dimension + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + buffer_strcat(wb, separator); + + calculated_number n = cn[c]; + + if(co[c] & RRDR_EMPTY) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_strcat(wb, "0"); + else + buffer_strcat(wb, "null"); + } + else { + if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) + n = n * 100 / total; + + buffer_rrd_value(wb, n); + } + } + + buffer_strcat(wb, endline); + } + //info("RRD2CSV(): %s: END", r->st->id); +} + +inline static calculated_number rrdr2value(RRDR *r, long i, uint32_t options, int *all_values_are_null) { + long c; + RRDDIM *d; + + calculated_number *cn = &r->v[ i * r->d ]; + uint8_t *co = &r->o[ i * r->d ]; + + calculated_number sum = 0, min = 0, max = 0, v; + int all_null = 1, init = 1; + + calculated_number total = 1; + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { + total = 0; + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + total += n; + } + // prevent a division by zero + if(total == 0) total = 1; + } + + // for each dimension + for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { + if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; + + calculated_number n = cn[c]; + + if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) + n = -n; + + if(unlikely(options & RRDR_OPTION_PERCENTAGE)) + n = n * 100 / total; + + if(unlikely(init)) { + if(n > 0) { + min = 0; + max = n; + } + else { + min = n; + max = 0; + } + init = 0; + } + + if(likely(!(co[c] & RRDR_EMPTY))) { + all_null = 0; + sum += n; + } + + if(n < min) min = n; + if(n > max) max = n; + } + + if(unlikely(all_null)) { + if(likely(all_values_are_null)) + *all_values_are_null = 1; + return 0; + } + else { + if(likely(all_values_are_null)) + *all_values_are_null = 0; + } + + if(options & RRDR_OPTION_MIN2MAX) + v = max - min; + else + v = sum; + + return v; } static void rrdr2ssv(RRDR *r, BUFFER *wb, uint32_t options, const char *prefix, const char *separator, const char *suffix) { - //info("RRD2SSV(): %s: BEGIN", r->st->id); - long c, i; - RRDDIM *d; - - buffer_strcat(wb, prefix); - long start = 0, end = rrdr_rows(r), step = 1; - if((options & RRDR_OPTION_REVERSED)) { - start = rrdr_rows(r) - 1; - end = -1; - step = -1; - } - - // for each line in the array - calculated_number total = 1; - for(i = start; i != end ;i += step) { - - calculated_number *cn = &r->v[ i * r->d ]; - uint8_t *co = &r->o[ i * r->d ]; - - calculated_number sum = 0, min = 0, max = 0, v; - int all_null = 1, init = 1; - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - total = 0; - for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { - calculated_number n = cn[c]; - - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - total += n; - } - // prevent a division by zero - if(total == 0) total = 1; - } - - // for each dimension - for(c = 0, d = r->st->dimensions; d && c < r->d ;c++, d = d->next) { - if(unlikely(r->od[c] & RRDR_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_NONZERO))) continue; - - calculated_number n = cn[c]; - - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) - n = n * 100 / total; - - if(unlikely(init)) { - if(n > 0) { - min = 0; - max = n; - } - else { - min = n; - max = 0; - } - init = 0; - } - - if(likely(!(co[c] & RRDR_EMPTY))) { - all_null = 0; - sum += n; - } - - if(n < min) min = n; - if(n > max) max = n; - } - - if(likely(i != start)) - buffer_strcat(wb, separator); - - if(all_null) { - if(options & RRDR_OPTION_NULL2ZERO) - buffer_strcat(wb, "0"); - else - buffer_strcat(wb, "null"); - } - else { - if(options & RRDR_OPTION_MIN2MAX) - v = max - min; - else - v = sum; - - if(likely(i != start)) { - if(r->min > v) r->min = v; - if(r->max < v) r->max = v; - } - else { - r->min = v; - r->max = v; - } - - buffer_rrd_value(wb, v); - } - } - buffer_strcat(wb, suffix); - //info("RRD2SSV(): %s: END", r->st->id); + //info("RRD2SSV(): %s: BEGIN", r->st->id); + long i; + + buffer_strcat(wb, prefix); + long start = 0, end = rrdr_rows(r), step = 1; + if((options & RRDR_OPTION_REVERSED)) { + start = rrdr_rows(r) - 1; + end = -1; + step = -1; + } + + // for each line in the array + for(i = start; i != end ;i += step) { + int all_values_are_null = 0; + calculated_number v = rrdr2value(r, i, options, &all_values_are_null); + + if(likely(i != start)) { + if(r->min > v) r->min = v; + if(r->max < v) r->max = v; + } + else { + r->min = v; + r->max = v; + } + + if(likely(i != start)) + buffer_strcat(wb, separator); + + if(all_values_are_null) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_strcat(wb, "0"); + else + buffer_strcat(wb, "null"); + } + else + buffer_rrd_value(wb, v); + } + buffer_strcat(wb, suffix); + //info("RRD2SSV(): %s: END", r->st->id); } inline static calculated_number *rrdr_line_values(RRDR *r) { - return &r->v[ r->c * r->d ]; + return &r->v[ r->c * r->d ]; } inline static uint8_t *rrdr_line_options(RRDR *r) { - return &r->o[ r->c * r->d ]; + return &r->o[ r->c * r->d ]; } inline static int rrdr_line_init(RRDR *r, time_t t) { - r->c++; + r->c++; - if(unlikely(r->c >= r->n)) { - error("requested to step above RRDR size for chart %s", r->st->name); - r->c = r->n - 1; - } + if(unlikely(r->c >= r->n)) { + error("requested to step above RRDR size for chart %s", r->st->name); + r->c = r->n - 1; + } - // save the time - r->t[r->c] = t; + // save the time + r->t[r->c] = t; - return 1; + return 1; } inline static void rrdr_lock_rrdset(RRDR *r) { - if(unlikely(!r)) { - error("NULL value given!"); - return; - } + if(unlikely(!r)) { + error("NULL value given!"); + return; + } - pthread_rwlock_rdlock(&r->st->rwlock); - r->has_st_lock = 1; + pthread_rwlock_rdlock(&r->st->rwlock); + r->has_st_lock = 1; } inline static void rrdr_unlock_rrdset(RRDR *r) { - if(unlikely(!r)) { - error("NULL value given!"); - return; - } - - if(likely(r->has_st_lock)) { - pthread_rwlock_unlock(&r->st->rwlock); - r->has_st_lock = 0; - } + if(unlikely(!r)) { + error("NULL value given!"); + return; + } + + if(likely(r->has_st_lock)) { + pthread_rwlock_unlock(&r->st->rwlock); + r->has_st_lock = 0; + } } inline static void rrdr_free(RRDR *r) { - if(unlikely(!r)) { - error("NULL value given!"); - return; - } - - rrdr_unlock_rrdset(r); - if(likely(r->t)) free(r->t); - if(likely(r->v)) free(r->v); - if(likely(r->o)) free(r->o); - if(likely(r->od)) free(r->od); - free(r); + if(unlikely(!r)) { + error("NULL value given!"); + return; + } + + rrdr_unlock_rrdset(r); + freez(r->t); + freez(r->v); + freez(r->o); + freez(r->od); + freez(r); } inline void rrdr_done(RRDR *r) { - r->rows = r->c + 1; - r->c = 0; + r->rows = r->c + 1; + r->c = 0; } static RRDR *rrdr_create(RRDSET *st, long n) { - if(unlikely(!st)) { - error("NULL value given!"); - return NULL; - } - - RRDR *r = calloc(1, sizeof(RRDR)); - if(unlikely(!r)) goto cleanup; - - r->st = st; - - rrdr_lock_rrdset(r); - - RRDDIM *rd; - for(rd = st->dimensions ; rd ; rd = rd->next) r->d++; + if(unlikely(!st)) { + error("NULL value given!"); + return NULL; + } - r->n = n; + RRDR *r = callocz(1, sizeof(RRDR)); + r->st = st; - r->t = malloc(n * sizeof(time_t)); - if(unlikely(!r->t)) goto cleanup; + rrdr_lock_rrdset(r); - r->v = malloc(n * r->d * sizeof(calculated_number)); - if(unlikely(!r->v)) goto cleanup; + RRDDIM *rd; + for(rd = st->dimensions ; rd ; rd = rd->next) r->d++; - r->o = malloc(n * r->d * sizeof(uint8_t)); - if(unlikely(!r->o)) goto cleanup; + r->n = n; - r->od = malloc(r->d * sizeof(uint8_t)); - if(unlikely(!r->od)) goto cleanup; + r->t = mallocz(n * sizeof(time_t)); + r->v = mallocz(n * r->d * sizeof(calculated_number)); + r->o = mallocz(n * r->d * sizeof(uint8_t)); + r->od = mallocz(r->d * sizeof(uint8_t)); - // set the hidden flag on hidden dimensions - int c; - for(c = 0, rd = st->dimensions ; rd ; c++, rd = rd->next) { - if(unlikely(rd->flags & RRDDIM_FLAG_HIDDEN)) r->od[c] = RRDR_HIDDEN; - else r->od[c] = 0; - } + // set the hidden flag on hidden dimensions + int c; + for(c = 0, rd = st->dimensions ; rd ; c++, rd = rd->next) { + if(unlikely(rd->flags & RRDDIM_FLAG_HIDDEN)) r->od[c] = RRDR_HIDDEN; + else r->od[c] = 0; + } - r->c = -1; + r->c = -1; + r->group = 1; + r->update_every = 1; - r->group = 1; - r->update_every = 1; - - return r; - -cleanup: - error("Cannot allocate RRDR memory for %d entries", n); - if(likely(r)) rrdr_free(r); - return NULL; + return r; } -RRDR *rrd2rrdr(RRDSET *st, long points, long long after, long long before, int group_method) +RRDR *rrd2rrdr(RRDSET *st, long points, long long after, long long before, int group_method, int aligned) { - int debug = st->debug; - int absolute_period_requested = -1; - - time_t first_entry_t = rrdset_first_entry_t(st); - time_t last_entry_t = rrdset_last_entry_t(st); - - if(before == 0 && after == 0) { - before = last_entry_t; - after = first_entry_t; - absolute_period_requested = 0; - } - - // allow relative for before and after - if(before <= st->update_every * st->entries) { - before = last_entry_t + before; - absolute_period_requested = 0; - } - - if(after <= st->update_every * st->entries) { - after = last_entry_t + after; - absolute_period_requested = 0; - } - - if(absolute_period_requested == -1) - absolute_period_requested = 1; + int debug = st->debug; + int absolute_period_requested = -1; + + time_t first_entry_t = rrdset_first_entry_t(st); + time_t last_entry_t = rrdset_last_entry_t(st); + + if(before == 0 && after == 0) { + before = last_entry_t; + after = first_entry_t; + absolute_period_requested = 0; + } + + // allow relative for before and after + if(((before < 0)?-before:before) <= (st->update_every * st->entries)) { + before = last_entry_t + before; + absolute_period_requested = 0; + } + + if(((after < 0)?-after:after) <= (st->update_every * st->entries)) { + if(after == 0) after = -st->update_every; + after = before + after; + absolute_period_requested = 0; + } + + if(absolute_period_requested == -1) + absolute_period_requested = 1; + + // make sure they are within our timeframe + if(before > last_entry_t) before = last_entry_t; + if(before < first_entry_t) before = first_entry_t; + + if(after > last_entry_t) after = last_entry_t; + if(after < first_entry_t) after = first_entry_t; + + // check if they are upside down + if(after > before) { + time_t tmp = before; + before = after; + after = tmp; + } + + // the duration of the chart + time_t duration = before - after; + long available_points = duration / st->update_every; + + if(duration <= 0 || available_points <= 0) + return rrdr_create(st, 1); + + // check the wanted points + if(points < 0) points = -points; + if(points > available_points) points = available_points; + if(points == 0) points = available_points; + + // calculate proper grouping of source data + long group = available_points / points; + if(group <= 0) group = 1; + + // round group to the closest integer + if(available_points % points > points / 2) group++; + + time_t after_new = (aligned) ? (after - (after % (group * st->update_every))) : after; + time_t before_new = (aligned) ? (before - (before % (group * st->update_every))) : before; + long points_new = (before_new - after_new) / st->update_every / group; + + // find the starting and ending slots in our round robin db + long start_at_slot = rrdset_time2slot(st, before_new), + stop_at_slot = rrdset_time2slot(st, after_new); - // make sure they are within our timeframe - if(before > last_entry_t) before = last_entry_t; - if(before < first_entry_t) before = first_entry_t; +#ifdef NETDATA_INTERNAL_CHECKS + if(after_new < first_entry_t) { + error("after_new %u is too small, minimum %u", (uint32_t)after_new, (uint32_t)first_entry_t); + } + if(after_new > last_entry_t) { + error("after_new %u is too big, maximum %u", (uint32_t)after_new, (uint32_t)last_entry_t); + } + if(before_new < first_entry_t) { + error("before_new %u is too small, minimum %u", (uint32_t)before_new, (uint32_t)first_entry_t); + } + if(before_new > last_entry_t) { + error("before_new %u is too big, maximum %u", (uint32_t)before_new, (uint32_t)last_entry_t); + } + if(start_at_slot < 0 || start_at_slot >= st->entries) { + error("start_at_slot is invalid %ld, expected 0 to %ld", start_at_slot, st->entries - 1); + } + if(stop_at_slot < 0 || stop_at_slot >= st->entries) { + error("stop_at_slot is invalid %ld, expected 0 to %ld", stop_at_slot, st->entries - 1); + } + if(points_new > (before_new - after_new) / group / st->update_every + 1) { + error("points_new %ld is more than points %ld", points_new, (before_new - after_new) / group / st->update_every + 1); + } +#endif - if(after > last_entry_t) after = last_entry_t; - if(after < first_entry_t) after = first_entry_t; + //info("RRD2RRDR(): %s: wanted %ld points, got %ld - group=%ld, wanted duration=%u, got %u - wanted %ld - %ld, got %ld - %ld", st->id, points, points_new, group, before - after, before_new - after_new, after, before, after_new, before_new); - // check if they are upside down - if(after > before) { - time_t tmp = before; - before = after; - after = tmp; - } + after = after_new; + before = before_new; + duration = before - after; + points = points_new; - // the duration of the chart - time_t duration = before - after; - long available_points = duration / st->update_every; + // Now we have: + // before = the end time of the calculation + // after = the start time of the calculation + // duration = the duration of the calculation + // group = the number of source points to aggregate / group together + // method = the method of grouping source points + // points = the number of points to generate - if(duration <= 0 || available_points <= 0) - return rrdr_create(st, 1); - // check the wanted points - if(points < 0) points = -points; - if(points > available_points) points = available_points; - if(points == 0) points = available_points; + // ------------------------------------------------------------------------- + // initialize our result set - // calculate proper grouping of source data - long group = available_points / points; - if(group <= 0) group = 1; + RRDR *r = rrdr_create(st, points); + if(!r) { +#ifdef NETDATA_INTERNAL_CHECKS + error("Cannot create RRDR for %s, after=%u, before=%u, duration=%u, points=%ld", st->id, (uint32_t)after, (uint32_t)before, (uint32_t)duration, points); +#endif + return NULL; + } + if(!r->d) { +#ifdef NETDATA_INTERNAL_CHECKS + error("Returning empty RRDR (no dimensions in RRDSET) for %s, after=%u, before=%u, duration=%u, points=%ld", st->id, (uint32_t)after, (uint32_t)before, (uint32_t)duration, points); +#endif + return r; + } + + if(absolute_period_requested == 1) + r->result_options |= RRDR_RESULT_OPTION_ABSOLUTE; + else + r->result_options |= RRDR_RESULT_OPTION_RELATIVE; + + // find how many dimensions we have + long dimensions = r->d; + + + // ------------------------------------------------------------------------- + // checks for debugging + + if(debug) debug(D_RRD_STATS, "INFO %s first_t: %u, last_t: %u, all_duration: %u, after: %u, before: %u, duration: %u, points: %ld, group: %ld" + , st->id + , (uint32_t)first_entry_t + , (uint32_t)last_entry_t + , (uint32_t)(last_entry_t - first_entry_t) + , (uint32_t)after + , (uint32_t)before + , (uint32_t)duration + , points + , group + ); + + + // ------------------------------------------------------------------------- + // temp arrays for keeping values per dimension + + calculated_number last_values[dimensions]; // keep the last value of each dimension + calculated_number group_values[dimensions]; // keep sums when grouping + long group_counts[dimensions]; // keep the number of values added to group_values + uint8_t group_options[dimensions]; + uint8_t found_non_zero[dimensions]; + + + // initialize them + RRDDIM *rd; + long c; + for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { + last_values[c] = 0; + group_values[c] = (group_method == GROUP_MAX || group_method == GROUP_MIN)?NAN:0; + group_counts[c] = 0; + group_options[c] = 0; + found_non_zero[c] = 0; + } + + + // ------------------------------------------------------------------------- + // the main loop + + time_t now = rrdset_slot2time(st, start_at_slot), + dt = st->update_every, + group_start_t = 0; + + if(unlikely(debug)) debug(D_RRD_STATS, "BEGIN %s after_t: %u (stop_at_t: %ld), before_t: %u (start_at_t: %ld), start_t(now): %u, current_entry: %ld, entries: %ld" + , st->id + , (uint32_t)after + , stop_at_slot + , (uint32_t)before + , start_at_slot + , (uint32_t)now + , st->current_entry + , st->entries + ); + + r->group = group; + r->update_every = group * st->update_every; + r->before = now; + r->after = now; + + //info("RRD2RRDR(): %s: STARTING", st->id); + + long slot = start_at_slot, counter = 0, stop_now = 0, added = 0, group_count = 0, add_this = 0; + for(; !stop_now ; now -= dt, slot--, counter++) { + if(unlikely(slot < 0)) slot = st->entries - 1; + if(unlikely(slot == stop_at_slot)) stop_now = counter; + + if(unlikely(debug)) debug(D_RRD_STATS, "ROW %s slot: %ld, entries_counter: %ld, group_count: %ld, added: %ld, now: %ld, %s %s" + , st->id + , slot + , counter + , group_count + 1 + , added + , now + , (group_count + 1 == group)?"PRINT":" - " + , (now >= after && now <= before)?"RANGE":" - " + ); + + // make sure we return data in the proper time range + if(unlikely(now > before)) continue; + if(unlikely(now < after)) break; + + if(unlikely(group_count == 0)) { + group_start_t = now; + } + group_count++; + + if(unlikely(group_count == group)) { + if(unlikely(added >= points)) break; + add_this = 1; + } + + // do the calculations + for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { + storage_number n = rd->values[slot]; + if(unlikely(!does_storage_number_exist(n))) continue; + + group_counts[c]++; + + calculated_number value = unpack_storage_number(n); + if(likely(value != 0.0)) { + group_options[c] |= RRDR_NONZERO; + found_non_zero[c] = 1; + } + + if(unlikely(did_storage_number_reset(n))) + group_options[c] |= RRDR_RESET; + + switch(group_method) { + case GROUP_MIN: + if(unlikely(isnan(group_values[c])) || + fabsl(value) < fabsl(group_values[c])) + group_values[c] = value; + break; + + case GROUP_MAX: + if(unlikely(isnan(group_values[c])) || + fabsl(value) > fabsl(group_values[c])) + group_values[c] = value; + break; + + default: + case GROUP_SUM: + case GROUP_AVERAGE: + case GROUP_UNDEFINED: + group_values[c] += value; + break; + + case GROUP_INCREMENTAL_SUM: + if(unlikely(slot == start_at_slot)) + last_values[c] = value; + + group_values[c] += last_values[c] - value; + last_values[c] = value; + break; + } + } + + // added it + if(unlikely(add_this)) { + if(unlikely(!rrdr_line_init(r, group_start_t))) break; + + r->after = now; + + calculated_number *cn = rrdr_line_values(r); + uint8_t *co = rrdr_line_options(r); + + for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { + + // update the dimension options + if(likely(found_non_zero[c])) r->od[c] |= RRDR_NONZERO; + + // store the specific point options + co[c] = group_options[c]; + + // store the value + if(unlikely(group_counts[c] == 0)) { + cn[c] = 0.0; + co[c] |= RRDR_EMPTY; + group_values[c] = (group_method == GROUP_MAX || group_method == GROUP_MIN)?NAN:0; + } + else { + switch(group_method) { + case GROUP_MIN: + case GROUP_MAX: + if(unlikely(isnan(group_values[c]))) + cn[c] = 0; + else { + cn[c] = group_values[c]; + group_values[c] = NAN; + } + break; + + case GROUP_SUM: + case GROUP_INCREMENTAL_SUM: + cn[c] = group_values[c]; + group_values[c] = 0; + break; + + default: + case GROUP_AVERAGE: + case GROUP_UNDEFINED: + cn[c] = group_values[c] / group_counts[c]; + group_values[c] = 0; + break; + } + + if(cn[c] < r->min) r->min = cn[c]; + if(cn[c] > r->max) r->max = cn[c]; + } + + // reset for the next loop + group_counts[c] = 0; + group_options[c] = 0; + } + + added++; + group_count = 0; + add_this = 0; + } + } + + rrdr_done(r); + //info("RRD2RRDR(): %s: END %ld loops made, %ld points generated", st->id, counter, rrdr_rows(r)); + //error("SHIFT: %s: wanted %ld points, got %ld", st->id, points, rrdr_rows(r)); + return r; +} - // round group to the closest integer - if(available_points % points > points / 2) group++; +int rrd2value(RRDSET *st, BUFFER *wb, calculated_number *n, const char *dimensions, long points, long long after, long long before, int group_method, uint32_t options, time_t *db_after, time_t *db_before, int *value_is_null) +{ + RRDR *r = rrd2rrdr(st, points, after, before, group_method, !(options & RRDR_OPTION_NOT_ALIGNED)); + if(!r) { + if(value_is_null) *value_is_null = 1; + return 500; + } - time_t after_new = after - (after % (group * st->update_every)); - time_t before_new = before - (before % (group * st->update_every)); - long points_new = (before_new - after_new) / st->update_every / group; + if(rrdr_rows(r) == 0) { + rrdr_free(r); - // find the starting and ending slots in our round robin db - long start_at_slot = rrdset_time2slot(st, before_new), - stop_at_slot = rrdset_time2slot(st, after_new); + if(db_after) *db_after = 0; + if(db_before) *db_before = 0; + if(value_is_null) *value_is_null = 1; -#ifdef NETDATA_INTERNAL_CHECKS - if(after_new < first_entry_t) { - error("after_new %u is too small, minimum %u", after_new, first_entry_t); - } - if(after_new > last_entry_t) { - error("after_new %u is too big, maximum %u", after_new, last_entry_t); - } - if(before_new < first_entry_t) { - error("before_new %u is too small, minimum %u", before_new, first_entry_t); - } - if(before_new > last_entry_t) { - error("before_new %u is too big, maximum %u", before_new, last_entry_t); - } - if(start_at_slot < 0 || start_at_slot >= st->entries) { - error("start_at_slot is invalid %ld, expected %ld to %ld", start_at_slot, 0, st->entries - 1); - } - if(stop_at_slot < 0 || stop_at_slot >= st->entries) { - error("stop_at_slot is invalid %ld, expected %ld to %ld", stop_at_slot, 0, st->entries - 1); - } - if(points_new > (before_new - after_new) / group / st->update_every + 1) { - error("points_new %ld is more than points %ld", points_new, (before_new - after_new) / group / st->update_every + 1); - } -#endif + return 400; + } - //info("RRD2RRDR(): %s: wanted %ld points, got %ld - group=%ld, wanted duration=%u, got %u - wanted %ld - %ld, got %ld - %ld", st->id, points, points_new, group, before - after, before_new - after_new, after, before, after_new, before_new); + if(r->result_options & RRDR_RESULT_OPTION_RELATIVE) + wb->options |= WB_CONTENT_NO_CACHEABLE; + else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + wb->options |= WB_CONTENT_CACHEABLE; - after = after_new; - before = before_new; - duration = before - after; - points = points_new; + options = rrdr_check_options(r, options, dimensions); - // Now we have: - // before = the end time of the calculation - // after = the start time of the calculation - // duration = the duration of the calculation - // group = the number of source points to aggregate / group together - // method = the method of grouping source points - // points = the number of points to generate + if(dimensions) + rrdr_disable_not_selected_dimensions(r, dimensions); + if(db_after) *db_after = r->after; + if(db_before) *db_before = r->before; - // ------------------------------------------------------------------------- - // initialize our result set + long i = (options & RRDR_OPTION_REVERSED)?rrdr_rows(r) - 1:0; + *n = rrdr2value(r, i, options, value_is_null); - RRDR *r = rrdr_create(st, points); - if(!r) { -#ifdef NETDATA_INTERNAL_CHECKS - error("Cannot create RRDR for %s, after=%u, before=%u, duration=%u, points=%d", st->id, after, before, duration, points); -#endif - return NULL; - } - if(!r->d) { -#ifdef NETDATA_INTERNAL_CHECKS - error("Returning empty RRDR (no dimensions in RRDSET) for %s, after=%u, before=%u, duration=%u, points=%d", st->id, after, before, duration, points); -#endif - return r; - } - - if(absolute_period_requested == 1) - r->result_options |= RRDR_RESULT_OPTION_ABSOLUTE; - else - r->result_options |= RRDR_RESULT_OPTION_RELATIVE; - - // find how many dimensions we have - long dimensions = r->d; - - - // ------------------------------------------------------------------------- - // checks for debugging - - if(debug) debug(D_RRD_STATS, "INFO %s first_t: %lu, last_t: %lu, all_duration: %lu, after: %lu, before: %lu, duration: %lu, points: %ld, group: %ld" - , st->id - , first_entry_t - , last_entry_t - , last_entry_t - first_entry_t - , after - , before - , duration - , points - , group - ); - - - // ------------------------------------------------------------------------- - // temp arrays for keeping values per dimension - - calculated_number group_values[dimensions]; // keep sums when grouping - long group_counts[dimensions]; // keep the number of values added to group_values - uint8_t group_options[dimensions]; - uint8_t found_non_zero[dimensions]; - - - // initialize them - RRDDIM *rd; - long c; - for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { - group_values[c] = 0; - group_counts[c] = 0; - group_options[c] = 0; - found_non_zero[c] = 0; - } - - - // ------------------------------------------------------------------------- - // the main loop - - time_t now = rrdset_slot2time(st, start_at_slot), - dt = st->update_every, - group_start_t = 0; - - if(unlikely(debug)) debug(D_RRD_STATS, "BEGIN %s after_t: %lu (stop_at_t: %ld), before_t: %lu (start_at_t: %ld), start_t(now): %lu, current_entry: %ld, entries: %ld" - , st->id - , after - , stop_at_slot - , before - , start_at_slot - , now - , st->current_entry - , st->entries - ); - - r->group = group; - r->update_every = group * st->update_every; - r->before = now; - r->after = now; - - //info("RRD2RRDR(): %s: STARTING", st->id); - - long slot = start_at_slot, counter = 0, stop_now = 0, added = 0, group_count = 0, add_this = 0; - for(; !stop_now ; now -= dt, slot--, counter++) { - if(unlikely(slot < 0)) slot = st->entries - 1; - if(unlikely(slot == stop_at_slot)) stop_now = counter; - - if(unlikely(debug)) debug(D_RRD_STATS, "ROW %s slot: %ld, entries_counter: %ld, group_count: %ld, added: %ld, now: %lu, %s %s" - , st->id - , slot - , counter - , group_count + 1 - , added - , now - , (group_count + 1 == group)?"PRINT":" - " - , (now >= after && now <= before)?"RANGE":" - " - ); - - // make sure we return data in the proper time range - if(unlikely(now > before)) continue; - if(unlikely(now < after)) break; - - if(unlikely(group_count == 0)) { - group_start_t = now; - } - group_count++; - - if(unlikely(group_count == group)) { - if(unlikely(added >= points)) break; - add_this = 1; - } - - // do the calculations - for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { - storage_number n = rd->values[slot]; - if(unlikely(!does_storage_number_exist(n))) continue; - - group_counts[c]++; - - calculated_number value = unpack_storage_number(n); - if(likely(value != 0.0)) { - group_options[c] |= RRDR_NONZERO; - found_non_zero[c] = 1; - } - - if(unlikely(did_storage_number_reset(n))) - group_options[c] |= RRDR_RESET; - - switch(group_method) { - case GROUP_MAX: - if(unlikely(fabsl(value) > fabsl(group_values[c]))) - group_values[c] = value; - break; - - default: - case GROUP_SUM: - case GROUP_AVERAGE: - group_values[c] += value; - break; - } - } - - // added it - if(unlikely(add_this)) { - if(unlikely(!rrdr_line_init(r, group_start_t))) break; - - r->after = now; - - calculated_number *cn = rrdr_line_values(r); - uint8_t *co = rrdr_line_options(r); - - for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { - - // update the dimension options - if(likely(found_non_zero[c])) r->od[c] |= RRDR_NONZERO; - - // store the specific point options - co[c] = group_options[c]; - - // store the value - if(unlikely(group_counts[c] == 0)) { - cn[c] = 0.0; - co[c] |= RRDR_EMPTY; - } - else if(unlikely(group_method == GROUP_AVERAGE)) { - cn[c] = group_values[c] / group_counts[c]; - } - else { - cn[c] = group_values[c]; - } - - if(cn[c] < r->min) r->min = cn[c]; - if(cn[c] > r->max) r->max = cn[c]; - - // reset them for the next loop - group_values[c] = 0; - group_counts[c] = 0; - group_options[c] = 0; - } - - added++; - group_count = 0; - add_this = 0; - } - } - - rrdr_done(r); - //info("RRD2RRDR(): %s: END %ld loops made, %ld points generated", st->id, counter, rrdr_rows(r)); - //error("SHIFT: %s: wanted %ld points, got %ld", st->id, points, rrdr_rows(r)); - return r; + rrdr_free(r); + return 200; } int rrd2format(RRDSET *st, BUFFER *wb, BUFFER *dimensions, uint32_t format, long points, long long after, long long before, int group_method, uint32_t options, time_t *latest_timestamp) { - RRDR *r = rrd2rrdr(st, points, after, before, group_method); - if(!r) { - buffer_strcat(wb, "Cannot generate output with these parameters on this chart."); - return 500; - } - - if(r->result_options & RRDR_RESULT_OPTION_RELATIVE) - wb->options |= WB_CONTENT_NO_CACHEABLE; - else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) - wb->options |= WB_CONTENT_CACHEABLE; - - options = rrdr_check_options(r, options, (dimensions)?buffer_tostring(dimensions):NULL); - - if(dimensions) - rrdr_disable_not_selected_dimensions(r, buffer_tostring(dimensions)); - - if(latest_timestamp && rrdr_rows(r) > 0) - *latest_timestamp = r->before; - - switch(format) { - case DATASOURCE_SSV: - if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1); - rrdr2ssv(r, wb, options, "", " ", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); - } - else { - wb->contenttype = CT_TEXT_PLAIN; - rrdr2ssv(r, wb, options, "", " ", ""); - } - break; - - case DATASOURCE_SSV_COMMA: - if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1); - rrdr2ssv(r, wb, options, "", ",", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); - } - else { - wb->contenttype = CT_TEXT_PLAIN; - rrdr2ssv(r, wb, options, "", ",", ""); - } - break; - - case DATASOURCE_JS_ARRAY: - if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 0); - rrdr2ssv(r, wb, options, "[", ",", "]"); - rrdr_json_wrapper_end(r, wb, format, options, 0); - } - else { - wb->contenttype = CT_APPLICATION_JSON; - rrdr2ssv(r, wb, options, "[", ",", "]"); - } - break; - - case DATASOURCE_CSV: - if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1); - rrdr2csv(r, wb, options, "", ",", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); - } - else { - wb->contenttype = CT_TEXT_PLAIN; - rrdr2csv(r, wb, options, "", ",", "\r\n", ""); - } - break; - - case DATASOURCE_CSV_JSON_ARRAY: - wb->contenttype = CT_APPLICATION_JSON; - if(options & RRDR_OPTION_JSON_WRAP) { - rrdr_json_wrapper_begin(r, wb, format, options, 0); - buffer_strcat(wb, "[\n"); - rrdr2csv(r, wb, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n"); - buffer_strcat(wb, "\n]"); - rrdr_json_wrapper_end(r, wb, format, options, 0); - } - else { - wb->contenttype = CT_TEXT_PLAIN; - buffer_strcat(wb, "[\n"); - rrdr2csv(r, wb, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n"); - buffer_strcat(wb, "\n]"); - } - break; - - case DATASOURCE_TSV: - if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1); - rrdr2csv(r, wb, options, "", "\t", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); - } - else { - wb->contenttype = CT_TEXT_PLAIN; - rrdr2csv(r, wb, options, "", "\t", "\r\n", ""); - } - break; - - case DATASOURCE_HTML: - if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1); - buffer_strcat(wb, "<html>\\n<center>\\n<table border=\\\"0\\\" cellpadding=\\\"5\\\" cellspacing=\\\"5\\\">\\n"); - rrdr2csv(r, wb, options, "<tr><td>", "</td><td>", "</td></tr>\\n", ""); - buffer_strcat(wb, "</table>\\n</center>\\n</html>\\n"); - rrdr_json_wrapper_end(r, wb, format, options, 1); - } - else { - wb->contenttype = CT_TEXT_HTML; - buffer_strcat(wb, "<html>\n<center>\n<table border=\"0\" cellpadding=\"5\" cellspacing=\"5\">\n"); - rrdr2csv(r, wb, options, "<tr><td>", "</td><td>", "</td></tr>\n", ""); - buffer_strcat(wb, "</table>\n</center>\n</html>\n"); - } - break; - - case DATASOURCE_DATATABLE_JSONP: - wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; - - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0); - - rrdr2json(r, wb, options, 1); - - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); - break; - - case DATASOURCE_DATATABLE_JSON: - wb->contenttype = CT_APPLICATION_JSON; - - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0); - - rrdr2json(r, wb, options, 1); - - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); - break; - - case DATASOURCE_JSONP: - wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0); - - rrdr2json(r, wb, options, 0); - - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); - break; - - case DATASOURCE_JSON: - default: - wb->contenttype = CT_APPLICATION_JSON; - - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0); - - rrdr2json(r, wb, options, 0); - - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); - break; - } - - rrdr_free(r); - return 200; + RRDR *r = rrd2rrdr(st, points, after, before, group_method, !(options & RRDR_OPTION_NOT_ALIGNED)); + if(!r) { + buffer_strcat(wb, "Cannot generate output with these parameters on this chart."); + return 500; + } + + if(r->result_options & RRDR_RESULT_OPTION_RELATIVE) + wb->options |= WB_CONTENT_NO_CACHEABLE; + else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + wb->options |= WB_CONTENT_CACHEABLE; + + options = rrdr_check_options(r, options, (dimensions)?buffer_tostring(dimensions):NULL); + + if(dimensions) + rrdr_disable_not_selected_dimensions(r, buffer_tostring(dimensions)); + + if(latest_timestamp && rrdr_rows(r) > 0) + *latest_timestamp = r->before; + + switch(format) { + case DATASOURCE_SSV: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1); + rrdr2ssv(r, wb, options, "", " ", ""); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2ssv(r, wb, options, "", " ", ""); + } + break; + + case DATASOURCE_SSV_COMMA: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1); + rrdr2ssv(r, wb, options, "", ",", ""); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2ssv(r, wb, options, "", ",", ""); + } + break; + + case DATASOURCE_JS_ARRAY: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 0); + rrdr2ssv(r, wb, options, "[", ",", "]"); + rrdr_json_wrapper_end(r, wb, format, options, 0); + } + else { + wb->contenttype = CT_APPLICATION_JSON; + rrdr2ssv(r, wb, options, "[", ",", "]"); + } + break; + + case DATASOURCE_CSV: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1); + rrdr2csv(r, wb, options, "", ",", "\\n", ""); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2csv(r, wb, options, "", ",", "\r\n", ""); + } + break; + + case DATASOURCE_CSV_JSON_ARRAY: + wb->contenttype = CT_APPLICATION_JSON; + if(options & RRDR_OPTION_JSON_WRAP) { + rrdr_json_wrapper_begin(r, wb, format, options, 0); + buffer_strcat(wb, "[\n"); + rrdr2csv(r, wb, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n"); + buffer_strcat(wb, "\n]"); + rrdr_json_wrapper_end(r, wb, format, options, 0); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + buffer_strcat(wb, "[\n"); + rrdr2csv(r, wb, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n"); + buffer_strcat(wb, "\n]"); + } + break; + + case DATASOURCE_TSV: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1); + rrdr2csv(r, wb, options, "", "\t", "\\n", ""); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_PLAIN; + rrdr2csv(r, wb, options, "", "\t", "\r\n", ""); + } + break; + + case DATASOURCE_HTML: + if(options & RRDR_OPTION_JSON_WRAP) { + wb->contenttype = CT_APPLICATION_JSON; + rrdr_json_wrapper_begin(r, wb, format, options, 1); + buffer_strcat(wb, "<html>\\n<center>\\n<table border=\\\"0\\\" cellpadding=\\\"5\\\" cellspacing=\\\"5\\\">\\n"); + rrdr2csv(r, wb, options, "<tr><td>", "</td><td>", "</td></tr>\\n", ""); + buffer_strcat(wb, "</table>\\n</center>\\n</html>\\n"); + rrdr_json_wrapper_end(r, wb, format, options, 1); + } + else { + wb->contenttype = CT_TEXT_HTML; + buffer_strcat(wb, "<html>\n<center>\n<table border=\"0\" cellpadding=\"5\" cellspacing=\"5\">\n"); + rrdr2csv(r, wb, options, "<tr><td>", "</td><td>", "</td></tr>\n", ""); + buffer_strcat(wb, "</table>\n</center>\n</html>\n"); + } + break; + + case DATASOURCE_DATATABLE_JSONP: + wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0); + + rrdr2json(r, wb, options, 1); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + + case DATASOURCE_DATATABLE_JSON: + wb->contenttype = CT_APPLICATION_JSON; + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0); + + rrdr2json(r, wb, options, 1); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + + case DATASOURCE_JSONP: + wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0); + + rrdr2json(r, wb, options, 0); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + + case DATASOURCE_JSON: + default: + wb->contenttype = CT_APPLICATION_JSON; + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_begin(r, wb, format, options, 0); + + rrdr2json(r, wb, options, 0); + + if(options & RRDR_OPTION_JSON_WRAP) + rrdr_json_wrapper_end(r, wb, format, options, 0); + break; + } + + rrdr_free(r); + return 200; } time_t rrd_stats_json(int type, RRDSET *st, BUFFER *wb, long points, long group, int group_method, time_t after, time_t before, int only_non_zero) { - int c; - pthread_rwlock_rdlock(&st->rwlock); + int c; + pthread_rwlock_rdlock(&st->rwlock); - // ------------------------------------------------------------------------- - // switch from JSON to google JSON + // ------------------------------------------------------------------------- + // switch from JSON to google JSON - char kq[2] = "\""; - char sq[2] = "\""; - switch(type) { - case DATASOURCE_DATATABLE_JSON: - case DATASOURCE_DATATABLE_JSONP: - kq[0] = '\0'; - sq[0] = '\''; - break; + char kq[2] = "\""; + char sq[2] = "\""; + switch(type) { + case DATASOURCE_DATATABLE_JSON: + case DATASOURCE_DATATABLE_JSONP: + kq[0] = '\0'; + sq[0] = '\''; + break; - case DATASOURCE_JSON: - default: - break; - } + case DATASOURCE_JSON: + default: + break; + } - // ------------------------------------------------------------------------- - // validate the parameters + // ------------------------------------------------------------------------- + // validate the parameters - if(points < 1) points = 1; - if(group < 1) group = 1; + if(points < 1) points = 1; + if(group < 1) group = 1; - if(before == 0 || before > rrdset_last_entry_t(st)) before = rrdset_last_entry_t(st); - if(after == 0 || after < rrdset_first_entry_t(st)) after = rrdset_first_entry_t(st); + if(before == 0 || before > rrdset_last_entry_t(st)) before = rrdset_last_entry_t(st); + if(after == 0 || after < rrdset_first_entry_t(st)) after = rrdset_first_entry_t(st); - // --- + // --- - // our return value (the last timestamp printed) - // this is required to detect re-transmit in google JSONP - time_t last_timestamp = 0; + // our return value (the last timestamp printed) + // this is required to detect re-transmit in google JSONP + time_t last_timestamp = 0; - // ------------------------------------------------------------------------- - // find how many dimensions we have + // ------------------------------------------------------------------------- + // find how many dimensions we have - int dimensions = 0; - RRDDIM *rd; - for( rd = st->dimensions ; rd ; rd = rd->next) dimensions++; - if(!dimensions) { - pthread_rwlock_unlock(&st->rwlock); - buffer_strcat(wb, "No dimensions yet."); - return 0; - } + int dimensions = 0; + RRDDIM *rd; + for( rd = st->dimensions ; rd ; rd = rd->next) dimensions++; + if(!dimensions) { + pthread_rwlock_unlock(&st->rwlock); + buffer_strcat(wb, "No dimensions yet."); + return 0; + } - // ------------------------------------------------------------------------- - // prepare various strings, to speed up the loop + // ------------------------------------------------------------------------- + // prepare various strings, to speed up the loop - 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, "}"); + 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, "}"); - // ------------------------------------------------------------------------- - // checks for debugging + // ------------------------------------------------------------------------- + // checks for debugging - if(st->debug) { - debug(D_RRD_STATS, "%s first_entry_t = %lu, last_entry_t = %lu, duration = %lu, after = %lu, before = %lu, duration = %lu, entries_to_show = %lu, group = %lu" - , st->id - , rrdset_first_entry_t(st) - , rrdset_last_entry_t(st) - , rrdset_last_entry_t(st) - rrdset_first_entry_t(st) - , after - , before - , before - after - , points - , group - ); + if(st->debug) { + debug(D_RRD_STATS, "%s first_entry_t = %ld, last_entry_t = %ld, duration = %ld, after = %ld, before = %ld, duration = %ld, entries_to_show = %ld, group = %ld" + , st->id + , rrdset_first_entry_t(st) + , rrdset_last_entry_t(st) + , rrdset_last_entry_t(st) - rrdset_first_entry_t(st) + , after + , before + , before - after + , points + , group + ); - if(before < after) - debug(D_RRD_STATS, "WARNING: %s The newest value in the database (%lu) is earlier than the oldest (%lu)", st->name, before, after); + if(before < after) + debug(D_RRD_STATS, "WARNING: %s The newest value in the database (%ld) is earlier than the oldest (%ld)", st->name, before, after); - if((before - after) > st->entries * st->update_every) - debug(D_RRD_STATS, "WARNING: %s The time difference between the oldest and the newest entries (%lu) is higher than the capacity of the database (%lu)", st->name, before - after, st->entries * st->update_every); - } - - - // ------------------------------------------------------------------------- - // temp arrays for keeping values per dimension - - calculated_number group_values[dimensions]; // keep sums when grouping - int print_hidden[dimensions]; // keep hidden flags - int found_non_zero[dimensions]; - int found_non_existing[dimensions]; + if((before - after) > st->entries * st->update_every) + debug(D_RRD_STATS, "WARNING: %s The time difference between the oldest and the newest entries (%ld) is higher than the capacity of the database (%ld)", st->name, before - after, st->entries * st->update_every); + } + + + // ------------------------------------------------------------------------- + // temp arrays for keeping values per dimension + + calculated_number group_values[dimensions]; // keep sums when grouping + int print_hidden[dimensions]; // keep hidden flags + int found_non_zero[dimensions]; + int found_non_existing[dimensions]; - // initialize them - for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { - group_values[c] = 0; - print_hidden[c] = (rd->flags & RRDDIM_FLAG_HIDDEN)?1:0; - found_non_zero[c] = 0; - found_non_existing[c] = 0; - } + // initialize them + for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { + group_values[c] = 0; + print_hidden[c] = (rd->flags & RRDDIM_FLAG_HIDDEN)?1:0; + found_non_zero[c] = 0; + found_non_existing[c] = 0; + } - // error("OLD: points=%d after=%d before=%d group=%d, duration=%d", entries_to_show, before - (st->update_every * group * entries_to_show), before, group, before - after + 1); - // rrd2array(st, entries_to_show, before - (st->update_every * group * entries_to_show), before, group_method, only_non_zero); - // rrd2rrdr(st, entries_to_show, before - (st->update_every * group * entries_to_show), before, group_method); - - // ------------------------------------------------------------------------- - // remove dimensions that contain only zeros - - int max_loop = 1; - if(only_non_zero) max_loop = 2; - - for(; max_loop ; max_loop--) { - - // ------------------------------------------------------------------------- - // print the JSON header - - buffer_sprintf(wb, "{\n %scols%s:\n [\n", 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); - buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotation%s}},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); - buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotationText%s}}", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); - - // print the header for each dimension - // and update the print_hidden array for the dimensions that should be hidden - int pc = 0; - for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { - if(!print_hidden[c]) { - pc++; - buffer_sprintf(wb, ",\n {%sid%s:%s%s,%slabel%s:%s%s%s,%spattern%s:%s%s,%stype%s:%snumber%s}", kq, kq, sq, sq, kq, kq, sq, rd->name, sq, kq, kq, sq, sq, kq, kq, sq, sq); - } - } - if(!pc) { - buffer_sprintf(wb, ",\n {%sid%s:%s%s,%slabel%s:%s%s%s,%spattern%s:%s%s,%stype%s:%snumber%s}", kq, kq, sq, sq, kq, kq, sq, "no data", sq, kq, kq, sq, sq, kq, kq, sq, sq); - } - - // print the begin of row data - buffer_sprintf(wb, "\n ],\n %srows%s:\n [\n", kq, kq); - - - // ------------------------------------------------------------------------- - // the main loop - - int annotate_reset = 0; - int annotation_count = 0; - - long t = rrdset_time2slot(st, before), - stop_at_t = rrdset_time2slot(st, after), - stop_now = 0; - - t -= t % group; - - time_t now = rrdset_slot2time(st, t), - dt = st->update_every; - - long count = 0, printed = 0, group_count = 0; - last_timestamp = 0; - - if(st->debug) debug(D_RRD_STATS, "%s: REQUEST after:%lu before:%lu, points:%d, group:%d, CHART cur:%ld first: %lu last:%lu, CALC start_t:%ld, stop_t:%ld" - , st->id - , after - , before - , points - , group - , st->current_entry - , rrdset_first_entry_t(st) - , rrdset_last_entry_t(st) - , t - , stop_at_t - ); - - long counter = 0; - for(; !stop_now ; now -= dt, t--, counter++) { - if(t < 0) t = st->entries - 1; - if(t == stop_at_t) stop_now = counter; - - int print_this = 0; - - if(st->debug) debug(D_RRD_STATS, "%s t = %ld, count = %ld, group_count = %ld, printed = %ld, now = %lu, %s %s" - , st->id - , t - , count + 1 - , group_count + 1 - , printed - , now - , (group_count + 1 == group)?"PRINT":" - " - , (now >= after && now <= before)?"RANGE":" - " - ); - - - // make sure we return data in the proper time range - if(now > before) continue; - if(now < after) break; - - //if(rrdset_slot2time(st, t) != now) - // error("%s: slot=%ld, now=%ld, slot2time=%ld, diff=%ld, last_entry_t=%ld, rrdset_last_slot=%ld", st->id, t, now, rrdset_slot2time(st,t), now - rrdset_slot2time(st,t), rrdset_last_entry_t(st), rrdset_last_slot(st)); - - count++; - group_count++; - - // check if we have to print this now - if(group_count == group) { - if(printed >= points) { - // debug(D_RRD_STATS, "Already printed all rows. Stopping."); - break; - } - - // generate the local date time - struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); - if(!tm) { error("localtime() failed."); continue; } - if(now > last_timestamp) last_timestamp = now; - - if(printed) buffer_strcat(wb, "]},\n"); - buffer_strcat(wb, pre_date); - buffer_jsdate(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - buffer_strcat(wb, post_date); - - print_this = 1; - } - - // do the calculations - for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { - storage_number n = rd->values[t]; - calculated_number value = unpack_storage_number(n); - - if(!does_storage_number_exist(n)) { - value = 0.0; - found_non_existing[c]++; - } - if(did_storage_number_reset(n)) annotate_reset = 1; - - switch(group_method) { - case GROUP_MAX: - if(abs(value) > abs(group_values[c])) group_values[c] = value; - break; - - case GROUP_SUM: - group_values[c] += value; - break; - - default: - case GROUP_AVERAGE: - group_values[c] += value; - if(print_this) group_values[c] /= ( group_count - found_non_existing[c] ); - break; - } - } - - if(print_this) { - if(annotate_reset) { - annotation_count++; - buffer_strcat(wb, overflow_annotation); - annotate_reset = 0; - } - else - buffer_strcat(wb, normal_annotation); - - pc = 0; - for(c = 0 ; c < dimensions ; c++) { - if(found_non_existing[c] == group_count) { - // all entries are non-existing - pc++; - buffer_strcat(wb, pre_value); - buffer_strcat(wb, "null"); - buffer_strcat(wb, post_value); - } - else if(!print_hidden[c]) { - pc++; - buffer_strcat(wb, pre_value); - buffer_rrd_value(wb, group_values[c]); - buffer_strcat(wb, post_value); - - if(group_values[c]) found_non_zero[c]++; - } - - // reset them for the next loop - group_values[c] = 0; - found_non_existing[c] = 0; - } - - // if all dimensions are hidden, print a null - if(!pc) { - buffer_strcat(wb, pre_value); - buffer_strcat(wb, "null"); - buffer_strcat(wb, post_value); - } - - printed++; - group_count = 0; - } - } - - if(printed) buffer_strcat(wb, "]}"); - buffer_strcat(wb, "\n ]\n}\n"); - - if(only_non_zero && max_loop > 1) { - int changed = 0; - for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { - group_values[c] = 0; - found_non_existing[c] = 0; - - if(!print_hidden[c] && !found_non_zero[c]) { - changed = 1; - print_hidden[c] = 1; - } - } - - if(changed) buffer_flush(wb); - else break; - } - else break; - - } // max_loop - - debug(D_RRD_STATS, "RRD_STATS_JSON: %s total %ld bytes", st->name, wb->len); - - pthread_rwlock_unlock(&st->rwlock); - return last_timestamp; + // error("OLD: points=%d after=%d before=%d group=%d, duration=%d", entries_to_show, before - (st->update_every * group * entries_to_show), before, group, before - after + 1); + // rrd2array(st, entries_to_show, before - (st->update_every * group * entries_to_show), before, group_method, only_non_zero); + // rrd2rrdr(st, entries_to_show, before - (st->update_every * group * entries_to_show), before, group_method); + + // ------------------------------------------------------------------------- + // remove dimensions that contain only zeros + + int max_loop = 1; + if(only_non_zero) max_loop = 2; + + for(; max_loop ; max_loop--) { + + // ------------------------------------------------------------------------- + // print the JSON header + + buffer_sprintf(wb, "{\n %scols%s:\n [\n", 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); + buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotation%s}},\n", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); + buffer_sprintf(wb, " {%sid%s:%s%s,%slabel%s:%s%s,%spattern%s:%s%s,%stype%s:%sstring%s,%sp%s:{%srole%s:%sannotationText%s}}", kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, sq, sq, kq, kq, kq, kq, sq, sq); + + // print the header for each dimension + // and update the print_hidden array for the dimensions that should be hidden + int pc = 0; + for( rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { + if(!print_hidden[c]) { + pc++; + buffer_sprintf(wb, ",\n {%sid%s:%s%s,%slabel%s:%s%s%s,%spattern%s:%s%s,%stype%s:%snumber%s}", kq, kq, sq, sq, kq, kq, sq, rd->name, sq, kq, kq, sq, sq, kq, kq, sq, sq); + } + } + if(!pc) { + buffer_sprintf(wb, ",\n {%sid%s:%s%s,%slabel%s:%s%s%s,%spattern%s:%s%s,%stype%s:%snumber%s}", kq, kq, sq, sq, kq, kq, sq, "no data", sq, kq, kq, sq, sq, kq, kq, sq, sq); + } + + // print the begin of row data + buffer_sprintf(wb, "\n ],\n %srows%s:\n [\n", kq, kq); + + + // ------------------------------------------------------------------------- + // the main loop + + int annotate_reset = 0; + int annotation_count = 0; + + long t = rrdset_time2slot(st, before), + stop_at_t = rrdset_time2slot(st, after), + stop_now = 0; + + t -= t % group; + + time_t now = rrdset_slot2time(st, t), + dt = st->update_every; + + long count = 0, printed = 0, group_count = 0; + last_timestamp = 0; + + if(st->debug) debug(D_RRD_STATS, "%s: REQUEST after:%u before:%u, points:%ld, group:%ld, CHART cur:%ld first: %u last:%u, CALC start_t:%ld, stop_t:%ld" + , st->id + , (uint32_t)after + , (uint32_t)before + , points + , group + , st->current_entry + , (uint32_t)rrdset_first_entry_t(st) + , (uint32_t)rrdset_last_entry_t(st) + , t + , stop_at_t + ); + + long counter = 0; + for(; !stop_now ; now -= dt, t--, counter++) { + if(t < 0) t = st->entries - 1; + if(t == stop_at_t) stop_now = counter; + + int print_this = 0; + + if(st->debug) debug(D_RRD_STATS, "%s t = %ld, count = %ld, group_count = %ld, printed = %ld, now = %ld, %s %s" + , st->id + , t + , count + 1 + , group_count + 1 + , printed + , now + , (group_count + 1 == group)?"PRINT":" - " + , (now >= after && now <= before)?"RANGE":" - " + ); + + + // make sure we return data in the proper time range + if(now > before) continue; + if(now < after) break; + + //if(rrdset_slot2time(st, t) != now) + // error("%s: slot=%ld, now=%ld, slot2time=%ld, diff=%ld, last_entry_t=%ld, rrdset_last_slot=%ld", st->id, t, now, rrdset_slot2time(st,t), now - rrdset_slot2time(st,t), rrdset_last_entry_t(st), rrdset_last_slot(st)); + + count++; + group_count++; + + // check if we have to print this now + if(group_count == group) { + if(printed >= points) { + // debug(D_RRD_STATS, "Already printed all rows. Stopping."); + break; + } + + // generate the local date time + struct tm tmbuf, *tm = localtime_r(&now, &tmbuf); + if(!tm) { error("localtime() failed."); continue; } + if(now > last_timestamp) last_timestamp = now; + + if(printed) buffer_strcat(wb, "]},\n"); + buffer_strcat(wb, pre_date); + buffer_jsdate(wb, tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + buffer_strcat(wb, post_date); + + print_this = 1; + } + + // do the calculations + for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { + storage_number n = rd->values[t]; + calculated_number value = unpack_storage_number(n); + + if(!does_storage_number_exist(n)) { + value = 0.0; + found_non_existing[c]++; + } + if(did_storage_number_reset(n)) annotate_reset = 1; + + switch(group_method) { + case GROUP_MAX: + if(abs(value) > abs(group_values[c])) group_values[c] = value; + break; + + case GROUP_SUM: + group_values[c] += value; + break; + + default: + case GROUP_AVERAGE: + group_values[c] += value; + if(print_this) group_values[c] /= ( group_count - found_non_existing[c] ); + break; + } + } + + if(print_this) { + if(annotate_reset) { + annotation_count++; + buffer_strcat(wb, overflow_annotation); + annotate_reset = 0; + } + else + buffer_strcat(wb, normal_annotation); + + pc = 0; + for(c = 0 ; c < dimensions ; c++) { + if(found_non_existing[c] == group_count) { + // all entries are non-existing + pc++; + buffer_strcat(wb, pre_value); + buffer_strcat(wb, "null"); + buffer_strcat(wb, post_value); + } + else if(!print_hidden[c]) { + pc++; + buffer_strcat(wb, pre_value); + buffer_rrd_value(wb, group_values[c]); + buffer_strcat(wb, post_value); + + if(group_values[c]) found_non_zero[c]++; + } + + // reset them for the next loop + group_values[c] = 0; + found_non_existing[c] = 0; + } + + // if all dimensions are hidden, print a null + if(!pc) { + buffer_strcat(wb, pre_value); + buffer_strcat(wb, "null"); + buffer_strcat(wb, post_value); + } + + printed++; + group_count = 0; + } + } + + if(printed) buffer_strcat(wb, "]}"); + buffer_strcat(wb, "\n ]\n}\n"); + + if(only_non_zero && max_loop > 1) { + int changed = 0; + for(rd = st->dimensions, c = 0 ; rd && c < dimensions ; rd = rd->next, c++) { + group_values[c] = 0; + found_non_existing[c] = 0; + + if(!print_hidden[c] && !found_non_zero[c]) { + changed = 1; + print_hidden[c] = 1; + } + } + + if(changed) buffer_flush(wb); + else break; + } + else break; + + } // max_loop + + debug(D_RRD_STATS, "RRD_STATS_JSON: %s total %lu bytes", st->name, wb->len); + + pthread_rwlock_unlock(&st->rwlock); + return last_timestamp; } diff --git a/src/rrd2json.h b/src/rrd2json.h index d1f850d4a..e3e5cb690 100644 --- a/src/rrd2json.h +++ b/src/rrd2json.h @@ -1,8 +1,3 @@ -#include <time.h> - -#include "web_buffer.h" -#include "rrd.h" - #ifndef NETDATA_RRD2JSON_H #define NETDATA_RRD2JSON_H 1 @@ -35,22 +30,26 @@ extern char *hostname; #define DATASOURCE_FORMAT_SSV_COMMA "ssvcomma" #define DATASOURCE_FORMAT_CSV_JSON_ARRAY "csvjsonarray" -#define GROUP_AVERAGE 0 -#define GROUP_MAX 1 -#define GROUP_SUM 2 +#define GROUP_UNDEFINED 0 +#define GROUP_AVERAGE 1 +#define GROUP_MIN 2 +#define GROUP_MAX 3 +#define GROUP_SUM 4 +#define GROUP_INCREMENTAL_SUM 5 -#define RRDR_OPTION_NONZERO 0x00000001 // don't output dimensions will just zero values -#define RRDR_OPTION_REVERSED 0x00000002 // output the rows in reverse order (oldest to newest) -#define RRDR_OPTION_ABSOLUTE 0x00000004 // values positive, for DATASOURCE_SSV before summing -#define RRDR_OPTION_MIN2MAX 0x00000008 // for DATASOURCE_SSV, out max - min, instead of sum -#define RRDR_OPTION_SECONDS 0x00000010 // output seconds, instead of dates -#define RRDR_OPTION_MILLISECONDS 0x00000020 // output milliseconds, instead of dates -#define RRDR_OPTION_NULL2ZERO 0x00000040 // do not show nulls, convert them to zeros -#define RRDR_OPTION_OBJECTSROWS 0x00000080 // each row of values should be an object, not an array -#define RRDR_OPTION_GOOGLE_JSON 0x00000100 // comply with google JSON/JSONP specs -#define RRDR_OPTION_JSON_WRAP 0x00000200 // wrap the response in a JSON header with info about the result -#define RRDR_OPTION_LABEL_QUOTES 0x00000400 // in CSV output, wrap header labels in double quotes -#define RRDR_OPTION_PERCENTAGE 0x00000800 // give values as percentage of total +#define RRDR_OPTION_NONZERO 0x00000001 // don't output dimensions will just zero values +#define RRDR_OPTION_REVERSED 0x00000002 // output the rows in reverse order (oldest to newest) +#define RRDR_OPTION_ABSOLUTE 0x00000004 // values positive, for DATASOURCE_SSV before summing +#define RRDR_OPTION_MIN2MAX 0x00000008 // when adding dimensions, use max - min, instead of sum +#define RRDR_OPTION_SECONDS 0x00000010 // output seconds, instead of dates +#define RRDR_OPTION_MILLISECONDS 0x00000020 // output milliseconds, instead of dates +#define RRDR_OPTION_NULL2ZERO 0x00000040 // do not show nulls, convert them to zeros +#define RRDR_OPTION_OBJECTSROWS 0x00000080 // each row of values should be an object, not an array +#define RRDR_OPTION_GOOGLE_JSON 0x00000100 // comply with google JSON/JSONP specs +#define RRDR_OPTION_JSON_WRAP 0x00000200 // wrap the response in a JSON header with info about the result +#define RRDR_OPTION_LABEL_QUOTES 0x00000400 // in CSV output, wrap header labels in double quotes +#define RRDR_OPTION_PERCENTAGE 0x00000800 // give values as percentage of total +#define RRDR_OPTION_NOT_ALIGNED 0x00001000 // do not align charts for persistant timeframes extern void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb); extern void rrd_stats_api_v1_charts(BUFFER *wb); @@ -64,6 +63,6 @@ extern void rrd_stats_all_json(BUFFER *wb); extern time_t rrd_stats_json(int type, RRDSET *st, BUFFER *wb, long entries_to_show, long group, int group_method, time_t after, time_t before, int only_non_zero); extern int rrd2format(RRDSET *st, BUFFER *out, BUFFER *dimensions, uint32_t format, long points, long long after, long long before, int group_method, uint32_t options, time_t *latest_timestamp); - +extern int rrd2value(RRDSET *st, BUFFER *wb, calculated_number *n, const char *dimensions, long points, long long after, long long before, int group_method, uint32_t options, time_t *db_before, time_t *db_after, int *value_is_null); #endif /* NETDATA_RRD2JSON_H */ diff --git a/src/storage_number.c b/src/storage_number.c index b5c5f4067..27fe5f2c7 100644 --- a/src/storage_number.c +++ b/src/storage_number.c @@ -1,206 +1,168 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#ifdef STORAGE_WITH_MATH -#include <math.h> -#endif - #include "common.h" -#include "log.h" -#include "storage_number.h" -#if __GNUC__ -#if __x86_64__ || __ppc64__ -#define ENVIRONMENT64 -#else -#define ENVIRONMENT32 -#endif -#endif +extern char *print_number_lu_r(char *str, unsigned long uvalue); +extern char *print_number_llu_r(char *str, unsigned long long uvalue); storage_number pack_storage_number(calculated_number value, uint32_t flags) { - // bit 32 = sign 0:positive, 1:negative - // bit 31 = 0:divide, 1:multiply - // bit 30, 29, 28 = (multiplier or divider) 0-6 (7 total) - // bit 27, 26, 25 flags - // bit 24 to bit 1 = the value - - storage_number r = get_storage_number_flags(flags); - if(!value) return r; - - int m = 0; - calculated_number n = value; - - // if the value is negative - // add the sign bit and make it positive - if(n < 0) { - r += (1 << 31); // the sign bit 32 - n = -n; - } - - // make its integer part fit in 0x00ffffff - // by dividing it by 10 up to 7 times - // and increasing the multiplier - while(m < 7 && n > (calculated_number)0x00ffffff) { - n /= 10; - m++; - } - - if(m) { - // the value was too big and we divided it - // so we add a multiplier to unpack it - r += (1 << 30) + (m << 27); // the multiplier m - - if(n > (calculated_number)0x00ffffff) { - error("Number " CALCULATED_NUMBER_FORMAT " is too big.", value); - r += 0x00ffffff; - return r; - } - } - else { - // 0x0019999e is the number that can be multiplied - // by 10 to give 0x00ffffff - // while the value is below 0x0019999e we can - // multiply it by 10, up to 7 times, increasing - // the multiplier - while(m < 7 && n < (calculated_number)0x0019999e) { - n *= 10; - m++; - } - - // the value was small enough and we multiplied it - // so we add a divider to unpack it - r += (0 << 30) + (m << 27); // the divider m - } + // bit 32 = sign 0:positive, 1:negative + // bit 31 = 0:divide, 1:multiply + // bit 30, 29, 28 = (multiplier or divider) 0-6 (7 total) + // bit 27, 26, 25 flags + // bit 24 to bit 1 = the value + + storage_number r = get_storage_number_flags(flags); + if(!value) return r; + + int m = 0; + calculated_number n = value; + + // if the value is negative + // add the sign bit and make it positive + if(n < 0) { + r += (1 << 31); // the sign bit 32 + n = -n; + } + + // make its integer part fit in 0x00ffffff + // by dividing it by 10 up to 7 times + // and increasing the multiplier + while(m < 7 && n > (calculated_number)0x00ffffff) { + n /= 10; + m++; + } + + if(m) { + // the value was too big and we divided it + // so we add a multiplier to unpack it + r += (1 << 30) + (m << 27); // the multiplier m + + if(n > (calculated_number)0x00ffffff) { + error("Number " CALCULATED_NUMBER_FORMAT " is too big.", value); + r += 0x00ffffff; + return r; + } + } + else { + // 0x0019999e is the number that can be multiplied + // by 10 to give 0x00ffffff + // while the value is below 0x0019999e we can + // multiply it by 10, up to 7 times, increasing + // the multiplier + while(m < 7 && n < (calculated_number)0x0019999e) { + n *= 10; + m++; + } + + // the value was small enough and we multiplied it + // so we add a divider to unpack it + r += (0 << 30) + (m << 27); // the divider m + } #ifdef STORAGE_WITH_MATH - // without this there are rounding problems - // example: 0.9 becomes 0.89 - r += lrint((double) n); + // without this there are rounding problems + // example: 0.9 becomes 0.89 + r += lrint((double) n); #else - r += (storage_number)n; + r += (storage_number)n; #endif - return r; + return r; } calculated_number unpack_storage_number(storage_number value) { - if(!value) return 0; - - int sign = 0, exp = 0; + if(!value) return 0; - value ^= get_storage_number_flags(value); + int sign = 0, exp = 0; - if(value & (1 << 31)) { - sign = 1; - value ^= 1 << 31; - } + value ^= get_storage_number_flags(value); - if(value & (1 << 30)) { - exp = 1; - value ^= 1 << 30; - } + if(value & (1 << 31)) { + sign = 1; + value ^= 1 << 31; + } - int mul = value >> 27; - value ^= mul << 27; + if(value & (1 << 30)) { + exp = 1; + value ^= 1 << 30; + } - calculated_number n = value; + int mul = value >> 27; + value ^= mul << 27; - // fprintf(stderr, "UNPACK: %08X, sign = %d, exp = %d, mul = %d, n = " CALCULATED_NUMBER_FORMAT "\n", value, sign, exp, mul, n); + calculated_number n = value; - while(mul > 0) { - if(exp) n *= 10; - else n /= 10; - mul--; - } + // fprintf(stderr, "UNPACK: %08X, sign = %d, exp = %d, mul = %d, n = " CALCULATED_NUMBER_FORMAT "\n", value, sign, exp, mul, n); - if(sign) n = -n; - return n; -} - -#ifdef ENVIRONMENT32 -// This trick seems to give an 80% speed increase in 32bit systems -// print_calculated_number_llu_r() will just print the digits up to the -// point the remaining value fits in 32 bits, and then calls -// print_calculated_number_lu_r() to print the rest with 32 bit arithmetic. - -static char *print_calculated_number_lu_r(char *str, unsigned long uvalue) { - char *wstr = str; + while(mul > 0) { + if(exp) n *= 10; + else n /= 10; + mul--; + } - // print each digit - do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10); - return wstr; + if(sign) n = -n; + return n; } -static char *print_calculated_number_llu_r(char *str, unsigned long long uvalue) { - char *wstr = str; - - // print each digit - 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; -} -#endif - int print_calculated_number(char *str, calculated_number value) { - char *wstr = str; + char *wstr = str; - int sign = (value < 0) ? 1 : 0; - if(sign) value = -value; + int sign = (value < 0) ? 1 : 0; + if(sign) value = -value; #ifdef STORAGE_WITH_MATH - // without llrint() there are rounding problems - // for example 0.9 becomes 0.89 - unsigned long long uvalue = (unsigned long long int) llrint(value * (calculated_number)100000); + // without llrint() there are rounding problems + // for example 0.9 becomes 0.89 + unsigned long long uvalue = (unsigned long long int) llrint(value * (calculated_number)100000); #else - unsigned long long uvalue = value * (calculated_number)100000; + unsigned long long uvalue = value * (calculated_number)100000; #endif #ifdef ENVIRONMENT32 - if(uvalue > (unsigned long long)0xffffffff) - wstr = print_calculated_number_llu_r(str, uvalue); - else - wstr = print_calculated_number_lu_r(str, uvalue); + if(uvalue > (unsigned long long)0xffffffff) + wstr = print_number_llu_r(str, uvalue); + else + wstr = print_number_lu_r(str, uvalue); #else - do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10); + do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10); #endif - // make sure we have 6 bytes at least - while((wstr - str) < 6) *wstr++ = '0'; + // make sure we have 6 bytes at least + while((wstr - str) < 6) *wstr++ = '0'; - // put the sign back - if(sign) *wstr++ = '-'; + // put the sign back + if(sign) *wstr++ = '-'; - // reverse it + // reverse it char *begin = str, *end = --wstr, aux; while (end > begin) aux = *end, *end-- = *begin, *begin++ = aux; - // wstr--; - // strreverse(str, wstr); - - // remove trailing zeros - int decimal = 5; - while(decimal > 0 && *wstr == '0') { - *wstr-- = '\0'; - decimal--; - } - - // terminate it, one position to the right - // to let space for a dot - wstr[2] = '\0'; - - // make space for the dot - int i; - for(i = 0; i < decimal ;i++) { - wstr[1] = wstr[0]; - wstr--; - } - - // put the dot - if(wstr[2] == '\0') { wstr[1] = '\0'; decimal--; } - else wstr[1] = '.'; - - // return the buffer length - return (int) ((wstr - str) + 2 + decimal ); + // wstr--; + // strreverse(str, wstr); + + // remove trailing zeros + int decimal = 5; + while(decimal > 0 && *wstr == '0') { + *wstr-- = '\0'; + decimal--; + } + + // terminate it, one position to the right + // to let space for a dot + wstr[2] = '\0'; + + // make space for the dot + int i; + for(i = 0; i < decimal ;i++) { + wstr[1] = wstr[0]; + wstr--; + } + + // put the dot + if(wstr[2] == '\0') { wstr[1] = '\0'; decimal--; } + else wstr[1] = '.'; + + // return the buffer length + return (int) ((wstr - str) + 2 + decimal ); } diff --git a/src/storage_number.h b/src/storage_number.h index f35e99ddd..74d24a322 100644 --- a/src/storage_number.h +++ b/src/storage_number.h @@ -14,19 +14,18 @@ typedef long double collected_number; #define COLLECTED_NUMBER_FORMAT "%0.7Lf" */ -typedef int32_t storage_number; -typedef uint32_t ustorage_number; -#define STORAGE_NUMBER_FORMAT "%d" - -#define SN_NOT_EXISTS (0x0 << 24) -#define SN_EXISTS (0x1 << 24) -#define SN_EXISTS_RESET (0x2 << 24) -#define SN_EXISTS_UNDEF1 (0x3 << 24) -#define SN_EXISTS_UNDEF2 (0x4 << 24) -#define SN_EXISTS_UNDEF3 (0x5 << 24) -#define SN_EXISTS_UNDEF4 (0x6 << 24) - -#define SN_FLAGS_MASK (~(0x6 << 24)) +typedef uint32_t storage_number; +#define STORAGE_NUMBER_FORMAT "%u" + +#define SN_NOT_EXISTS (0x0 << 24) +#define SN_EXISTS (0x1 << 24) +#define SN_EXISTS_RESET (0x2 << 24) +#define SN_EXISTS_UNDEF1 (0x3 << 24) +#define SN_EXISTS_UNDEF2 (0x4 << 24) +#define SN_EXISTS_UNDEF3 (0x5 << 24) +#define SN_EXISTS_UNDEF4 (0x6 << 24) + +#define SN_FLAGS_MASK (~(0x6 << 24)) // extract the flags #define get_storage_number_flags(value) ((((storage_number)value) & (1 << 24)) | (((storage_number)value) & (2 << 24)) | (((storage_number)value) & (4 << 24))) diff --git a/src/sys_fs_cgroup.c b/src/sys_fs_cgroup.c index 9f3d3f0fd..892f737c1 100644 --- a/src/sys_fs_cgroup.c +++ b/src/sys_fs_cgroup.c @@ -1,22 +1,4 @@ -#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 @@ -24,164 +6,187 @@ 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_devices = 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 char *cgroup_devices_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_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_super_option(root, "cgroup", "cpuacct"); + if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "cpuacct"); + if(!mi) { + error("Cannot find cgroup cpuacct mountinfo. Assuming default: /sys/fs/cgroup/cpuacct"); + 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_super_option(root, "cgroup", "blkio"); + if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "blkio"); + if(!mi) { + error("Cannot find cgroup blkio mountinfo. Assuming default: /sys/fs/cgroup/blkio"); + 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_super_option(root, "cgroup", "memory"); + if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "memory"); + if(!mi) { + error("Cannot find cgroup memory mountinfo. Assuming default: /sys/fs/cgroup/memory"); + 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); + + mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "devices"); + if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "devices"); + if(!mi) { + error("Cannot find cgroup devices mountinfo. Assuming default: /sys/fs/cgroup/devices"); + s = "/sys/fs/cgroup/devices"; + } + else s = mi->mount_point; + snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, s); + cgroup_devices_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/devices", 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; + int updated; - char *filename; + char *filename; - unsigned long long Read; - unsigned long long Write; + unsigned long long Read; + unsigned long long Write; /* - unsigned long long Sync; - unsigned long long Async; - unsigned long long Total; + 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; + 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; + 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; + int updated; - char *filename; + char *filename; - unsigned long long user; - unsigned long long system; + unsigned long long user; + unsigned long long system; }; // https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt struct cpuacct_usage { - int updated; + int updated; - char *filename; + char *filename; - unsigned int cpus; - unsigned long long *cpu_percpu; + unsigned int cpus; + unsigned long long *cpu_percpu; }; struct cgroup { - int available; // found in the filesystem - int enabled; // enabled in the config + int available; // found in the filesystem + int enabled; // enabled in the config + + char *id; + uint32_t hash; - char *id; - uint32_t hash; + char *chart_id; + uint32_t hash_chart; - char *chart_id; - char *chart_title; + char *chart_title; - struct cpuacct_stat cpuacct_stat; - struct cpuacct_usage cpuacct_usage; + struct cpuacct_stat cpuacct_stat; + struct cpuacct_usage cpuacct_usage; - struct memory memory; + struct memory memory; - struct blkio io_service_bytes; // bytes - struct blkio io_serviced; // operations + 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 throttle_io_service_bytes; // bytes + struct blkio throttle_io_serviced; // operations - struct blkio io_merged; // operations - struct blkio io_queued; // operations + struct blkio io_merged; // operations + struct blkio io_queued; // operations - struct cgroup *next; + struct cgroup *next; } *cgroup_root = NULL; @@ -189,393 +194,389 @@ struct cgroup { // read values from /sys void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) { - static procfile *ff = NULL; + static procfile *ff = NULL; - static uint32_t user_hash = 0; - static uint32_t system_hash = 0; + 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"); - } + 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; + 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; + ff = procfile_readall(ff); + if(!ff) return; - unsigned long i, lines = procfile_lines(ff); + unsigned long i, lines = procfile_lines(ff); - if(lines < 1) { - error("File '%s' should have 1+ lines.", cp->filename); - return; - } + 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); + 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); + 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); - } + else if(hash == system_hash && !strcmp(s, "system")) + cp->system = strtoull(procfile_lineword(ff, i, 1), NULL, 10); + } - cp->updated = 1; + cp->updated = 1; - // fprintf(stderr, "READ '%s': user: %llu, system: %llu\n", cp->filename, cp->user, cp->system); - } + // 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; - } + 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 %u.", 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) { + freez(ca->cpu_percpu); + ca->cpu_percpu = mallocz(sizeof(unsigned long long) * i); + ca->cpus = (unsigned int)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 procfile *ff = NULL; - static uint32_t Read_hash = 0; - static uint32_t Write_hash = 0; + 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; + 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"); + 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"); + 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; + 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; + ff = procfile_readall(ff); + if(!ff) return; - unsigned long i, lines = procfile_lines(ff); + unsigned long i, lines = procfile_lines(ff); - if(lines < 1) { - error("File '%s' should have 1+ lines.", io->filename); - return; - } + if(lines < 1) { + error("File '%s' should have 1+ lines.", io->filename); + return; + } - io->Read = 0; - io->Write = 0; + io->Read = 0; + io->Write = 0; /* - io->Sync = 0; - io->Async = 0; - io->Total = 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); + 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); + 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 == 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 == 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 == 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); + 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); - } + 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 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; + 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"); + 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"); + 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; + 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; + ff = procfile_readall(ff); + if(!ff) return; - unsigned long i, lines = procfile_lines(ff); + unsigned long i, lines = procfile_lines(ff); - if(lines < 1) { - error("File '%s' should have 1+ lines.", mem->filename); - return; - } + 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); + 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); + 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_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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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); + 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); + // 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; - } + 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); + 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"); + debug(D_CGROUP, "reading metrics for all cgroups"); - struct cgroup *cg; + struct cgroup *cg; - for(cg = root; cg ; cg = cg->next) - if(cg->enabled && cg->available) - cgroup_read(cg); + for(cg = root; cg ; cg = cg->next) + if(cg->enabled && cg->available) + cgroup_read(cg); } // ---------------------------------------------------------------------------- @@ -584,177 +585,189 @@ void read_all_cgroups(struct cgroup *root) { #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); + 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); + + freez(cg->chart_title); + cg->chart_title = strdupz(s); + netdata_fix_chart_name(cg->chart_title); + + freez(cg->chart_id); + cg->chart_id = strdupz(s); + netdata_fix_chart_id(cg->chart_id); + cg->hash_chart = simple_hash(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; + 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, "init.scope") || + !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 = callocz(1, sizeof(struct cgroup)); + + cg->id = strdupz(id); + cg->hash = simple_hash(cg->id); + + cg->chart_id = strdupz(chart_id); + netdata_fix_chart_id(cg->chart_id); + cg->hash_chart = simple_hash(cg->chart_id); + + cg->chart_title = strdupz(chart_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); + + debug(D_CGROUP, "adding cgroup '%s' with chart id '%s'", id, chart_id); + + char option[FILENAME_MAX + 1]; + snprintfz(option, FILENAME_MAX, "enable cgroup %s", cg->chart_title); + cg->enabled = config_get_boolean("plugin:cgroups", option, def); + + if(cg->enabled) { + struct cgroup *t; + for (t = cgroup_root; t; t = t->next) { + if (t != cg && t->enabled && t->hash_chart == cg->hash_chart && !strcmp(t->chart_id, cg->chart_id)) { + if (!strncmp(t->chart_id, "/system.slice/", 14) && !strncmp(cg->chart_id, "/init.scope/system.slice/", 25)) { + error("Control group with chart id '%s' already exists with id '%s' and is enabled. Swapping them by enabling cgroup with id '%s' and disabling cgroup with id '%s'.", + cg->chart_id, t->id, cg->id, t->id); + t->enabled = 0; + } else { + error("Control group with chart id '%s' already exists with id '%s' and is enabled. Disabling cgroup with id '%s'.", + cg->chart_id, t->id, cg->id); + cg->enabled = 0; + } + + break; + } + } + } + + 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--; + 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"); + + freez(cg->cpuacct_usage.cpu_percpu); + + freez(cg->cpuacct_stat.filename); + freez(cg->cpuacct_usage.filename); + freez(cg->memory.filename); + freez(cg->io_service_bytes.filename); + freez(cg->io_serviced.filename); + freez(cg->throttle_io_service_bytes.filename); + freez(cg->throttle_io_serviced.filename); + freez(cg->io_merged.filename); + freez(cg->io_queued.filename); + + freez(cg->id); + freez(cg->chart_id); + freez(cg->chart_title); + freez(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); + debug(D_CGROUP, "searching for cgroup '%s'", id); - uint32_t hash = simple_hash(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; - } + 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; + debug(D_CGROUP, "cgroup_find('%s') %s", id, (cg)?"found":"not found"); + return cg; } // ---------------------------------------------------------------------------- @@ -762,198 +775,252 @@ struct cgroup *cgroup_find(const char *id) { // 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; + 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); +int find_dir_in_subdirs(const char *base, const char *this, void (*callback)(const char *)) { + debug(D_CGROUP, "searching for directories in '%s'", base); + + int ret = -1; + 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) { + error("Cannot read cgroups directory '%s'", base); + return ret; + } + ret = 1; + + 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 = mallocz(dirlen + strlen(de->d_name) + 2); + strcpy(s, this); + strcat(s, "/"); + strcat(s, de->d_name); + int ret2 = find_dir_in_subdirs(base, s, callback); + if(ret2 > 0) ret += ret2; + freez(s); + } + } + } + + closedir(dir); + return ret; } void mark_all_cgroups_as_not_available() { - debug(D_CGROUP, "marking all cgroups as not available"); + debug(D_CGROUP, "marking all cgroups as not available"); - struct cgroup *cg; + struct cgroup *cg; - // mark all as not available - for(cg = cgroup_root; cg ; cg = cg->next) - cg->available = 0; + // 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; - } - } + 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; + debug(D_CGROUP, "searching for cgroups"); + + mark_all_cgroups_as_not_available(); + + if(cgroup_enable_cpuacct_stat || cgroup_enable_cpuacct_usage) { + if (find_dir_in_subdirs(cgroup_cpuacct_base, NULL, found_subdir_in_dir) == -1) { + cgroup_enable_cpuacct_stat = cgroup_enable_cpuacct_usage = 0; + error("disabled cgroup cpu statistics."); + } + } + + if(cgroup_enable_blkio) { + if (find_dir_in_subdirs(cgroup_blkio_base, NULL, found_subdir_in_dir) == -1) { + cgroup_enable_blkio = 0; + error("disabled cgroup blkio statistics."); + } + } + + if(cgroup_enable_memory) { + if(find_dir_in_subdirs(cgroup_memory_base, NULL, found_subdir_in_dir) == -1) { + cgroup_enable_memory = 0; + error("disabled cgroup memory statistics."); + } + } + + if(cgroup_enable_devices) { + if(find_dir_in_subdirs(cgroup_devices_base, NULL, found_subdir_in_dir) == -1) { + cgroup_enable_devices = 0; + error("disabled cgroup devices statistics."); + } + } + + // remove any non-existing cgroups + cleanup_all_cgroups(); + + struct cgroup *cg; + struct stat buf; + 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); + if(stat(filename, &buf) != -1) { + cg->cpuacct_stat.filename = strdupz(filename); + debug(D_CGROUP, "cpuacct.stat filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_stat.filename); + } + else debug(D_CGROUP, "cpuacct.stat file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + if(cgroup_enable_cpuacct_usage && !cg->cpuacct_usage.filename) { + snprintfz(filename, FILENAME_MAX, "%s%s/cpuacct.usage_percpu", cgroup_cpuacct_base, cg->id); + if(stat(filename, &buf) != -1) { + cg->cpuacct_usage.filename = strdupz(filename); + debug(D_CGROUP, "cpuacct.usage_percpu filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_usage.filename); + } + else debug(D_CGROUP, "cpuacct.usage_percpu file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + if(cgroup_enable_memory && !cg->memory.filename) { + snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id); + if(stat(filename, &buf) != -1) { + cg->memory.filename = strdupz(filename); + debug(D_CGROUP, "memory.stat filename for cgroup '%s': '%s'", cg->id, cg->memory.filename); + } + else debug(D_CGROUP, "memory.stat file for cgroup '%s': '%s' does not exist.", cg->id, 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); + if(stat(filename, &buf) != -1) { + cg->io_service_bytes.filename = strdupz(filename); + debug(D_CGROUP, "io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->io_service_bytes.filename); + } + else debug(D_CGROUP, "io_service_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + if(!cg->io_serviced.filename) { + snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id); + if(stat(filename, &buf) != -1) { + cg->io_serviced.filename = strdupz(filename); + debug(D_CGROUP, "io_serviced filename for cgroup '%s': '%s'", cg->id, cg->io_serviced.filename); + } + else debug(D_CGROUP, "io_serviced file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + if(!cg->throttle_io_service_bytes.filename) { + snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id); + if(stat(filename, &buf) != -1) { + cg->throttle_io_service_bytes.filename = strdupz(filename); + debug(D_CGROUP, "throttle_io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_service_bytes.filename); + } + else debug(D_CGROUP, "throttle_io_service_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + if(!cg->throttle_io_serviced.filename) { + snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id); + if(stat(filename, &buf) != -1) { + cg->throttle_io_serviced.filename = strdupz(filename); + debug(D_CGROUP, "throttle_io_serviced filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_serviced.filename); + } + else debug(D_CGROUP, "throttle_io_serviced file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + if(!cg->io_merged.filename) { + snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id); + if(stat(filename, &buf) != -1) { + cg->io_merged.filename = strdupz(filename); + debug(D_CGROUP, "io_merged filename for cgroup '%s': '%s'", cg->id, cg->io_merged.filename); + } + else debug(D_CGROUP, "io_merged file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + if(!cg->io_queued.filename) { + snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id); + if(stat(filename, &buf) != -1) { + cg->io_queued.filename = strdupz(filename); + debug(D_CGROUP, "io_queued filename for cgroup '%s': '%s'", cg->id, cg->io_queued.filename); + } + else debug(D_CGROUP, "io_queued file for cgroup '%s': '%s' does not exist.", cg->id, filename); + } + } + } + + debug(D_CGROUP, "done searching for cgroups"); + return; } // ---------------------------------------------------------------------------- @@ -962,349 +1029,349 @@ void find_all_cgroups() { #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"); + 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%u", i); + rrddim_add(st, id, NULL, 100, 1000000000, RRDDIM_INCREMENTAL); + } + } + else rrdset_next(st); + + for(i = 0; i < cg->cpuacct_usage.cpus ;i++) { + snprintfz(id, CHART_TITLE_MAX, "cpu%u", 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); + (void)dt; - if(dt) {}; + static int cgroup_global_config_read = 0; + static time_t last_run = 0; + time_t now = time(NULL); - if(unlikely(!cgroup_global_config_read)) { - read_cgroup_plugin_configuration(); - cgroup_global_config_read = 1; - } + 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; - } + 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); + read_all_cgroups(cgroup_root); + update_cgroup_charts(update_every); - return 0; + return 0; } void *cgroups_main(void *ptr) { - if(ptr) { ; } + if(ptr) { ; } - info("CGROUP Plugin thread created with task id %d", gettid()); + 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_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."); + if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) + error("Cannot set pthread cancel state to ENABLE."); - struct rusage thread; + 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); + // 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; + // 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; + // 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; + RRDSET *stcpu_thread = NULL; - for(;1;) { - if(unlikely(netdata_exit)) break; + for(;1;) { + if(unlikely(netdata_exit)) break; - // delay until it is our time to run - while((sunow = timems()) < sunext) - usleep((useconds_t)(sunext - sunow)); + // delay until it is our time to run + while((sunow = time_usec()) < sunext) + sleep_usec(sunext - sunow); - // find the next time we need to run - while(timems() > sunext) - sunext += rrd_update_every * 1000000ULL; + // find the next time we need to run + while(time_usec() > sunext) + sunext += rrd_update_every * 1000000ULL; - if(unlikely(netdata_exit)) break; + if(unlikely(netdata_exit)) break; - // BEGIN -- the job to be done + // 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; + if(!vdo_sys_fs_cgroup) { + debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_fs_cgroup()."); + sunow = time_usec(); + 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 + // END -- the job is done - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - if(!vdo_cpu_netdata) { - getrusage(RUSAGE_THREAD, &thread); + 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); + 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_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); - } - } + 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; + pthread_exit(NULL); + return NULL; } diff --git a/src/sys_kernel_mm_ksm.c b/src/sys_kernel_mm_ksm.c index 928ac8c62..8c51be1df 100644 --- a/src/sys_kernel_mm_ksm.c +++ b/src/sys_kernel_mm_ksm.c @@ -1,21 +1,9 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "procfile.h" -#include "rrd.h" -#include "plugin_proc.h" -typedef struct name_value { - char filename[FILENAME_MAX + 1]; - unsigned long long value; -} NAME_VALUE; +typedef struct ksm_name_value { + char filename[FILENAME_MAX + 1]; + unsigned long long value; +} KSM_NAME_VALUE; #define PAGES_SHARED 0 #define PAGES_SHARING 1 @@ -23,128 +11,128 @@ typedef struct name_value { #define PAGES_VOLATILE 3 #define PAGES_TO_SCAN 4 -NAME_VALUE values[] = { - [PAGES_SHARED] = { "/sys/kernel/mm/ksm/pages_shared", 0ULL }, - [PAGES_SHARING] = { "/sys/kernel/mm/ksm/pages_sharing", 0ULL }, - [PAGES_UNSHARED] = { "/sys/kernel/mm/ksm/pages_unshared", 0ULL }, - [PAGES_VOLATILE] = { "/sys/kernel/mm/ksm/pages_volatile", 0ULL }, - [PAGES_TO_SCAN] = { "/sys/kernel/mm/ksm/pages_to_scan", 0ULL }, +KSM_NAME_VALUE values[] = { + [PAGES_SHARED] = { "/sys/kernel/mm/ksm/pages_shared", 0ULL }, + [PAGES_SHARING] = { "/sys/kernel/mm/ksm/pages_sharing", 0ULL }, + [PAGES_UNSHARED] = { "/sys/kernel/mm/ksm/pages_unshared", 0ULL }, + [PAGES_VOLATILE] = { "/sys/kernel/mm/ksm/pages_volatile", 0ULL }, + [PAGES_TO_SCAN] = { "/sys/kernel/mm/ksm/pages_to_scan", 0ULL }, }; int do_sys_kernel_mm_ksm(int update_every, unsigned long long dt) { - static procfile *ff_pages_shared = NULL, *ff_pages_sharing = NULL, *ff_pages_unshared = NULL, *ff_pages_volatile = NULL, *ff_pages_to_scan = NULL; - static long page_size = -1; + static procfile *ff_pages_shared = NULL, *ff_pages_sharing = NULL, *ff_pages_unshared = NULL, *ff_pages_volatile = NULL, *ff_pages_to_scan = NULL; + static long page_size = -1; - if(dt) {}; - - if(page_size == -1) - page_size = sysconf(_SC_PAGESIZE); - - if(!ff_pages_shared) { - 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(dt) {}; + + if(page_size == -1) + page_size = sysconf(_SC_PAGESIZE); + + if(!ff_pages_shared) { + 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) { - 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_sharing) { + 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) { - 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_unshared) { + 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) { - 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_volatile) { + 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) { - 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); - } + if(!ff_pages_to_scan) { + 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); + } - if(!ff_pages_shared || !ff_pages_sharing || !ff_pages_unshared || !ff_pages_volatile || !ff_pages_to_scan) return 1; + if(!ff_pages_shared || !ff_pages_sharing || !ff_pages_unshared || !ff_pages_volatile || !ff_pages_to_scan) return 1; - unsigned long long pages_shared = 0, pages_sharing = 0, pages_unshared = 0, pages_volatile = 0, pages_to_scan = 0, offered = 0, saved = 0; + unsigned long long pages_shared = 0, pages_sharing = 0, pages_unshared = 0, pages_volatile = 0, pages_to_scan = 0, offered = 0, saved = 0; - ff_pages_shared = procfile_readall(ff_pages_shared); - if(!ff_pages_shared) return 0; // we return 0, so that we will retry to open it next time - pages_shared = strtoull(procfile_lineword(ff_pages_shared, 0, 0), NULL, 10); + ff_pages_shared = procfile_readall(ff_pages_shared); + if(!ff_pages_shared) return 0; // we return 0, so that we will retry to open it next time + pages_shared = strtoull(procfile_lineword(ff_pages_shared, 0, 0), NULL, 10); - ff_pages_sharing = procfile_readall(ff_pages_sharing); - if(!ff_pages_sharing) return 0; // we return 0, so that we will retry to open it next time - pages_sharing = strtoull(procfile_lineword(ff_pages_sharing, 0, 0), NULL, 10); + ff_pages_sharing = procfile_readall(ff_pages_sharing); + if(!ff_pages_sharing) return 0; // we return 0, so that we will retry to open it next time + pages_sharing = strtoull(procfile_lineword(ff_pages_sharing, 0, 0), NULL, 10); - ff_pages_unshared = procfile_readall(ff_pages_unshared); - if(!ff_pages_unshared) return 0; // we return 0, so that we will retry to open it next time - pages_unshared = strtoull(procfile_lineword(ff_pages_unshared, 0, 0), NULL, 10); + ff_pages_unshared = procfile_readall(ff_pages_unshared); + if(!ff_pages_unshared) return 0; // we return 0, so that we will retry to open it next time + pages_unshared = strtoull(procfile_lineword(ff_pages_unshared, 0, 0), NULL, 10); - ff_pages_volatile = procfile_readall(ff_pages_volatile); - if(!ff_pages_volatile) return 0; // we return 0, so that we will retry to open it next time - pages_volatile = strtoull(procfile_lineword(ff_pages_volatile, 0, 0), NULL, 10); + ff_pages_volatile = procfile_readall(ff_pages_volatile); + if(!ff_pages_volatile) return 0; // we return 0, so that we will retry to open it next time + pages_volatile = strtoull(procfile_lineword(ff_pages_volatile, 0, 0), NULL, 10); - ff_pages_to_scan = procfile_readall(ff_pages_to_scan); - if(!ff_pages_to_scan) return 0; // we return 0, so that we will retry to open it next time - pages_to_scan = strtoull(procfile_lineword(ff_pages_to_scan, 0, 0), NULL, 10); + ff_pages_to_scan = procfile_readall(ff_pages_to_scan); + if(!ff_pages_to_scan) return 0; // we return 0, so that we will retry to open it next time + pages_to_scan = strtoull(procfile_lineword(ff_pages_to_scan, 0, 0), NULL, 10); - offered = pages_sharing + pages_unshared + pages_volatile; - saved = pages_sharing - pages_shared; + offered = pages_sharing + pages_shared + pages_unshared + pages_volatile; + saved = pages_sharing - pages_shared; - if(!offered || !pages_to_scan) return 0; + if(!offered || !pages_to_scan) return 0; - RRDSET *st; + RRDSET *st; - // -------------------------------------------------------------------- + // -------------------------------------------------------------------- - st = rrdset_find("mem.ksm"); - if(!st) { - st = rrdset_create("mem", "ksm", NULL, "ksm", NULL, "Kernel Same Page Merging", "MB", 5000, update_every, RRDSET_TYPE_AREA); + st = rrdset_find("mem.ksm"); + if(!st) { + st = rrdset_create("mem", "ksm", NULL, "ksm", NULL, "Kernel Same Page Merging", "MB", 5000, update_every, RRDSET_TYPE_AREA); - rrddim_add(st, "shared", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "unshared", NULL, -1, 1024 * 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "sharing", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "volatile", NULL, -1, 1024 * 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "to_scan", "to scan", -1, 1024 * 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); + rrddim_add(st, "shared", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "unshared", NULL, -1, 1024 * 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "sharing", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "volatile", NULL, -1, 1024 * 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "to_scan", "to scan", -1, 1024 * 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); - rrddim_set(st, "shared", pages_shared * page_size); - rrddim_set(st, "unshared", pages_unshared * page_size); - rrddim_set(st, "sharing", pages_sharing * page_size); - rrddim_set(st, "volatile", pages_volatile * page_size); - rrddim_set(st, "to_scan", pages_to_scan * page_size); - rrdset_done(st); + rrddim_set(st, "shared", pages_shared * page_size); + rrddim_set(st, "unshared", pages_unshared * page_size); + rrddim_set(st, "sharing", pages_sharing * page_size); + rrddim_set(st, "volatile", pages_volatile * page_size); + rrddim_set(st, "to_scan", pages_to_scan * page_size); + rrdset_done(st); - st = rrdset_find("mem.ksm_savings"); - if(!st) { - st = rrdset_create("mem", "ksm_savings", NULL, "ksm", NULL, "Kernel Same Page Merging Savings", "MB", 5001, update_every, RRDSET_TYPE_AREA); + st = rrdset_find("mem.ksm_savings"); + if(!st) { + st = rrdset_create("mem", "ksm_savings", NULL, "ksm", NULL, "Kernel Same Page Merging Savings", "MB", 5001, update_every, RRDSET_TYPE_AREA); - rrddim_add(st, "savings", NULL, -1, 1024 * 1024, RRDDIM_ABSOLUTE); - rrddim_add(st, "offered", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); + rrddim_add(st, "savings", NULL, -1, 1024 * 1024, RRDDIM_ABSOLUTE); + rrddim_add(st, "offered", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); - rrddim_set(st, "savings", saved * page_size); - rrddim_set(st, "offered", offered * page_size); - rrdset_done(st); + rrddim_set(st, "savings", saved * page_size); + rrddim_set(st, "offered", offered * page_size); + rrdset_done(st); - st = rrdset_find("mem.ksm_ratios"); - if(!st) { - st = rrdset_create("mem", "ksm_ratios", NULL, "ksm", NULL, "Kernel Same Page Merging Effectiveness", "percentage", 5002, update_every, RRDSET_TYPE_LINE); - - rrddim_add(st, "savings", NULL, 1, 10000, RRDDIM_ABSOLUTE); - } - else rrdset_next(st); - - rrddim_set(st, "savings", (saved * 1000000) / offered); - rrdset_done(st); + st = rrdset_find("mem.ksm_ratios"); + if(!st) { + st = rrdset_create("mem", "ksm_ratios", NULL, "ksm", NULL, "Kernel Same Page Merging Effectiveness", "percentage", 5002, update_every, RRDSET_TYPE_LINE); + + rrddim_add(st, "savings", NULL, 1, 10000, RRDDIM_ABSOLUTE); + } + else rrdset_next(st); + + rrddim_set(st, "savings", (saved * 1000000) / offered); + rrdset_done(st); - return 0; + return 0; } diff --git a/src/unit_test.c b/src/unit_test.c index 06b7afacb..a77fdb134 100644 --- a/src/unit_test.c +++ b/src/unit_test.c @@ -1,267 +1,258 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/resource.h> - #include "common.h" -#include "storage_number.h" -#include "rrd.h" -#include "log.h" -#include "web_buffer.h" int check_storage_number(calculated_number n, int debug) { - char buffer[100]; - uint32_t flags = SN_EXISTS; - - storage_number s = pack_storage_number(n, flags); - calculated_number d = unpack_storage_number(s); - - if(!does_storage_number_exist(s)) { - fprintf(stderr, "Exists flags missing for number " CALCULATED_NUMBER_FORMAT "!\n", n); - return 5; - } - - calculated_number ddiff = d - n; - calculated_number dcdiff = ddiff * 100.0 / n; - - if(dcdiff < 0) dcdiff = -dcdiff; - - size_t len = print_calculated_number(buffer, d); - calculated_number p = strtold(buffer, NULL); - calculated_number pdiff = n - p; - calculated_number pcdiff = pdiff * 100.0 / n; - if(pcdiff < 0) pcdiff = -pcdiff; - - if(debug) { - fprintf(stderr, - CALCULATED_NUMBER_FORMAT " original\n" - CALCULATED_NUMBER_FORMAT " packed and unpacked, (stored as 0x%08X, diff " CALCULATED_NUMBER_FORMAT ", " CALCULATED_NUMBER_FORMAT "%%)\n" - "%s printed after unpacked (%zu bytes)\n" - CALCULATED_NUMBER_FORMAT " re-parsed from printed (diff " CALCULATED_NUMBER_FORMAT ", " CALCULATED_NUMBER_FORMAT "%%)\n\n", - n, - d, s, ddiff, dcdiff, - buffer, - len, p, pdiff, pcdiff - ); - if(len != strlen(buffer)) fprintf(stderr, "ERROR: printed number %s is reported to have length %zu but it has %zu\n", buffer, len, strlen(buffer)); - if(dcdiff > ACCURACY_LOSS) fprintf(stderr, "WARNING: packing number " CALCULATED_NUMBER_FORMAT " has accuracy loss %0.7Lf %%\n", n, dcdiff); - if(pcdiff > ACCURACY_LOSS) fprintf(stderr, "WARNING: re-parsing the packed, unpacked and printed number " CALCULATED_NUMBER_FORMAT " has accuracy loss %0.7Lf %%\n", n, pcdiff); - } - - if(len != strlen(buffer)) return 1; - if(dcdiff > ACCURACY_LOSS) return 3; - if(pcdiff > ACCURACY_LOSS) return 4; - return 0; + char buffer[100]; + uint32_t flags = SN_EXISTS; + + storage_number s = pack_storage_number(n, flags); + calculated_number d = unpack_storage_number(s); + + if(!does_storage_number_exist(s)) { + fprintf(stderr, "Exists flags missing for number " CALCULATED_NUMBER_FORMAT "!\n", n); + return 5; + } + + calculated_number ddiff = d - n; + calculated_number dcdiff = ddiff * 100.0 / n; + + if(dcdiff < 0) dcdiff = -dcdiff; + + size_t len = print_calculated_number(buffer, d); + calculated_number p = strtold(buffer, NULL); + calculated_number pdiff = n - p; + calculated_number pcdiff = pdiff * 100.0 / n; + if(pcdiff < 0) pcdiff = -pcdiff; + + if(debug) { + fprintf(stderr, + CALCULATED_NUMBER_FORMAT " original\n" + CALCULATED_NUMBER_FORMAT " packed and unpacked, (stored as 0x%08X, diff " CALCULATED_NUMBER_FORMAT ", " CALCULATED_NUMBER_FORMAT "%%)\n" + "%s printed after unpacked (%zu bytes)\n" + CALCULATED_NUMBER_FORMAT " re-parsed from printed (diff " CALCULATED_NUMBER_FORMAT ", " CALCULATED_NUMBER_FORMAT "%%)\n\n", + n, + d, s, ddiff, dcdiff, + buffer, + len, p, pdiff, pcdiff + ); + if(len != strlen(buffer)) fprintf(stderr, "ERROR: printed number %s is reported to have length %zu but it has %zu\n", buffer, len, strlen(buffer)); + if(dcdiff > ACCURACY_LOSS) fprintf(stderr, "WARNING: packing number " CALCULATED_NUMBER_FORMAT " has accuracy loss %0.7Lf %%\n", n, dcdiff); + if(pcdiff > ACCURACY_LOSS) fprintf(stderr, "WARNING: re-parsing the packed, unpacked and printed number " CALCULATED_NUMBER_FORMAT " has accuracy loss %0.7Lf %%\n", n, pcdiff); + } + + if(len != strlen(buffer)) return 1; + if(dcdiff > ACCURACY_LOSS) return 3; + if(pcdiff > ACCURACY_LOSS) return 4; + return 0; } void benchmark_storage_number(int loop, int multiplier) { - int i, j; - calculated_number n, d; - storage_number s; - unsigned long long user, system, total, mine, their; + int i, j; + calculated_number n, d; + storage_number s; + unsigned long long user, system, total, mine, their; - char buffer[100]; + char buffer[100]; - struct rusage now, last; + struct rusage now, last; - fprintf(stderr, "\n\nBenchmarking %d numbers, please wait...\n\n", loop); + fprintf(stderr, "\n\nBenchmarking %d numbers, please wait...\n\n", loop); - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ - fprintf(stderr, "SYSTEM LONG DOUBLE SIZE: %zu bytes\n", sizeof(calculated_number)); - fprintf(stderr, "NETDATA FLOATING POINT SIZE: %zu bytes\n", sizeof(storage_number)); + fprintf(stderr, "SYSTEM LONG DOUBLE SIZE: %zu bytes\n", sizeof(calculated_number)); + fprintf(stderr, "NETDATA FLOATING POINT SIZE: %zu bytes\n", sizeof(storage_number)); - mine = (calculated_number)sizeof(storage_number) * (calculated_number)loop; - their = (calculated_number)sizeof(calculated_number) * (calculated_number)loop; + mine = (calculated_number)sizeof(storage_number) * (calculated_number)loop; + their = (calculated_number)sizeof(calculated_number) * (calculated_number)loop; - if(mine > their) { - fprintf(stderr, "\nNETDATA NEEDS %0.2Lf TIMES MORE MEMORY. Sorry!\n", (long double)(mine / their)); - } - else { - fprintf(stderr, "\nNETDATA INTERNAL FLOATING POINT ARITHMETICS NEEDS %0.2Lf TIMES LESS MEMORY.\n", (long double)(their / mine)); - } + if(mine > their) { + fprintf(stderr, "\nNETDATA NEEDS %0.2Lf TIMES MORE MEMORY. Sorry!\n", (long double)(mine / their)); + } + else { + fprintf(stderr, "\nNETDATA INTERNAL FLOATING POINT ARITHMETICS NEEDS %0.2Lf TIMES LESS MEMORY.\n", (long double)(their / mine)); + } - fprintf(stderr, "\nNETDATA FLOATING POINT\n"); - fprintf(stderr, "MIN POSITIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_POSITIVE_MIN); - fprintf(stderr, "MAX POSITIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_POSITIVE_MAX); - fprintf(stderr, "MIN NEGATIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_NEGATIVE_MIN); - fprintf(stderr, "MAX NEGATIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_NEGATIVE_MAX); - fprintf(stderr, "Maximum accuracy loss: " CALCULATED_NUMBER_FORMAT "%%\n\n\n", (calculated_number)ACCURACY_LOSS); + fprintf(stderr, "\nNETDATA FLOATING POINT\n"); + fprintf(stderr, "MIN POSITIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_POSITIVE_MIN); + fprintf(stderr, "MAX POSITIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_POSITIVE_MAX); + fprintf(stderr, "MIN NEGATIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_NEGATIVE_MIN); + fprintf(stderr, "MAX NEGATIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", (calculated_number)STORAGE_NUMBER_NEGATIVE_MAX); + fprintf(stderr, "Maximum accuracy loss: " CALCULATED_NUMBER_FORMAT "%%\n\n\n", (calculated_number)ACCURACY_LOSS); - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ - fprintf(stderr, "INTERNAL LONG DOUBLE PRINTING: "); - getrusage(RUSAGE_SELF, &last); + fprintf(stderr, "INTERNAL LONG DOUBLE PRINTING: "); + getrusage(RUSAGE_SELF, &last); - // do the job - for(j = 1; j < 11 ;j++) { - n = STORAGE_NUMBER_POSITIVE_MIN * j; + // do the job + for(j = 1; j < 11 ;j++) { + n = STORAGE_NUMBER_POSITIVE_MIN * j; - for(i = 0; i < loop ;i++) { - n *= multiplier; - if(n > STORAGE_NUMBER_POSITIVE_MAX) n = STORAGE_NUMBER_POSITIVE_MIN; + for(i = 0; i < loop ;i++) { + n *= multiplier; + if(n > STORAGE_NUMBER_POSITIVE_MAX) n = STORAGE_NUMBER_POSITIVE_MIN; - print_calculated_number(buffer, n); - } - } + print_calculated_number(buffer, n); + } + } - getrusage(RUSAGE_SELF, &now); - user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec; - system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec; - total = user + system; - mine = total; + getrusage(RUSAGE_SELF, &now); + user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec; + system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec; + total = user + system; + mine = total; - fprintf(stderr, "user %0.5Lf, system %0.5Lf, total %0.5Lf\n", (long double)(user / 1000000.0), (long double)(system / 1000000.0), (long double)(total / 1000000.0)); + fprintf(stderr, "user %0.5Lf, system %0.5Lf, total %0.5Lf\n", (long double)(user / 1000000.0), (long double)(system / 1000000.0), (long double)(total / 1000000.0)); - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ - fprintf(stderr, "SYSTEM LONG DOUBLE PRINTING: "); - getrusage(RUSAGE_SELF, &last); + fprintf(stderr, "SYSTEM LONG DOUBLE PRINTING: "); + getrusage(RUSAGE_SELF, &last); - // do the job - for(j = 1; j < 11 ;j++) { - n = STORAGE_NUMBER_POSITIVE_MIN * j; + // do the job + for(j = 1; j < 11 ;j++) { + n = STORAGE_NUMBER_POSITIVE_MIN * j; - for(i = 0; i < loop ;i++) { - n *= multiplier; - if(n > STORAGE_NUMBER_POSITIVE_MAX) n = STORAGE_NUMBER_POSITIVE_MIN; - snprintfz(buffer, 100, CALCULATED_NUMBER_FORMAT, n); - } - } + for(i = 0; i < loop ;i++) { + n *= multiplier; + if(n > STORAGE_NUMBER_POSITIVE_MAX) n = STORAGE_NUMBER_POSITIVE_MIN; + snprintfz(buffer, 100, CALCULATED_NUMBER_FORMAT, n); + } + } - getrusage(RUSAGE_SELF, &now); - user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec; - system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec; - total = user + system; - their = total; + getrusage(RUSAGE_SELF, &now); + user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec; + system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec; + total = user + system; + their = total; - fprintf(stderr, "user %0.5Lf, system %0.5Lf, total %0.5Lf\n", (long double)(user / 1000000.0), (long double)(system / 1000000.0), (long double)(total / 1000000.0)); + fprintf(stderr, "user %0.5Lf, system %0.5Lf, total %0.5Lf\n", (long double)(user / 1000000.0), (long double)(system / 1000000.0), (long double)(total / 1000000.0)); - if(mine > total) { - fprintf(stderr, "NETDATA CODE IS SLOWER %0.2Lf %%\n", (long double)(mine * 100.0 / their - 100.0)); - } - else { - fprintf(stderr, "NETDATA CODE IS F A S T E R %0.2Lf %%\n", (long double)(their * 100.0 / mine - 100.0)); - } + if(mine > total) { + fprintf(stderr, "NETDATA CODE IS SLOWER %0.2Lf %%\n", (long double)(mine * 100.0 / their - 100.0)); + } + else { + fprintf(stderr, "NETDATA CODE IS F A S T E R %0.2Lf %%\n", (long double)(their * 100.0 / mine - 100.0)); + } - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ - fprintf(stderr, "\nINTERNAL LONG DOUBLE PRINTING WITH PACK / UNPACK: "); - getrusage(RUSAGE_SELF, &last); + fprintf(stderr, "\nINTERNAL LONG DOUBLE PRINTING WITH PACK / UNPACK: "); + getrusage(RUSAGE_SELF, &last); - // do the job - for(j = 1; j < 11 ;j++) { - n = STORAGE_NUMBER_POSITIVE_MIN * j; + // do the job + for(j = 1; j < 11 ;j++) { + n = STORAGE_NUMBER_POSITIVE_MIN * j; - for(i = 0; i < loop ;i++) { - n *= multiplier; - if(n > STORAGE_NUMBER_POSITIVE_MAX) n = STORAGE_NUMBER_POSITIVE_MIN; + for(i = 0; i < loop ;i++) { + n *= multiplier; + if(n > STORAGE_NUMBER_POSITIVE_MAX) n = STORAGE_NUMBER_POSITIVE_MIN; - s = pack_storage_number(n, 1); - d = unpack_storage_number(s); - print_calculated_number(buffer, d); - } - } + s = pack_storage_number(n, 1); + d = unpack_storage_number(s); + print_calculated_number(buffer, d); + } + } - getrusage(RUSAGE_SELF, &now); - user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec; - system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec; - total = user + system; - mine = total; - - fprintf(stderr, "user %0.5Lf, system %0.5Lf, total %0.5Lf\n", (long double)(user / 1000000.0), (long double)(system / 1000000.0), (long double)(total / 1000000.0)); + getrusage(RUSAGE_SELF, &now); + user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec; + system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec; + total = user + system; + mine = total; + + fprintf(stderr, "user %0.5Lf, system %0.5Lf, total %0.5Lf\n", (long double)(user / 1000000.0), (long double)(system / 1000000.0), (long double)(total / 1000000.0)); - if(mine > their) { - fprintf(stderr, "WITH PACKING UNPACKING NETDATA CODE IS SLOWER %0.2Lf %%\n", (long double)(mine * 100.0 / their - 100.0)); - } - else { - fprintf(stderr, "EVEN WITH PACKING AND UNPACKING, NETDATA CODE IS F A S T E R %0.2Lf %%\n", (long double)(their * 100.0 / mine - 100.0)); - } + if(mine > their) { + fprintf(stderr, "WITH PACKING UNPACKING NETDATA CODE IS SLOWER %0.2Lf %%\n", (long double)(mine * 100.0 / their - 100.0)); + } + else { + fprintf(stderr, "EVEN WITH PACKING AND UNPACKING, NETDATA CODE IS F A S T E R %0.2Lf %%\n", (long double)(their * 100.0 / mine - 100.0)); + } - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ } static int check_storage_number_exists() { - uint32_t flags = SN_EXISTS; - - - for(flags = 0; flags < 7 ; flags++) { - if(get_storage_number_flags(flags << 24) != flags << 24) { - fprintf(stderr, "Flag 0x%08x is not checked correctly. It became 0x%08x\n", flags << 24, get_storage_number_flags(flags << 24)); - return 1; - } - } - - flags = SN_EXISTS; - calculated_number n = 0.0; - - storage_number s = pack_storage_number(n, flags); - calculated_number d = unpack_storage_number(s); - if(get_storage_number_flags(s) != flags) { - fprintf(stderr, "Wrong flags. Given %08x, Got %08x!\n", flags, get_storage_number_flags(s)); - return 1; - } - if(n != d) { - fprintf(stderr, "Wrong number returned. Expected " CALCULATED_NUMBER_FORMAT ", returned " CALCULATED_NUMBER_FORMAT "!\n", n, d); - return 1; - } - - return 0; + uint32_t flags = SN_EXISTS; + + + for(flags = 0; flags < 7 ; flags++) { + if(get_storage_number_flags(flags << 24) != flags << 24) { + fprintf(stderr, "Flag 0x%08x is not checked correctly. It became 0x%08x\n", flags << 24, get_storage_number_flags(flags << 24)); + return 1; + } + } + + flags = SN_EXISTS; + calculated_number n = 0.0; + + storage_number s = pack_storage_number(n, flags); + calculated_number d = unpack_storage_number(s); + if(get_storage_number_flags(s) != flags) { + fprintf(stderr, "Wrong flags. Given %08x, Got %08x!\n", flags, get_storage_number_flags(s)); + return 1; + } + if(n != d) { + fprintf(stderr, "Wrong number returned. Expected " CALCULATED_NUMBER_FORMAT ", returned " CALCULATED_NUMBER_FORMAT "!\n", n, d); + return 1; + } + + return 0; } int unit_test_storage() { - if(check_storage_number_exists()) return 0; + if(check_storage_number_exists()) return 0; - calculated_number c, a = 0; - int i, j, g, r = 0; + calculated_number c, a = 0; + int i, j, g, r = 0; - for(g = -1; g <= 1 ; g++) { - a = 0; + for(g = -1; g <= 1 ; g++) { + a = 0; - if(!g) continue; + if(!g) continue; - for(j = 0; j < 9 ;j++) { - a += 0.0000001; - c = a * g; - for(i = 0; i < 21 ;i++, c *= 10) { - if(c > 0 && c < STORAGE_NUMBER_POSITIVE_MIN) continue; - if(c < 0 && c > STORAGE_NUMBER_NEGATIVE_MAX) continue; + for(j = 0; j < 9 ;j++) { + a += 0.0000001; + c = a * g; + for(i = 0; i < 21 ;i++, c *= 10) { + if(c > 0 && c < STORAGE_NUMBER_POSITIVE_MIN) continue; + if(c < 0 && c > STORAGE_NUMBER_NEGATIVE_MAX) continue; - if(check_storage_number(c, 1)) return 1; - } - } - } + if(check_storage_number(c, 1)) return 1; + } + } + } - benchmark_storage_number(1000000, 2); - return r; + benchmark_storage_number(1000000, 2); + return r; } // -------------------------------------------------------------------------------------------------------------------- struct feed_values { - unsigned long long microseconds; - calculated_number value; + unsigned long long microseconds; + collected_number value; }; struct test { - char name[100]; - char description[1024]; + char name[100]; + char description[1024]; + + int update_every; + unsigned long long multiplier; + unsigned long long divisor; + int algorithm; - int update_every; - unsigned long long multiplier; - unsigned long long divisor; - int algorithm; + unsigned long feed_entries; + unsigned long result_entries; + struct feed_values *feed; + calculated_number *results; - unsigned long feed_entries; - unsigned long result_entries; - struct feed_values *feed; - calculated_number *results; + collected_number *feed2; + calculated_number *results2; }; // -------------------------------------------------------------------------------------------------------------------- @@ -269,33 +260,35 @@ struct test { // test absolute values stored struct feed_values test1_feed[] = { - { 0, 10 }, - { 1000000, 20 }, - { 1000000, 30 }, - { 1000000, 40 }, - { 1000000, 50 }, - { 1000000, 60 }, - { 1000000, 70 }, - { 1000000, 80 }, - { 1000000, 90 }, - { 1000000, 100 }, + { 0, 10 }, + { 1000000, 20 }, + { 1000000, 30 }, + { 1000000, 40 }, + { 1000000, 50 }, + { 1000000, 60 }, + { 1000000, 70 }, + { 1000000, 80 }, + { 1000000, 90 }, + { 1000000, 100 }, }; calculated_number test1_results[] = { - 20, 30, 40, 50, 60, 70, 80, 90, 100 + 20, 30, 40, 50, 60, 70, 80, 90, 100 }; struct test test1 = { - "test1", // name - "test absolute values stored at exactly second boundaries", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_ABSOLUTE, // algorithm - 10, // feed entries - 9, // result entries - test1_feed, // feed - test1_results // results + "test1", // name + "test absolute values stored at exactly second boundaries", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_ABSOLUTE, // algorithm + 10, // feed entries + 9, // result entries + test1_feed, // feed + test1_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- @@ -303,497 +296,667 @@ struct test test1 = { // test absolute values stored in the middle of second boundaries struct feed_values test2_feed[] = { - { 500000, 10 }, - { 1000000, 20 }, - { 1000000, 30 }, - { 1000000, 40 }, - { 1000000, 50 }, - { 1000000, 60 }, - { 1000000, 70 }, - { 1000000, 80 }, - { 1000000, 90 }, - { 1000000, 100 }, + { 500000, 10 }, + { 1000000, 20 }, + { 1000000, 30 }, + { 1000000, 40 }, + { 1000000, 50 }, + { 1000000, 60 }, + { 1000000, 70 }, + { 1000000, 80 }, + { 1000000, 90 }, + { 1000000, 100 }, }; calculated_number test2_results[] = { - 20, 30, 40, 50, 60, 70, 80, 90, 100 + 20, 30, 40, 50, 60, 70, 80, 90, 100 }; struct test test2 = { - "test2", // name - "test absolute values stored in the middle of second boundaries", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_ABSOLUTE, // algorithm - 10, // feed entries - 9, // result entries - test2_feed, // feed - test2_results // results + "test2", // name + "test absolute values stored in the middle of second boundaries", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_ABSOLUTE, // algorithm + 10, // feed entries + 9, // result entries + test2_feed, // feed + test2_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test3 struct feed_values test3_feed[] = { - { 0, 10 }, - { 1000000, 20 }, - { 1000000, 30 }, - { 1000000, 40 }, - { 1000000, 50 }, - { 1000000, 60 }, - { 1000000, 70 }, - { 1000000, 80 }, - { 1000000, 90 }, - { 1000000, 100 }, + { 0, 10 }, + { 1000000, 20 }, + { 1000000, 30 }, + { 1000000, 40 }, + { 1000000, 50 }, + { 1000000, 60 }, + { 1000000, 70 }, + { 1000000, 80 }, + { 1000000, 90 }, + { 1000000, 100 }, }; calculated_number test3_results[] = { - 10, 10, 10, 10, 10, 10, 10, 10, 10 + 10, 10, 10, 10, 10, 10, 10, 10, 10 }; struct test test3 = { - "test3", // name - "test incremental values stored at exactly second boundaries", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_INCREMENTAL, // algorithm - 10, // feed entries - 9, // result entries - test3_feed, // feed - test3_results // results + "test3", // name + "test incremental values stored at exactly second boundaries", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_INCREMENTAL, // algorithm + 10, // feed entries + 9, // result entries + test3_feed, // feed + test3_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test4 struct feed_values test4_feed[] = { - { 500000, 10 }, - { 1000000, 20 }, - { 1000000, 30 }, - { 1000000, 40 }, - { 1000000, 50 }, - { 1000000, 60 }, - { 1000000, 70 }, - { 1000000, 80 }, - { 1000000, 90 }, - { 1000000, 100 }, + { 500000, 10 }, + { 1000000, 20 }, + { 1000000, 30 }, + { 1000000, 40 }, + { 1000000, 50 }, + { 1000000, 60 }, + { 1000000, 70 }, + { 1000000, 80 }, + { 1000000, 90 }, + { 1000000, 100 }, }; calculated_number test4_results[] = { - 5, 10, 10, 10, 10, 10, 10, 10, 10 + 5, 10, 10, 10, 10, 10, 10, 10, 10 }; struct test test4 = { - "test4", // name - "test incremental values stored in the middle of second boundaries", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_INCREMENTAL, // algorithm - 10, // feed entries - 9, // result entries - test4_feed, // feed - test4_results // results + "test4", // name + "test incremental values stored in the middle of second boundaries", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_INCREMENTAL, // algorithm + 10, // feed entries + 9, // result entries + test4_feed, // feed + test4_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test5 struct feed_values test5_feed[] = { - { 500000, 1000 }, - { 1000000, 2000 }, - { 1000000, 2000 }, - { 1000000, 2000 }, - { 1000000, 3000 }, - { 1000000, 2000 }, - { 1000000, 2000 }, - { 1000000, 2000 }, - { 1000000, 2000 }, - { 1000000, 2000 }, + { 500000, 1000 }, + { 1000000, 2000 }, + { 1000000, 2000 }, + { 1000000, 2000 }, + { 1000000, 3000 }, + { 1000000, 2000 }, + { 1000000, 2000 }, + { 1000000, 2000 }, + { 1000000, 2000 }, + { 1000000, 2000 }, }; calculated_number test5_results[] = { - 500, 500, 0, 500, 500, 0, 0, 0, 0 + 500, 500, 0, 500, 500, 0, 0, 0, 0 }; struct test test5 = { - "test5", // name - "test incremental values ups and downs", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_INCREMENTAL, // algorithm - 10, // feed entries - 9, // result entries - test5_feed, // feed - test5_results // results + "test5", // name + "test incremental values ups and downs", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_INCREMENTAL, // algorithm + 10, // feed entries + 9, // result entries + test5_feed, // feed + test5_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test6 struct feed_values test6_feed[] = { - { 250000, 1000 }, - { 250000, 2000 }, - { 250000, 3000 }, - { 250000, 4000 }, - { 250000, 5000 }, - { 250000, 6000 }, - { 250000, 7000 }, - { 250000, 8000 }, - { 250000, 9000 }, - { 250000, 10000 }, - { 250000, 11000 }, - { 250000, 12000 }, - { 250000, 13000 }, - { 250000, 14000 }, - { 250000, 15000 }, - { 250000, 16000 }, + { 250000, 1000 }, + { 250000, 2000 }, + { 250000, 3000 }, + { 250000, 4000 }, + { 250000, 5000 }, + { 250000, 6000 }, + { 250000, 7000 }, + { 250000, 8000 }, + { 250000, 9000 }, + { 250000, 10000 }, + { 250000, 11000 }, + { 250000, 12000 }, + { 250000, 13000 }, + { 250000, 14000 }, + { 250000, 15000 }, + { 250000, 16000 }, }; calculated_number test6_results[] = { - 3000, 4000, 4000, 4000 + 3000, 4000, 4000, 4000 }; struct test test6 = { - "test6", // name - "test incremental values updated within the same second", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_INCREMENTAL, // algorithm - 16, // feed entries - 4, // result entries - test6_feed, // feed - test6_results // results + "test6", // name + "test incremental values updated within the same second", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_INCREMENTAL, // algorithm + 16, // feed entries + 4, // result entries + test6_feed, // feed + test6_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test7 struct feed_values test7_feed[] = { - { 500000, 1000 }, - { 2000000, 2000 }, - { 2000000, 3000 }, - { 2000000, 4000 }, - { 2000000, 5000 }, - { 2000000, 6000 }, - { 2000000, 7000 }, - { 2000000, 8000 }, - { 2000000, 9000 }, - { 2000000, 10000 }, + { 500000, 1000 }, + { 2000000, 2000 }, + { 2000000, 3000 }, + { 2000000, 4000 }, + { 2000000, 5000 }, + { 2000000, 6000 }, + { 2000000, 7000 }, + { 2000000, 8000 }, + { 2000000, 9000 }, + { 2000000, 10000 }, }; calculated_number test7_results[] = { - 250, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500 + 250, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500 }; struct test test7 = { - "test7", // name - "test incremental values updated in long durations", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_INCREMENTAL, // algorithm - 10, // feed entries - 18, // result entries - test7_feed, // feed - test7_results // results + "test7", // name + "test incremental values updated in long durations", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_INCREMENTAL, // algorithm + 10, // feed entries + 18, // result entries + test7_feed, // feed + test7_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test8 struct feed_values test8_feed[] = { - { 500000, 1000 }, - { 2000000, 2000 }, - { 2000000, 3000 }, - { 2000000, 4000 }, - { 2000000, 5000 }, - { 2000000, 6000 }, + { 500000, 1000 }, + { 2000000, 2000 }, + { 2000000, 3000 }, + { 2000000, 4000 }, + { 2000000, 5000 }, + { 2000000, 6000 }, }; calculated_number test8_results[] = { - 1250, 2000, 2250, 3000, 3250, 4000, 4250, 5000, 5250, 6000 + 1250, 2000, 2250, 3000, 3250, 4000, 4250, 5000, 5250, 6000 }; struct test test8 = { - "test8", // name - "test absolute values updated in long durations", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_ABSOLUTE, // algorithm - 6, // feed entries - 10, // result entries - test8_feed, // feed - test8_results // results + "test8", // name + "test absolute values updated in long durations", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_ABSOLUTE, // algorithm + 6, // feed entries + 10, // result entries + test8_feed, // feed + test8_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test9 struct feed_values test9_feed[] = { - { 250000, 1000 }, - { 250000, 2000 }, - { 250000, 3000 }, - { 250000, 4000 }, - { 250000, 5000 }, - { 250000, 6000 }, - { 250000, 7000 }, - { 250000, 8000 }, - { 250000, 9000 }, - { 250000, 10000 }, - { 250000, 11000 }, - { 250000, 12000 }, - { 250000, 13000 }, - { 250000, 14000 }, - { 250000, 15000 }, - { 250000, 16000 }, + { 250000, 1000 }, + { 250000, 2000 }, + { 250000, 3000 }, + { 250000, 4000 }, + { 250000, 5000 }, + { 250000, 6000 }, + { 250000, 7000 }, + { 250000, 8000 }, + { 250000, 9000 }, + { 250000, 10000 }, + { 250000, 11000 }, + { 250000, 12000 }, + { 250000, 13000 }, + { 250000, 14000 }, + { 250000, 15000 }, + { 250000, 16000 }, }; calculated_number test9_results[] = { - 4000, 8000, 12000, 16000 + 4000, 8000, 12000, 16000 }; struct test test9 = { - "test9", // name - "test absolute values updated within the same second", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_ABSOLUTE, // algorithm - 16, // feed entries - 4, // result entries - test9_feed, // feed - test9_results // results + "test9", // name + "test absolute values updated within the same second", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_ABSOLUTE, // algorithm + 16, // feed entries + 4, // result entries + test9_feed, // feed + test9_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- // test10 struct feed_values test10_feed[] = { - { 500000, 1000 }, - { 600000, 1000 + 600 }, - { 200000, 1600 + 200 }, - { 1000000, 1800 + 1000 }, - { 200000, 2800 + 200 }, - { 2000000, 3000 + 2000 }, - { 600000, 5000 + 600 }, - { 400000, 5600 + 400 }, - { 900000, 6000 + 900 }, - { 1000000, 6900 + 1000 }, + { 500000, 1000 }, + { 600000, 1000 + 600 }, + { 200000, 1600 + 200 }, + { 1000000, 1800 + 1000 }, + { 200000, 2800 + 200 }, + { 2000000, 3000 + 2000 }, + { 600000, 5000 + 600 }, + { 400000, 5600 + 400 }, + { 900000, 6000 + 900 }, + { 1000000, 6900 + 1000 }, }; calculated_number test10_results[] = { - 500, 1000, 1000, 1000, 1000, 1000, 1000 + 500, 1000, 1000, 1000, 1000, 1000, 1000 }; struct test test10 = { - "test10", // name - "test incremental values updated in short and long durations", - 1, // update_every - 1, // multiplier - 1, // divisor - RRDDIM_INCREMENTAL, // algorithm - 10, // feed entries - 7, // result entries - test10_feed, // feed - test10_results // results + "test10", // name + "test incremental values updated in short and long durations", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_INCREMENTAL, // algorithm + 10, // feed entries + 7, // result entries + test10_feed, // feed + test10_results, // results + NULL, // feed2 + NULL // results2 +}; + +// -------------------------------------------------------------------------------------------------------------------- +// test11 + +struct feed_values test11_feed[] = { + { 0, 10 }, + { 1000000, 20 }, + { 1000000, 30 }, + { 1000000, 40 }, + { 1000000, 50 }, + { 1000000, 60 }, + { 1000000, 70 }, + { 1000000, 80 }, + { 1000000, 90 }, + { 1000000, 100 }, +}; + +collected_number test11_feed2[] = { + 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 +}; + +calculated_number test11_results[] = { + 50, 50, 50, 50, 50, 50, 50, 50, 50 +}; + +calculated_number test11_results2[] = { + 50, 50, 50, 50, 50, 50, 50, 50, 50 +}; + +struct test test11 = { + "test11", // name + "test percentage-of-incremental-row with equal values", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_PCENT_OVER_DIFF_TOTAL, // algorithm + 10, // feed entries + 9, // result entries + test11_feed, // feed + test11_results, // results + test11_feed2, // feed2 + test11_results2 // results2 +}; + +// -------------------------------------------------------------------------------------------------------------------- +// test12 + +struct feed_values test12_feed[] = { + { 0, 10 }, + { 1000000, 20 }, + { 1000000, 30 }, + { 1000000, 40 }, + { 1000000, 50 }, + { 1000000, 60 }, + { 1000000, 70 }, + { 1000000, 80 }, + { 1000000, 90 }, + { 1000000, 100 }, +}; + +collected_number test12_feed2[] = { + 10*3, 20*3, 30*3, 40*3, 50*3, 60*3, 70*3, 80*3, 90*3, 100*3 +}; + +calculated_number test12_results[] = { + 25, 25, 25, 25, 25, 25, 25, 25, 25 +}; + +calculated_number test12_results2[] = { + 75, 75, 75, 75, 75, 75, 75, 75, 75 +}; + +struct test test12 = { + "test12", // name + "test percentage-of-incremental-row with equal values", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_PCENT_OVER_DIFF_TOTAL, // algorithm + 10, // feed entries + 9, // result entries + test12_feed, // feed + test12_results, // results + test12_feed2, // feed2 + test12_results2 // results2 +}; + +// -------------------------------------------------------------------------------------------------------------------- +// test13 + +struct feed_values test13_feed[] = { + { 500000, 1000 }, + { 600000, 1000 + 600 }, + { 200000, 1600 + 200 }, + { 1000000, 1800 + 1000 }, + { 200000, 2800 + 200 }, + { 2000000, 3000 + 2000 }, + { 600000, 5000 + 600 }, + { 400000, 5600 + 400 }, + { 900000, 6000 + 900 }, + { 1000000, 6900 + 1000 }, +}; + +calculated_number test13_results[] = { + 83.3333300, 100, 100, 100, 100, 100, 100 +}; + +struct test test13 = { + "test13", // name + "test incremental values updated in short and long durations", + 1, // update_every + 1, // multiplier + 1, // divisor + RRDDIM_PCENT_OVER_DIFF_TOTAL, // algorithm + 10, // feed entries + 7, // result entries + test13_feed, // feed + test13_results, // results + NULL, // feed2 + NULL // results2 }; // -------------------------------------------------------------------------------------------------------------------- int run_test(struct test *test) { - fprintf(stderr, "\nRunning test '%s':\n%s\n", test->name, test->description); - - rrd_memory_mode = RRD_MEMORY_MODE_RAM; - rrd_update_every = test->update_every; - - char name[101]; - 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); - RRDDIM *rd = rrddim_add(st, "dimension", NULL, test->multiplier, test->divisor, test->algorithm); - st->debug = 1; - - // feed it with the test data - unsigned long c; - for(c = 0; c < test->feed_entries; c++) { - if(debug_flags) fprintf(stderr, "\n\n"); - - if(c) { - fprintf(stderr, " > %s: feeding position %lu, after %llu microseconds, with value " CALCULATED_NUMBER_FORMAT "\n", test->name, c+1, test->feed[c].microseconds, test->feed[c].value); - rrdset_next_usec(st, test->feed[c].microseconds); - } - else { - fprintf(stderr, " > %s: feeding position %lu with value " CALCULATED_NUMBER_FORMAT "\n", test->name, c+1, test->feed[c].value); - } - - rrddim_set(st, "dimension", test->feed[c].value); - rrdset_done(st); - - // align the first entry to second boundary - if(!c) { - fprintf(stderr, " > %s: fixing first collection time to be %llu microseconds to second boundary\n", test->name, test->feed[c].microseconds); - rd->last_collected_time.tv_usec = st->last_collected_time.tv_usec = st->last_updated.tv_usec = test->feed[c].microseconds; - } - } - - // check the result - int errors = 0; - - if(st->counter != test->result_entries) { - fprintf(stderr, " %s stored %lu entries, but we were expecting %lu, ### E R R O R ###\n", test->name, st->counter, test->result_entries); - errors++; - } - - unsigned long max = (st->counter < test->result_entries)?st->counter:test->result_entries; - for(c = 0 ; c < max ; c++) { - calculated_number v = unpack_storage_number(rd->values[c]), n = test->results[c]; - fprintf(stderr, " %s: checking position %lu, expecting value " CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT ", %s\n", test->name, c+1, n, v, (v == n)?"OK":"### E R R O R ###"); - if(v != n) errors++; - } - - return errors; + fprintf(stderr, "\nRunning test '%s':\n%s\n", test->name, test->description); + + rrd_memory_mode = RRD_MEMORY_MODE_RAM; + rrd_update_every = test->update_every; + + char name[101]; + 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); + RRDDIM *rd = rrddim_add(st, "dim1", NULL, test->multiplier, test->divisor, test->algorithm); + + RRDDIM *rd2 = NULL; + if(test->feed2) + rd2 = rrddim_add(st, "dim2", NULL, test->multiplier, test->divisor, test->algorithm); + + st->debug = 1; + + // feed it with the test data + unsigned long c; + for(c = 0; c < test->feed_entries; c++) { + if(debug_flags) fprintf(stderr, "\n\n"); + + if(c) { + fprintf(stderr, " > %s: feeding position %lu, after %llu microseconds\n", test->name, c+1, test->feed[c].microseconds); + rrdset_next_usec(st, test->feed[c].microseconds); + } + else { + fprintf(stderr, " > %s: feeding position %lu\n", test->name, c+1); + } + + fprintf(stderr, " >> %s with value " COLLECTED_NUMBER_FORMAT "\n", rd->name, test->feed[c].value); + rrddim_set(st, "dim1", test->feed[c].value); + + if(rd2) { + fprintf(stderr, " >> %s with value " COLLECTED_NUMBER_FORMAT "\n", rd2->name, test->feed2[c]); + rrddim_set(st, "dim2", test->feed2[c]); + } + + rrdset_done(st); + + // align the first entry to second boundary + if(!c) { + fprintf(stderr, " > %s: fixing first collection time to be %llu microseconds to second boundary\n", test->name, test->feed[c].microseconds); + rd->last_collected_time.tv_usec = st->last_collected_time.tv_usec = st->last_updated.tv_usec = test->feed[c].microseconds; + } + } + + // check the result + int errors = 0; + + if(st->counter != test->result_entries) { + fprintf(stderr, " %s stored %lu entries, but we were expecting %lu, ### E R R O R ###\n", test->name, st->counter, test->result_entries); + errors++; + } + + unsigned long max = (st->counter < test->result_entries)?st->counter:test->result_entries; + for(c = 0 ; c < max ; c++) { + calculated_number v = unpack_storage_number(rd->values[c]); + calculated_number n = test->results[c]; + int same = (roundl(v * 10000000.0) == roundl(n * 10000000.0))?1:0; + fprintf(stderr, " %s/%s: checking position %lu, expecting value " CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT ", %s\n", test->name, rd->name, c+1, n, v, (same)?"OK":"### E R R O R ###"); + if(!same) errors++; + + if(rd2) { + v = unpack_storage_number(rd2->values[c]); + n = test->results2[c]; + same = (roundl(v * 10000000.0) == roundl(n * 10000000.0))?1:0; + fprintf(stderr, " %s/%s: checking position %lu, expecting value " CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT ", %s\n", test->name, rd2->name, c+1, n, v, (same)?"OK":"### E R R O R ###"); + if(!same) errors++; + } + } + + return errors; } int run_all_mockup_tests(void) { - if(run_test(&test1)) - return 1; + if(run_test(&test1)) + return 1; + + if(run_test(&test2)) + return 1; + + if(run_test(&test3)) + return 1; + + if(run_test(&test4)) + return 1; - if(run_test(&test2)) - return 1; + if(run_test(&test5)) + return 1; - if(run_test(&test3)) - return 1; + if(run_test(&test6)) + return 1; - if(run_test(&test4)) - return 1; + if(run_test(&test7)) + return 1; - if(run_test(&test5)) - return 1; + if(run_test(&test8)) + return 1; - if(run_test(&test6)) - return 1; + if(run_test(&test9)) + return 1; - if(run_test(&test7)) - return 1; + if(run_test(&test10)) + return 1; - if(run_test(&test8)) - return 1; + if(run_test(&test11)) + return 1; - if(run_test(&test9)) - return 1; + if(run_test(&test12)) + return 1; - if(run_test(&test10)) - return 1; + if(run_test(&test13)) + return 1; - return 0; + return 0; } int unit_test(long delay, long shift) { - static int repeat = 0; - repeat++; - - char name[101]; - snprintfz(name, 100, "unittest-%d-%ld-%ld", repeat, delay, shift); - - //debug_flags = 0xffffffff; - rrd_memory_mode = RRD_MEMORY_MODE_RAM; - rrd_update_every = 1; - - int do_abs = 1; - int do_inc = 1; - int do_abst = 0; - int do_absi = 0; - - RRDSET *st = rrdset_create("netdata", name, name, "netdata", NULL, "Unit Testing", "a value", 1, 1, RRDSET_TYPE_LINE); - st->debug = 1; - - RRDDIM *rdabs = NULL; - RRDDIM *rdinc = NULL; - RRDDIM *rdabst = NULL; - RRDDIM *rdabsi = NULL; - - if(do_abs) rdabs = rrddim_add(st, "absolute", "absolute", 1, 1, RRDDIM_ABSOLUTE); - if(do_inc) rdinc = rrddim_add(st, "incremental", "incremental", 1, 1, RRDDIM_INCREMENTAL); - if(do_abst) rdabst = rrddim_add(st, "percentage-of-absolute-row", "percentage-of-absolute-row", 1, 1, RRDDIM_PCENT_OVER_ROW_TOTAL); - if(do_absi) rdabsi = rrddim_add(st, "percentage-of-incremental-row", "percentage-of-incremental-row", 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); - - long increment = 1000; - collected_number i = 0; - - unsigned long c, dimensions = 0; - RRDDIM *rd; - for(rd = st->dimensions ; rd ; rd = rd->next) dimensions++; - - for(c = 0; c < 20 ;c++) { - i += increment; - - fprintf(stderr, "\n\nLOOP = %lu, DELAY = %ld, VALUE = " COLLECTED_NUMBER_FORMAT "\n", c, delay, i); - if(c) { - rrdset_next_usec(st, delay); - } - if(do_abs) rrddim_set(st, "absolute", i); - if(do_inc) rrddim_set(st, "incremental", i); - if(do_abst) rrddim_set(st, "percentage-of-absolute-row", i); - if(do_absi) rrddim_set(st, "percentage-of-incremental-row", i); - - if(!c) { - gettimeofday(&st->last_collected_time, NULL); - st->last_collected_time.tv_usec = shift; - } - - // prevent it from deleting the dimensions - for(rd = st->dimensions ; rd ; rd = rd->next) - rd->last_collected_time.tv_sec = st->last_collected_time.tv_sec; - - rrdset_done(st); - } - - unsigned long oincrement = increment; - increment = increment * st->update_every * 1000000 / delay; - fprintf(stderr, "\n\nORIGINAL INCREMENT: %lu, INCREMENT %lu, DELAY %lu, SHIFT %lu\n", oincrement * 10, increment * 10, delay, shift); - - int ret = 0; - storage_number sn; - calculated_number cn, v; - for(c = 0 ; c < st->counter ; c++) { - fprintf(stderr, "\nPOSITION: c = %lu, EXPECTED VALUE %lu\n", c, (oincrement + c * increment + increment * (1000000 - shift) / 1000000 )* 10); - - for(rd = st->dimensions ; rd ; rd = rd->next) { - sn = rd->values[c]; - cn = unpack_storage_number(sn); - fprintf(stderr, "\t %s " CALCULATED_NUMBER_FORMAT " (PACKED AS " STORAGE_NUMBER_FORMAT ") -> ", rd->id, cn, sn); - - if(rd == rdabs) v = - ( oincrement - // + (increment * (1000000 - shift) / 1000000) - + (c + 1) * increment - ); - - else if(rd == rdinc) v = (c?(increment):(increment * (1000000 - shift) / 1000000)); - else if(rd == rdabst) v = oincrement / dimensions / 10; - else if(rd == rdabsi) v = oincrement / dimensions / 10; - else v = 0; - - if(v == cn) fprintf(stderr, "passed.\n"); - else { - fprintf(stderr, "ERROR! (expected " CALCULATED_NUMBER_FORMAT ")\n", v); - ret = 1; - } - } - } - - if(ret) - fprintf(stderr, "\n\nUNIT TEST(%ld, %ld) FAILED\n\n", delay, shift); - - return ret; + static int repeat = 0; + repeat++; + + char name[101]; + snprintfz(name, 100, "unittest-%d-%ld-%ld", repeat, delay, shift); + + //debug_flags = 0xffffffff; + rrd_memory_mode = RRD_MEMORY_MODE_RAM; + rrd_update_every = 1; + + int do_abs = 1; + int do_inc = 1; + int do_abst = 0; + int do_absi = 0; + + RRDSET *st = rrdset_create("netdata", name, name, "netdata", NULL, "Unit Testing", "a value", 1, 1, RRDSET_TYPE_LINE); + st->debug = 1; + + RRDDIM *rdabs = NULL; + RRDDIM *rdinc = NULL; + RRDDIM *rdabst = NULL; + RRDDIM *rdabsi = NULL; + + if(do_abs) rdabs = rrddim_add(st, "absolute", "absolute", 1, 1, RRDDIM_ABSOLUTE); + if(do_inc) rdinc = rrddim_add(st, "incremental", "incremental", 1, 1, RRDDIM_INCREMENTAL); + if(do_abst) rdabst = rrddim_add(st, "percentage-of-absolute-row", "percentage-of-absolute-row", 1, 1, RRDDIM_PCENT_OVER_ROW_TOTAL); + if(do_absi) rdabsi = rrddim_add(st, "percentage-of-incremental-row", "percentage-of-incremental-row", 1, 1, RRDDIM_PCENT_OVER_DIFF_TOTAL); + + long increment = 1000; + collected_number i = 0; + + unsigned long c, dimensions = 0; + RRDDIM *rd; + for(rd = st->dimensions ; rd ; rd = rd->next) dimensions++; + + for(c = 0; c < 20 ;c++) { + i += increment; + + fprintf(stderr, "\n\nLOOP = %lu, DELAY = %ld, VALUE = " COLLECTED_NUMBER_FORMAT "\n", c, delay, i); + if(c) { + rrdset_next_usec(st, delay); + } + if(do_abs) rrddim_set(st, "absolute", i); + if(do_inc) rrddim_set(st, "incremental", i); + if(do_abst) rrddim_set(st, "percentage-of-absolute-row", i); + if(do_absi) rrddim_set(st, "percentage-of-incremental-row", i); + + if(!c) { + gettimeofday(&st->last_collected_time, NULL); + st->last_collected_time.tv_usec = shift; + } + + // prevent it from deleting the dimensions + for(rd = st->dimensions ; rd ; rd = rd->next) + rd->last_collected_time.tv_sec = st->last_collected_time.tv_sec; + + rrdset_done(st); + } + + unsigned long oincrement = increment; + increment = increment * st->update_every * 1000000 / delay; + fprintf(stderr, "\n\nORIGINAL INCREMENT: %lu, INCREMENT %ld, DELAY %ld, SHIFT %ld\n", oincrement * 10, increment * 10, delay, shift); + + int ret = 0; + storage_number sn; + calculated_number cn, v; + for(c = 0 ; c < st->counter ; c++) { + fprintf(stderr, "\nPOSITION: c = %lu, EXPECTED VALUE %lu\n", c, (oincrement + c * increment + increment * (1000000 - shift) / 1000000 )* 10); + + for(rd = st->dimensions ; rd ; rd = rd->next) { + sn = rd->values[c]; + cn = unpack_storage_number(sn); + fprintf(stderr, "\t %s " CALCULATED_NUMBER_FORMAT " (PACKED AS " STORAGE_NUMBER_FORMAT ") -> ", rd->id, cn, sn); + + if(rd == rdabs) v = + ( oincrement + // + (increment * (1000000 - shift) / 1000000) + + (c + 1) * increment + ); + + else if(rd == rdinc) v = (c?(increment):(increment * (1000000 - shift) / 1000000)); + else if(rd == rdabst) v = oincrement / dimensions / 10; + else if(rd == rdabsi) v = oincrement / dimensions / 10; + else v = 0; + + if(v == cn) fprintf(stderr, "passed.\n"); + else { + fprintf(stderr, "ERROR! (expected " CALCULATED_NUMBER_FORMAT ")\n", v); + ret = 1; + } + } + } + + if(ret) + fprintf(stderr, "\n\nUNIT TEST(%ld, %ld) FAILED\n\n", delay, shift); + + return ret; } @@ -1,13 +1,4 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdlib.h> -#include <string.h> -#include <ctype.h> - #include "common.h" -#include "log.h" -#include "url.h" // ---------------------------------------------------------------------------- // URL encode / decode @@ -15,79 +6,72 @@ /* Converts a hex character to its integer value */ char from_hex(char ch) { - return (char)(isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10); + return (char)(isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10); } /* Converts an integer value to its hex character*/ char to_hex(char code) { - static char hex[] = "0123456789abcdef"; - return hex[code & 15]; + static char hex[] = "0123456789abcdef"; + return hex[code & 15]; } /* Returns a url-encoded version of str */ /* IMPORTANT: be sure to free() the returned string after use */ char *url_encode(char *str) { - char *buf, *pbuf; - - pbuf = buf = malloc(strlen(str) * 3 + 1); - - if(!buf) - fatal("Cannot allocate memory."); + char *buf, *pbuf; - while (*str) { - if (isalnum(*str) || *str == '-' || *str == '_' || *str == '.' || *str == '~') - *pbuf++ = *str; + pbuf = buf = mallocz(strlen(str) * 3 + 1); - else if (*str == ' ') - *pbuf++ = '+'; + while (*str) { + if (isalnum(*str) || *str == '-' || *str == '_' || *str == '.' || *str == '~') + *pbuf++ = *str; - else - *pbuf++ = '%', *pbuf++ = to_hex(*str >> 4), *pbuf++ = to_hex(*str & 15); + else if (*str == ' ') + *pbuf++ = '+'; - str++; - } - *pbuf = '\0'; + else + *pbuf++ = '%', *pbuf++ = to_hex(*str >> 4), *pbuf++ = to_hex(*str & 15); - // 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; + str++; + } + *pbuf = '\0'; - return buf; + pbuf = strdupz(buf); + freez(buf); + return pbuf; } /* Returns a url-decoded version of str */ /* IMPORTANT: be sure to free() the returned string after use */ char *url_decode(char *str) { - char *pstr = str, - *buf = malloc(strlen(str) + 1), - *pbuf = buf; + size_t size = strlen(str) + 1; - if(!buf) - fatal("Cannot allocate memory."); + char *buf = mallocz(size); + return url_decode_r(buf, str, size); +} - while (*pstr) { - if (*pstr == '%') { - if (pstr[1] && pstr[2]) { - *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]); - pstr += 2; - } - } - else if (*pstr == '+') - *pbuf++ = ' '; +char *url_decode_r(char *to, char *url, size_t size) { + char *s = url, // source + *d = to, // destination + *e = &to[size - 1]; // destination end - else - *pbuf++ = *pstr; + while(*s && d < e) { + if(unlikely(*s == '%')) { + if(likely(s[1] && s[2])) { + *d++ = from_hex(s[1]) << 4 | from_hex(s[2]); + s += 2; + } + } + else if(unlikely(*s == '+')) + *d++ = ' '; - pstr++; - } + else + *d++ = *s; - *pbuf = '\0'; + s++; + } - return buf; -} + *d = '\0'; + return to; +} @@ -19,4 +19,6 @@ extern char *url_encode(char *str); /* IMPORTANT: be sure to free() the returned string after use */ extern char *url_decode(char *str); +extern char *url_decode_r(char *to, char *url, size_t size); + #endif /* NETDATA_URL_H */ diff --git a/src/web_buffer.c b/src/web_buffer.c index a0f153721..01a97ddc9 100644 --- a/src/web_buffer.c +++ b/src/web_buffer.c @@ -1,23 +1,11 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <stdlib.h> -#include <string.h> - -#ifdef STORAGE_WITH_MATH -#include <math.h> -#endif - #include "common.h" -#include "web_buffer.h" -#include "log.h" #define BUFFER_OVERFLOW_EOF "EOF" static inline void buffer_overflow_init(BUFFER *b) { - b->buffer[b->size] = '\0'; - strcpy(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF); + b->buffer[b->size] = '\0'; + strcpy(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF); } #ifdef NETDATA_INTERNAL_CHECKS @@ -28,174 +16,224 @@ static inline void buffer_overflow_init(BUFFER *b) static inline void _buffer_overflow_check(BUFFER *b, const char *file, const char *function, const unsigned long line) { - if(b->len > b->size) { - error("BUFFER: length %ld is above size %ld, at line %lu, at function %s() of file '%s'.", b->len, b->size, line, function, file); - b->len = b->size; - } - - if(b->buffer[b->size] != '\0' || strcmp(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF)) { - error("BUFFER: detected overflow at line %lu, at function %s() of file '%s'.", line, function, file); - buffer_overflow_init(b); - } + if(b->len > b->size) { + error("BUFFER: length %zu is above size %zu, at line %lu, at function %s() of file '%s'.", b->len, b->size, line, function, file); + b->len = b->size; + } + + if(b->buffer[b->size] != '\0' || strcmp(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF)) { + error("BUFFER: detected overflow at line %lu, at function %s() of file '%s'.", line, function, file); + buffer_overflow_init(b); + } } void buffer_reset(BUFFER *wb) { - buffer_flush(wb); + buffer_flush(wb); - wb->contenttype = CT_TEXT_PLAIN; - wb->options = 0; - wb->date = 0; + wb->contenttype = CT_TEXT_PLAIN; + wb->options = 0; + wb->date = 0; - buffer_overflow_check(wb); + buffer_overflow_check(wb); } const char *buffer_tostring(BUFFER *wb) { - buffer_need_bytes(wb, 1); - wb->buffer[wb->len] = '\0'; + buffer_need_bytes(wb, 1); + wb->buffer[wb->len] = '\0'; - buffer_overflow_check(wb); + buffer_overflow_check(wb); - return(wb->buffer); + return(wb->buffer); } void buffer_char_replace(BUFFER *wb, char from, char to) { - char *s = wb->buffer, *end = &wb->buffer[wb->len]; + char *s = wb->buffer, *end = &wb->buffer[wb->len]; - while(s != end) { - if(*s == from) *s = to; - s++; - } + while(s != end) { + if(*s == from) *s = to; + s++; + } - buffer_overflow_check(wb); + buffer_overflow_check(wb); } +// This trick seems to give an 80% speed increase in 32bit systems +// print_calculated_number_llu_r() will just print the digits up to the +// point the remaining value fits in 32 bits, and then calls +// print_calculated_number_lu_r() to print the rest with 32 bit arithmetic. + +inline char *print_number_lu_r(char *str, unsigned long uvalue) { + char *wstr = str; + + // print each digit + do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10); + return wstr; +} + +inline char *print_number_llu_r(char *str, unsigned long long uvalue) { + char *wstr = str; + + // print each digit + do *wstr++ = (char)('0' + (uvalue % 10)); while((uvalue /= 10) && uvalue > (unsigned long long)0xffffffff); + if(uvalue) return print_number_lu_r(wstr, uvalue); + return wstr; +} + +void buffer_print_llu(BUFFER *wb, unsigned long long uvalue) +{ + buffer_need_bytes(wb, 50); + + char *str = &wb->buffer[wb->len]; + char *wstr = str; + +#ifdef ENVIRONMENT32 + if(uvalue > (unsigned long long)0xffffffff) + wstr = print_number_llu_r(wstr, uvalue); + else + wstr = print_number_lu_r(wstr, uvalue); +#else + do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10); +#endif + + // terminate it + *wstr = '\0'; + + // reverse it + char *begin = str, *end = wstr - 1, aux; + while (end > begin) aux = *end, *end-- = *begin, *begin++ = aux; + + // return the buffer length + wb->len += wstr - str; +} void buffer_strcat(BUFFER *wb, const char *txt) { - if(unlikely(!txt || !*txt)) return; - - buffer_need_bytes(wb, 1); - - char *s = &wb->buffer[wb->len], *start, *end = &wb->buffer[wb->size]; - long len = wb->len; - - start = s; - while(*txt && s != end) - *s++ = *txt++; - - len += s - start; - - wb->len = len; - buffer_overflow_check(wb); - - if(*txt) { - debug(D_WEB_BUFFER, "strcat(): increasing web_buffer at position %ld, size = %ld\n", wb->len, wb->size); - len = strlen(txt); - buffer_increase(wb, len); - buffer_strcat(wb, txt); - } - else { - // terminate the string - // without increasing the length - buffer_need_bytes(wb, (size_t)1); - wb->buffer[wb->len] = '\0'; - } + if(unlikely(!txt || !*txt)) return; + + buffer_need_bytes(wb, 1); + + char *s = &wb->buffer[wb->len], *start, *end = &wb->buffer[wb->size]; + size_t len = wb->len; + + start = s; + while(*txt && s != end) + *s++ = *txt++; + + len += s - start; + + wb->len = len; + buffer_overflow_check(wb); + + if(*txt) { + debug(D_WEB_BUFFER, "strcat(): increasing web_buffer at position %zu, size = %zu\n", wb->len, wb->size); + len = strlen(txt); + buffer_increase(wb, len); + buffer_strcat(wb, txt); + } + else { + // terminate the string + // without increasing the length + buffer_need_bytes(wb, (size_t)1); + wb->buffer[wb->len] = '\0'; + } } void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...) { - if(unlikely(!fmt || !*fmt)) return; + 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 += vsnprintfz(&wb->buffer[wb->len], len, fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + wb->len += vsnprintfz(&wb->buffer[wb->len], len, fmt, args); + va_end(args); - buffer_overflow_check(wb); + buffer_overflow_check(wb); - // the buffer is \0 terminated by vsnprintfz + // the buffer is \0 terminated by vsnprintfz } void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args) { - if(unlikely(!fmt || !*fmt)) return; + if(unlikely(!fmt || !*fmt)) return; - buffer_need_bytes(wb, 2); + buffer_need_bytes(wb, 2); - size_t len = wb->size - wb->len - 1; + size_t len = wb->size - wb->len - 1; - wb->len += vsnprintfz(&wb->buffer[wb->len], len, fmt, args); + wb->len += vsnprintfz(&wb->buffer[wb->len], len, fmt, args); - buffer_overflow_check(wb); + buffer_overflow_check(wb); - // the buffer is \0 terminated by vsnprintfz + // the buffer is \0 terminated by vsnprintfz } void buffer_sprintf(BUFFER *wb, const char *fmt, ...) { - if(unlikely(!fmt || !*fmt)) return; + if(unlikely(!fmt || !*fmt)) return; - buffer_need_bytes(wb, 2); + buffer_need_bytes(wb, 2); - size_t len = wb->size - wb->len - 1; - size_t wrote; + size_t len = wb->size - wb->len - 1; + size_t wrote; - va_list args; - va_start(args, fmt); - wrote = (size_t) vsnprintfz(&wb->buffer[wb->len], len, fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + wrote = (size_t) vsnprintfz(&wb->buffer[wb->len], len, fmt, args); + va_end(args); - if(unlikely(wrote >= len)) { - // there is bug in vsnprintf() and it returns - // a number higher to len, but it does not - // overflow the buffer. - // our buffer overflow detector will log it - // if it does. - buffer_overflow_check(wb); + if(unlikely(wrote >= len)) { + // truncated + buffer_overflow_check(wb); - debug(D_WEB_BUFFER, "web_buffer_sprintf(): increasing web_buffer at position %ld, size = %ld\n", wb->len, wb->size); - buffer_need_bytes(wb, len + WEB_DATA_LENGTH_INCREASE_STEP); + debug(D_WEB_BUFFER, "web_buffer_sprintf(): increasing web_buffer at position %zu, size = %zu\n", wb->len, wb->size); + buffer_need_bytes(wb, len + WEB_DATA_LENGTH_INCREASE_STEP); - va_start(args, fmt); - buffer_vsprintf(wb, fmt, args); - va_end(args); - } - else - wb->len += wrote; + va_start(args, fmt); + buffer_vsprintf(wb, fmt, args); + va_end(args); + } + else + wb->len += wrote; - // the buffer is \0 terminated by vsnprintf + // the buffer is \0 terminated by vsnprintf } void buffer_rrd_value(BUFFER *wb, calculated_number value) { - buffer_need_bytes(wb, 50); - wb->len += print_calculated_number(&wb->buffer[wb->len], value); + buffer_need_bytes(wb, 50); + + if(isnan(value) || isinf(value)) { + buffer_strcat(wb, "null"); + return; + } + else + wb->len += print_calculated_number(&wb->buffer[wb->len], value); - // terminate it - buffer_need_bytes(wb, 1); - wb->buffer[wb->len] = '\0'; + // terminate it + buffer_need_bytes(wb, 1); + wb->buffer[wb->len] = '\0'; - buffer_overflow_check(wb); + buffer_overflow_check(wb); } // 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 - // 01234567890123456789012345678901234 - // Date(2014,04,01,03,28,20) + // 01234567890123456789012345678901234 + // Date(2014,04,01,03,28,20) - buffer_need_bytes(wb, 30); + buffer_need_bytes(wb, 30); - char *b = &wb->buffer[wb->len], *p; + char *b = &wb->buffer[wb->len], *p; unsigned int *q = (unsigned int *)b; #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ @@ -234,23 +272,23 @@ void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minu *r++ = 0x2900; // ")\0" #endif - wb->len += (size_t)((char *)r - b - 1); + wb->len += (size_t)((char *)r - b - 1); - // terminate it - wb->buffer[wb->len] = '\0'; - buffer_overflow_check(wb); + // terminate it + wb->buffer[wb->len] = '\0'; + buffer_overflow_check(wb); } // generate a date, the fastest possible way... void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds) { - // 10 20 30 = 35 - // 01234567890123456789012345678901234 - // 2014-04-01 03:28:20 + // 10 20 30 = 35 + // 01234567890123456789012345678901234 + // 2014-04-01 03:28:20 - buffer_need_bytes(wb, 36); + buffer_need_bytes(wb, 36); - char *b = &wb->buffer[wb->len]; + char *b = &wb->buffer[wb->len]; char *p = b; *p++ = '0' + year / 1000; year %= 1000; @@ -274,68 +312,56 @@ void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minute *p++ = '0' + seconds % 10; *p = '\0'; - wb->len += (size_t)(p - b); + wb->len += (size_t)(p - b); - // terminate it - wb->buffer[wb->len] = '\0'; - buffer_overflow_check(wb); + // terminate it + wb->buffer[wb->len] = '\0'; + buffer_overflow_check(wb); } -BUFFER *buffer_create(long size) +BUFFER *buffer_create(size_t size) { - BUFFER *b; - - debug(D_WEB_BUFFER, "Creating new web buffer of size %d.", size); - - b = calloc(1, sizeof(BUFFER)); - if(!b) { - error("Cannot allocate a web_buffer."); - return NULL; - } - - b->buffer = malloc(size + sizeof(BUFFER_OVERFLOW_EOF) + 2); - if(!b->buffer) { - error("Cannot allocate a buffer of size %u.", size + sizeof(BUFFER_OVERFLOW_EOF) + 2); - free(b); - return NULL; - } - b->buffer[0] = '\0'; - b->size = size; - b->contenttype = CT_TEXT_PLAIN; - buffer_overflow_init(b); - buffer_overflow_check(b); - - return(b); + BUFFER *b; + + debug(D_WEB_BUFFER, "Creating new web buffer of size %zu.", size); + + b = callocz(1, sizeof(BUFFER)); + b->buffer = mallocz(size + sizeof(BUFFER_OVERFLOW_EOF) + 2); + b->buffer[0] = '\0'; + b->size = size; + b->contenttype = CT_TEXT_PLAIN; + buffer_overflow_init(b); + buffer_overflow_check(b); + + return(b); } void buffer_free(BUFFER *b) { - buffer_overflow_check(b); + buffer_overflow_check(b); - debug(D_WEB_BUFFER, "Freeing web buffer of size %d.", b->size); + debug(D_WEB_BUFFER, "Freeing web buffer of size %zu.", b->size); - if(b->buffer) free(b->buffer); - free(b); + freez(b->buffer); + freez(b); } void buffer_increase(BUFFER *b, size_t free_size_required) { - buffer_overflow_check(b); - - size_t left = b->size - b->len; + buffer_overflow_check(b); - if(left >= free_size_required) return; + size_t left = b->size - b->len; - size_t increase = free_size_required - left; - if(increase < WEB_DATA_LENGTH_INCREASE_STEP) increase = WEB_DATA_LENGTH_INCREASE_STEP; + if(left >= free_size_required) return; - debug(D_WEB_BUFFER, "Increasing data buffer from size %d to %d.", b->size, b->size + increase); + size_t increase = free_size_required - left; + if(increase < WEB_DATA_LENGTH_INCREASE_STEP) increase = WEB_DATA_LENGTH_INCREASE_STEP; - b->buffer = realloc(b->buffer, b->size + increase + sizeof(BUFFER_OVERFLOW_EOF) + 2); - if(!b->buffer) fatal("Failed to increase data buffer from size %d to %d.", b->size + sizeof(BUFFER_OVERFLOW_EOF) + 2, b->size + increase + sizeof(BUFFER_OVERFLOW_EOF) + 2); + debug(D_WEB_BUFFER, "Increasing data buffer from size %zu to %zu.", b->size, b->size + increase); - b->size += increase; + b->buffer = reallocz(b->buffer, b->size + increase + sizeof(BUFFER_OVERFLOW_EOF) + 2); + b->size += increase; - buffer_overflow_init(b); - buffer_overflow_check(b); + buffer_overflow_init(b); + buffer_overflow_check(b); } diff --git a/src/web_buffer.h b/src/web_buffer.h index 73533f499..c4cd05632 100644 --- a/src/web_buffer.h +++ b/src/web_buffer.h @@ -1,47 +1,43 @@ -#include <stdarg.h> -#include <time.h> -#include "storage_number.h" - #ifndef NETDATA_WEB_BUFFER_H #define NETDATA_WEB_BUFFER_H 1 -#define WEB_DATA_LENGTH_INCREASE_STEP 16384 +#define WEB_DATA_LENGTH_INCREASE_STEP 1024 typedef struct web_buffer { - size_t size; // allocation size of buffer - size_t len; // current data length in buffer - char *buffer; // the buffer - uint8_t contenttype; - uint8_t options; - time_t date; // the date this content has been generated + size_t size; // allocation size of buffer + size_t len; // current data length in buffer + char *buffer; // the buffer + uint8_t contenttype; + uint8_t options; + time_t date; // the date this content has been generated } BUFFER; // options -#define WB_CONTENT_CACHEABLE 1 -#define WB_CONTENT_NO_CACHEABLE 2 +#define WB_CONTENT_CACHEABLE 1 +#define WB_CONTENT_NO_CACHEABLE 2 // content-types -#define CT_APPLICATION_JSON 1 -#define CT_TEXT_PLAIN 2 -#define CT_TEXT_HTML 3 -#define CT_APPLICATION_X_JAVASCRIPT 4 -#define CT_TEXT_CSS 5 -#define CT_TEXT_XML 6 -#define CT_APPLICATION_XML 7 -#define CT_TEXT_XSL 8 -#define CT_APPLICATION_OCTET_STREAM 9 -#define CT_APPLICATION_X_FONT_TRUETYPE 10 -#define CT_APPLICATION_X_FONT_OPENTYPE 11 -#define CT_APPLICATION_FONT_WOFF 12 -#define CT_APPLICATION_FONT_WOFF2 13 -#define CT_APPLICATION_VND_MS_FONTOBJ 14 -#define CT_IMAGE_SVG_XML 15 -#define CT_IMAGE_PNG 16 -#define CT_IMAGE_JPG 17 -#define CT_IMAGE_GIF 18 -#define CT_IMAGE_XICON 19 -#define CT_IMAGE_ICNS 20 -#define CT_IMAGE_BMP 21 +#define CT_APPLICATION_JSON 1 +#define CT_TEXT_PLAIN 2 +#define CT_TEXT_HTML 3 +#define CT_APPLICATION_X_JAVASCRIPT 4 +#define CT_TEXT_CSS 5 +#define CT_TEXT_XML 6 +#define CT_APPLICATION_XML 7 +#define CT_TEXT_XSL 8 +#define CT_APPLICATION_OCTET_STREAM 9 +#define CT_APPLICATION_X_FONT_TRUETYPE 10 +#define CT_APPLICATION_X_FONT_OPENTYPE 11 +#define CT_APPLICATION_FONT_WOFF 12 +#define CT_APPLICATION_FONT_WOFF2 13 +#define CT_APPLICATION_VND_MS_FONTOBJ 14 +#define CT_IMAGE_SVG_XML 15 +#define CT_IMAGE_PNG 16 +#define CT_IMAGE_JPG 17 +#define CT_IMAGE_GIF 18 +#define CT_IMAGE_XICON 19 +#define CT_IMAGE_ICNS 20 +#define CT_IMAGE_BMP 21 #define buffer_strlen(wb) ((wb)->len) extern const char *buffer_tostring(BUFFER *wb); @@ -57,14 +53,19 @@ extern void buffer_rrd_value(BUFFER *wb, calculated_number value); extern void buffer_date(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds); extern void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds); -extern BUFFER *buffer_create(long size); +extern BUFFER *buffer_create(size_t size); extern void buffer_free(BUFFER *b); extern void buffer_increase(BUFFER *b, size_t free_size_required); -extern void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...); +extern void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...) __attribute__ (( format (printf, 3, 4))); extern void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args); -extern void buffer_sprintf(BUFFER *wb, const char *fmt, ...); +extern void buffer_sprintf(BUFFER *wb, const char *fmt, ...) __attribute__ (( format (printf, 2, 3))); extern void buffer_char_replace(BUFFER *wb, char from, char to); +extern char *print_number_lu_r(char *str, unsigned long uvalue); +extern char *print_number_llu_r(char *str, unsigned long long uvalue); + +extern void buffer_print_llu(BUFFER *wb, unsigned long long uvalue); + #endif /* NETDATA_WEB_BUFFER_H */ diff --git a/src/web_buffer_svg.c b/src/web_buffer_svg.c new file mode 100644 index 000000000..2f7627cc5 --- /dev/null +++ b/src/web_buffer_svg.c @@ -0,0 +1,616 @@ +#include "common.h" + +#define BADGE_HORIZONTAL_PADDING 4 +#define VERDANA_KERNING 0.2 +#define VERDANA_PADDING 1.0 + +/* + * verdana11_widths[] has been generated with this method: + * https://github.com/badges/shields/blob/master/measure-text.js +*/ + +double verdana11_widths[256] = { + [0] = 0.0, + [1] = 0.0, + [2] = 0.0, + [3] = 0.0, + [4] = 0.0, + [5] = 0.0, + [6] = 0.0, + [7] = 0.0, + [8] = 0.0, + [9] = 0.0, + [10] = 0.0, + [11] = 0.0, + [12] = 0.0, + [13] = 0.0, + [14] = 0.0, + [15] = 0.0, + [16] = 0.0, + [17] = 0.0, + [18] = 0.0, + [19] = 0.0, + [20] = 0.0, + [21] = 0.0, + [22] = 0.0, + [23] = 0.0, + [24] = 0.0, + [25] = 0.0, + [26] = 0.0, + [27] = 0.0, + [28] = 0.0, + [29] = 0.0, + [30] = 0.0, + [31] = 0.0, + [32] = 3.8671874999999996, // + [33] = 4.3291015625, // ! + [34] = 5.048828125, // " + [35] = 9.001953125, // # + [36] = 6.9931640625, // $ + [37] = 11.837890625, // % + [38] = 7.992187499999999, // & + [39] = 2.9541015625, // ' + [40] = 4.9951171875, // ( + [41] = 4.9951171875, // ) + [42] = 6.9931640625, // * + [43] = 9.001953125, // + + [44] = 4.00146484375, // , + [45] = 4.9951171875, // - + [46] = 4.00146484375, // . + [47] = 4.9951171875, // / + [48] = 6.9931640625, // 0 + [49] = 6.9931640625, // 1 + [50] = 6.9931640625, // 2 + [51] = 6.9931640625, // 3 + [52] = 6.9931640625, // 4 + [53] = 6.9931640625, // 5 + [54] = 6.9931640625, // 6 + [55] = 6.9931640625, // 7 + [56] = 6.9931640625, // 8 + [57] = 6.9931640625, // 9 + [58] = 4.9951171875, // : + [59] = 4.9951171875, // ; + [60] = 9.001953125, // < + [61] = 9.001953125, // = + [62] = 9.001953125, // > + [63] = 5.99951171875, // ? + [64] = 11.0, // @ + [65] = 7.51953125, // A + [66] = 7.541015625, // B + [67] = 7.680664062499999, // C + [68] = 8.4755859375, // D + [69] = 6.95556640625, // E + [70] = 6.32177734375, // F + [71] = 8.529296875, // G + [72] = 8.26611328125, // H + [73] = 4.6298828125, // I + [74] = 5.00048828125, // J + [75] = 7.62158203125, // K + [76] = 6.123046875, // L + [77] = 9.2705078125, // M + [78] = 8.228515625, // N + [79] = 8.658203125, // O + [80] = 6.63330078125, // P + [81] = 8.658203125, // Q + [82] = 7.6484375, // R + [83] = 7.51953125, // S + [84] = 6.7783203125, // T + [85] = 8.05126953125, // U + [86] = 7.51953125, // V + [87] = 10.87646484375, // W + [88] = 7.53564453125, // X + [89] = 6.767578125, // Y + [90] = 7.53564453125, // Z + [91] = 4.9951171875, // [ + [92] = 4.9951171875, // backslash + [93] = 4.9951171875, // ] + [94] = 9.001953125, // ^ + [95] = 6.9931640625, // _ + [96] = 6.9931640625, // ` + [97] = 6.6064453125, // a + [98] = 6.853515625, // b + [99] = 5.73095703125, // c + [100] = 6.853515625, // d + [101] = 6.552734375, // e + [102] = 3.8671874999999996, // f + [103] = 6.853515625, // g + [104] = 6.9609375, // h + [105] = 3.0185546875, // i + [106] = 3.78662109375, // j + [107] = 6.509765625, // k + [108] = 3.0185546875, // l + [109] = 10.69921875, // m + [110] = 6.9609375, // n + [111] = 6.67626953125, // o + [112] = 6.853515625, // p + [113] = 6.853515625, // q + [114] = 4.6943359375, // r + [115] = 5.73095703125, // s + [116] = 4.33447265625, // t + [117] = 6.9609375, // u + [118] = 6.509765625, // v + [119] = 9.001953125, // w + [120] = 6.509765625, // x + [121] = 6.509765625, // y + [122] = 5.779296875, // z + [123] = 6.982421875, // { + [124] = 4.9951171875, // | + [125] = 6.982421875, // } + [126] = 9.001953125, // ~ + [127] = 0.0, + [128] = 0.0, + [129] = 0.0, + [130] = 0.0, + [131] = 0.0, + [132] = 0.0, + [133] = 0.0, + [134] = 0.0, + [135] = 0.0, + [136] = 0.0, + [137] = 0.0, + [138] = 0.0, + [139] = 0.0, + [140] = 0.0, + [141] = 0.0, + [142] = 0.0, + [143] = 0.0, + [144] = 0.0, + [145] = 0.0, + [146] = 0.0, + [147] = 0.0, + [148] = 0.0, + [149] = 0.0, + [150] = 0.0, + [151] = 0.0, + [152] = 0.0, + [153] = 0.0, + [154] = 0.0, + [155] = 0.0, + [156] = 0.0, + [157] = 0.0, + [158] = 0.0, + [159] = 0.0, + [160] = 0.0, + [161] = 0.0, + [162] = 0.0, + [163] = 0.0, + [164] = 0.0, + [165] = 0.0, + [166] = 0.0, + [167] = 0.0, + [168] = 0.0, + [169] = 0.0, + [170] = 0.0, + [171] = 0.0, + [172] = 0.0, + [173] = 0.0, + [174] = 0.0, + [175] = 0.0, + [176] = 0.0, + [177] = 0.0, + [178] = 0.0, + [179] = 0.0, + [180] = 0.0, + [181] = 0.0, + [182] = 0.0, + [183] = 0.0, + [184] = 0.0, + [185] = 0.0, + [186] = 0.0, + [187] = 0.0, + [188] = 0.0, + [189] = 0.0, + [190] = 0.0, + [191] = 0.0, + [192] = 0.0, + [193] = 0.0, + [194] = 0.0, + [195] = 0.0, + [196] = 0.0, + [197] = 0.0, + [198] = 0.0, + [199] = 0.0, + [200] = 0.0, + [201] = 0.0, + [202] = 0.0, + [203] = 0.0, + [204] = 0.0, + [205] = 0.0, + [206] = 0.0, + [207] = 0.0, + [208] = 0.0, + [209] = 0.0, + [210] = 0.0, + [211] = 0.0, + [212] = 0.0, + [213] = 0.0, + [214] = 0.0, + [215] = 0.0, + [216] = 0.0, + [217] = 0.0, + [218] = 0.0, + [219] = 0.0, + [220] = 0.0, + [221] = 0.0, + [222] = 0.0, + [223] = 0.0, + [224] = 0.0, + [225] = 0.0, + [226] = 0.0, + [227] = 0.0, + [228] = 0.0, + [229] = 0.0, + [230] = 0.0, + [231] = 0.0, + [232] = 0.0, + [233] = 0.0, + [234] = 0.0, + [235] = 0.0, + [236] = 0.0, + [237] = 0.0, + [238] = 0.0, + [239] = 0.0, + [240] = 0.0, + [241] = 0.0, + [242] = 0.0, + [243] = 0.0, + [244] = 0.0, + [245] = 0.0, + [246] = 0.0, + [247] = 0.0, + [248] = 0.0, + [249] = 0.0, + [250] = 0.0, + [251] = 0.0, + [252] = 0.0, + [253] = 0.0, + [254] = 0.0, + [255] = 0.0 +}; + +// find the width of the string using the verdana 11points font +// re-write the string in place, skiping zero-length characters +static inline int verdana11_width(char *s) { + double w = 0.0; + char *d = s; + + while(*s) { + double t = verdana11_widths[(unsigned char)*s]; + if(t == 0.0) + s++; + else { + w += t + VERDANA_KERNING; + if(d != s) + *d++ = *s++; + else + d = ++s; + } + } + + *d = '\0'; + w -= VERDANA_KERNING; + w += VERDANA_PADDING; + return ceil(w); +} + +static inline size_t escape_xmlz(char *dst, const char *src, size_t len) { + size_t i = len; + + // required escapes from + // https://github.com/badges/shields/blob/master/badge.js + while(*src && i) { + switch(*src) { + case '\\': + *dst++ = '/'; + src++; + i--; + break; + + case '&': + if(i > 5) { + strcpy(dst, "&"); + i -= 5; + dst += 5; + src++; + } + else goto cleanup; + break; + + case '<': + if(i > 4) { + strcpy(dst, "<"); + i -= 4; + dst += 4; + src++; + } + else goto cleanup; + break; + + case '>': + if(i > 4) { + strcpy(dst, ">"); + i -= 4; + dst += 4; + src++; + } + else goto cleanup; + break; + + case '"': + if(i > 6) { + strcpy(dst, """); + i -= 6; + dst += 6; + src++; + } + else goto cleanup; + break; + + case '\'': + if(i > 6) { + strcpy(dst, "'"); + i -= 6; + dst += 6; + src++; + } + else goto cleanup; + break; + + default: + i--; + *dst++ = *src++; + break; + } + } + +cleanup: + *dst = '\0'; + return len - i; +} + +static inline const char *fix_units(const char *units) { + if(!units || !*units || !strcmp(units, "empty") || !strcmp(units, "null")) return ""; + if(!strcmp(units, "percentage") || !strcmp(units, "percent") || !strcmp(units, "pcent")) return "%"; + return units; +} + +static inline const char *color_map(const char *color) { + // colors from: + // https://github.com/badges/shields/blob/master/colorscheme.json + if(!strcmp(color, "brightgreen")) return "#4c1"; + else if(!strcmp(color, "green")) return "#97CA00"; + else if(!strcmp(color, "yellow")) return "#dfb317"; + else if(!strcmp(color, "yellowgreen")) return "#a4a61d"; + else if(!strcmp(color, "orange")) return "#fe7d37"; + else if(!strcmp(color, "red")) return "#e05d44"; + else if(!strcmp(color, "blue")) return "#007ec6"; + else if(!strcmp(color, "grey")) return "#555"; + else if(!strcmp(color, "gray")) return "#555"; + else if(!strcmp(color, "lightgrey")) return "#9f9f9f"; + else if(!strcmp(color, "lightgray")) return "#9f9f9f"; + return color; +} + +static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value, int value_is_null) { + char color_buffer[256 + 1] = ""; + char value_buffer[256 + 1] = ""; + char comparison = '>'; + + // example input: + // color<max|color>min|color:null... + + const char *c = color; + while(*c) { + char *dc = color_buffer, *dv = NULL; + size_t ci = 0, vi = 0; + + const char *t = c; + + while(*t && *t != '|') { + switch(*t) { + case ':': + comparison = '='; + dv = value_buffer; + break; + + case '}': + case ')': + case '>': + if(t[1] == '=') { + comparison = ')'; + t++; + } + else + comparison = '>'; + dv = value_buffer; + break; + + case '{': + case '(': + case '<': + if(t[1] == '=') { + comparison = '('; + t++; + } + else + comparison = '<'; + dv = value_buffer; + break; + + default: + if(dv) { + if(vi < 256) { + vi++; + *dv++ = *t; + } + } + else { + if(ci < 256) { + ci++; + *dc++ = *t; + } + } + break; + } + + t++; + } + + // prepare for next iteration + if(*t == '|') t++; + c = t; + + // do the math + *dc = '\0'; + if(dv) { + *dv = '\0'; + + if(value_is_null) { + if(!*value_buffer || !strcmp(value_buffer, "null")) + break; + } + else { + calculated_number v = strtold(value_buffer, NULL); + + if(comparison == '<' && value < v) break; + else if(comparison == '(' && value <= v) break; + else if(comparison == '>' && value > v) break; + else if(comparison == ')' && value >= v) break; + else if(comparison == '=' && value == v) break; + } + } + else + break; + } + + const char *b; + if(color_buffer[0]) + b = color_buffer; + else + b = color; + + strncpyz(final, b, len); +} + +// value + units +#define VALUE_STRING_SIZE 100 + +// label +#define LABEL_STRING_SIZE 200 + +// colors +#define COLOR_STRING_SIZE 100 + +void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int value_is_null, int precision) { + char label_buffer[LABEL_STRING_SIZE + 1] + , value_color_buffer[COLOR_STRING_SIZE + 1] + , value_string[VALUE_STRING_SIZE + 1] + , label_escaped[LABEL_STRING_SIZE + 1] + , value_escaped[VALUE_STRING_SIZE + 1] + , label_color_escaped[COLOR_STRING_SIZE + 1] + , value_color_escaped[COLOR_STRING_SIZE + 1]; + + int label_width, value_width, total_width; + + if(unlikely(!label_color || !*label_color)) + label_color = "#555"; + + if(unlikely(!value_color || !*value_color)) + value_color = (value_is_null)?"#999":"#4c1"; + + units = fix_units(units); + calc_colorz(value_color, value_color_buffer, COLOR_STRING_SIZE, value, value_is_null); + + char *separator = ""; + if(unlikely(isalnum(*units))) + separator = " "; + + if(unlikely(value_is_null)) + strcpy(value_string, "-"); + + else if(precision < 0) { + int len, l, lstop = 0; + + calculated_number abs = value; + if(isless(value, 0)) { + lstop = 1; + abs = -value; + } + + if(isgreaterequal(abs, 1000)) len = snprintfz(value_string, VALUE_STRING_SIZE, "%0.0Lf", (long double)value); + else if(isgreaterequal(abs, 100)) len = snprintfz(value_string, VALUE_STRING_SIZE, "%0.1Lf", (long double)value); + else if(isgreaterequal(abs, 1)) len = snprintfz(value_string, VALUE_STRING_SIZE, "%0.2Lf", (long double)value); + else if(isgreaterequal(abs, 0.1)) len = snprintfz(value_string, VALUE_STRING_SIZE, "%0.3Lf", (long double)value); + else len = snprintfz(value_string, VALUE_STRING_SIZE, "%0.4Lf", (long double)value); + + // remove trailing zeros + for(l = len - 1; l > lstop ; l--) { + if(likely(value_string[l] == '0')) { + value_string[l] = '\0'; + len--; + } + + else if(unlikely(value_string[l] == '.')) { + value_string[l] = '\0'; + len--; + break; + } + + else + break; + } + + if(len >= 0) + snprintfz(&value_string[len], VALUE_STRING_SIZE - len, "%s%s", separator, units); + } + else { + if(precision > 50) precision = 50; + snprintfz(value_string, VALUE_STRING_SIZE, "%0.*Lf%s%s", precision, (long double)value, separator, units); + } + + // we need to copy the label, since verdana11_width may write to it + strncpyz(label_buffer, label, LABEL_STRING_SIZE); + + label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2); + value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2); + total_width = label_width + value_width; + + escape_xmlz(label_escaped, label_buffer, LABEL_STRING_SIZE); + escape_xmlz(value_escaped, value_string, VALUE_STRING_SIZE); + escape_xmlz(label_color_escaped, color_map(label_color), COLOR_STRING_SIZE); + escape_xmlz(value_color_escaped, color_map(value_color_buffer), COLOR_STRING_SIZE); + + wb->contenttype = CT_IMAGE_SVG_XML; + + // svg template from: + // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg + buffer_sprintf(wb, + "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"20\">" + "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">" + "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>" + "<stop offset=\"1\" stop-opacity=\".1\"/>" + "</linearGradient>" + "<mask id=\"round\">" + "<rect width=\"%d\" height=\"20\" rx=\"3\" fill=\"#fff\"/>" + "</mask>" + "<g mask=\"url(#round)\">" + "<rect width=\"%d\" height=\"20\" fill=\"%s\"/>" + "<rect x=\"%d\" width=\"%d\" height=\"20\" fill=\"%s\"/>" + "<rect width=\"%d\" height=\"20\" fill=\"url(#smooth)\"/>" + "</g>" + "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">" + "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>" + "<text x=\"%d\" y=\"14\">%s</text>" + "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>" + "<text x=\"%d\" y=\"14\">%s</text>" + "</g>" + "</svg>", + total_width, total_width, + label_width, label_color_escaped, + label_width, value_width, value_color_escaped, + total_width, + label_width / 2, label_escaped, + label_width / 2, label_escaped, + label_width + value_width / 2 -1, value_escaped, + label_width + value_width / 2 -1, value_escaped); +} diff --git a/src/web_buffer_svg.h b/src/web_buffer_svg.h new file mode 100644 index 000000000..1281847eb --- /dev/null +++ b/src/web_buffer_svg.h @@ -0,0 +1,6 @@ +#ifndef NETDATA_WEB_BUFFER_SVG_H +#define NETDATA_WEB_BUFFER_SVG_H 1 + +extern void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int value_is_null, int precision); + +#endif /* NETDATA_WEB_BUFFER_SVG_H */ diff --git a/src/web_client.c b/src/web_client.c index 601dda083..4036d4c81 100644 --- a/src/web_client.c +++ b/src/web_client.c @@ -1,2032 +1,2537 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <unistd.h> -#include <stdlib.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <errno.h> -#include <pthread.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <netinet/tcp.h> -#include <malloc.h> -#include <pwd.h> -#include <grp.h> -#include <ctype.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "url.h" -#include "web_buffer.h" -#include "web_server.h" -#include "global_statistics.h" -#include "rrd.h" -#include "rrd2json.h" -#include "registry.h" - -#include "web_client.h" -#include "../config.h" #define INITIAL_WEB_DATA_LENGTH 16384 #define WEB_REQUEST_LENGTH 16384 #define TOO_BIG_REQUEST 16384 int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS; -int web_enable_gzip = 1; +int web_donotrack_comply = 0; -extern int netdata_exit; +#ifdef NETDATA_WITH_ZLIB +int web_enable_gzip = 1, web_gzip_level = 3, web_gzip_strategy = Z_DEFAULT_STRATEGY; +#endif /* NETDATA_WITH_ZLIB */ struct web_client *web_clients = NULL; unsigned long long web_clients_count = 0; +inline int web_client_crock_socket(struct web_client *w) { +#ifdef TCP_CORK + if(likely(!w->tcp_cork && w->ofd != -1)) { + w->tcp_cork = 1; + if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) { + error("%llu: failed to enable TCP_CORK on socket.", w->id); + w->tcp_cork = 0; + return -1; + } + } +#endif /* TCP_CORK */ + + return 0; +} + +inline int web_client_uncrock_socket(struct web_client *w) { +#ifdef TCP_CORK + if(likely(w->tcp_cork && w->ofd != -1)) { + w->tcp_cork = 0; + if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) { + error("%llu: failed to disable TCP_CORK on socket.", w->id); + w->tcp_cork = 1; + return -1; + } + } +#endif /* TCP_CORK */ + + return 0; +} + struct web_client *web_client_create(int listener) { - struct web_client *w; - - w = calloc(1, sizeof(struct web_client)); - if(!w) { - error("Cannot allocate new web_client memory."); - return NULL; - } - - w->id = ++web_clients_count; - w->mode = WEB_CLIENT_MODE_NORMAL; - - { - struct sockaddr *sadr; - socklen_t addrlen; - - sadr = (struct sockaddr*) &w->clientaddr; - addrlen = sizeof(w->clientaddr); - - w->ifd = accept(listener, sadr, &addrlen); - if (w->ifd == -1) { - error("%llu: Cannot accept new incoming connection.", w->id); - free(w); - return NULL; - } - w->ofd = w->ifd; - - 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."); - 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'; - - switch(sadr->sa_family) { - - case AF_INET: - debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv4 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); - break; - - case AF_INET6: - if(strncmp(w->client_ip, "::ffff:", 7) == 0) { - strcpy(w->client_ip, &w->client_ip[7]); - debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv4 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); - } - debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv6 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); - break; - - default: - debug(D_WEB_CLIENT_ACCESS, "%llu: New UNKNOWN web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); - break; - } - - int flag = 1; - if(setsockopt(w->ifd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int)) != 0) error("%llu: Cannot set SO_KEEPALIVE on socket.", w->id); - } - - w->response.data = buffer_create(INITIAL_WEB_DATA_LENGTH); - if(unlikely(!w->response.data)) { - // no need for error log - web_buffer_create already logged the error - close(w->ifd); - free(w); - return NULL; - } - - w->response.header = buffer_create(HTTP_RESPONSE_HEADER_SIZE); - if(unlikely(!w->response.header)) { - // no need for error log - web_buffer_create already logged the error - buffer_free(w->response.data); - close(w->ifd); - free(w); - return NULL; - } - - w->response.header_output = buffer_create(HTTP_RESPONSE_HEADER_SIZE); - if(unlikely(!w->response.header_output)) { - // no need for error log - web_buffer_create already logged the error - buffer_free(w->response.header); - buffer_free(w->response.data); - close(w->ifd); - free(w); - return NULL; - } - - w->origin[0] = '*'; - w->wait_receive = 1; - - if(web_clients) web_clients->prev = w; - w->next = web_clients; - web_clients = w; - - global_statistics.connected_clients++; - - return(w); + struct web_client *w; + + w = callocz(1, sizeof(struct web_client)); + w->id = ++web_clients_count; + w->mode = WEB_CLIENT_MODE_NORMAL; + + { + struct sockaddr *sadr; + socklen_t addrlen; + + sadr = (struct sockaddr*) &w->clientaddr; + addrlen = sizeof(w->clientaddr); + + w->ifd = accept4(listener, sadr, &addrlen, SOCK_NONBLOCK); + if (w->ifd == -1) { + error("%llu: Cannot accept new incoming connection.", w->id); + freez(w); + return NULL; + } + w->ofd = w->ifd; + + 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."); + 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'; + + switch(sadr->sa_family) { + case AF_INET: + debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv4 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); + break; + + case AF_INET6: + if(strncmp(w->client_ip, "::ffff:", 7) == 0) { + memmove(w->client_ip, &w->client_ip[7], strlen(&w->client_ip[7]) + 1); + debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv4 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); + } + else + debug(D_WEB_CLIENT_ACCESS, "%llu: New IPv6 web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); + break; + + default: + debug(D_WEB_CLIENT_ACCESS, "%llu: New UNKNOWN web client from %s port %s on socket %d.", w->id, w->client_ip, w->client_port, w->ifd); + break; + } + + int flag = 1; + if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) + error("%llu: failed to enable TCP_NODELAY on socket.", w->id); + + flag = 1; + if(setsockopt(w->ifd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int)) != 0) + error("%llu: Cannot set SO_KEEPALIVE on socket.", w->id); + } + + w->response.data = buffer_create(INITIAL_WEB_DATA_LENGTH); + w->response.header = buffer_create(HTTP_RESPONSE_HEADER_SIZE); + w->response.header_output = buffer_create(HTTP_RESPONSE_HEADER_SIZE); + w->origin[0] = '*'; + w->wait_receive = 1; + + if(web_clients) web_clients->prev = w; + w->next = web_clients; + web_clients = w; + + web_client_connected(); + + return(w); } -void web_client_reset(struct web_client *w) -{ - struct timeval tv; - gettimeofday(&tv, NULL); +void web_client_reset(struct web_client *w) { + web_client_uncrock_socket(w); + + debug(D_WEB_CLIENT, "%llu: Reseting client.", w->id); - long sent = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len; + if(likely(w->last_url[0])) { + struct timeval tv; + gettimeofday(&tv, NULL); + size_t size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len; + size_t sent = size; #ifdef NETDATA_WITH_ZLIB - if(likely(w->response.zoutput)) sent = (long)w->response.zstream.total_out; + if(likely(w->response.zoutput)) sent = (size_t)w->response.zstream.total_out; #endif - long size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len; - - if(likely(w->last_url[0])) - log_access("%llu: (sent/all = %ld/%ld bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %s: %d '%s'", - w->id, - sent, size, -((size>0)?((float)(size-sent)/(float)size * 100.0):0.0), - (float)usecdiff(&w->tv_ready, &w->tv_in) / 1000.0, - (float)usecdiff(&tv, &w->tv_ready) / 1000.0, - (float)usecdiff(&tv, &w->tv_in) / 1000.0, - (w->mode == WEB_CLIENT_MODE_FILECOPY)?"filecopy":((w->mode == WEB_CLIENT_MODE_OPTIONS)?"options":"data"), - w->response.code, - w->last_url - ); - - debug(D_WEB_CLIENT, "%llu: Reseting client.", w->id); - - if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) { - debug(D_WEB_CLIENT, "%llu: Closing filecopy input file.", w->id); - close(w->ifd); - w->ifd = w->ofd; - } - - 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); - buffer_reset(w->response.data); - w->response.rlen = 0; - w->response.sent = 0; - w->response.code = 0; - - w->wait_receive = 1; - w->wait_send = 0; - - w->response.zoutput = 0; - - // if we had enabled compression, release it + // -------------------------------------------------------------------- + // global statistics + + finished_web_request_statistics(usec_dt(&tv, &w->tv_in), + w->stats_received_bytes, + w->stats_sent_bytes, + size, + sent); + + w->stats_received_bytes = 0; + w->stats_sent_bytes = 0; + + + // -------------------------------------------------------------------- + // access log + + log_access("%llu: (sent/all = %zu/%zu bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %s: %d '%s'", + w->id, + sent, size, -((size > 0) ? ((size - sent) / (double) size * 100.0) : 0.0), + usec_dt(&w->tv_ready, &w->tv_in) / 1000.0, + usec_dt(&tv, &w->tv_ready) / 1000.0, + usec_dt(&tv, &w->tv_in) / 1000.0, + (w->mode == WEB_CLIENT_MODE_FILECOPY) ? "filecopy" : ((w->mode == WEB_CLIENT_MODE_OPTIONS) + ? "options" : "data"), + w->response.code, + w->last_url + ); + } + + if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) { + if(w->ifd != w->ofd) { + debug(D_WEB_CLIENT, "%llu: Closing filecopy input file descriptor %d.", w->id, w->ifd); + if(w->ifd != -1) close(w->ifd); + w->ifd = w->ofd; + } + } + + 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->tcp_cork = 0; + w->donottrack = 0; + w->tracking_required = 0; + w->keepalive = 0; + w->decoded_url[0] = '\0'; + + buffer_reset(w->response.header_output); + buffer_reset(w->response.header); + buffer_reset(w->response.data); + w->response.rlen = 0; + w->response.sent = 0; + w->response.code = 0; + + w->wait_receive = 1; + w->wait_send = 0; + + w->response.zoutput = 0; + + // if we had enabled compression, release it #ifdef NETDATA_WITH_ZLIB - if(w->response.zinitialized) { - debug(D_DEFLATE, "%llu: Reseting compression.", w->id); - deflateEnd(&w->response.zstream); - w->response.zsent = 0; - w->response.zhave = 0; - w->response.zstream.avail_in = 0; - w->response.zstream.avail_out = 0; - w->response.zstream.total_in = 0; - w->response.zstream.total_out = 0; - w->response.zinitialized = 0; - } + if(w->response.zinitialized) { + debug(D_DEFLATE, "%llu: Freeing compression resources.", w->id); + deflateEnd(&w->response.zstream); + w->response.zsent = 0; + w->response.zhave = 0; + w->response.zstream.avail_in = 0; + w->response.zstream.avail_out = 0; + w->response.zstream.total_in = 0; + w->response.zstream.total_out = 0; + w->response.zinitialized = 0; + } #endif // NETDATA_WITH_ZLIB } -struct web_client *web_client_free(struct web_client *w) -{ - struct web_client *n = w->next; - - debug(D_WEB_CLIENT_ACCESS, "%llu: Closing web client from %s port %s.", w->id, w->client_ip, w->client_port); +struct web_client *web_client_free(struct web_client *w) { + web_client_reset(w); - if(w->prev) w->prev->next = w->next; - if(w->next) w->next->prev = w->prev; + struct web_client *n = w->next; + if(w == web_clients) web_clients = n; - if(w == web_clients) web_clients = w->next; + debug(D_WEB_CLIENT_ACCESS, "%llu: Closing web client from %s port %s.", w->id, w->client_ip, w->client_port); - if(w->response.header_output) buffer_free(w->response.header_output); - if(w->response.header) buffer_free(w->response.header); - if(w->response.data) buffer_free(w->response.data); - close(w->ifd); - if(w->ofd != w->ifd) close(w->ofd); - free(w); + if(w->prev) w->prev->next = w->next; + if(w->next) w->next->prev = w->prev; + if(w->response.header_output) buffer_free(w->response.header_output); + if(w->response.header) buffer_free(w->response.header); + if(w->response.data) buffer_free(w->response.data); + if(w->ifd != -1) close(w->ifd); + if(w->ofd != -1 && w->ofd != w->ifd) close(w->ofd); + freez(w); - global_statistics.connected_clients--; + web_client_disconnected(); - return(n); + return(n); } -uid_t web_files_uid(void) -{ - static char *web_owner = NULL; - static uid_t owner_uid = 0; - - if(unlikely(!web_owner)) { - web_owner = config_get("global", "web files owner", config_get("global", "run as user", "")); - if(!web_owner || !*web_owner) - owner_uid = geteuid(); - else { - // getpwnam() is not thread safe, - // but we have called this function once - // while single threaded - struct passwd *pw = getpwnam(web_owner); - if(!pw) { - error("User %s is not present. Ignoring option.", web_owner); - owner_uid = geteuid(); - } - else { - debug(D_WEB_CLIENT, "Web files owner set to %s.\n", web_owner); - owner_uid = pw->pw_uid; - } - } - } - - return(owner_uid); +uid_t web_files_uid(void) { + static char *web_owner = NULL; + static uid_t owner_uid = 0; + + if(unlikely(!web_owner)) { + web_owner = config_get("global", "web files owner", config_get("global", "run as user", "")); + if(!web_owner || !*web_owner) + owner_uid = geteuid(); + else { + // getpwnam() is not thread safe, + // but we have called this function once + // while single threaded + struct passwd *pw = getpwnam(web_owner); + if(!pw) { + error("User '%s' is not present. Ignoring option.", web_owner); + owner_uid = geteuid(); + } + else { + debug(D_WEB_CLIENT, "Web files owner set to %s.", web_owner); + owner_uid = pw->pw_uid; + } + } + } + + return(owner_uid); } -gid_t web_files_gid(void) -{ - static char *web_group = NULL; - static gid_t owner_gid = 0; - - if(unlikely(!web_group)) { - web_group = config_get("global", "web files group", config_get("global", "web files owner", "")); - if(!web_group || !*web_group) - owner_gid = getegid(); - else { - // getgrnam() is not thread safe, - // but we have called this function once - // while single threaded - struct group *gr = getgrnam(web_group); - if(!gr) { - error("Group %s is not present. Ignoring option.", web_group); - owner_gid = getegid(); - } - else { - debug(D_WEB_CLIENT, "Web files group set to %s.\n", web_group); - owner_gid = gr->gr_gid; - } - } - } - - return(owner_gid); +gid_t web_files_gid(void) { + static char *web_group = NULL; + static gid_t owner_gid = 0; + + if(unlikely(!web_group)) { + web_group = config_get("global", "web files group", config_get("global", "web files owner", "")); + if(!web_group || !*web_group) + owner_gid = getegid(); + else { + // getgrnam() is not thread safe, + // but we have called this function once + // while single threaded + struct group *gr = getgrnam(web_group); + if(!gr) { + error("Group '%s' is not present. Ignoring option.", web_group); + owner_gid = getegid(); + } + else { + debug(D_WEB_CLIENT, "Web files group set to %s.", web_group); + owner_gid = gr->gr_gid; + } + } + } + + return(owner_gid); } int mysendfile(struct web_client *w, char *filename) { - static char *web_dir = NULL; - - // initialize our static data - if(unlikely(!web_dir)) web_dir = config_get("global", "web files directory", WEB_DIR); - - debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, web_dir, filename); - - // skip leading slashes - while (*filename == '/') filename++; - - // if the filename contain known paths, skip them - if(strncmp(filename, WEB_PATH_FILE "/", strlen(WEB_PATH_FILE) + 1) == 0) filename = &filename[strlen(WEB_PATH_FILE) + 1]; - - char *s; - for(s = filename; *s ;s++) { - if( !isalnum(*s) && *s != '/' && *s != '.' && *s != '-' && *s != '_') { - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); - buffer_sprintf(w->response.data, "File '%s' cannot be served. Filename contains invalid character '%c'", filename, *s); - return 400; - } - } - - // if the filename contains a .. refuse to serve it - if(strstr(filename, "..") != 0) { - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); - buffer_sprintf(w->response.data, "File '%s' cannot be served. Relative filenames with '..' in them are not supported.", filename); - return 400; - } - - // access the file - char webfilename[FILENAME_MAX + 1]; - snprintfz(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename); - - // check if the file exists - struct stat stat; - if(lstat(webfilename, &stat) != 0) { - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not found.", w->id, webfilename); - buffer_sprintf(w->response.data, "File '%s' does not exist, or is not accessible.", webfilename); - return 404; - } - - // check if the file is owned by expected user - if(stat.st_uid != web_files_uid()) { - error("%llu: File '%s' is owned by user %d (expected user %d). Access Denied.", w->id, webfilename, stat.st_uid, web_files_uid()); - buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); - return 403; - } - - // check if the file is owned by expected group - if(stat.st_gid != web_files_gid()) { - error("%llu: File '%s' is owned by group %d (expected group %d). Access Denied.", w->id, webfilename, stat.st_gid, web_files_gid()); - buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); - return 403; - } - - if((stat.st_mode & S_IFMT) == S_IFDIR) { - snprintfz(webfilename, FILENAME_MAX, "%s/index.html", filename); - return mysendfile(w, webfilename); - } - - if((stat.st_mode & S_IFMT) != S_IFREG) { - error("%llu: File '%s' is not a regular file. Access Denied.", w->id, webfilename); - buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); - return 403; - } - - // open the file - w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY); - if(w->ifd == -1) { - w->ifd = w->ofd; - - if(errno == EBUSY || errno == EAGAIN) { - error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, webfilename); - buffer_sprintf(w->response.header, "Location: /" WEB_PATH_FILE "/%s\r\n", filename); - buffer_sprintf(w->response.data, "The file '%s' is currently busy. Please try again later.", webfilename); - return 307; - } - else { - error("%llu: Cannot open file '%s'.", w->id, webfilename); - buffer_sprintf(w->response.data, "Cannot open file '%s'.", webfilename); - return 404; - } - } - - // pick a Content-Type for the file - if(strstr(filename, ".html") != NULL) w->response.data->contenttype = CT_TEXT_HTML; - else if(strstr(filename, ".js") != NULL) w->response.data->contenttype = CT_APPLICATION_X_JAVASCRIPT; - else if(strstr(filename, ".css") != NULL) w->response.data->contenttype = CT_TEXT_CSS; - else if(strstr(filename, ".xml") != NULL) w->response.data->contenttype = CT_TEXT_XML; - else if(strstr(filename, ".xsl") != NULL) w->response.data->contenttype = CT_TEXT_XSL; - else if(strstr(filename, ".txt") != NULL) w->response.data->contenttype = CT_TEXT_PLAIN; - else if(strstr(filename, ".svg") != NULL) w->response.data->contenttype = CT_IMAGE_SVG_XML; - else if(strstr(filename, ".ttf") != NULL) w->response.data->contenttype = CT_APPLICATION_X_FONT_TRUETYPE; - else if(strstr(filename, ".otf") != NULL) w->response.data->contenttype = CT_APPLICATION_X_FONT_OPENTYPE; - else if(strstr(filename, ".woff2")!= NULL) w->response.data->contenttype = CT_APPLICATION_FONT_WOFF2; - else if(strstr(filename, ".woff") != NULL) w->response.data->contenttype = CT_APPLICATION_FONT_WOFF; - else if(strstr(filename, ".eot") != NULL) w->response.data->contenttype = CT_APPLICATION_VND_MS_FONTOBJ; - else if(strstr(filename, ".png") != NULL) w->response.data->contenttype = CT_IMAGE_PNG; - else if(strstr(filename, ".jpg") != NULL) w->response.data->contenttype = CT_IMAGE_JPG; - else if(strstr(filename, ".jpeg") != NULL) w->response.data->contenttype = CT_IMAGE_JPG; - else if(strstr(filename, ".gif") != NULL) w->response.data->contenttype = CT_IMAGE_GIF; - else if(strstr(filename, ".bmp") != NULL) w->response.data->contenttype = CT_IMAGE_BMP; - else if(strstr(filename, ".ico") != NULL) w->response.data->contenttype = CT_IMAGE_XICON; - else if(strstr(filename, ".icns") != NULL) w->response.data->contenttype = CT_IMAGE_ICNS; - else w->response.data->contenttype = CT_APPLICATION_OCTET_STREAM; - - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, webfilename, stat.st_size, w->ifd, w->ofd); - - w->mode = WEB_CLIENT_MODE_FILECOPY; - w->wait_receive = 1; - w->wait_send = 0; - buffer_flush(w->response.data); - w->response.rlen = stat.st_size; - w->response.data->date = stat.st_mtim.tv_sec; - - return 200; + static char *web_dir = NULL; + + // initialize our static data + if(unlikely(!web_dir)) web_dir = config_get("global", "web files directory", WEB_DIR); + + debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, web_dir, filename); + + // skip leading slashes + while (*filename == '/') filename++; + + // if the filename contain known paths, skip them + if(strncmp(filename, WEB_PATH_FILE "/", strlen(WEB_PATH_FILE) + 1) == 0) + filename = &filename[strlen(WEB_PATH_FILE) + 1]; + + char *s; + for(s = filename; *s ;s++) { + if( !isalnum(*s) && *s != '/' && *s != '.' && *s != '-' && *s != '_') { + debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); + buffer_sprintf(w->response.data, "File '%s' cannot be served. Filename contains invalid character '%c'", filename, *s); + return 400; + } + } + + // if the filename contains a .. refuse to serve it + if(strstr(filename, "..") != 0) { + debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); + buffer_sprintf(w->response.data, "File '%s' cannot be served. Relative filenames with '..' in them are not supported.", filename); + return 400; + } + + // access the file + char webfilename[FILENAME_MAX + 1]; + snprintfz(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename); + + // check if the file exists + struct stat stat; + if(lstat(webfilename, &stat) != 0) { + debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not found.", w->id, webfilename); + buffer_sprintf(w->response.data, "File '%s' does not exist, or is not accessible.", webfilename); + return 404; + } + + // check if the file is owned by expected user + if(stat.st_uid != web_files_uid()) { + error("%llu: File '%s' is owned by user %u (expected user %u). Access Denied.", w->id, webfilename, stat.st_uid, web_files_uid()); + buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); + return 403; + } + + // check if the file is owned by expected group + if(stat.st_gid != web_files_gid()) { + error("%llu: File '%s' is owned by group %u (expected group %u). Access Denied.", w->id, webfilename, stat.st_gid, web_files_gid()); + buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); + return 403; + } + + if((stat.st_mode & S_IFMT) == S_IFDIR) { + snprintfz(webfilename, FILENAME_MAX, "%s/index.html", filename); + return mysendfile(w, webfilename); + } + + if((stat.st_mode & S_IFMT) != S_IFREG) { + error("%llu: File '%s' is not a regular file. Access Denied.", w->id, webfilename); + buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); + return 403; + } + + // open the file + w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY); + if(w->ifd == -1) { + w->ifd = w->ofd; + + if(errno == EBUSY || errno == EAGAIN) { + error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, webfilename); + buffer_sprintf(w->response.header, "Location: /" WEB_PATH_FILE "/%s\r\n", filename); + buffer_sprintf(w->response.data, "The file '%s' is currently busy. Please try again later.", webfilename); + return 307; + } + else { + error("%llu: Cannot open file '%s'.", w->id, webfilename); + buffer_sprintf(w->response.data, "Cannot open file '%s'.", webfilename); + return 404; + } + } + if(fcntl(w->ifd, F_SETFL, O_NONBLOCK) < 0) + error("%llu: Cannot set O_NONBLOCK on file '%s'.", w->id, webfilename); + + // pick a Content-Type for the file + if(strstr(filename, ".html") != NULL) w->response.data->contenttype = CT_TEXT_HTML; + else if(strstr(filename, ".js") != NULL) w->response.data->contenttype = CT_APPLICATION_X_JAVASCRIPT; + else if(strstr(filename, ".css") != NULL) w->response.data->contenttype = CT_TEXT_CSS; + else if(strstr(filename, ".xml") != NULL) w->response.data->contenttype = CT_TEXT_XML; + else if(strstr(filename, ".xsl") != NULL) w->response.data->contenttype = CT_TEXT_XSL; + else if(strstr(filename, ".txt") != NULL) w->response.data->contenttype = CT_TEXT_PLAIN; + else if(strstr(filename, ".svg") != NULL) w->response.data->contenttype = CT_IMAGE_SVG_XML; + else if(strstr(filename, ".ttf") != NULL) w->response.data->contenttype = CT_APPLICATION_X_FONT_TRUETYPE; + else if(strstr(filename, ".otf") != NULL) w->response.data->contenttype = CT_APPLICATION_X_FONT_OPENTYPE; + else if(strstr(filename, ".woff2")!= NULL) w->response.data->contenttype = CT_APPLICATION_FONT_WOFF2; + else if(strstr(filename, ".woff") != NULL) w->response.data->contenttype = CT_APPLICATION_FONT_WOFF; + else if(strstr(filename, ".eot") != NULL) w->response.data->contenttype = CT_APPLICATION_VND_MS_FONTOBJ; + else if(strstr(filename, ".png") != NULL) w->response.data->contenttype = CT_IMAGE_PNG; + else if(strstr(filename, ".jpg") != NULL) w->response.data->contenttype = CT_IMAGE_JPG; + else if(strstr(filename, ".jpeg") != NULL) w->response.data->contenttype = CT_IMAGE_JPG; + else if(strstr(filename, ".gif") != NULL) w->response.data->contenttype = CT_IMAGE_GIF; + else if(strstr(filename, ".bmp") != NULL) w->response.data->contenttype = CT_IMAGE_BMP; + else if(strstr(filename, ".ico") != NULL) w->response.data->contenttype = CT_IMAGE_XICON; + else if(strstr(filename, ".icns") != NULL) w->response.data->contenttype = CT_IMAGE_ICNS; + else w->response.data->contenttype = CT_APPLICATION_OCTET_STREAM; + + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, webfilename, stat.st_size, w->ifd, w->ofd); + + w->mode = WEB_CLIENT_MODE_FILECOPY; + w->wait_receive = 1; + w->wait_send = 0; + buffer_flush(w->response.data); + w->response.rlen = stat.st_size; + w->response.data->date = stat.st_mtim.tv_sec; + + return 200; } #ifdef NETDATA_WITH_ZLIB -void web_client_enable_deflate(struct web_client *w) { - if(w->response.zinitialized == 1) { - error("%llu: Compression has already be initialized for this client.", w->id); - return; - } - - if(w->response.sent) { - error("%llu: Cannot enable compression in the middle of a conversation.", w->id); - return; - } - - w->response.zstream.zalloc = Z_NULL; - w->response.zstream.zfree = Z_NULL; - w->response.zstream.opaque = Z_NULL; - - w->response.zstream.next_in = (Bytef *)w->response.data->buffer; - w->response.zstream.avail_in = 0; - w->response.zstream.total_in = 0; - - w->response.zstream.next_out = w->response.zbuffer; - w->response.zstream.avail_out = 0; - w->response.zstream.total_out = 0; - - w->response.zstream.zalloc = Z_NULL; - w->response.zstream.zfree = Z_NULL; - w->response.zstream.opaque = Z_NULL; - -// if(deflateInit(&w->response.zstream, Z_DEFAULT_COMPRESSION) != Z_OK) { -// error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); -// return; -// } - - // Select GZIP compression: windowbits = 15 + 16 = 31 - if(deflateInit2(&w->response.zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) != Z_OK) { - error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); - return; - } - - w->response.zsent = 0; - w->response.zoutput = 1; - w->response.zinitialized = 1; - - debug(D_DEFLATE, "%llu: Initialized compression.", w->id); +void web_client_enable_deflate(struct web_client *w, int gzip) { + if(unlikely(w->response.zinitialized)) { + error("%llu: Compression has already be initialized for this client.", w->id); + return; + } + + if(unlikely(w->response.sent)) { + error("%llu: Cannot enable compression in the middle of a conversation.", w->id); + return; + } + + w->response.zstream.zalloc = Z_NULL; + w->response.zstream.zfree = Z_NULL; + w->response.zstream.opaque = Z_NULL; + + w->response.zstream.next_in = (Bytef *)w->response.data->buffer; + w->response.zstream.avail_in = 0; + w->response.zstream.total_in = 0; + + w->response.zstream.next_out = w->response.zbuffer; + w->response.zstream.avail_out = 0; + w->response.zstream.total_out = 0; + + w->response.zstream.zalloc = Z_NULL; + w->response.zstream.zfree = Z_NULL; + w->response.zstream.opaque = Z_NULL; + +// if(deflateInit(&w->response.zstream, Z_DEFAULT_COMPRESSION) != Z_OK) { +// error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); +// return; +// } + + // Select GZIP compression: windowbits = 15 + 16 = 31 + if(deflateInit2(&w->response.zstream, web_gzip_level, Z_DEFLATED, 15 + ((gzip)?16:0), 8, web_gzip_strategy) != Z_OK) { + error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id); + return; + } + + w->response.zsent = 0; + w->response.zoutput = 1; + w->response.zinitialized = 1; + + debug(D_DEFLATE, "%llu: Initialized compression.", w->id); } #endif // NETDATA_WITH_ZLIB +void buffer_data_options2string(BUFFER *wb, uint32_t options) { + int count = 0; + + if(options & RRDR_OPTION_NONZERO) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "nonzero"); + } + + if(options & RRDR_OPTION_REVERSED) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "flip"); + } + + if(options & RRDR_OPTION_JSON_WRAP) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "jsonwrap"); + } + + if(options & RRDR_OPTION_MIN2MAX) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "min2max"); + } + + if(options & RRDR_OPTION_MILLISECONDS) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "ms"); + } + + if(options & RRDR_OPTION_ABSOLUTE) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "abs"); + } + + if(options & RRDR_OPTION_SECONDS) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "seconds"); + } + + if(options & RRDR_OPTION_NULL2ZERO) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "null2zero"); + } + + if(options & RRDR_OPTION_OBJECTSROWS) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "objectrows"); + } + + if(options & RRDR_OPTION_GOOGLE_JSON) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "google_json"); + } + + if(options & RRDR_OPTION_PERCENTAGE) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "percentage"); + } + + if(options & RRDR_OPTION_NOT_ALIGNED) { + if(count++) buffer_strcat(wb, " "); + buffer_strcat(wb, "unaligned"); + } +} + uint32_t web_client_api_request_v1_data_options(char *o) { - uint32_t ret = 0x00000000; - char *tok; - - while(o && *o && (tok = mystrsep(&o, ", |"))) { - if(!*tok) continue; - - if(!strcmp(tok, "nonzero")) - ret |= RRDR_OPTION_NONZERO; - else if(!strcmp(tok, "flip") || !strcmp(tok, "reversed") || !strcmp(tok, "reverse")) - ret |= RRDR_OPTION_REVERSED; - else if(!strcmp(tok, "jsonwrap")) - ret |= RRDR_OPTION_JSON_WRAP; - else if(!strcmp(tok, "min2max")) - ret |= RRDR_OPTION_MIN2MAX; - else if(!strcmp(tok, "ms") || !strcmp(tok, "milliseconds")) - ret |= RRDR_OPTION_MILLISECONDS; - else if(!strcmp(tok, "abs") || !strcmp(tok, "absolute") || !strcmp(tok, "absolute_sum") || !strcmp(tok, "absolute-sum")) - ret |= RRDR_OPTION_ABSOLUTE; - else if(!strcmp(tok, "seconds")) - ret |= RRDR_OPTION_SECONDS; - else if(!strcmp(tok, "null2zero")) - ret |= RRDR_OPTION_NULL2ZERO; - else if(!strcmp(tok, "objectrows")) - ret |= RRDR_OPTION_OBJECTSROWS; - else if(!strcmp(tok, "google_json")) - ret |= RRDR_OPTION_GOOGLE_JSON; - else if(!strcmp(tok, "percentage")) - ret |= RRDR_OPTION_PERCENTAGE; - } - - return ret; + uint32_t ret = 0x00000000; + char *tok; + + while(o && *o && (tok = mystrsep(&o, ", |"))) { + if(!*tok) continue; + + if(!strcmp(tok, "nonzero")) + ret |= RRDR_OPTION_NONZERO; + else if(!strcmp(tok, "flip") || !strcmp(tok, "reversed") || !strcmp(tok, "reverse")) + ret |= RRDR_OPTION_REVERSED; + else if(!strcmp(tok, "jsonwrap")) + ret |= RRDR_OPTION_JSON_WRAP; + else if(!strcmp(tok, "min2max")) + ret |= RRDR_OPTION_MIN2MAX; + else if(!strcmp(tok, "ms") || !strcmp(tok, "milliseconds")) + ret |= RRDR_OPTION_MILLISECONDS; + else if(!strcmp(tok, "abs") || !strcmp(tok, "absolute") || !strcmp(tok, "absolute_sum") || !strcmp(tok, "absolute-sum")) + ret |= RRDR_OPTION_ABSOLUTE; + else if(!strcmp(tok, "seconds")) + ret |= RRDR_OPTION_SECONDS; + else if(!strcmp(tok, "null2zero")) + ret |= RRDR_OPTION_NULL2ZERO; + else if(!strcmp(tok, "objectrows")) + ret |= RRDR_OPTION_OBJECTSROWS; + else if(!strcmp(tok, "google_json")) + ret |= RRDR_OPTION_GOOGLE_JSON; + else if(!strcmp(tok, "percentage")) + ret |= RRDR_OPTION_PERCENTAGE; + else if(!strcmp(tok, "unaligned")) + ret |= RRDR_OPTION_NOT_ALIGNED; + } + + return ret; } uint32_t web_client_api_request_v1_data_format(char *name) { - if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSON)) // datatable - return DATASOURCE_DATATABLE_JSON; + if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSON)) // datatable + return DATASOURCE_DATATABLE_JSON; - else if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSONP)) // datasource - return DATASOURCE_DATATABLE_JSONP; + else if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSONP)) // datasource + return DATASOURCE_DATATABLE_JSONP; - else if(!strcmp(name, DATASOURCE_FORMAT_JSON)) // json - return DATASOURCE_JSON; + else if(!strcmp(name, DATASOURCE_FORMAT_JSON)) // json + return DATASOURCE_JSON; - else if(!strcmp(name, DATASOURCE_FORMAT_JSONP)) // jsonp - return DATASOURCE_JSONP; + else if(!strcmp(name, DATASOURCE_FORMAT_JSONP)) // jsonp + return DATASOURCE_JSONP; - else if(!strcmp(name, DATASOURCE_FORMAT_SSV)) // ssv - return DATASOURCE_SSV; + else if(!strcmp(name, DATASOURCE_FORMAT_SSV)) // ssv + return DATASOURCE_SSV; - else if(!strcmp(name, DATASOURCE_FORMAT_CSV)) // csv - return DATASOURCE_CSV; + else if(!strcmp(name, DATASOURCE_FORMAT_CSV)) // csv + return DATASOURCE_CSV; - else if(!strcmp(name, DATASOURCE_FORMAT_TSV) || !strcmp(name, "tsv-excel")) // tsv - return DATASOURCE_TSV; + else if(!strcmp(name, DATASOURCE_FORMAT_TSV) || !strcmp(name, "tsv-excel")) // tsv + return DATASOURCE_TSV; - else if(!strcmp(name, DATASOURCE_FORMAT_HTML)) // html - return DATASOURCE_HTML; + else if(!strcmp(name, DATASOURCE_FORMAT_HTML)) // html + return DATASOURCE_HTML; - else if(!strcmp(name, DATASOURCE_FORMAT_JS_ARRAY)) // array - return DATASOURCE_JS_ARRAY; + else if(!strcmp(name, DATASOURCE_FORMAT_JS_ARRAY)) // array + return DATASOURCE_JS_ARRAY; - else if(!strcmp(name, DATASOURCE_FORMAT_SSV_COMMA)) // ssvcomma - return DATASOURCE_SSV_COMMA; + else if(!strcmp(name, DATASOURCE_FORMAT_SSV_COMMA)) // ssvcomma + return DATASOURCE_SSV_COMMA; - else if(!strcmp(name, DATASOURCE_FORMAT_CSV_JSON_ARRAY)) // csvjsonarray - return DATASOURCE_CSV_JSON_ARRAY; + else if(!strcmp(name, DATASOURCE_FORMAT_CSV_JSON_ARRAY)) // csvjsonarray + return DATASOURCE_CSV_JSON_ARRAY; - return DATASOURCE_JSON; + return DATASOURCE_JSON; } uint32_t web_client_api_request_v1_data_google_format(char *name) { - if(!strcmp(name, "json")) - return DATASOURCE_DATATABLE_JSONP; + if(!strcmp(name, "json")) + return DATASOURCE_DATATABLE_JSONP; - else if(!strcmp(name, "html")) - return DATASOURCE_HTML; + else if(!strcmp(name, "html")) + return DATASOURCE_HTML; - else if(!strcmp(name, "csv")) - return DATASOURCE_CSV; + else if(!strcmp(name, "csv")) + return DATASOURCE_CSV; - else if(!strcmp(name, "tsv-excel")) - return DATASOURCE_TSV; + else if(!strcmp(name, "tsv-excel")) + return DATASOURCE_TSV; - return DATASOURCE_JSON; + return DATASOURCE_JSON; } -int web_client_api_request_v1_data_group(char *name) +const char *group_method2string(int group) { + switch(group) { + case GROUP_UNDEFINED: + return ""; + + case GROUP_AVERAGE: + return "average"; + + case GROUP_MIN: + return "min"; + + case GROUP_MAX: + return "max"; + + case GROUP_SUM: + return "sum"; + + case GROUP_INCREMENTAL_SUM: + return "incremental-sum"; + + default: + return "unknown-group-method"; + } +} + +int web_client_api_request_v1_data_group(char *name, int def) { - if(!strcmp(name, "max")) - return GROUP_MAX; + if(!strcmp(name, "average")) + return GROUP_AVERAGE; + + else if(!strcmp(name, "min")) + return GROUP_MIN; + + else if(!strcmp(name, "max")) + return GROUP_MAX; + + else if(!strcmp(name, "sum")) + return GROUP_SUM; - else if(!strcmp(name, "average")) - return GROUP_AVERAGE; + else if(!strcmp(name, "incremental-sum")) + return GROUP_INCREMENTAL_SUM; - return GROUP_MAX; + return def; +} + +int web_client_api_request_v1_alarms(struct web_client *w, char *url) +{ + int all = 0; + + while(url) { + char *value = mystrsep(&url, "?&[]"); + if (!value || !*value) continue; + + if(!strcmp(value, "all")) all = 1; + else if(!strcmp(value, "active")) all = 0; + } + + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; + health_alarms2json(&localhost, w->response.data, all); + return 200; +} + +int web_client_api_request_v1_alarm_log(struct web_client *w, char *url) +{ + (void)url; + + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; + health_alarm_log2json(&localhost, w->response.data); + return 200; } int web_client_api_request_v1_charts(struct web_client *w, char *url) { - if(url) { ; } + if(url) { ; } - buffer_flush(w->response.data); - w->response.data->contenttype = CT_APPLICATION_JSON; - rrd_stats_api_v1_charts(w->response.data); - return 200; + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; + rrd_stats_api_v1_charts(w->response.data); + return 200; } int web_client_api_request_v1_chart(struct web_client *w, char *url) { - int ret = 400; - char *chart = NULL; - - buffer_flush(w->response.data); - - while(url) { - char *value = mystrsep(&url, "?&[]"); - if(!value || !*value) continue; - - char *name = mystrsep(&value, "="); - if(!name || !*name) continue; - if(!value || !*value) continue; - - // name and value are now the parameters - // they are not null and not empty - - if(!strcmp(name, "chart")) chart = value; - //else { - /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name); - // goto cleanup; - //} - } - - if(!chart || !*chart) { - buffer_sprintf(w->response.data, "No chart id is given at the request."); - goto cleanup; - } - - RRDSET *st = rrdset_find(chart); - if(!st) st = rrdset_find_byname(chart); - if(!st) { - buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart); - ret = 404; - goto cleanup; - } - - w->response.data->contenttype = CT_APPLICATION_JSON; - rrd_stats_api_v1_chart(st, w->response.data); - return 200; + int ret = 400; + char *chart = NULL; + + buffer_flush(w->response.data); + + while(url) { + char *value = mystrsep(&url, "?&[]"); + if(!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + // name and value are now the parameters + // they are not null and not empty + + if(!strcmp(name, "chart")) chart = value; + //else { + /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name); + // goto cleanup; + //} + } + + if(!chart || !*chart) { + buffer_sprintf(w->response.data, "No chart id is given at the request."); + goto cleanup; + } + + RRDSET *st = rrdset_find(chart); + if(!st) st = rrdset_find_byname(chart); + if(!st) { + buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart); + ret = 404; + goto cleanup; + } + + w->response.data->contenttype = CT_APPLICATION_JSON; + rrd_stats_api_v1_chart(st, w->response.data); + return 200; cleanup: - return ret; + return ret; +} + +int web_client_api_request_v1_badge(struct web_client *w, char *url) { + int ret = 400; + buffer_flush(w->response.data); + + BUFFER *dimensions = NULL; + + const char *chart = NULL + , *before_str = NULL + , *after_str = NULL + , *points_str = NULL + , *multiply_str = NULL + , *divide_str = NULL + , *label = NULL + , *units = NULL + , *label_color = NULL + , *value_color = NULL + , *refresh_str = NULL + , *precision_str = NULL + , *alarm = NULL; + + int group = GROUP_AVERAGE; + uint32_t options = 0x00000000; + + 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 badge.svg query param '%s' with value '%s'", w->id, name, value); + + // name and value are now the parameters + // they are not null and not empty + + if(!strcmp(name, "chart")) chart = value; + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { + if(!dimensions) + dimensions = buffer_create(100); + + buffer_strcat(dimensions, "|"); + buffer_strcat(dimensions, value); + } + else if(!strcmp(name, "after")) after_str = value; + else if(!strcmp(name, "before")) before_str = value; + else if(!strcmp(name, "points")) points_str = value; + else if(!strcmp(name, "group")) { + group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE); + } + else if(!strcmp(name, "options")) { + options |= web_client_api_request_v1_data_options(value); + } + else if(!strcmp(name, "label")) label = value; + else if(!strcmp(name, "units")) units = value; + else if(!strcmp(name, "label_color")) label_color = value; + else if(!strcmp(name, "value_color")) value_color = value; + else if(!strcmp(name, "multiply")) multiply_str = value; + else if(!strcmp(name, "divide")) divide_str = value; + else if(!strcmp(name, "refresh")) refresh_str = value; + else if(!strcmp(name, "precision")) precision_str = value; + else if(!strcmp(name, "alarm")) alarm = value; + } + + if(!chart || !*chart) { + buffer_sprintf(w->response.data, "No chart id is given at the request."); + goto cleanup; + } + + RRDSET *st = rrdset_find(chart); + if(!st) st = rrdset_find_byname(chart); + if(!st) { + buffer_svg(w->response.data, "chart not found", 0, "", NULL, NULL, 1, -1); + ret = 200; + goto cleanup; + } + + RRDCALC *rc = NULL; + if(alarm) { + rc = rrdcalc_find(st, alarm); + if (!rc) { + buffer_svg(w->response.data, "alarm not found", 0, "", NULL, NULL, 1, -1); + ret = 200; + goto cleanup; + } + } + + long long multiply = (multiply_str && *multiply_str )?atol(multiply_str):1; + long long divide = (divide_str && *divide_str )?atol(divide_str):1; + long long before = (before_str && *before_str )?atol(before_str):0; + long long after = (after_str && *after_str )?atol(after_str):-st->update_every; + int points = (points_str && *points_str )?atoi(points_str):1; + int precision = (precision_str && *precision_str)?atoi(precision_str):-1; + + if(!multiply) multiply = 1; + if(!divide) divide = 1; + + int refresh = 0; + if(refresh_str && *refresh_str) { + if(!strcmp(refresh_str, "auto")) { + if(rc) refresh = rc->update_every; + else if(options & RRDR_OPTION_NOT_ALIGNED) + refresh = st->update_every; + else { + refresh = (before - after); + if(refresh < 0) refresh = -refresh; + } + } + else { + refresh = atoi(refresh_str); + if(refresh < 0) refresh = -refresh; + } + } + + if(!label) { + if(alarm) { + char *s = (char *)alarm; + while(*s) { + if(*s == '_') *s = ' '; + s++; + } + label = alarm; + } + else if(dimensions) { + const char *dim = buffer_tostring(dimensions); + if(*dim == '|') dim++; + label = dim; + } + else + label = st->name; + } + if(!units) { + if(alarm) { + if(rc->units) + units = rc->units; + else + units = ""; + } + else if(options & RRDR_OPTION_PERCENTAGE) + units = "%"; + else + units = st->units; + } + + debug(D_WEB_CLIENT, "%llu: API command 'badge.svg' for chart '%s', alarm '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', options '0x%08x'" + , w->id + , chart + , alarm?alarm:"" + , (dimensions)?buffer_tostring(dimensions):"" + , after + , before + , points + , group + , options + ); + + if(rc) { + calculated_number n = rc->value; + if(isnan(n) || isinf(n)) n = 0; + + if (refresh > 0) + buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh); + + if(!value_color) { + switch(rc->status) { + case RRDCALC_STATUS_CRITICAL: + value_color = "red"; + break; + + case RRDCALC_STATUS_WARNING: + value_color = "orange"; + break; + + case RRDCALC_STATUS_CLEAR: + value_color = "brightgreen"; + break; + + case RRDCALC_STATUS_UNDEFINED: + value_color = "lightgrey"; + break; + + case RRDCALC_STATUS_UNINITIALIZED: + value_color = "#000"; + break; + + default: + value_color = "grey"; + break; + } + } + + buffer_svg(w->response.data, label, rc->value * multiply / divide, units, label_color, value_color, 0, precision); + ret = 200; + } + else { + time_t latest_timestamp = 0; + int value_is_null = 1; + calculated_number n = 0; + ret = 500; + + // if the collected value is too old, don't calculate its value + if (rrdset_last_entry_t(st) >= (time(NULL) - (st->update_every * st->gap_when_lost_iterations_above))) + ret = rrd2value(st, w->response.data, &n, (dimensions) ? buffer_tostring(dimensions) : NULL, points, after, + before, group, options, NULL, &latest_timestamp, &value_is_null); + + // if the value cannot be calculated, show empty badge + if (ret != 200) { + value_is_null = 1; + n = 0; + ret = 200; + } + else if (refresh > 0) + buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh); + + // render the badge + buffer_svg(w->response.data, label, n * multiply / divide, units, label_color, value_color, value_is_null, + precision); + } + +cleanup: + if(dimensions) + buffer_free(dimensions); + return ret; } // returns the HTTP code int web_client_api_request_v1_data(struct web_client *w, char *url) { - debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url); - - int ret = 400; - BUFFER *dimensions = NULL; - - buffer_flush(w->response.data); - - char *google_version = "0.6", - *google_reqId = "0", - *google_sig = "0", - *google_out = "json", - *responseHandler = NULL, - *outFileName = NULL; - - time_t last_timestamp_in_data = 0, google_timestamp = 0; - - char *chart = NULL - , *before_str = NULL - , *after_str = NULL - , *points_str = NULL; - - int group = GROUP_MAX; - uint32_t format = DATASOURCE_JSON; - uint32_t options = 0x00000000; - - 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 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 - - if(!strcmp(name, "chart")) chart = value; - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { - if(!dimensions) dimensions = buffer_create(strlen(value)); - if(dimensions) { - buffer_strcat(dimensions, "|"); - buffer_strcat(dimensions, value); - } - } - else if(!strcmp(name, "after")) after_str = value; - else if(!strcmp(name, "before")) before_str = value; - else if(!strcmp(name, "points")) points_str = value; - else if(!strcmp(name, "group")) { - group = web_client_api_request_v1_data_group(value); - } - else if(!strcmp(name, "format")) { - format = web_client_api_request_v1_data_format(value); - } - else if(!strcmp(name, "options")) { - options |= web_client_api_request_v1_data_options(value); - } - else if(!strcmp(name, "callback")) { - responseHandler = value; - } - else if(!strcmp(name, "filename")) { - outFileName = value; - } - else if(!strcmp(name, "tqx")) { - // parse Google Visualization API options - // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source - char *tqx_name, *tqx_value; - - while(value) { - tqx_value = mystrsep(&value, ";"); - if(!tqx_value || !*tqx_value) continue; - - tqx_name = mystrsep(&tqx_value, ":"); - if(!tqx_name || !*tqx_name) continue; - if(!tqx_value || !*tqx_value) continue; - - if(!strcmp(tqx_name, "version")) - google_version = tqx_value; - else if(!strcmp(tqx_name, "reqId")) - google_reqId = tqx_value; - else if(!strcmp(tqx_name, "sig")) { - google_sig = tqx_value; - google_timestamp = strtoul(google_sig, NULL, 0); - } - else if(!strcmp(tqx_name, "out")) { - google_out = tqx_value; - format = web_client_api_request_v1_data_google_format(google_out); - } - else if(!strcmp(tqx_name, "responseHandler")) - responseHandler = tqx_value; - else if(!strcmp(tqx_name, "outFileName")) - outFileName = tqx_value; - } - } - } - - if(!chart || !*chart) { - buffer_sprintf(w->response.data, "No chart id is given at the request."); - goto cleanup; - } - - RRDSET *st = rrdset_find(chart); - if(!st) st = rrdset_find_byname(chart); - if(!st) { - buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart); - ret = 404; - goto cleanup; - } - - long long before = (before_str && *before_str)?atol(before_str):0; - long long after = (after_str && *after_str) ?atol(after_str):0; - int points = (points_str && *points_str)?atoi(points_str):0; - - debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%u', format '%u', options '0x%08x'" - , w->id - , chart - , (dimensions)?buffer_tostring(dimensions):"" - , after - , before - , points - , group - , format - , options - ); - - if(outFileName && *outFileName) { - buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName); - error("generating outfilename header: '%s'", outFileName); - } - - if(format == DATASOURCE_DATATABLE_JSONP) { - if(responseHandler == NULL) - responseHandler = "google.visualization.Query.setResponse"; - - debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'", - w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName - ); - - buffer_sprintf(w->response.data, - "%s({version:'%s',reqId:'%s',status:'ok',sig:'%lu',table:", - responseHandler, google_version, google_reqId, st->last_updated.tv_sec); - } - else if(format == DATASOURCE_JSONP) { - if(responseHandler == NULL) - responseHandler = "callback"; - - buffer_strcat(w->response.data, responseHandler); - buffer_strcat(w->response.data, "("); - } - - ret = rrd2format(st, w->response.data, dimensions, format, points, after, before, group, options, &last_timestamp_in_data); - - if(format == DATASOURCE_DATATABLE_JSONP) { - if(google_timestamp < last_timestamp_in_data) - buffer_strcat(w->response.data, "});"); - - else { - // the client already has the latest data - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, - "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});", - responseHandler, google_version, google_reqId); - } - } - else if(format == DATASOURCE_JSONP) - buffer_strcat(w->response.data, ");"); + debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url); + + int ret = 400; + BUFFER *dimensions = NULL; + + buffer_flush(w->response.data); + + char *google_version = "0.6", + *google_reqId = "0", + *google_sig = "0", + *google_out = "json", + *responseHandler = NULL, + *outFileName = NULL; + + time_t last_timestamp_in_data = 0, google_timestamp = 0; + + char *chart = NULL + , *before_str = NULL + , *after_str = NULL + , *points_str = NULL; + + int group = GROUP_AVERAGE; + uint32_t format = DATASOURCE_JSON; + uint32_t options = 0x00000000; + + 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 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 + + if(!strcmp(name, "chart")) chart = value; + else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { + if(!dimensions) dimensions = buffer_create(100); + buffer_strcat(dimensions, "|"); + buffer_strcat(dimensions, value); + } + else if(!strcmp(name, "after")) after_str = value; + else if(!strcmp(name, "before")) before_str = value; + else if(!strcmp(name, "points")) points_str = value; + else if(!strcmp(name, "group")) { + group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE); + } + else if(!strcmp(name, "format")) { + format = web_client_api_request_v1_data_format(value); + } + else if(!strcmp(name, "options")) { + options |= web_client_api_request_v1_data_options(value); + } + else if(!strcmp(name, "callback")) { + responseHandler = value; + } + else if(!strcmp(name, "filename")) { + outFileName = value; + } + else if(!strcmp(name, "tqx")) { + // parse Google Visualization API options + // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source + char *tqx_name, *tqx_value; + + while(value) { + tqx_value = mystrsep(&value, ";"); + if(!tqx_value || !*tqx_value) continue; + + tqx_name = mystrsep(&tqx_value, ":"); + if(!tqx_name || !*tqx_name) continue; + if(!tqx_value || !*tqx_value) continue; + + if(!strcmp(tqx_name, "version")) + google_version = tqx_value; + else if(!strcmp(tqx_name, "reqId")) + google_reqId = tqx_value; + else if(!strcmp(tqx_name, "sig")) { + google_sig = tqx_value; + google_timestamp = strtoul(google_sig, NULL, 0); + } + else if(!strcmp(tqx_name, "out")) { + google_out = tqx_value; + format = web_client_api_request_v1_data_google_format(google_out); + } + else if(!strcmp(tqx_name, "responseHandler")) + responseHandler = tqx_value; + else if(!strcmp(tqx_name, "outFileName")) + outFileName = tqx_value; + } + } + } + + if(!chart || !*chart) { + buffer_sprintf(w->response.data, "No chart id is given at the request."); + goto cleanup; + } + + RRDSET *st = rrdset_find(chart); + if(!st) st = rrdset_find_byname(chart); + if(!st) { + buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart); + ret = 404; + goto cleanup; + } + + long long before = (before_str && *before_str)?atol(before_str):0; + long long after = (after_str && *after_str) ?atol(after_str):0; + int points = (points_str && *points_str)?atoi(points_str):0; + + debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', format '%u', options '0x%08x'" + , w->id + , chart + , (dimensions)?buffer_tostring(dimensions):"" + , after + , before + , points + , group + , format + , options + ); + + if(outFileName && *outFileName) { + buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName); + debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName); + } + + if(format == DATASOURCE_DATATABLE_JSONP) { + if(responseHandler == NULL) + responseHandler = "google.visualization.Query.setResponse"; + + debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'", + w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName + ); + + buffer_sprintf(w->response.data, + "%s({version:'%s',reqId:'%s',status:'ok',sig:'%ld',table:", + responseHandler, google_version, google_reqId, st->last_updated.tv_sec); + } + else if(format == DATASOURCE_JSONP) { + if(responseHandler == NULL) + responseHandler = "callback"; + + buffer_strcat(w->response.data, responseHandler); + buffer_strcat(w->response.data, "("); + } + + ret = rrd2format(st, w->response.data, dimensions, format, points, after, before, group, options, &last_timestamp_in_data); + + if(format == DATASOURCE_DATATABLE_JSONP) { + if(google_timestamp < last_timestamp_in_data) + buffer_strcat(w->response.data, "});"); + + else { + // the client already has the latest data + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, + "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});", + responseHandler, google_version, google_reqId); + } + } + else if(format == DATASOURCE_JSONP) + buffer_strcat(w->response.data, ");"); cleanup: - if(dimensions) buffer_free(dimensions); - return ret; + if(dimensions) buffer_free(dimensions); + 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; + static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0, + hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0, + hash_to = 0 /*, hash_redirects = 0 */; + + if(unlikely(!hash_action)) { + hash_action = simple_hash("action"); + hash_access = simple_hash("access"); + hash_hello = simple_hash("hello"); + hash_delete = simple_hash("delete"); + hash_search = simple_hash("search"); + hash_switch = simple_hash("switch"); + hash_machine = simple_hash("machine"); + hash_url = simple_hash("url"); + hash_name = simple_hash("name"); + hash_delete_url = simple_hash("delete_url"); + hash_for = simple_hash("for"); + hash_to = simple_hash("to"); +/* + hash_redirects = simple_hash("redirects"); +*/ + } + + 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)], 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; +/* + int redirects = 0; +*/ + + 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); + + uint32_t hash = simple_hash(name); + + if(hash == hash_action && !strcmp(name, "action")) { + uint32_t vhash = simple_hash(value); + + if(vhash == hash_access && !strcmp(value, "access")) action = 'A'; + else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H'; + else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D'; + else if(vhash == hash_search && !strcmp(value, "search")) action = 'S'; + else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W'; +#ifdef NETDATA_INTERNAL_CHECKS + else error("unknown registry action '%s'", value); +#endif /* NETDATA_INTERNAL_CHECKS */ + } +/* + else if(hash == hash_redirects && !strcmp(name, "redirects")) + redirects = atoi(value); +*/ + else if(hash == hash_machine && !strcmp(name, "machine")) + machine_guid = value; + + else if(hash == hash_url && !strcmp(name, "url")) + machine_url = value; + + else if(action == 'A') { + if(hash == hash_name && !strcmp(name, "name")) + url_name = value; + } + else if(action == 'D') { + if(hash == hash_delete_url && !strcmp(name, "delete_url")) + delete_url = value; + } + else if(action == 'S') { + if(hash == hash_for && !strcmp(name, "for")) + search_machine_guid = value; + } + else if(action == 'W') { + if(hash == hash_to && !strcmp(name, "to")) + to_person_guid = value; + } +#ifdef NETDATA_INTERNAL_CHECKS + else error("unused registry URL parameter '%s' with value '%s'", name, value); +#endif /* NETDATA_INTERNAL_CHECKS */ + } + + if(web_donotrack_comply && w->donottrack) { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work."); + return 400; + } + + 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': + w->tracking_required = 1; + if(registry_verify_cookies_redirects() > 0 && (!cookie || !person_guid[0])) { + buffer_flush(w->response.data); + + registry_set_cookie(w, "give-me-back-this-cookie-please"); + w->response.data->contenttype = CT_APPLICATION_JSON; + buffer_sprintf(w->response.data, "{ \"status\": \"redirect\", \"registry\": \"%s\" }", registry_to_announce()); + return 200; + +/* + * it seems that web browsers are ignoring 307 (Moved Temporarily) + * under certain conditions, when using CORS + * so this is commented and we use application level redirects instead + * + redirects++; + + if(redirects > registry_verify_cookies_redirects()) { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Your browser does not support cookies"); + return 400; + } + + char *encoded_url = url_encode(machine_url); + if(!encoded_url) { + error("%llu: Cannot URL encode string '%s'", w->id, machine_url); + return 500; + } + + char *encoded_name = url_encode(url_name); + if(!encoded_name) { + free(encoded_url); + error("%llu: Cannot URL encode string '%s'", w->id, url_name); + return 500; + } + + char *encoded_guid = url_encode(machine_guid); + if(!encoded_guid) { + free(encoded_url); + free(encoded_name); + error("%llu: Cannot URL encode string '%s'", w->id, machine_guid); + return 500; + } + + buffer_sprintf(w->response.header, "Location: %s/api/v1/registry?action=access&machine=%s&name=%s&url=%s&redirects=%d\r\n", + registry_to_announce(), encoded_guid, encoded_name, encoded_url, redirects); + + free(encoded_guid); + free(encoded_name); + free(encoded_url); + return 307 +*/ + } + return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, time(NULL)); + + case 'D': + w->tracking_required = 1; + return registry_request_delete_json(w, person_guid, machine_guid, machine_url, delete_url, time(NULL)); + + case 'S': + w->tracking_required = 1; + return registry_request_search_json(w, person_guid, machine_guid, machine_url, search_machine_guid, time(NULL)); + + case 'W': + w->tracking_required = 1; + 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_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search"); + 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(hash == data_hash && !strcmp(tok, "data")) - return web_client_api_request_v1_data(w, url); - - else if(hash == chart_hash && !strcmp(tok, "chart")) - return web_client_api_request_v1_chart(w, url); - - 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); - return 404; - } - } - else { - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "API v1 command?"); - return 400; - } +int web_client_api_request_v1(struct web_client *w, char *url) { + static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0, hash_alarms = 0, hash_alarm_log = 0; + + if(unlikely(hash_data == 0)) { + hash_data = simple_hash("data"); + hash_chart = simple_hash("chart"); + hash_charts = simple_hash("charts"); + hash_registry = simple_hash("registry"); + hash_badge = simple_hash("badge.svg"); + hash_alarms = simple_hash("alarms"); + hash_alarm_log = simple_hash("alarm_log"); + } + + // 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(hash == hash_data && !strcmp(tok, "data")) + return web_client_api_request_v1_data(w, url); + + else if(hash == hash_chart && !strcmp(tok, "chart")) + return web_client_api_request_v1_chart(w, url); + + else if(hash == hash_charts && !strcmp(tok, "charts")) + return web_client_api_request_v1_charts(w, url); + + else if(hash == hash_registry && !strcmp(tok, "registry")) + return web_client_api_request_v1_registry(w, url); + + else if(hash == hash_badge && !strcmp(tok, "badge.svg")) + return web_client_api_request_v1_badge(w, url); + + else if(hash == hash_alarms && !strcmp(tok, "alarms")) + return web_client_api_request_v1_alarms(w, url); + + else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log")) + return web_client_api_request_v1_alarm_log(w, url); + + else { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok); + return 404; + } + } + else { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "API v1 command?"); + return 400; + } } int web_client_api_request(struct web_client *w, char *url) { - // get the api version - char *tok = mystrsep(&url, "/?&"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok); - if(strcmp(tok, "v1") == 0) - return web_client_api_request_v1(w, url); - else { - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Unsupported API version: %s", tok); - return 404; - } - } - else { - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Which API version?"); - return 400; - } + // get the api version + char *tok = mystrsep(&url, "/?&"); + if(tok && *tok) { + debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok); + if(strcmp(tok, "v1") == 0) + return web_client_api_request_v1(w, url); + else { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Unsupported API version: %s", tok); + return 404; + } + } + else { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Which API version?"); + return 400; + } } -int web_client_data_request(struct web_client *w, char *url, int datasource_type) +int web_client_api_old_data_request(struct web_client *w, char *url, int datasource_type) { - RRDSET *st = NULL; - - char *args = strchr(url, '?'); - if(args) { - *args='\0'; - args = &args[1]; - } - - // get the name of the data to show - char *tok = mystrsep(&url, "/"); - - // do we have such a data set? - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); - st = rrdset_find_byname(tok); - if(!st) st = rrdset_find(tok); - } - - if(!st) { - // we don't have it - // try to send a file with that name - buffer_flush(w->response.data); - return(mysendfile(w, tok)); - } - - // we have it - debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok); - - // how many entries does the client want? - long lines = rrd_default_history_entries; - long group_count = 1; - time_t after = 0, before = 0; - int group_method = GROUP_AVERAGE; - int nonzero = 0; - - if(url) { - // parse the lines required - tok = mystrsep(&url, "/"); - if(tok) lines = atoi(tok); - if(lines < 1) lines = 1; - } - if(url) { - // parse the group count required - tok = mystrsep(&url, "/"); - if(tok && *tok) group_count = atoi(tok); - if(group_count < 1) group_count = 1; - //if(group_count > save_history / 20) group_count = save_history / 20; - } - if(url) { - // parse the grouping method required - tok = mystrsep(&url, "/"); - if(tok && *tok) { - if(strcmp(tok, "max") == 0) group_method = GROUP_MAX; - else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE; - else if(strcmp(tok, "sum") == 0) group_method = GROUP_SUM; - else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok); - } - } - if(url) { - // parse after time - tok = mystrsep(&url, "/"); - if(tok && *tok) after = strtoul(tok, NULL, 10); - if(after < 0) after = 0; - } - if(url) { - // parse before time - tok = mystrsep(&url, "/"); - if(tok && *tok) before = strtoul(tok, NULL, 10); - if(before < 0) before = 0; - } - if(url) { - // parse nonzero - tok = mystrsep(&url, "/"); - if(tok && *tok && strcmp(tok, "nonzero") == 0) nonzero = 1; - } - - w->response.data->contenttype = CT_APPLICATION_JSON; - buffer_flush(w->response.data); - - char *google_version = "0.6"; - char *google_reqId = "0"; - char *google_sig = "0"; - char *google_out = "json"; - char *google_responseHandler = "google.visualization.Query.setResponse"; - char *google_outFileName = NULL; - time_t last_timestamp_in_data = 0; - if(datasource_type == DATASOURCE_DATATABLE_JSON || datasource_type == DATASOURCE_DATATABLE_JSONP) { - - w->response.data->contenttype = CT_APPLICATION_X_JAVASCRIPT; - - while(args) { - tok = mystrsep(&args, "&"); - if(tok && *tok) { - char *name = mystrsep(&tok, "="); - if(name && *name && strcmp(name, "tqx") == 0) { - char *key = mystrsep(&tok, ":"); - char *value = mystrsep(&tok, ";"); - if(key && value && *key && *value) { - if(strcmp(key, "version") == 0) - google_version = value; - - else if(strcmp(key, "reqId") == 0) - google_reqId = value; - - else if(strcmp(key, "sig") == 0) - google_sig = value; - - else if(strcmp(key, "out") == 0) - google_out = value; - - else if(strcmp(key, "responseHandler") == 0) - google_responseHandler = value; - - else if(strcmp(key, "outFileName") == 0) - google_outFileName = value; - } - } - } - } - - debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'", - w->id, google_version, google_reqId, google_sig, google_out, google_responseHandler, google_outFileName - ); - - if(datasource_type == DATASOURCE_DATATABLE_JSONP) { - last_timestamp_in_data = strtoul(google_sig, NULL, 0); - - // check the client wants json - if(strcmp(google_out, "json") != 0) { - buffer_sprintf(w->response.data, - "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'invalid_query',message:'output format is not supported',detailed_message:'the format %s requested is not supported by netdata.'}]});", - google_responseHandler, google_version, google_reqId, google_out); - return 200; - } - } - } - - if(datasource_type == DATASOURCE_DATATABLE_JSONP) { - buffer_sprintf(w->response.data, - "%s({version:'%s',reqId:'%s',status:'ok',sig:'%lu',table:", - google_responseHandler, google_version, google_reqId, st->last_updated.tv_sec); - } - - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending RRD data '%s' (id %s, %d lines, %d group, %d group_method, %lu after, %lu before).", w->id, st->name, st->id, lines, group_count, group_method, after, before); - time_t timestamp_in_data = rrd_stats_json(datasource_type, st, w->response.data, lines, group_count, group_method, after, before, nonzero); - - if(datasource_type == DATASOURCE_DATATABLE_JSONP) { - if(timestamp_in_data > last_timestamp_in_data) - buffer_strcat(w->response.data, "});"); - - else { - // the client already has the latest data - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, - "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});", - google_responseHandler, google_version, google_reqId); - } - } - - return 200; + RRDSET *st = NULL; + + char *args = strchr(url, '?'); + if(args) { + *args='\0'; + args = &args[1]; + } + + // get the name of the data to show + char *tok = mystrsep(&url, "/"); + if(!tok) tok = ""; + + // do we have such a data set? + if(*tok) { + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); + st = rrdset_find_byname(tok); + if(!st) st = rrdset_find(tok); + } + + if(!st) { + // we don't have it + // try to send a file with that name + buffer_flush(w->response.data); + return(mysendfile(w, tok)); + } + + // we have it + debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok); + + // how many entries does the client want? + int lines = rrd_default_history_entries; + int group_count = 1; + time_t after = 0, before = 0; + int group_method = GROUP_AVERAGE; + int nonzero = 0; + + if(url) { + // parse the lines required + tok = mystrsep(&url, "/"); + if(tok) lines = atoi(tok); + if(lines < 1) lines = 1; + } + if(url) { + // parse the group count required + tok = mystrsep(&url, "/"); + if(tok && *tok) group_count = atoi(tok); + if(group_count < 1) group_count = 1; + //if(group_count > save_history / 20) group_count = save_history / 20; + } + if(url) { + // parse the grouping method required + tok = mystrsep(&url, "/"); + if(tok && *tok) { + if(strcmp(tok, "max") == 0) group_method = GROUP_MAX; + else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE; + else if(strcmp(tok, "sum") == 0) group_method = GROUP_SUM; + else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok); + } + } + if(url) { + // parse after time + tok = mystrsep(&url, "/"); + if(tok && *tok) after = strtoul(tok, NULL, 10); + if(after < 0) after = 0; + } + if(url) { + // parse before time + tok = mystrsep(&url, "/"); + if(tok && *tok) before = strtoul(tok, NULL, 10); + if(before < 0) before = 0; + } + if(url) { + // parse nonzero + tok = mystrsep(&url, "/"); + if(tok && *tok && strcmp(tok, "nonzero") == 0) nonzero = 1; + } + + w->response.data->contenttype = CT_APPLICATION_JSON; + buffer_flush(w->response.data); + + char *google_version = "0.6"; + char *google_reqId = "0"; + char *google_sig = "0"; + char *google_out = "json"; + char *google_responseHandler = "google.visualization.Query.setResponse"; + char *google_outFileName = NULL; + time_t last_timestamp_in_data = 0; + if(datasource_type == DATASOURCE_DATATABLE_JSON || datasource_type == DATASOURCE_DATATABLE_JSONP) { + + w->response.data->contenttype = CT_APPLICATION_X_JAVASCRIPT; + + while(args) { + tok = mystrsep(&args, "&"); + if(tok && *tok) { + char *name = mystrsep(&tok, "="); + if(name && *name && strcmp(name, "tqx") == 0) { + char *key = mystrsep(&tok, ":"); + char *value = mystrsep(&tok, ";"); + if(key && value && *key && *value) { + if(strcmp(key, "version") == 0) + google_version = value; + + else if(strcmp(key, "reqId") == 0) + google_reqId = value; + + else if(strcmp(key, "sig") == 0) + google_sig = value; + + else if(strcmp(key, "out") == 0) + google_out = value; + + else if(strcmp(key, "responseHandler") == 0) + google_responseHandler = value; + + else if(strcmp(key, "outFileName") == 0) + google_outFileName = value; + } + } + } + } + + debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'", + w->id, google_version, google_reqId, google_sig, google_out, google_responseHandler, google_outFileName + ); + + if(datasource_type == DATASOURCE_DATATABLE_JSONP) { + last_timestamp_in_data = strtoul(google_sig, NULL, 0); + + // check the client wants json + if(strcmp(google_out, "json") != 0) { + buffer_sprintf(w->response.data, + "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'invalid_query',message:'output format is not supported',detailed_message:'the format %s requested is not supported by netdata.'}]});", + google_responseHandler, google_version, google_reqId, google_out); + return 200; + } + } + } + + if(datasource_type == DATASOURCE_DATATABLE_JSONP) { + buffer_sprintf(w->response.data, + "%s({version:'%s',reqId:'%s',status:'ok',sig:'%ld',table:", + google_responseHandler, google_version, google_reqId, st->last_updated.tv_sec); + } + + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending RRD data '%s' (id %s, %d lines, %d group, %d group_method, %ld after, %ld before).", + w->id, st->name, st->id, lines, group_count, group_method, after, before); + + time_t timestamp_in_data = rrd_stats_json(datasource_type, st, w->response.data, lines, group_count, group_method, (unsigned long)after, (unsigned long)before, nonzero); + + if(datasource_type == DATASOURCE_DATATABLE_JSONP) { + if(timestamp_in_data > last_timestamp_in_data) + buffer_strcat(w->response.data, "});"); + + else { + // the client already has the latest data + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, + "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});", + google_responseHandler, google_version, google_reqId); + } + } + + return 200; } -/* -int web_client_parse_request(struct web_client *w) { - // protocol - // hostname - // path - // query string name-value - // http version - // method - // http request headers name-value +const char *web_content_type_to_string(uint8_t contenttype) { + switch(contenttype) { + case CT_TEXT_HTML: + return "text/html; charset=utf-8"; - web_client_clean_request(w); + case CT_APPLICATION_XML: + return "application/xml; charset=utf-8"; - debug(D_WEB_DATA, "%llu: Processing data buffer of %d bytes: '%s'.", w->id, w->response.data->bytes, w->response.data->buffer); + case CT_APPLICATION_JSON: + return "application/json; charset=utf-8"; - char *buf = w->response.data->buffer; - char *line, *tok; + case CT_APPLICATION_X_JAVASCRIPT: + return "application/x-javascript; charset=utf-8"; - // ------------------------------------------------------------------------ - // the first line + case CT_TEXT_CSS: + return "text/css; charset=utf-8"; - if(buf && (line = strsep(&buf, "\r\n"))) { - // method - if(line && (tok = strsep(&line, " "))) { - w->request.protocol = strdup(tok); - } - else goto cleanup; + case CT_TEXT_XML: + return "text/xml; charset=utf-8"; - // url - } - else goto cleanup; + case CT_TEXT_XSL: + return "text/xsl; charset=utf-8"; - // ------------------------------------------------------------------------ - // the rest of the lines + case CT_APPLICATION_OCTET_STREAM: + return "application/octet-stream"; - while(buf && (line = strsep(&buf, "\r\n"))) { - while(line && (tok = strsep(&line, ": "))) { - } - } + case CT_IMAGE_SVG_XML: + return "image/svg+xml"; - char *url = NULL; + case CT_APPLICATION_X_FONT_TRUETYPE: + return "application/x-font-truetype"; + case CT_APPLICATION_X_FONT_OPENTYPE: + return "application/x-font-opentype"; -cleanup: - web_client_clean_request(w); - return 0; + case CT_APPLICATION_FONT_WOFF: + return "application/font-woff"; + + case CT_APPLICATION_FONT_WOFF2: + return "application/font-woff2"; + + case CT_APPLICATION_VND_MS_FONTOBJ: + return "application/vnd.ms-fontobject"; + + case CT_IMAGE_PNG: + return "image/png"; + + case CT_IMAGE_JPG: + return "image/jpeg"; + + case CT_IMAGE_GIF: + return "image/gif"; + + case CT_IMAGE_XICON: + return "image/x-icon"; + + case CT_IMAGE_BMP: + return "image/bmp"; + + case CT_IMAGE_ICNS: + return "image/icns"; + + default: + case CT_TEXT_PLAIN: + return "text/plain; charset=utf-8"; + } } -*/ -static inline char *http_header_parse(struct web_client *w, char *s) { - char *e = s; +const char *web_response_code_to_string(int code) { + switch(code) { + case 200: + return "OK"; + + case 307: + return "Temporary Redirect"; - // find the : - while(*e && *e != ':') e++; - if(!*e || e[1] != ' ') return e; + case 400: + return "Bad Request"; - // get the name - *e = '\0'; + case 403: + return "Forbidden"; - // find the value - char *v, *ve; - v = ve = e + 2; + case 404: + return "Not Found"; - // find the \r - while(*ve && *ve != '\r') ve++; - if(!*ve || ve[1] != '\n') { - *e = ':'; - return ve; - } + case 412: + return "Preconditions Failed"; - // terminate the value - *ve = '\0'; + default: + if(code >= 100 && code < 200) + return "Informational"; - // fprintf(stderr, "HEADER: '%s' = '%s'\n", s, v); + if(code >= 200 && code < 300) + return "Successful"; - if(!strcasecmp(s, "Origin")) - strncpyz(w->origin, v, ORIGIN_MAX); + if(code >= 300 && code < 400) + return "Redirection"; + + if(code >= 400 && code < 500) + return "Bad Request"; + + if(code >= 500 && code < 600) + return "Server Error"; + + return "Undefined Error"; + } +} - else if(!strcasecmp(s, "Connection")) { - if(strcasestr(v, "keep-alive")) - w->keepalive = 1; - } +static inline char *http_header_parse(struct web_client *w, char *s) { + static uint32_t hash_origin = 0, hash_connection = 0, hash_accept_encoding = 0, hash_donottrack = 0; + + if(unlikely(!hash_origin)) { + hash_origin = simple_uhash("Origin"); + hash_connection = simple_uhash("Connection"); + hash_accept_encoding = simple_uhash("Accept-Encoding"); + hash_donottrack = simple_uhash("DNT"); + } + + char *e = s; + + // find the : + while(*e && *e != ':') e++; + if(!*e) return e; + + // get the name + *e = '\0'; + + // find the value + char *v = e + 1, *ve; + + // skip leading spaces from value + while(*v == ' ') v++; + ve = v; + + // find the \r + while(*ve && *ve != '\r') ve++; + if(!*ve || ve[1] != '\n') { + *e = ':'; + return ve; + } + + // terminate the value + *ve = '\0'; + + // fprintf(stderr, "HEADER: '%s' = '%s'\n", s, v); + uint32_t hash = simple_uhash(s); + + if(hash == hash_origin && !strcasecmp(s, "Origin")) + strncpyz(w->origin, v, ORIGIN_MAX); + + else if(hash == hash_connection && !strcasecmp(s, "Connection")) { + if(strcasestr(v, "keep-alive")) + w->keepalive = 1; + } + else if(web_donotrack_comply && hash == hash_donottrack && !strcasecmp(s, "DNT")) { + if(*v == '0') w->donottrack = 0; + else if(*v == '1') w->donottrack = 1; + } #ifdef NETDATA_WITH_ZLIB - else if(!strcasecmp(s, "Accept-Encoding")) { - if(web_enable_gzip && strcasestr(v, "gzip")) { - w->enable_gzip = 1; - } - } + else if(hash == hash_accept_encoding && !strcasecmp(s, "Accept-Encoding")) { + if(web_enable_gzip) { + if(strcasestr(v, "gzip")) + web_client_enable_deflate(w, 1); + // + // does not seem to work + // else if(strcasestr(v, "deflate")) + // web_client_enable_deflate(w, 0); + } + } #endif /* NETDATA_WITH_ZLIB */ - *e = ':'; - *ve = '\r'; - return ve; + *e = ':'; + *ve = '\r'; + return ve; } // http_request_validate() // returns: // = 0 : all good, process the request -// > 0 : request is complete, but is not supported +// > 0 : request is not supported // < 0 : request is incomplete - wait for more data static inline int http_request_validate(struct web_client *w) { - char *s = w->response.data->buffer, *encoded_url = NULL; - - // is is a valid request? - if(!strncmp(s, "GET ", 4)) { - encoded_url = s = &s[4]; - w->mode = WEB_CLIENT_MODE_NORMAL; - } - else if(!strncmp(s, "OPTIONS ", 8)) { - encoded_url = s = &s[8]; - w->mode = WEB_CLIENT_MODE_OPTIONS; - } - else { - w->wait_receive = 0; - return 1; - } - - // find the SPACE + "HTTP/" - while(*s) { - // find the space - while (*s && *s != ' ') s++; - - // is it SPACE + "HTTP/" ? - if(*s && !strncmp(s, " HTTP/", 6)) break; - else s++; - } - - // incomplete requests - if(!*s) { - w->wait_receive = 1; - return -2; - } - - // we have the end of encoded_url - remember it - char *ue = s; - - while(*s) { - // find a line feed - while (*s && *s != '\r') s++; - - // did we reach the end? - if(unlikely(!*s)) break; - - // is it \r\n ? - if (likely(s[1] == '\n')) { - - // is it again \r\n ? (header end) - if(unlikely(s[2] == '\r' && s[3] == '\n')) { - // a valid complete HTTP request found - - *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 s++; - } - - // incomplete request - w->wait_receive = 1; - return -3; + 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 next space + while (*s && *s != ' ') s++; + + // is it SPACE + "HTTP/" ? + if(*s && !strncmp(s, " HTTP/", 6)) break; + else s++; + } + + // incomplete requests + if(unlikely(!*s)) { + w->wait_receive = 1; + return -2; + } + + // we have the end of encoded_url - remember it + char *ue = s; + + // make sure we have complete request + // complete requests contain: \r\n\r\n + while(*s) { + // find a line feed + while(*s && *s++ != '\r'); + + // did we reach the end? + if(unlikely(!*s)) break; + + // is it \r\n ? + if(likely(*s++ == '\n')) { + + // is it again \r\n ? (header end) + if(unlikely(*s == '\r' && s[1] == '\n')) { + // a valid complete HTTP request found + + *ue = '\0'; + url_decode_r(w->decoded_url, encoded_url, URL_MAX + 1); + *ue = ' '; + + // 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); + + w->wait_receive = 0; + return 0; + } + + // another header line + s = http_header_parse(w, 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 { - // wait for more data - return; - } - } - else if(what_to_do > 0) { - strcpy(w->last_url, "not a valid response"); - - debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer); - - 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 { -#ifdef NETDATA_WITH_ZLIB - if(w->enable_gzip) - web_client_enable_deflate(w); -#endif - - 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 - code = web_client_api_request(w, url); - } - 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); - } - else if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data" - // 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 -- 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 -- OLD API - - // get the name of the data to show - tok = mystrsep(&url, "/?&"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); - - // do we have such a data set? - RRDSET *st = rrdset_find_byname(tok); - if(!st) st = rrdset_find(tok); - if(!st) { - // we don't have it - // try to send a file with that name - buffer_flush(w->response.data); - code = mysendfile(w, tok); - } - else { - code = 200; - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name); - w->response.data->contenttype = CT_APPLICATION_JSON; - buffer_flush(w->response.data); - rrd_stats_graph_json(st, url, w->response.data); - } - } - else { - code = 400; - buffer_flush(w->response.data); - 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); - } + static uint32_t hash_api = 0, hash_netdata_conf = 0, hash_data = 0, hash_datasource = 0, hash_graph = 0, + hash_list = 0, hash_all_json = 0, hash_exit = 0, hash_debug = 0, hash_mirror = 0; + + // start timing us + gettimeofday(&w->tv_in, NULL); + + if(unlikely(!hash_api)) { + hash_api = simple_hash("api"); + hash_netdata_conf = simple_hash("netdata.conf"); + hash_data = simple_hash(WEB_PATH_DATA); + hash_datasource = simple_hash(WEB_PATH_DATASOURCE); + hash_graph = simple_hash(WEB_PATH_GRAPH); + hash_list = simple_hash("list"); + hash_all_json = simple_hash("all.json"); + hash_exit = simple_hash("exit"); + hash_debug = simple_hash("debug"); + hash_mirror = simple_hash("mirror"); + } + + 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 (%zu bytes).", w->id, w->response.data->len); + + code = 400; + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Received request is too big (%zu bytes).\r\n", w->response.data->len); + } + else { + // wait for more data + return; + } + } + else if(what_to_do > 0) { + // strcpy(w->last_url, "not a valid request"); + + debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer); + + code = 500; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "I don't understand you...\r\n"); + } + else { // what_to_do == 0 + if(w->mode == WEB_CLIENT_MODE_OPTIONS) { + code = 200; + w->response.data->contenttype = CT_TEXT_PLAIN; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "OK"); + } + else { + char *url = w->decoded_url; + char *tok = mystrsep(&url, "/?"); + if(tok && *tok) { + uint32_t hash = simple_hash(tok); + debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok); + + if(hash == hash_api && strcmp(tok, "api") == 0) { + // the client is requesting api access + code = web_client_api_request(w, url); + } + else if(hash == hash_netdata_conf && 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); + } + else if(hash == hash_data && strcmp(tok, WEB_PATH_DATA) == 0) { // "data" + // the client is requesting rrd data -- OLD API + code = web_client_api_old_data_request(w, url, DATASOURCE_JSON); + } + else if(hash == hash_datasource && strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource" + // the client is requesting google datasource -- OLD API + code = web_client_api_old_data_request(w, url, DATASOURCE_DATATABLE_JSONP); + } + else if(hash == hash_graph && strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph" + // the client is requesting an rrd graph -- OLD API + + // get the name of the data to show + tok = mystrsep(&url, "/?&"); + if(tok && *tok) { + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); + + // do we have such a data set? + RRDSET *st = rrdset_find_byname(tok); + if(!st) st = rrdset_find(tok); + if(!st) { + // we don't have it + // try to send a file with that name + buffer_flush(w->response.data); + code = mysendfile(w, tok); + } + else { + code = 200; + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name); + w->response.data->contenttype = CT_APPLICATION_JSON; + buffer_flush(w->response.data); + rrd_stats_graph_json(st, url, w->response.data); + } + } + else { + code = 400; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "Graph name?\r\n"); + } + } + else if(hash == hash_list && 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 = localhost.rrdset_root; + + for ( ; st ; st = st->next ) + buffer_sprintf(w->response.data, "%s\n", st->name); + } + else if(hash == hash_all_json && 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); - - // get the name of the data to show - tok = mystrsep(&url, "/?&"); - if(tok && *tok) { - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); - - // do we have such a data set? - RRDSET *st = rrdset_find_byname(tok); - if(!st) st = rrdset_find(tok); - if(!st) { - code = 404; - buffer_sprintf(w->response.data, "Chart %s is not found.\r\n", tok); - debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok); - } - else { - code = 200; - debug_flags |= D_RRD_STATS; - 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"); - } - } - else { - code = 500; - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "debug which chart?\r\n"); - } - } - else if(strcmp(tok, "mirror") == 0) { - code = 200; - - debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id); - - // replace the zero bytes with spaces - buffer_char_replace(w->response.data, '\0', ' '); - - // just leave the buffer as is - // it will be copied back to the client - } -#endif /* NETDATA_INTERNAL_CHECKS */ - else { - char filename[FILENAME_MAX+1]; - url = filename; - strncpyz(filename, w->last_url, FILENAME_MAX); - tok = mystrsep(&url, "?"); - buffer_flush(w->response.data); - code = mysendfile(w, (tok && *tok)?tok:"/"); - } - } - else { - char filename[FILENAME_MAX+1]; - url = filename; - strncpyz(filename, w->last_url, FILENAME_MAX); - tok = mystrsep(&url, "?"); - buffer_flush(w->response.data); - code = mysendfile(w, (tok && *tok)?tok:"/"); - } - } - } - - gettimeofday(&w->tv_ready, NULL); - w->response.data->date = time(NULL); - w->response.sent = 0; - w->response.code = code; - - // prepare the HTTP response header - debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code); - - char *content_type_string; - switch(w->response.data->contenttype) { - case CT_TEXT_HTML: - content_type_string = "text/html; charset=utf-8"; - break; - - case CT_APPLICATION_XML: - content_type_string = "application/xml; charset=utf-8"; - break; - - case CT_APPLICATION_JSON: - content_type_string = "application/json; charset=utf-8"; - break; - - case CT_APPLICATION_X_JAVASCRIPT: - content_type_string = "application/x-javascript; charset=utf-8"; - break; - - case CT_TEXT_CSS: - content_type_string = "text/css; charset=utf-8"; - break; - - case CT_TEXT_XML: - content_type_string = "text/xml; charset=utf-8"; - break; - - case CT_TEXT_XSL: - content_type_string = "text/xsl; charset=utf-8"; - break; - - case CT_APPLICATION_OCTET_STREAM: - content_type_string = "application/octet-stream"; - break; - - case CT_IMAGE_SVG_XML: - content_type_string = "image/svg+xml"; - break; - - case CT_APPLICATION_X_FONT_TRUETYPE: - content_type_string = "application/x-font-truetype"; - break; - - case CT_APPLICATION_X_FONT_OPENTYPE: - content_type_string = "application/x-font-opentype"; - break; - - case CT_APPLICATION_FONT_WOFF: - content_type_string = "application/font-woff"; - break; - - case CT_APPLICATION_FONT_WOFF2: - content_type_string = "application/font-woff2"; - break; - - case CT_APPLICATION_VND_MS_FONTOBJ: - content_type_string = "application/vnd.ms-fontobject"; - break; - - case CT_IMAGE_PNG: - content_type_string = "image/png"; - break; - - case CT_IMAGE_JPG: - content_type_string = "image/jpeg"; - break; - - case CT_IMAGE_GIF: - content_type_string = "image/gif"; - break; - - case CT_IMAGE_XICON: - content_type_string = "image/x-icon"; - break; - - case CT_IMAGE_BMP: - content_type_string = "image/bmp"; - break; - - case CT_IMAGE_ICNS: - content_type_string = "image/icns"; - break; - - default: - case CT_TEXT_PLAIN: - content_type_string = "text/plain; charset=utf-8"; - break; - } - - char *code_msg; - switch(code) { - case 200: - code_msg = "OK"; - break; - - case 307: - code_msg = "Temporary Redirect"; - break; - - case 400: - code_msg = "Bad Request"; - break; - - case 403: - code_msg = "Forbidden"; - break; - - case 404: - code_msg = "Not Found"; - break; - - case 412: - code_msg = "Preconditions Failed"; - break; - - default: - code_msg = "Internal Server Error"; - break; - } - - char date[100]; - struct tm tmbuf, *tm = gmtime_r(&w->response.data->date, &tmbuf); - strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", tm); - - buffer_sprintf(w->response.header_output, - "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" - "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)); - - if(w->mode == WEB_CLIENT_MODE_NORMAL && (w->response.data->options & WB_CONTENT_NO_CACHEABLE)) { - buffer_sprintf(w->response.header_output, - "Expires: %s\r\n" - "Cache-Control: no-cache\r\n" - , date); - } - 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); - strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", etm); - - buffer_sprintf(w->response.header_output, - "Expires: %s\r\n" - "Cache-Control: public\r\n" - , edate); - } - - // if we know the content length, put it - if(!w->response.zoutput && (w->response.data->len || w->response.rlen)) - buffer_sprintf(w->response.header_output, - "Content-Length: %ld\r\n" - , w->response.data->len? w->response.data->len: w->response.rlen - ); - else if(!w->response.zoutput) - w->keepalive = 0; // content-length is required for keep-alive - - if(w->response.zoutput) { - buffer_strcat(w->response.header_output, - "Content-Encoding: gzip\r\n" - "Transfer-Encoding: chunked\r\n" - ); - } - - buffer_strcat(w->response.header_output, "\r\n"); - - // disable TCP_NODELAY, to buffer the header - int flag = 0; - if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) - error("%llu: failed to disable TCP_NODELAY on socket.", w->id); - - // sent the HTTP header - debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %d: '%s'" - , w->id - , buffer_strlen(w->response.header_output) - , buffer_tostring(w->response.header_output) - ); - - bytes = send(w->ofd, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output), 0); - if(bytes != (ssize_t) buffer_strlen(w->response.header_output)) - error("%llu: HTTP Header failed to be sent (I sent %d bytes but the system sent %d bytes)." - , w->id - , buffer_strlen(w->response.header_output) - , bytes); - else { - global_statistics_lock(); - global_statistics.bytes_sent += bytes; - global_statistics_unlock(); - } - - // enable TCP_NODELAY, to send all data immediately at the next send() - flag = 1; - if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to enable TCP_NODELAY on socket.", w->id); - - // enable sending immediately if we have data - if(w->response.data->len) w->wait_send = 1; - else w->wait_send = 0; - - // pretty logging - switch(w->mode) { - case WEB_CLIENT_MODE_OPTIONS: - debug(D_WEB_CLIENT, "%llu: Done preparing the OPTIONS response. Sending data (%d bytes) to client.", w->id, w->response.data->len); - break; - - case WEB_CLIENT_MODE_NORMAL: - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%d bytes) to client.", w->id, w->response.data->len); - break; - - case WEB_CLIENT_MODE_FILECOPY: - if(w->response.rlen) { - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %d bytes to client.", w->id, w->response.rlen); - w->wait_receive = 1; - - /* - // utilize the kernel sendfile() for copying the file to the socket. - // this block of code can be commented, without anything missing. - // when it is commented, the program will copy the data using async I/O. - { - long len = sendfile(w->ofd, w->ifd, NULL, w->response.data->rbytes); - if(len != w->response.data->rbytes) error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->response.data->rbytes, len); - else web_client_reset(w); - } - */ - } - else - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id); - break; - - default: - fatal("%llu: Unknown client mode %d.", w->id, w->mode); - break; - } + else if(hash == hash_exit && 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"); + + error("web request to exit received."); + netdata_cleanup_and_exit(0); + netdata_exit = 1; + } + else if(hash == hash_debug && strcmp(tok, "debug") == 0) { + buffer_flush(w->response.data); + + // get the name of the data to show + tok = mystrsep(&url, "/?&"); + if(tok && *tok) { + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok); + + // do we have such a data set? + RRDSET *st = rrdset_find_byname(tok); + if(!st) st = rrdset_find(tok); + if(!st) { + code = 404; + buffer_sprintf(w->response.data, "Chart %s is not found.\r\n", tok); + debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok); + } + else { + code = 200; + debug_flags |= D_RRD_STATS; + 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"); + } + } + else { + code = 500; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "debug which chart?\r\n"); + } + } + else if(hash == hash_mirror && strcmp(tok, "mirror") == 0) { + code = 200; + + debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id); + + // replace the zero bytes with spaces + buffer_char_replace(w->response.data, '\0', ' '); + + // just leave the buffer as is + // it will be copied back to the client + } +#endif /* NETDATA_INTERNAL_CHECKS */ + else { + char filename[FILENAME_MAX+1]; + url = filename; + strncpyz(filename, w->last_url, FILENAME_MAX); + tok = mystrsep(&url, "?"); + buffer_flush(w->response.data); + code = mysendfile(w, (tok && *tok)?tok:"/"); + } + } + else { + char filename[FILENAME_MAX+1]; + url = filename; + strncpyz(filename, w->last_url, FILENAME_MAX); + tok = mystrsep(&url, "?"); + buffer_flush(w->response.data); + code = mysendfile(w, (tok && *tok)?tok:"/"); + } + } + } + + gettimeofday(&w->tv_ready, NULL); + w->response.data->date = time(NULL); + w->response.sent = 0; + w->response.code = code; + + // prepare the HTTP response header + debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code); + + const char *content_type_string = web_content_type_to_string(w->response.data->contenttype); + const char *code_msg = web_response_code_to_string(code); + + char date[32]; + struct tm tmbuf, *tm = gmtime_r(&w->response.data->date, &tmbuf); + strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", tm); + + buffer_sprintf(w->response.header_output, + "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" + "Date: %s\r\n" + , code, code_msg + , w->keepalive?"keep-alive":"close" + , w->origin + , content_type_string + , date + ); + + if(w->cookie1[0] || w->cookie2[0]) { + 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(web_donotrack_comply) + buffer_sprintf(w->response.header_output, + "Tk: T;cookies\r\n"); + } + else { + if(web_donotrack_comply) { + if(w->tracking_required) + buffer_sprintf(w->response.header_output, + "Tk: T;cookies\r\n"); + else + buffer_sprintf(w->response.header_output, + "Tk: N\r\n"); + } + } + + 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)); + + if(w->mode == WEB_CLIENT_MODE_NORMAL && (w->response.data->options & WB_CONTENT_NO_CACHEABLE)) { + buffer_sprintf(w->response.header_output, + "Expires: %s\r\n" + "Cache-Control: no-cache\r\n" + , date); + } + else if(w->mode != WEB_CLIENT_MODE_OPTIONS) { + char edate[32]; + time_t et = w->response.data->date + (86400 * 14); + struct tm etmbuf, *etm = gmtime_r(&et, &etmbuf); + strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", etm); + + buffer_sprintf(w->response.header_output, + "Expires: %s\r\n" + "Cache-Control: public\r\n" + , edate); + } + + // if we know the content length, put it + if(!w->response.zoutput && (w->response.data->len || w->response.rlen)) + buffer_sprintf(w->response.header_output, + "Content-Length: %zu\r\n" + , w->response.data->len? w->response.data->len: w->response.rlen + ); + else if(!w->response.zoutput) + w->keepalive = 0; // content-length is required for keep-alive + + if(w->response.zoutput) { + buffer_strcat(w->response.header_output, + "Content-Encoding: gzip\r\n" + "Transfer-Encoding: chunked\r\n" + ); + } + + buffer_strcat(w->response.header_output, "\r\n"); + + // sent the HTTP header + debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %zu: '%s'" + , w->id + , buffer_strlen(w->response.header_output) + , buffer_tostring(w->response.header_output) + ); + + web_client_crock_socket(w); + + bytes = send(w->ofd, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output), 0); + if(bytes != (ssize_t) buffer_strlen(w->response.header_output)) { + if(bytes > 0) + w->stats_sent_bytes += bytes; + + debug(D_WEB_CLIENT, "%llu: HTTP Header failed to be sent (I sent %zu bytes but the system sent %zd bytes). Closing web client." + , w->id + , buffer_strlen(w->response.header_output) + , bytes); + + WEB_CLIENT_IS_DEAD(w); + return; + } + else + w->stats_sent_bytes += bytes; + + // enable sending immediately if we have data + if(w->response.data->len) w->wait_send = 1; + else w->wait_send = 0; + + // pretty logging + switch(w->mode) { + case WEB_CLIENT_MODE_OPTIONS: + debug(D_WEB_CLIENT, "%llu: Done preparing the OPTIONS response. Sending data (%zu bytes) to client.", w->id, w->response.data->len); + break; + + case WEB_CLIENT_MODE_NORMAL: + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%zu bytes) to client.", w->id, w->response.data->len); + break; + + case WEB_CLIENT_MODE_FILECOPY: + if(w->response.rlen) { + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %zu bytes to client.", w->id, w->response.rlen); + w->wait_receive = 1; + + /* + // utilize the kernel sendfile() for copying the file to the socket. + // this block of code can be commented, without anything missing. + // when it is commented, the program will copy the data using async I/O. + { + long len = sendfile(w->ofd, w->ifd, NULL, w->response.data->rbytes); + if(len != w->response.data->rbytes) + error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->response.data->rbytes, len); + else + web_client_reset(w); + } + */ + } + else + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id); + break; + + default: + fatal("%llu: Unknown client mode %d.", w->id, w->mode); + break; + } } -long web_client_send_chunk_header(struct web_client *w, long len) +ssize_t web_client_send_chunk_header(struct web_client *w, size_t len) { - debug(D_DEFLATE, "%llu: OPEN CHUNK of %d bytes (hex: %x).", w->id, len, len); - char buf[1024]; - sprintf(buf, "%lX\r\n", len); - ssize_t bytes = send(w->ofd, buf, strlen(buf), MSG_DONTWAIT); - - if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk header %d bytes.", w->id, bytes); - else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk header to the client.", w->id); - else debug(D_DEFLATE, "%llu: Failed to send chunk header to client.", w->id); - - return bytes; + debug(D_DEFLATE, "%llu: OPEN CHUNK of %zu bytes (hex: %zx).", w->id, len, len); + char buf[24]; + sprintf(buf, "%zX\r\n", len); + + ssize_t bytes = send(w->ofd, buf, strlen(buf), 0); + if(bytes > 0) { + debug(D_DEFLATE, "%llu: Sent chunk header %zd bytes.", w->id, bytes); + w->stats_sent_bytes += bytes; + } + + else if(bytes == 0) { + debug(D_WEB_CLIENT, "%llu: Did not send chunk header to the client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + else { + debug(D_WEB_CLIENT, "%llu: Failed to send chunk header to client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + + return bytes; } -long web_client_send_chunk_close(struct web_client *w) +ssize_t web_client_send_chunk_close(struct web_client *w) { - //debug(D_DEFLATE, "%llu: CLOSE CHUNK.", w->id); - - ssize_t bytes = send(w->ofd, "\r\n", 2, MSG_DONTWAIT); - - if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes); - else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id); - else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client.", w->id); - - return bytes; + //debug(D_DEFLATE, "%llu: CLOSE CHUNK.", w->id); + + ssize_t bytes = send(w->ofd, "\r\n", 2, 0); + if(bytes > 0) { + debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes); + w->stats_sent_bytes += bytes; + } + + else if(bytes == 0) { + debug(D_WEB_CLIENT, "%llu: Did not send chunk suffix to the client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + else { + debug(D_WEB_CLIENT, "%llu: Failed to send chunk suffix to client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + + return bytes; } -long web_client_send_chunk_finalize(struct web_client *w) +ssize_t web_client_send_chunk_finalize(struct web_client *w) { - //debug(D_DEFLATE, "%llu: FINALIZE CHUNK.", w->id); - - ssize_t bytes = send(w->ofd, "\r\n0\r\n\r\n", 7, MSG_DONTWAIT); - - if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes); - else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id); - else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client.", w->id); - - return bytes; + //debug(D_DEFLATE, "%llu: FINALIZE CHUNK.", w->id); + + ssize_t bytes = send(w->ofd, "\r\n0\r\n\r\n", 7, 0); + if(bytes > 0) { + debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes); + w->stats_sent_bytes += bytes; + } + + else if(bytes == 0) { + debug(D_WEB_CLIENT, "%llu: Did not send chunk finalize suffix to the client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + else { + debug(D_WEB_CLIENT, "%llu: Failed to send chunk finalize suffix to client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + + return bytes; } #ifdef NETDATA_WITH_ZLIB -long web_client_send_deflate(struct web_client *w) +ssize_t web_client_send_deflate(struct web_client *w) { - long len = 0, t = 0; - - // when using compression, - // w->response.sent is the amount of bytes passed through compression - - debug(D_DEFLATE, "%llu: web_client_send_deflate(): w->response.data->len = %d, w->response.sent = %d, w->response.zhave = %d, w->response.zsent = %d, w->response.zstream.avail_in = %d, w->response.zstream.avail_out = %d, w->response.zstream.total_in = %d, w->response.zstream.total_out = %d.", w->id, w->response.data->len, w->response.sent, w->response.zhave, w->response.zsent, w->response.zstream.avail_in, w->response.zstream.avail_out, w->response.zstream.total_in, w->response.zstream.total_out); - - if(w->response.data->len - w->response.sent == 0 && w->response.zstream.avail_in == 0 && w->response.zhave == w->response.zsent && w->response.zstream.avail_out != 0) { - // there is nothing to send - - debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); - - // finalize the chunk - if(w->response.sent != 0) - t += web_client_send_chunk_finalize(w); - - // there can be two cases for this - // A. we have done everything - // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd - - if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->response.rlen && w->response.rlen > w->response.data->len) { - // we have to wait, more data will come - debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); - w->wait_send = 0; - return(0); - } - - if(w->keepalive == 0) { - debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->response.sent); - errno = 0; - return(-1); - } - - // reset the client - web_client_reset(w); - debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id); - return(0); - } - - if(w->response.zhave == w->response.zsent) { - // compress more input data - - // close the previous open chunk - if(w->response.sent != 0) t += web_client_send_chunk_close(w); - - debug(D_DEFLATE, "%llu: Compressing %d new bytes starting from %d (and %d left behind).", w->id, (w->response.data->len - w->response.sent), w->response.sent, w->response.zstream.avail_in); - - // give the compressor all the data not passed through the compressor yet - if(w->response.data->len > w->response.sent) { -#ifdef NETDATA_INTERNAL_CHECKS - if((long)w->response.sent - (long)w->response.zstream.avail_in < 0) - error("internal error: avail_in is corrupted."); -#endif - w->response.zstream.next_in = (Bytef *)&w->response.data->buffer[w->response.sent - w->response.zstream.avail_in]; - w->response.zstream.avail_in += (uInt) (w->response.data->len - w->response.sent); - } - - // reset the compressor output buffer - w->response.zstream.next_out = w->response.zbuffer; - w->response.zstream.avail_out = ZLIB_CHUNK; - - // ask for FINISH if we have all the input - int flush = Z_SYNC_FLUSH; - if(w->mode == WEB_CLIENT_MODE_NORMAL - || (w->mode == WEB_CLIENT_MODE_FILECOPY && !w->wait_receive && w->response.data->len == w->response.rlen)) { - flush = Z_FINISH; - debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id); - } - else { - debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id); - } - - // compress - if(deflate(&w->response.zstream, flush) == Z_STREAM_ERROR) { - error("%llu: Compression failed. Closing down client.", w->id); - web_client_reset(w); - return(-1); - } - - w->response.zhave = ZLIB_CHUNK - w->response.zstream.avail_out; - w->response.zsent = 0; - - // keep track of the bytes passed through the compressor - w->response.sent = w->response.data->len; - - debug(D_DEFLATE, "%llu: Compression produced %d bytes.", w->id, w->response.zhave); - - // open a new chunk - t += web_client_send_chunk_header(w, w->response.zhave); - } - - debug(D_WEB_CLIENT, "%llu: Sending %d bytes of data (+%d of chunk header).", w->id, w->response.zhave - w->response.zsent, t); - - len = send(w->ofd, &w->response.zbuffer[w->response.zsent], (size_t) (w->response.zhave - w->response.zsent), MSG_DONTWAIT); - if(len > 0) { - w->response.zsent += len; - if(t > 0) len += t; - debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, len); - } - else if(len == 0) debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client (zhave = %ld, zsent = %ld, need to send = %ld).", w->id, w->response.zhave, w->response.zsent, w->response.zhave - w->response.zsent); - else debug(D_WEB_CLIENT, "%llu: Failed to send data to client. Reason: %s", w->id, strerror(errno)); - - return(len); + ssize_t len = 0, t = 0; + + // when using compression, + // w->response.sent is the amount of bytes passed through compression + + debug(D_DEFLATE, "%llu: web_client_send_deflate(): w->response.data->len = %zu, w->response.sent = %zu, w->response.zhave = %zu, w->response.zsent = %zu, w->response.zstream.avail_in = %u, w->response.zstream.avail_out = %u, w->response.zstream.total_in = %lu, w->response.zstream.total_out = %lu.", + w->id, w->response.data->len, w->response.sent, w->response.zhave, w->response.zsent, w->response.zstream.avail_in, w->response.zstream.avail_out, w->response.zstream.total_in, w->response.zstream.total_out); + + if(w->response.data->len - w->response.sent == 0 && w->response.zstream.avail_in == 0 && w->response.zhave == w->response.zsent && w->response.zstream.avail_out != 0) { + // there is nothing to send + + debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); + + // finalize the chunk + if(w->response.sent != 0) { + t = web_client_send_chunk_finalize(w); + if(t < 0) return t; + } + + if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->response.rlen && w->response.rlen > w->response.data->len) { + // we have to wait, more data will come + debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); + w->wait_send = 0; + return t; + } + + if(unlikely(!w->keepalive)) { + debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %zu bytes sent.", w->id, w->response.sent); + WEB_CLIENT_IS_DEAD(w); + return t; + } + + // reset the client + web_client_reset(w); + debug(D_WEB_CLIENT, "%llu: Done sending all data on socket.", w->id); + return t; + } + + if(w->response.zhave == w->response.zsent) { + // compress more input data + + // close the previous open chunk + if(w->response.sent != 0) { + t = web_client_send_chunk_close(w); + if(t < 0) return t; + } + + debug(D_DEFLATE, "%llu: Compressing %zu new bytes starting from %zu (and %u left behind).", w->id, (w->response.data->len - w->response.sent), w->response.sent, w->response.zstream.avail_in); + + // give the compressor all the data not passed through the compressor yet + if(w->response.data->len > w->response.sent) { + w->response.zstream.next_in = (Bytef *)&w->response.data->buffer[w->response.sent - w->response.zstream.avail_in]; + w->response.zstream.avail_in += (uInt) (w->response.data->len - w->response.sent); + } + + // reset the compressor output buffer + w->response.zstream.next_out = w->response.zbuffer; + w->response.zstream.avail_out = ZLIB_CHUNK; + + // ask for FINISH if we have all the input + int flush = Z_SYNC_FLUSH; + if(w->mode == WEB_CLIENT_MODE_NORMAL + || (w->mode == WEB_CLIENT_MODE_FILECOPY && !w->wait_receive && w->response.data->len == w->response.rlen)) { + flush = Z_FINISH; + debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id); + } + else { + debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id); + } + + // compress + if(deflate(&w->response.zstream, flush) == Z_STREAM_ERROR) { + error("%llu: Compression failed. Closing down client.", w->id); + web_client_reset(w); + return(-1); + } + + w->response.zhave = ZLIB_CHUNK - w->response.zstream.avail_out; + w->response.zsent = 0; + + // keep track of the bytes passed through the compressor + w->response.sent = w->response.data->len; + + debug(D_DEFLATE, "%llu: Compression produced %zu bytes.", w->id, w->response.zhave); + + // open a new chunk + ssize_t t2 = web_client_send_chunk_header(w, w->response.zhave); + if(t2 < 0) return t2; + t += t2; + } + + debug(D_WEB_CLIENT, "%llu: Sending %zu bytes of data (+%zd of chunk header).", w->id, w->response.zhave - w->response.zsent, t); + + len = send(w->ofd, &w->response.zbuffer[w->response.zsent], (size_t) (w->response.zhave - w->response.zsent), MSG_DONTWAIT); + if(len > 0) { + w->stats_sent_bytes += len; + w->response.zsent += len; + len += t; + debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, len); + } + else if(len == 0) { + debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client (zhave = %zu, zsent = %zu, need to send = %zu).", + w->id, w->response.zhave, w->response.zsent, w->response.zhave - w->response.zsent); + + WEB_CLIENT_IS_DEAD(w); + } + else { + debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + + return(len); } #endif // NETDATA_WITH_ZLIB -long web_client_send(struct web_client *w) -{ +ssize_t web_client_send(struct web_client *w) { #ifdef NETDATA_WITH_ZLIB - if(likely(w->response.zoutput)) return web_client_send_deflate(w); + if(likely(w->response.zoutput)) return web_client_send_deflate(w); #endif // NETDATA_WITH_ZLIB - long bytes; - - if(unlikely(w->response.data->len - w->response.sent == 0)) { - // there is nothing to send - - debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); - - // there can be two cases for this - // A. we have done everything - // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd - - if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->response.rlen && w->response.rlen > w->response.data->len) { - // we have to wait, more data will come - debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); - w->wait_send = 0; - return(0); - } - - if(unlikely(w->keepalive == 0)) { - debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->response.sent); - errno = 0; - return(-1); - } - - web_client_reset(w); - debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id); - return(0); - } - - bytes = send(w->ofd, &w->response.data->buffer[w->response.sent], w->response.data->len - w->response.sent, MSG_DONTWAIT); - if(likely(bytes > 0)) { - w->response.sent += bytes; - debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes); - } - else if(likely(bytes == 0)) debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id); - else debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id); - - return(bytes); + ssize_t bytes; + + if(unlikely(w->response.data->len - w->response.sent == 0)) { + // there is nothing to send + + debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id); + + // there can be two cases for this + // A. we have done everything + // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd + + if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->response.rlen && w->response.rlen > w->response.data->len) { + // we have to wait, more data will come + debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id); + w->wait_send = 0; + return 0; + } + + if(unlikely(!w->keepalive)) { + debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %zu bytes sent.", w->id, w->response.sent); + WEB_CLIENT_IS_DEAD(w); + return 0; + } + + web_client_reset(w); + debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id); + return 0; + } + + bytes = send(w->ofd, &w->response.data->buffer[w->response.sent], w->response.data->len - w->response.sent, MSG_DONTWAIT); + if(likely(bytes > 0)) { + w->stats_sent_bytes += bytes; + w->response.sent += bytes; + debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, bytes); + } + else if(likely(bytes == 0)) { + debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + else { + debug(D_WEB_CLIENT, "%llu: Failed to send data to client.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + + return(bytes); } -long web_client_receive(struct web_client *w) +ssize_t web_client_receive(struct web_client *w) { - // do we have any space for more data? - buffer_need_bytes(w->response.data, WEB_REQUEST_LENGTH); - - long left = w->response.data->size - w->response.data->len; - long bytes; - - if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) - bytes = read(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1)); - else - bytes = recv(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1), MSG_DONTWAIT); - - if(likely(bytes > 0)) { - size_t old = w->response.data->len; - w->response.data->len += bytes; - w->response.data->buffer[w->response.data->len] = '\0'; - - debug(D_WEB_CLIENT, "%llu: Received %d bytes.", w->id, bytes); - debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->response.data->buffer[old]); - - if(w->mode == WEB_CLIENT_MODE_FILECOPY) { - w->wait_send = 1; - if(w->response.rlen && w->response.data->len >= w->response.rlen) w->wait_receive = 0; - } - } - else if(likely(bytes == 0)) { - debug(D_WEB_CLIENT, "%llu: Out of input data.", w->id); - - // if we cannot read, it means we have an error on input. - // if however, we are copying a file from ifd to ofd, we should not return an error. - // in this case, the error should be generated when the file has been sent to the client. - - if(w->mode == WEB_CLIENT_MODE_FILECOPY) { - // we are copying data from ifd to ofd - // let it finish copying... - w->wait_receive = 0; - debug(D_WEB_CLIENT, "%llu: Disabling input.", w->id); - } - else { - bytes = -1; - errno = 0; - } - } - - return(bytes); + // do we have any space for more data? + buffer_need_bytes(w->response.data, WEB_REQUEST_LENGTH); + + ssize_t left = w->response.data->size - w->response.data->len; + ssize_t bytes; + + if(unlikely(w->mode == WEB_CLIENT_MODE_FILECOPY)) + bytes = read(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1)); + else + bytes = recv(w->ifd, &w->response.data->buffer[w->response.data->len], (size_t) (left - 1), MSG_DONTWAIT); + + if(likely(bytes > 0)) { + if(w->mode != WEB_CLIENT_MODE_FILECOPY) + w->stats_received_bytes += bytes; + + size_t old = w->response.data->len; + w->response.data->len += bytes; + w->response.data->buffer[w->response.data->len] = '\0'; + + debug(D_WEB_CLIENT, "%llu: Received %zd bytes.", w->id, bytes); + debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->response.data->buffer[old]); + + if(w->mode == WEB_CLIENT_MODE_FILECOPY) { + w->wait_send = 1; + + if(w->response.rlen && w->response.data->len >= w->response.rlen) + w->wait_receive = 0; + } + } + else if(likely(bytes == 0)) { + debug(D_WEB_CLIENT, "%llu: Out of input data.", w->id); + + // if we cannot read, it means we have an error on input. + // if however, we are copying a file from ifd to ofd, we should not return an error. + // in this case, the error should be generated when the file has been sent to the client. + + if(w->mode == WEB_CLIENT_MODE_FILECOPY) { + // we are copying data from ifd to ofd + // let it finish copying... + w->wait_receive = 0; + + debug(D_WEB_CLIENT, "%llu: Read the whole file.", w->id); + if(w->ifd != w->ofd) close(w->ifd); + w->ifd = w->ofd; + } + else { + debug(D_WEB_CLIENT, "%llu: failed to receive data.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + } + else { + debug(D_WEB_CLIENT, "%llu: receive data failed.", w->id); + WEB_CLIENT_IS_DEAD(w); + } + + return(bytes); } @@ -2040,104 +2545,131 @@ long web_client_receive(struct web_client *w) void *web_client_main(void *ptr) { - 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 timeval tv; - struct web_client *w = ptr; - int retval; - fd_set ifds, ofds, efds; - int fdmax = 0; - - log_access("%llu: %s port %s connected on thread task id %d", w->id, w->client_ip, w->client_port, gettid()); - - for(;;) { - FD_ZERO (&ifds); - FD_ZERO (&ofds); - FD_ZERO (&efds); - - FD_SET(w->ifd, &efds); - - if(w->ifd != w->ofd) - FD_SET(w->ofd, &efds); - - if (w->wait_receive) { - FD_SET(w->ifd, &ifds); - if(w->ifd > fdmax) fdmax = w->ifd; - } - - if (w->wait_send) { - FD_SET(w->ofd, &ofds); - if(w->ofd > fdmax) fdmax = w->ofd; - } - - tv.tv_sec = web_client_timeout; - tv.tv_usec = 0; - - debug(D_WEB_CLIENT, "%llu: Waiting socket async I/O for %s %s", w->id, w->wait_receive?"INPUT":"", w->wait_send?"OUTPUT":""); - retval = select(fdmax+1, &ifds, &ofds, &efds, &tv); - - if(retval == -1) { - debug(D_WEB_CLIENT_ACCESS, "%llu: LISTENER: select() failed.", w->id); - continue; - } - else if(!retval) { - // timeout - debug(D_WEB_CLIENT_ACCESS, "%llu: LISTENER: timeout.", w->id); - break; - } - - if(FD_ISSET(w->ifd, &efds)) { - debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on input socket.", w->id); - break; - } - - if(FD_ISSET(w->ofd, &efds)) { - debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on output socket.", w->id); - break; - } - - if(w->wait_send && FD_ISSET(w->ofd, &ofds)) { - long bytes; - if((bytes = web_client_send(w)) < 0) { - debug(D_WEB_CLIENT, "%llu: Cannot send data to client. Closing client.", w->id); - errno = 0; - break; - } - - global_statistics_lock(); - global_statistics.bytes_sent += bytes; - global_statistics_unlock(); - } - - if(w->wait_receive && FD_ISSET(w->ifd, &ifds)) { - long bytes; - if((bytes = web_client_receive(w)) < 0) { - debug(D_WEB_CLIENT, "%llu: Cannot receive data from client. Closing client.", w->id); - errno = 0; - break; - } - - if(w->mode == WEB_CLIENT_MODE_NORMAL) { - debug(D_WEB_CLIENT, "%llu: Attempting to process received data (%ld bytes).", w->id, bytes); - // info("%llu: Attempting to process received data (%ld bytes).", w->id, bytes); - web_client_process(w); - } - - global_statistics_lock(); - global_statistics.bytes_received += bytes; - global_statistics_unlock(); - } - } - - log_access("%llu: %s port %s disconnected from thread task id %d", w->id, w->client_ip, w->client_port, gettid()); - debug(D_WEB_CLIENT, "%llu: done...", w->id); - - web_client_reset(w); - w->obsolete = 1; - - return NULL; + 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 web_client *w = ptr; + struct pollfd fds[2], *ifd, *ofd; + int retval, fdmax = 0, timeout; + + log_access("%llu: %s port %s connected on thread task id %d", w->id, w->client_ip, w->client_port, gettid()); + + for(;;) { + if(unlikely(w->dead)) { + debug(D_WEB_CLIENT, "%llu: client is dead.", w->id); + break; + } + else if(unlikely(!w->wait_receive && !w->wait_send)) { + debug(D_WEB_CLIENT, "%llu: client is not set for neither receiving nor sending data.", w->id); + break; + } + + if(unlikely(w->ifd < 0 || w->ofd < 0)) { + error("%llu: invalid file descriptor, ifd = %d, ofd = %d (required 0 <= fd", w->id, w->ifd, w->ofd); + break; + } + + if(w->ifd == w->ofd) { + fds[0].fd = w->ifd; + fds[0].events = 0; + fds[0].revents = 0; + + if(w->wait_receive) fds[0].events |= POLLIN; + if(w->wait_send) fds[0].events |= POLLOUT; + + fds[1].fd = -1; + fds[1].events = 0; + fds[1].revents = 0; + + ifd = ofd = &fds[0]; + + fdmax = 1; + } + else { + fds[0].fd = w->ifd; + fds[0].events = 0; + fds[0].revents = 0; + if(w->wait_receive) fds[0].events |= POLLIN; + ifd = &fds[0]; + + fds[1].fd = w->ofd; + fds[1].events = 0; + fds[1].revents = 0; + if(w->wait_send) fds[1].events |= POLLOUT; + ofd = &fds[1]; + + fdmax = 2; + } + + debug(D_WEB_CLIENT, "%llu: Waiting socket async I/O for %s %s", w->id, w->wait_receive?"INPUT":"", w->wait_send?"OUTPUT":""); + errno = 0; + timeout = web_client_timeout * 1000; + retval = poll(fds, fdmax, timeout); + + if(unlikely(retval == -1)) { + if(errno == EAGAIN || errno == EINTR) { + debug(D_WEB_CLIENT, "%llu: EAGAIN received.", w->id); + continue; + } + + debug(D_WEB_CLIENT, "%llu: LISTENER: poll() failed (input fd = %d, output fd = %d). Closing client.", w->id, w->ifd, w->ofd); + break; + } + else if(unlikely(!retval)) { + debug(D_WEB_CLIENT, "%llu: Timeout while waiting socket async I/O for %s %s", w->id, w->wait_receive?"INPUT":"", w->wait_send?"OUTPUT":""); + break; + } + + int used = 0; + if(w->wait_send && ofd->revents & POLLOUT) { + used++; + if(web_client_send(w) < 0) { + debug(D_WEB_CLIENT, "%llu: Cannot send data to client. Closing client.", w->id); + break; + } + } + + if(w->wait_receive && (ifd->revents & POLLIN || ifd->revents & POLLPRI)) { + used++; + if(web_client_receive(w) < 0) { + debug(D_WEB_CLIENT, "%llu: Cannot receive data from client. Closing client.", w->id); + break; + } + + if(w->mode == WEB_CLIENT_MODE_NORMAL) { + debug(D_WEB_CLIENT, "%llu: Attempting to process received data.", w->id); + web_client_process(w); + } + } + + if(unlikely(!used)) { + debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on socket.", w->id); + break; + } + } + + web_client_reset(w); + + log_access("%llu: %s port %s disconnected from thread task id %d", w->id, w->client_ip, w->client_port, gettid()); + debug(D_WEB_CLIENT, "%llu: done...", w->id); + + // close the sockets/files now + // to free file descriptors + if(w->ifd == w->ofd) { + if(w->ifd != -1) close(w->ifd); + } + else { + if(w->ifd != -1) close(w->ifd); + if(w->ofd != -1) close(w->ofd); + } + w->ifd = -1; + w->ofd = -1; + + w->obsolete = 1; + + pthread_exit(NULL); + return NULL; } diff --git a/src/web_client.h b/src/web_client.h index f663be4a1..2555a0c24 100644 --- a/src/web_client.h +++ b/src/web_client.h @@ -1,92 +1,94 @@ - -#ifdef NETDATA_WITH_ZLIB -#include <zlib.h> -#endif - -#include <sys/time.h> -#include <string.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <netdb.h> - -#include "web_buffer.h" -#include "dictionary.h" +#ifndef NETDATA_WEB_CLIENT_H +#define NETDATA_WEB_CLIENT_H 1 #define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60 extern int web_client_timeout; -extern int web_enable_gzip; -#ifndef NETDATA_WEB_CLIENT_H -#define NETDATA_WEB_CLIENT_H 1 +#ifdef NETDATA_WITH_ZLIB +extern int web_enable_gzip, web_gzip_level, web_gzip_strategy, web_donotrack_comply; +#endif /* NETDATA_WITH_ZLIB */ -#define WEB_CLIENT_MODE_NORMAL 0 -#define WEB_CLIENT_MODE_FILECOPY 1 -#define WEB_CLIENT_MODE_OPTIONS 2 +#define WEB_CLIENT_MODE_NORMAL 0 +#define WEB_CLIENT_MODE_FILECOPY 1 +#define WEB_CLIENT_MODE_OPTIONS 2 #define URL_MAX 8192 -#define ZLIB_CHUNK 16384 +#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 - BUFFER *header_output; // internal use - BUFFER *data; // our response data buffer + BUFFER *header; // our response header + BUFFER *header_output; // internal use + BUFFER *data; // our response data buffer - int code; // the HTTP response code + int code; // the HTTP response code - size_t rlen; // if non-zero, the excepted size of ifd (input) - size_t sent; // current data length sent to output + size_t rlen; // if non-zero, the excepted size of ifd (input of firecopy) + size_t sent; // current data length sent to output - int zoutput; // if set to 1, web_client_send() will send compressed data + int zoutput; // if set to 1, web_client_send() will send compressed data #ifdef NETDATA_WITH_ZLIB - z_stream zstream; // zlib stream for sending compressed output to client - Bytef zbuffer[ZLIB_CHUNK]; // temporary buffer for storing compressed output - long zsent; // the compressed bytes we have sent to the client - long zhave; // the compressed bytes that we have to send - int zinitialized; -#endif + z_stream zstream; // zlib stream for sending compressed output to client + Bytef zbuffer[ZLIB_CHUNK]; // temporary buffer for storing compressed output + size_t zsent; // the compressed bytes we have sent to the client + size_t zhave; // the compressed bytes that we have received from zlib + int zinitialized:1; +#endif /* NETDATA_WITH_ZLIB */ }; struct web_client { - unsigned long long id; + unsigned long long id; + + uint8_t obsolete:1; // if set to 1, the listener will remove this client + // after setting this to 1, you should not touch + // this web_client + + uint8_t dead:1; // if set to 1, this client is dead + + uint8_t keepalive:1; // if set to 1, the web client will be re-used - char client_ip[NI_MAXHOST+1]; - char client_port[NI_MAXSERV+1]; + uint8_t mode:3; // the operational mode of the client - char last_url[URL_MAX+1]; + uint8_t wait_receive:1; // 1 = we are waiting more input data + uint8_t wait_send:1; // 1 = we have data to send to the client - struct timeval tv_in, tv_ready; + uint8_t donottrack:1; // 1 = we should not set cookies on this client + uint8_t tracking_required:1; // 1 = if the request requires cookies - char cookie1[COOKIE_MAX+1]; - char cookie2[COOKIE_MAX+1]; - char origin[ORIGIN_MAX+1]; + int tcp_cork; // 1 = we have a cork on the socket - int mode; - int keepalive; - int enable_gzip; - char *decoded_url; + int ifd; + int ofd; - struct sockaddr_storage clientaddr; + char client_ip[NI_MAXHOST+1]; + char client_port[NI_MAXSERV+1]; - pthread_t thread; // the thread servicing this client - int obsolete; // if set to 1, the listener will remove this client + char decoded_url[URL_MAX + 1]; // we decode the URL in this buffer + char last_url[URL_MAX+1]; // we keep a copy of the decoded URL here - int ifd; - int ofd; + struct timeval tv_in, tv_ready; - struct response response; + char cookie1[COOKIE_MAX+1]; + char cookie2[COOKIE_MAX+1]; + char origin[ORIGIN_MAX+1]; - int wait_receive; - int wait_send; + struct sockaddr_storage clientaddr; + struct response response; - struct web_client *prev; - struct web_client *next; + size_t stats_received_bytes; + size_t stats_sent_bytes; + + pthread_t thread; // the thread servicing this client + + struct web_client *prev; + struct web_client *next; }; +#define WEB_CLIENT_IS_DEAD(w) (w)->dead=1 + extern struct web_client *web_clients; extern uid_t web_files_uid(void); @@ -94,7 +96,15 @@ extern uid_t web_files_gid(void); extern struct web_client *web_client_create(int listener); extern struct web_client *web_client_free(struct web_client *w); +extern ssize_t web_client_send(struct web_client *w); +extern ssize_t web_client_receive(struct web_client *w); +extern void web_client_process(struct web_client *w); +extern void web_client_reset(struct web_client *w); extern void *web_client_main(void *ptr); +extern int web_client_api_request_v1_data_group(char *name, int def); +extern const char *group_method2string(int group); + +extern void buffer_data_options2string(BUFFER *wb, uint32_t options); #endif diff --git a/src/web_server.c b/src/web_server.c index 0da72b5be..cf3687f3e 100644 --- a/src/web_server.c +++ b/src/web_server.c @@ -1,270 +1,601 @@ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif -#include <unistd.h> -#include <stdlib.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <errno.h> -#include <pthread.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <netinet/tcp.h> -#include <malloc.h> - #include "common.h" -#include "log.h" -#include "appconfig.h" -#include "url.h" -#include "web_buffer.h" -#include "web_client.h" -#include "web_server.h" -#include "global_statistics.h" -#include "rrd.h" -#include "rrd2json.h" -#include "../config.h" int listen_backlog = LISTEN_BACKLOG; - -int listen_fd = -1; +size_t listen_fds_count = 0; +int listen_fds[MAX_LISTEN_FDS] = { [0 ... 99] = -1 }; +char *listen_fds_names[MAX_LISTEN_FDS] = { [0 ... 99] = NULL }; int listen_port = LISTEN_PORT; +int web_server_mode = WEB_SERVER_MODE_MULTI_THREADED; #ifdef NETDATA_INTERNAL_CHECKS static void log_allocations(void) { - static int mem = 0; + static int mem = 0; - struct mallinfo mi; + struct mallinfo mi; - mi = mallinfo(); - if(mi.uordblks > mem) { - int clients = 0; - struct web_client *w; - for(w = web_clients; w ; w = w->next) clients++; + mi = mallinfo(); + if(mi.uordblks > mem) { + int clients = 0; + struct web_client *w; + for(w = web_clients; w ; w = w->next) clients++; - info("Allocated memory increased from %d to %d (increased by %d bytes). There are %d web clients connected.", mem, mi.uordblks, mi.uordblks - mem, clients); - mem = mi.uordblks; - } + info("Allocated memory increased from %d to %d (increased by %d bytes). There are %d web clients connected.", mem, mi.uordblks, mi.uordblks - mem, clients); + mem = mi.uordblks; + } } #endif -static int is_ip_anything(const char *ip) -{ - if(!ip || !*ip - || !strcmp(ip, "any") - || !strcmp(ip, "all") - || !strcmp(ip, "*") - || !strcmp(ip, "::") - || !strcmp(ip, "0.0.0.0") - ) return 1; - - return 0; +#ifndef HAVE_ACCEPT4 +int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) { + int fd = accept(sock, addr, addrlen); + int newflags = 0; + + if (fd < 0) return fd; + + if (flags & SOCK_NONBLOCK) { + newflags |= O_NONBLOCK; + flags &= ~SOCK_NONBLOCK; + } + + if (flags & SOCK_CLOEXEC) { + newflags |= O_CLOEXEC; + flags &= ~SOCK_CLOEXEC; + } + + if (flags) { + errno = -EINVAL; + return -1; + } + + if (fcntl(fd, F_SETFL, newflags) < 0) { + int saved_errno = errno; + close(fd); + errno = saved_errno; + return -1; + } + + return fd; } +#endif -int create_listen_socket4(const char *ip, int port, int listen_backlog) -{ - int sock; - int sockopt = 1; - - debug(D_LISTENER, "IPv4 creating new listening socket on port %d", port); - - sock = socket(AF_INET, SOCK_STREAM, 0); - if(sock < 0) { - error("IPv4 socket() failed."); - return -1; - } - - /* 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); - - if(is_ip_anything(ip)) { - name.sin_addr.s_addr = htonl(INADDR_ANY); - info("Listening on any IPs (IPv4)."); - } - else { - int ret = inet_pton(AF_INET, ip, (void *)&name.sin_addr.s_addr); - if(ret != 1) { - error("Failed to convert IP '%s' to a valid IPv4 address.", ip); - close(sock); - return -1; - } - info("Listening on IP '%s' (IPv4).", ip); - } - - if(bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { - close(sock); - error("IPv4 bind() failed."); - return -1; - } - - if(listen(sock, listen_backlog) < 0) { - close(sock); - fatal("IPv4 listen() failed."); - return -1; - } - - debug(D_LISTENER, "IPv4 listening port %d created", port); - return sock; +int create_listen_socket4(const char *ip, int port, int listen_backlog) { + int sock; + int sockopt = 1; + + debug(D_LISTENER, "IPv4 creating new listening socket on ip '%s' port %d", ip, port); + + sock = socket(AF_INET, SOCK_STREAM, 0); + if(sock < 0) { + error("IPv4 socket() on ip '%s' port %d failed.", ip, port); + return -1; + } + + /* avoid "address already in use" */ + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&sockopt, sizeof(sockopt)) != 0) + error("Cannot set SO_REUSEADDR on ip '%s' port's %d.", ip, port); + + struct sockaddr_in name; + memset(&name, 0, sizeof(struct sockaddr_in)); + name.sin_family = AF_INET; + name.sin_port = htons (port); + + int ret = inet_pton(AF_INET, ip, (void *)&name.sin_addr.s_addr); + if(ret != 1) { + error("Failed to convert IP '%s' to a valid IPv4 address.", ip); + close(sock); + return -1; + } + + if(bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { + close(sock); + error("IPv4 bind() on ip '%s' port %d failed.", ip, port); + return -1; + } + + if(listen(sock, listen_backlog) < 0) { + close(sock); + fatal("IPv4 listen() on ip '%s' port %d failed.", ip, port); + return -1; + } + + debug(D_LISTENER, "Listening on IPv4 ip '%s' port %d", ip, port); + return sock; } -int create_listen_socket6(const char *ip, int port, int listen_backlog) -{ - int sock = -1; - int sockopt = 1; - - debug(D_LISTENER, "IPv6 creating new listening socket on port %d", port); - - sock = socket(AF_INET6, SOCK_STREAM, 0); - if (sock < 0) { - error("IPv6 socket() failed. Disabling IPv6."); - return -1; - } - - /* 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); - - if(is_ip_anything(ip)) { - name.sin6_addr = in6addr_any; - info("Listening on all IPs (IPv6 and IPv4)"); - } - else { - int ret = inet_pton(AF_INET6, ip, (void *)&name.sin6_addr.s6_addr); - if(ret != 1) { - error("Failed to convert IP '%s' to a valid IPv6 address. Disabling IPv6.", ip); - close(sock); - return -1; - } - info("Listening on IP '%s' (IPv6)", ip); - } - - name.sin6_scope_id = 0; - - if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { - close(sock); - error("IPv6 bind() failed. Disabling IPv6."); - return -1; - } - - if (listen(sock, listen_backlog) < 0) { - close(sock); - error("IPv6 listen() failed. Disabling IPv6."); - return -1; - } - - debug(D_LISTENER, "IPv6 listening port %d created", port); - return sock; +int create_listen_socket6(const char *ip, int port, int listen_backlog) { + int sock = -1; + int sockopt = 1; + int ipv6only = 1; + + debug(D_LISTENER, "IPv6 creating new listening socket on ip '%s' port %d", ip, port); + + sock = socket(AF_INET6, SOCK_STREAM, 0); + if (sock < 0) { + error("IPv6 socket() on ip '%s' port %d failed.", ip, port); + return -1; + } + + /* avoid "address already in use" */ + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&sockopt, sizeof(sockopt)) != 0) + error("Cannot set SO_REUSEADDR on ip '%s' port's %d.", ip, port); + + /* IPv6 only */ + if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&ipv6only, sizeof(ipv6only)) != 0) + error("Cannot set IPV6_V6ONLY on ip '%s' port's %d.", ip, port); + + struct sockaddr_in6 name; + memset(&name, 0, sizeof(struct sockaddr_in6)); + name.sin6_family = AF_INET6; + name.sin6_port = htons ((uint16_t) port); + + int ret = inet_pton(AF_INET6, ip, (void *)&name.sin6_addr.s6_addr); + if(ret != 1) { + error("Failed to convert IP '%s' to a valid IPv6 address.", ip); + close(sock); + return -1; + } + + name.sin6_scope_id = 0; + + if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { + close(sock); + error("IPv6 bind() on ip '%s' port %d failed.", ip, port); + return -1; + } + + if (listen(sock, listen_backlog) < 0) { + close(sock); + error("IPv6 listen() on ip '%s' port %d failed.", ip, port); + return -1; + } + + debug(D_LISTENER, "Listening on IPv6 ip '%s' port %d", ip, port); + return sock; +} + +static inline int add_listen_socket(int fd, const char *ip, int port) { + if(listen_fds_count >= MAX_LISTEN_FDS) { + error("Too many listening sockets. Failed to add listening socket at ip '%s' port %d", ip, port); + close(fd); + return -1; + } + + listen_fds[listen_fds_count] = fd; + + char buffer[100 + 1]; + snprintfz(buffer, 100, "[%s]:%d", ip, port); + listen_fds_names[listen_fds_count] = strdupz(buffer); + + listen_fds_count++; + return 0; +} + +int is_listen_socket(int fd) { + size_t i; + for(i = 0; i < listen_fds_count ;i++) + if(listen_fds[i] == fd) return 1; + + return 0; +} + +static inline void close_listen_sockets(void) { + size_t i; + for(i = 0; i < listen_fds_count ;i++) { + close(listen_fds[i]); + listen_fds[i] = -1; + + freez(listen_fds_names[i]); + listen_fds_names[i] = NULL; + } + + listen_fds_count = 0; +} + +static inline int bind_to_one(const char *definition, int default_port, int listen_backlog) { + int added = 0; + struct addrinfo hints; + struct addrinfo *result = NULL, *rp = NULL; + + char buffer[strlen(definition) + 1]; + strcpy(buffer, definition); + + char buffer2[10 + 1]; + snprintfz(buffer2, 10, "%d", default_port); + + char *ip = buffer, *port = buffer2; + + char *e = ip; + if(*e == '[') { + e = ++ip; + while(*e && *e != ']') e++; + if(*e == ']') { + *e = '\0'; + e++; + } + } + else { + while(*e && *e != ':') e++; + } + + if(*e == ':') { + port = e + 1; + *e = '\0'; + } + + if(!*ip || *ip == '*' || !strcmp(ip, "any") || !strcmp(ip, "all")) + ip = NULL; + if(!*port) + port = buffer2; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + int r = getaddrinfo(ip, port, &hints, &result); + if (r != 0) { + error("getaddrinfo('%s', '%s'): %s\n", ip, port, gai_strerror(r)); + return -1; + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + int fd = -1; + + char rip[INET_ADDRSTRLEN + INET6_ADDRSTRLEN] = "INVALID"; + int rport = default_port; + + switch (rp->ai_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *) rp->ai_addr; + inet_ntop(AF_INET, &sin->sin_addr, rip, INET_ADDRSTRLEN); + rport = ntohs(sin->sin_port); + fd = create_listen_socket4(rip, rport, listen_backlog); + break; + } + + case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) rp->ai_addr; + inet_ntop(AF_INET6, &sin6->sin6_addr, rip, INET6_ADDRSTRLEN); + rport = ntohs(sin6->sin6_port); + fd = create_listen_socket6(rip, rport, listen_backlog); + break; + } + } + + if (fd == -1) + error("Cannot bind to ip '%s', port %d", rip, default_port); + else { + add_listen_socket(fd, rip, rport); + added++; + } + } + + freeaddrinfo(result); + + return added; } +int create_listen_sockets(void) { + listen_backlog = (int) config_get_number("global", "http port listen backlog", LISTEN_BACKLOG); + + if(config_exists("global", "bind socket to IP") && !config_exists("global", "bind to")) + config_rename("global", "bind socket to IP", "bind to"); + + if(config_exists("global", "port") && !config_exists("global", "default port")) + config_rename("global", "port", "default port"); + + listen_port = (int) config_get_number("global", "default port", LISTEN_PORT); + if(listen_port < 1 || listen_port > 65535) { + error("Invalid listen port %d given. Defaulting to %d.", listen_port, LISTEN_PORT); + listen_port = (int) config_set_number("global", "default port", LISTEN_PORT); + } + debug(D_OPTIONS, "Default listen port set to %d.", listen_port); + + char *s = config_get("global", "bind to", "*"); + while(*s) { + char *e = s; + + // skip separators, moving both s(tart) and e(nd) + while(isspace(*e) || *e == ',') s = ++e; + + // move e(nd) to the first separator + while(*e && !isspace(*e) && *e != ',') e++; + + // is there anything? + if(!*s || s == e) break; + + char buf[e - s + 1]; + strncpyz(buf, s, e - s); + bind_to_one(buf, listen_port, listen_backlog); + + s = e; + } + + if(!listen_fds_count) + fatal("Cannot listen on any socket. Exiting..."); + + return (int)listen_fds_count; +} // -------------------------------------------------------------------------------------- // the main socket listener +static inline void cleanup_web_clients(void) { + struct web_client *w; + + for (w = web_clients; w;) { + if (w->obsolete) { + debug(D_WEB_CLIENT, "%llu: Removing client.", w->id); + // pthread_cancel(w->thread); + // pthread_join(w->thread, NULL); + w = web_client_free(w); +#ifdef NETDATA_INTERNAL_CHECKS + log_allocations(); +#endif + } + else w = w->next; + } +} + // 1. it accepts new incoming requests on our port // 2. creates a new web_client for each connection received // 3. spawns a new pthread to serve the client (this is optimal for keep-alive clients) // 4. cleans up old web_clients that their pthreads have been exited -void *socket_listen_main(void *ptr) -{ - if(ptr) { ; } - - info("WEB SERVER thread created with task id %d", gettid()); - - struct web_client *w; - struct timeval tv; - int retval; - - if(ptr) { ; } - - 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."); - - web_client_timeout = (int) config_get_number("global", "disconnect idle web clients after seconds", DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS); - web_enable_gzip = config_get_boolean("global", "enable web responses gzip compression", web_enable_gzip); - - if(listen_fd < 0) fatal("LISTENER: Listen socket is not ready."); - - fd_set ifds, ofds, efds; - int fdmax = listen_fd; - - FD_ZERO (&ifds); - FD_ZERO (&ofds); - FD_ZERO (&efds); - - for(;;) { - tv.tv_sec = 0; - tv.tv_usec = 200000; - - if(listen_fd >= 0) { - FD_SET(listen_fd, &ifds); - FD_SET(listen_fd, &efds); - } - - // debug(D_WEB_CLIENT, "LISTENER: Waiting..."); - retval = select(fdmax+1, &ifds, &ofds, &efds, &tv); - - if(retval == -1) { - error("LISTENER: select() failed."); - continue; - } - else if(retval) { - // check for new incoming connections - if(FD_ISSET(listen_fd, &ifds)) { - w = web_client_create(listen_fd); - if(unlikely(!w)) { - // no need for error log - web_client_create already logged the error - continue; - } - - if(pthread_create(&w->thread, NULL, web_client_main, w) != 0) { - error("%llu: failed to create new thread for web client."); - w->obsolete = 1; - } - else if(pthread_detach(w->thread) != 0) { - error("%llu: Cannot request detach of newly created web client thread.", w->id); - w->obsolete = 1; - } - } - else debug(D_WEB_CLIENT, "LISTENER: select() didn't do anything."); - - } - //else { - // debug(D_WEB_CLIENT, "LISTENER: select() timeout."); - //} - - // cleanup unused clients - for(w = web_clients; w ; w = w?w->next:NULL) { - if(w->obsolete) { - debug(D_WEB_CLIENT, "%llu: Removing client.", w->id); - // pthread_join(w->thread, NULL); - w = web_client_free(w); -#ifdef NETDATA_INTERNAL_CHECKS - log_allocations(); -#endif - } - } - } +#define CLEANUP_EVERY_EVENTS 100 + +void *socket_listen_main_multi_threaded(void *ptr) { + (void)ptr; + + web_server_mode = WEB_SERVER_MODE_MULTI_THREADED; + info("Multi-threaded WEB SERVER thread created with task id %d", gettid()); + + struct web_client *w; + int retval, counter = 0; + + 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."); + + if(!listen_fds_count) + fatal("LISTENER: No sockets to listen to."); + + struct pollfd *fds = callocz(sizeof(struct pollfd), listen_fds_count); + + size_t i; + for(i = 0; i < listen_fds_count ;i++) { + fds[i].fd = listen_fds[i]; + fds[i].events = POLLIN; + fds[i].revents = 0; + + info("Listening on '%s'", (listen_fds_names[i])?listen_fds_names[i]:"UNKNOWN"); + } + + int timeout = 10 * 1000; + + for(;;) { + // debug(D_WEB_CLIENT, "LISTENER: Waiting..."); + retval = poll(fds, listen_fds_count, timeout); + + if(unlikely(retval == -1)) { + error("LISTENER: poll() failed."); + continue; + } + else if(unlikely(!retval)) { + debug(D_WEB_CLIENT, "LISTENER: select() timeout."); + counter = 0; + cleanup_web_clients(); + continue; + } + + for(i = 0 ; i < listen_fds_count ; i++) { + short int revents = fds[i].revents; + + // check for new incoming connections + if(revents & POLLIN || revents & POLLPRI) { + fds[i].revents = 0; + + w = web_client_create(fds[i].fd); + if(unlikely(!w)) { + // no need for error log - web_client_create already logged the error + continue; + } + + if(pthread_create(&w->thread, NULL, web_client_main, w) != 0) { + error("%llu: failed to create new thread for web client.", w->id); + w->obsolete = 1; + } + else if(pthread_detach(w->thread) != 0) { + error("%llu: Cannot request detach of newly created web client thread.", w->id); + w->obsolete = 1; + } + } + } + + // cleanup unused clients + counter++; + if(counter >= CLEANUP_EVERY_EVENTS) { + counter = 0; + cleanup_web_clients(); + } + } + + debug(D_WEB_CLIENT, "LISTENER: exit!"); + close_listen_sockets(); + + return NULL; +} - error("LISTENER: exit!"); +struct web_client *single_threaded_clients[FD_SETSIZE]; - if(listen_fd >= 0) close(listen_fd); - exit(2); +static inline int single_threaded_link_client(struct web_client *w, fd_set *ifds, fd_set *ofds, fd_set *efds, int *max) { + if(unlikely(w->obsolete || w->dead || (!w->wait_receive && !w->wait_send))) + return 1; - return NULL; + if(unlikely(w->ifd < 0 || w->ifd >= FD_SETSIZE || w->ofd < 0 || w->ofd >= FD_SETSIZE)) { + error("%llu: invalid file descriptor, ifd = %d, ofd = %d (required 0 <= fd < FD_SETSIZE (%d)", w->id, w->ifd, w->ofd, FD_SETSIZE); + return 1; + } + + FD_SET(w->ifd, efds); + if(unlikely(*max < w->ifd)) *max = w->ifd; + + if(unlikely(w->ifd != w->ofd)) { + if(*max < w->ofd) *max = w->ofd; + FD_SET(w->ofd, efds); + } + + if(w->wait_receive) FD_SET(w->ifd, ifds); + if(w->wait_send) FD_SET(w->ofd, ofds); + + single_threaded_clients[w->ifd] = w; + single_threaded_clients[w->ofd] = w; + + return 0; } +static inline int single_threaded_unlink_client(struct web_client *w, fd_set *ifds, fd_set *ofds, fd_set *efds) { + FD_CLR(w->ifd, efds); + if(unlikely(w->ifd != w->ofd)) FD_CLR(w->ofd, efds); + + if(w->wait_receive) FD_CLR(w->ifd, ifds); + if(w->wait_send) FD_CLR(w->ofd, ofds); + + single_threaded_clients[w->ifd] = NULL; + single_threaded_clients[w->ofd] = NULL; + + if(unlikely(w->obsolete || w->dead || (!w->wait_receive && !w->wait_send))) + return 1; + + return 0; +} + +void *socket_listen_main_single_threaded(void *ptr) { + (void)ptr; + + web_server_mode = WEB_SERVER_MODE_SINGLE_THREADED; + + info("Single-threaded WEB SERVER thread created with task id %d", gettid()); + + struct web_client *w; + int retval; + + 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."); + + if(!listen_fds_count) + fatal("LISTENER: no listen sockets available."); + + size_t i; + for(i = 0; i < FD_SETSIZE ; i++) + single_threaded_clients[i] = NULL; + + fd_set ifds, ofds, efds, rifds, rofds, refds; + FD_ZERO (&ifds); + FD_ZERO (&ofds); + FD_ZERO (&efds); + int fdmax = 0; + + for(i = 0; i < listen_fds_count ; i++) { + if (listen_fds[i] < 0 || listen_fds[i] >= FD_SETSIZE) + fatal("LISTENER: Listen socket %d is not ready, or invalid.", listen_fds[i]); + + info("Listening on '%s'", (listen_fds_names[i])?listen_fds_names[i]:"UNKNOWN"); + + FD_SET(listen_fds[i], &ifds); + FD_SET(listen_fds[i], &efds); + if(fdmax < listen_fds[i]) + fdmax = listen_fds[i]; + } + + for(;;) { + debug(D_WEB_CLIENT_ACCESS, "LISTENER: single threaded web server waiting (fdmax = %d)...", fdmax); + + struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; + rifds = ifds; + rofds = ofds; + refds = efds; + retval = select(fdmax+1, &rifds, &rofds, &refds, &tv); + + if(unlikely(retval == -1)) { + error("LISTENER: select() failed."); + continue; + } + else if(likely(retval)) { + debug(D_WEB_CLIENT_ACCESS, "LISTENER: got something."); + + for(i = 0; i < listen_fds_count ; i++) { + if (FD_ISSET(listen_fds[i], &rifds)) { + debug(D_WEB_CLIENT_ACCESS, "LISTENER: new connection."); + w = web_client_create(listen_fds[i]); + if (single_threaded_link_client(w, &ifds, &ofds, &ifds, &fdmax) != 0) { + web_client_free(w); + } + } + } + + for(i = 0 ; i <= (size_t)fdmax ; i++) { + if(likely(!FD_ISSET(i, &rifds) && !FD_ISSET(i, &rofds) && !FD_ISSET(i, &refds))) + continue; + + w = single_threaded_clients[i]; + if(unlikely(!w)) + continue; + + if(unlikely(single_threaded_unlink_client(w, &ifds, &ofds, &efds) != 0)) { + web_client_free(w); + continue; + } + + if (unlikely(FD_ISSET(w->ifd, &refds) || FD_ISSET(w->ofd, &refds))) { + web_client_free(w); + continue; + } + + if (unlikely(w->wait_receive && FD_ISSET(w->ifd, &rifds))) { + if (unlikely(web_client_receive(w) < 0)) { + web_client_free(w); + continue; + } + + if (w->mode != WEB_CLIENT_MODE_FILECOPY) { + debug(D_WEB_CLIENT, "%llu: Processing received data.", w->id); + web_client_process(w); + } + } + + if (unlikely(w->wait_send && FD_ISSET(w->ofd, &rofds))) { + if (unlikely(web_client_send(w) < 0)) { + debug(D_WEB_CLIENT, "%llu: Cannot send data to client. Closing client.", w->id); + web_client_free(w); + continue; + } + } + + if(unlikely(single_threaded_link_client(w, &ifds, &ofds, &efds, &fdmax) != 0)) { + web_client_free(w); + } + } + } + else { + debug(D_WEB_CLIENT_ACCESS, "LISTENER: single threaded web server timeout."); +#ifdef NETDATA_INTERNAL_CHECKS + log_allocations(); +#endif + } + } + + debug(D_WEB_CLIENT, "LISTENER: exit!"); + close_listen_sockets(); + return NULL; +} diff --git a/src/web_server.h b/src/web_server.h index 39f3bad91..93adc5b28 100644 --- a/src/web_server.h +++ b/src/web_server.h @@ -1,20 +1,38 @@ #ifndef NETDATA_WEB_SERVER_H #define NETDATA_WEB_SERVER_H 1 -#define WEB_PATH_FILE "file" -#define WEB_PATH_DATA "data" -#define WEB_PATH_DATASOURCE "datasource" -#define WEB_PATH_GRAPH "graph" +#define WEB_PATH_FILE "file" +#define WEB_PATH_DATA "data" +#define WEB_PATH_DATASOURCE "datasource" +#define WEB_PATH_GRAPH "graph" #define LISTEN_PORT 19999 #define LISTEN_BACKLOG 100 -extern int listen_backlog; -extern int listen_fd; -extern int listen_port; +#ifndef MAX_LISTEN_FDS +#define MAX_LISTEN_FDS 100 +#endif -extern int create_listen_socket4(const char *ip, int port, int listen_backlog); -extern int create_listen_socket6(const char *ip, int port, int listen_backlog); -extern void *socket_listen_main(void *ptr); +#define WEB_SERVER_MODE_MULTI_THREADED 0 +#define WEB_SERVER_MODE_SINGLE_THREADED 1 +extern int web_server_mode; + +extern void *socket_listen_main_multi_threaded(void *ptr); +extern void *socket_listen_main_single_threaded(void *ptr); +extern int create_listen_sockets(void); +extern int is_listen_socket(int fd); + +#ifndef HAVE_ACCEPT4 +extern int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags); + +#ifndef SOCK_NONBLOCK +#define SOCK_NONBLOCK 00004000 +#endif /* #ifndef SOCK_NONBLOCK */ + +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 02000000 +#endif /* #ifndef SOCK_CLOEXEC */ + +#endif /* #ifndef HAVE_ACCEPT4 */ #endif /* NETDATA_WEB_SERVER_H */ |