]> arthur.barton.de Git - netdata.git/commitdiff
Merge pull request #1132 from x4FF3/master
authorCosta Tsaousis <costa@tsaousis.gr>
Sun, 23 Oct 2016 19:17:33 +0000 (22:17 +0300)
committerGitHub <noreply@github.com>
Sun, 23 Oct 2016 19:17:33 +0000 (22:17 +0300)
Wheezy Instructions for 1.4.0

61 files changed:
.gitignore
CMakeLists.txt
LICENSE.md
Makefile.am
README.md
ansible/netdata.yml [deleted file]
charts.d/ap.chart.sh
charts.d/apache.chart.sh
charts.d/apcupsd.chart.sh
charts.d/cpu_apps.chart.sh
charts.d/cpufreq.chart.sh
charts.d/example.chart.sh
charts.d/exim.chart.sh
charts.d/hddtemp.chart.sh
charts.d/load_average.chart.sh
charts.d/mem_apps.chart.sh
charts.d/mysql.chart.sh
charts.d/nginx.chart.sh
charts.d/nut.chart.sh
charts.d/opensips.chart.sh
charts.d/phpfpm.chart.sh
charts.d/postfix.chart.sh
charts.d/sensors.chart.sh
charts.d/squid.chart.sh
charts.d/tomcat.chart.sh
conf.d/Makefile.am
conf.d/fping.conf [new file with mode: 0644]
configs.signatures
diagrams/build.sh [new file with mode: 0755]
diagrams/config.puml [new file with mode: 0644]
diagrams/registry.puml [new file with mode: 0644]
netdata-installer.sh
netdata.spec.in
node.d/node_modules/netdata.js
plugins.d/Makefile.am
plugins.d/alarm-notify.sh
plugins.d/cgroup-name.sh
plugins.d/charts.d.plugin
plugins.d/fping.plugin [new file with mode: 0755]
plugins.d/node.d.plugin
plugins.d/tc-qos-helper.sh
python.d/python_modules/msg.py
src/common.c
src/common.h
src/daemon.c
src/health.c
src/health.h
src/log.c
src/log.h
src/main.c
src/popen.c
src/proc_diskstats.c
src/proc_self_mountinfo.c
src/proc_self_mountinfo.h
src/registry.c
src/rrd2json.c
src/web_client.c
web/dashboard_info.js
web/index.html
web/netdata-swagger.json
web/netdata-swagger.yaml

index c281ea32468481f134d53bf3c6fc4ad93946930a..30962eaa59f0337cfb97cbe369fd8788702526d4 100644 (file)
@@ -89,3 +89,10 @@ profile/benchmark-dictionary
 profile/benchmark-registry
 
 *.pyc
+
+diagrams/*.png
+diagrams/*.svg
+diagrams/*.atxt
+diagrams/plantuml.jar
+
+netdata.cppcheck
index e973f6ebec54cf96bb4f868b88dc5bff92f5606c..9c4aa760729de421d1a1887ff4a66ffbd94b8a9b 100755 (executable)
@@ -20,8 +20,12 @@ set(NETDATA_SOURCE_FILES
         src/daemon.h
         src/dictionary.c
         src/dictionary.h
+        src/eval.c
+        src/eval.h
         src/global_statistics.c
         src/global_statistics.h
+        src/health.c
+        src/health.h
         src/log.c
         src/log.h
         src/main.c
@@ -53,9 +57,9 @@ set(NETDATA_SOURCE_FILES
         src/proc_net_rpc_nfsd.c
         src/proc_net_snmp6.c
         src/proc_net_snmp.c
+        src/proc_net_softnet_stat.c
         src/proc_net_stat_conntrack.c
         src/proc_net_stat_synproxy.c
-        src/proc_net_softnet_stat.c
         src/proc_self_mountinfo.c
         src/proc_self_mountinfo.h
         src/proc_softirqs.c
@@ -70,8 +74,8 @@ set(NETDATA_SOURCE_FILES
         src/rrd.h
         src/storage_number.c
         src/storage_number.h
-        src/sys_kernel_mm_ksm.c
         src/sys_fs_cgroup.c
+        src/sys_kernel_mm_ksm.c
         src/unit_test.c
         src/unit_test.h
         src/url.c
@@ -84,7 +88,7 @@ set(NETDATA_SOURCE_FILES
         src/web_client.h
         src/web_server.c
         src/web_server.h
-        config.h src/health.h src/health.c src/eval.h src/eval.c)
+)
 
 set(APPS_PLUGIN_SOURCE_FILES
         src/appconfig.c
index f0742900047edfa9fabcfeb2ab76c418ba4b70ff..564b084e062270819565f1ebdd8a939a44d0e7ce 100644 (file)
@@ -55,22 +55,28 @@ connectivity is not available.
     [MIT License](http://getbootstrap.com/getting-started/#license-faqs)
 
 
-- [NanoScroller](https://jamesflorentino.github.io/nanoScrollerJS/)
-
-    Copyright 2012, James Florentino
-    [MIT License](https://github.com/jamesflorentino/nanoScrollerJS/blob/master/LICENSE)
-
-
 - [Bootstrap Toggle](http://www.bootstraptoggle.com/)
 
     Copyright (c) 2011-2014 Min Hur, The New York Times Company
     [MIT License](https://github.com/minhur/bootstrap-toggle/blob/master/LICENSE)
 
 
-- [AVL C Library](http://freecode.com/projects/avl)
+- [bootstrap-table](http://bootstrap-table.wenzhixin.net.cn/)
+
+    Copyright (c) 2012-2016 Zhixin Wen <wenzhixin2010@gmail.com>
+    [MIT License](https://github.com/wenzhixin/bootstrap-table/blob/master/LICENSE)
+
+
+- [tableExport.jquery.plugin](https://github.com/hhurz/tableExport.jquery.plugin)
+
+    Copyright (c) 2015,2016 hhurz
+    [MIT License](http://rawgit.com/hhurz/tableExport.jquery.plugin/master/tableExport.js)
+
+
+- [NanoScroller](https://jamesflorentino.github.io/nanoScrollerJS/)
 
-    Copyright 2000, Daniel Nagy, Budapest University of Technology and Economics
-    Released under GNU General Public License (GPL) version 2
+    Copyright 2012, James Florentino
+    [MIT License](https://github.com/jamesflorentino/nanoScrollerJS/blob/master/LICENSE)
 
 
 - [FontAwesome](https://fortawesome.github.io/Font-Awesome/)
@@ -142,14 +148,3 @@ connectivity is not available.
     Copyright 2006, Kirill Simonov
     [MIT License](http://pyyaml.org)
 
-- [bootstrap-table](http://bootstrap-table.wenzhixin.net.cn/)
-
-    Copyright (c) 2012-2016 Zhixin Wen <wenzhixin2010@gmail.com>
-    [MIT License](https://github.com/wenzhixin/bootstrap-table/blob/master/LICENSE)
-
-- [tableExport.jquery.plugin](https://github.com/hhurz/tableExport.jquery.plugin)
-
-    Copyright (c) 2015,2016 hhurz
-    [MIT License](http://rawgit.com/hhurz/tableExport.jquery.plugin/master/tableExport.js)
-
-
index ea1ac256bbbbb8fb5e3cff33868c43ef8d848aec..8b4bc8376906902079a6c5484be743c92fea2ef9 100644 (file)
@@ -39,6 +39,8 @@ SUBDIRS = \
        $(NULL)
 
 dist_noinst_DATA= \
+       diagrams/config.puml \
+       diagrams/registry.puml \
        configs.signatures \
        Dockerfile \
        netdata.spec \
@@ -47,7 +49,7 @@ dist_noinst_DATA= \
 # until integrated within build
 # should be proper init.d/openrc/systemd usable
 dist_noinst_SCRIPTS= \
-       ansible/netdata.yml \
+       diagrams/build.sh \
        coverity-scan.sh \
        docker-build.sh \
        netdata-installer.sh \
index c44b3efd5319b031dff4f3f0bffe0b26ce3a8126..3eae8d8a57df92972663e69c130fb8e516fc5945 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,9 +2,14 @@
 
 > *New to netdata? Here is a live demo: [http://my-netdata.io](http://my-netdata.io)*
 
-**netdata** is an application for **real-time performance and health monitoring**. It provides **unparalleled insights, in real-time**, of everything happening on the system it runs (including key applications such as web and database servers), using **modern interactive web dashboards**.
+**netdata** is a system for **distributed real-time performance and health monitoring**.
+It provides **unparalleled insights, in real-time**, of everything happening on the
+system it runs (including applications such as web, or database servers), using
+**modern interactive web dashboards**.
 
-_netdata is **very fast** and **super efficient**, designed to permanently run on all systems (**physical** & **virtual** servers, **containers**, **IoT** devices), without disrupting their core function._
+_netdata is **fast** and **efficient**, designed to permanently run on all systems
+(**physical** & **virtual** servers, **containers**, **IoT** devices), without
+disrupting their core function._
 
 ---
 
@@ -26,10 +31,10 @@ _netdata is **very fast** and **super efficient**, designed to permanently run o
 
 `Oct 4th, 2016` - **[netdata v1.4.0 released!](https://github.com/firehol/netdata/releases)**
 
- - the **fastest** netdata ever (with a better look too)!
- - improved **IoT** and **containers** support!
- - **alarms** improved in almost every way!
- - new plugins:  softnet netdev, extended TCP metrics, UDPLite, NFS v2, v3 client (server was there already), NFS v4 server & client, APCUPSd, RetroShare
+ - the **fastest** netdata ever (with a better look too)
+ - improved **IoT** and **containers** support
+ - **alarms** improved in almost every way
+ - new plugins: softnet netdev, extended TCP metrics, UDPLite, NFS v2, v3 client (server was there already), NFS v4 server & client, APCUPSd, RetroShare
  - improved plugins: mysql, cgroups, hddtemp, sensors, phpfm, tc (QoS)
 
 ---
@@ -40,17 +45,45 @@ _netdata is **very fast** and **super efficient**, designed to permanently run o
 <img src="https://cloud.githubusercontent.com/assets/2662304/19168687/f6a567be-8c19-11e6-8561-ce8d589e8346.gif"/>
 </p>
 
- - **Stunning interactive bootstrap dashboards**<br/>2 themes are provided: dark, light
- - **Blazingly fast visualization**<br/>responds to all queries in less than 0.5 ms per metric, even on low-end hardware
- - **Highly efficient data collection**<br/>collects thousands of metrics per server per second, with just 1% CPU utilization of a single core on modern hardware
- - **Sophisticated alarming**<br/>supports dynamic thresholds, hysteresis, templating, multiple notification methods
- - **Extensible**<br/>you can monitor anything you can get a metric for, using its Plugin API (anything can be a netdata plugin, BASH, python, perl, node.js, java, Go, etc)
- - **Zero configuration**<br/>auto-detects everything, out of the box it can collect up to 5000 metrics per server
- - **Zero dependencies**<br/>it is even its own web server, for its static web files and its web API
- - **Zero maintenance**<br/>you just run it, it does the rest
- - **Embeddable**<br/>it can run anywhere a Linux kernel runs (even IoT) and its charts can be embedded on your web pages too
- - **Custom dashboards**<br/>that can be built using simple HTML (no javascript necessary)
- - **scales to infinity**<br/>requiring minimal central resources!
+ - **Stunning interactive bootstrap dashboards**<br/>
+   mouse and touch friendly, in 2 themes: dark, light
+   
+ - **Blazingly fast**<br/>
+   responds to all queries in less than 0.5 ms per metric,
+   even on low-end hardware (such as a raspberry pi 1)
+   
+ - **Highly efficient data collection**<br/>
+   collects thousands of metrics per server per second,
+   with just 1% CPU utilization of a single core, a few MB or RAM and no disk I/O at all
+   
+ - **Sophisticated alarming**<br/>
+   supports dynamic thresholds, hysteresis, alarm templates,
+   multiple role-based notification methods (such as slack.com, pushover.net, telegram.org, email)
+   
+ - **Extensible**<br/>
+   you can monitor anything you can get a metric for,
+   using its Plugin API (anything can be a netdata plugin,
+   BASH, python, perl, node.js, java, Go, ruby, etc)
+   
+ - **Embeddable**<br/>
+   it can run anywhere a Linux kernel runs (even IoT)
+   and its charts can be embedded on your web pages too
+   
+ - **Zero configuration**<br/>
+   auto-detects everything, it can collect up to 5000 metrics
+   per server out of the box
+   
+ - **Zero dependencies**<br/>
+   it is even its own web server, for its static web files and its web API
+   
+ - **Zero maintenance**<br/>
+   you just run it, it does the rest
+   
+ - **Custom dashboards**<br/>
+   that can be built using simple HTML (no javascript necessary)
+   
+ - **scales to infinity**<br/>
+   requiring minimal central resources
 
 ![netdata](https://cloud.githubusercontent.com/assets/2662304/14092712/93b039ea-f551-11e5-822c-beadbf2b2a2e.gif)
 
@@ -58,77 +91,126 @@ _netdata is **very fast** and **super efficient**, designed to permanently run o
 
 ## What does it monitor?
 
-This is what it currently monitors (most with zero configuration):
+netdata monitors several thousands of metrics per device.
+All these metrics are collected and visualized in real-time.
 
-- **CPU usage, interrupts, softirqs and frequency** (total and per core)
+> _Almost all metrics are auto-detected, without any configuration._
 
-- **RAM, swap and kernel memory usage** (including KSM and kernel memory deduper)
+This is a list of what it currently monitors:
 
-- **Disks** (per disk: I/O, operations, backlog, utilization, space, etc)
+- **CPU**<br/>
+  usage, interrupts, softirqs, frequency, total and per core
+
+- **Memory**<br/>
+  RAM, swap and kernel memory usage, including KSM the kernel memory deduper
+
+- **Disks**<br/>
+  per disk: I/O, operations, backlog, utilization, space
 
    ![sda](https://cloud.githubusercontent.com/assets/2662304/14093195/c882bbf4-f554-11e5-8863-1788d643d2c0.gif)
 
-- **Network interfaces** (per interface: bandwidth, packets, errors, drops, etc)
+- **Network interfaces**<br/>
+  per interface: bandwidth, packets, errors, drops
 
    ![dsl0](https://cloud.githubusercontent.com/assets/2662304/14093128/4d566494-f554-11e5-8ee4-5392e0ac51f0.gif)
 
-- **IPv4 networking** (bandwidth, packets, errors, fragments, tcp: connections, packets, errors, handshake, udp: packets, errors, broadcast: bandwidth, packets, multicast: bandwidth, packets)
+- **IPv4 networking**<br/>
+  bandwidth, packets, errors, fragments,
+  tcp: connections, packets, errors, handshake,
+  udp: packets, errors,
+  broadcast: bandwidth, packets,
+  multicast: bandwidth, packets
 
-- **IPv6 networking** (bandwidth, packets, errors, fragments, ECT, udp: packets, errors, udplite: packets, errors, broadcast: bandwidth, multicast: bandwidth, packets, icmp: messages, errors, echos, router, neighbor, MLDv2, group membership, break down by type)
+- **IPv6 networking**<br/>
+  bandwidth, packets, errors, fragments, ECT,
+  udp: packets, errors,
+  udplite: packets, errors,
+  broadcast: bandwidth,
+  multicast: bandwidth, packets,
+  icmp: messages, errors, echos, router, neighbor, MLDv2, group membership,
+  break down by type
 
-- **netfilter / iptables Linux firewall** (connections, connection tracker events, errors, etc)
+- **netfilter / iptables Linux firewall**<br/>
+  connections, connection tracker events, errors
 
-- **Linux DDoS protection** (SYNPROXY metrics)
+- **Linux DDoS protection**<br/>
+  SYNPROXY metrics
 
-- **Processes** (running, blocked, forks, active, etc)
+- **Processes**<br/>
+  running, blocked, forks, active
 
-- **Entropy** (random numbers pool, using in cryptography)
+- **Entropy**<br/>
+  random numbers pool, using in cryptography
 
-- **NFS file servers and clients**, v2, v3, v4 (I/O, cache, read ahead, RPC calls)
+- **NFS file servers and clients**<br/>
+  NFS v2, v3, v4: I/O, cache, read ahead, RPC calls
 
-- **Network QoS** (yes, the only tool that visualizes network `tc` classes in realtime)
+- **Network QoS**<br/>
+  the only tool that visualizes network `tc` classes in realtime
 
    ![qos-tc-classes](https://cloud.githubusercontent.com/assets/2662304/14093004/68966020-f553-11e5-98fe-ffee2086fafd.gif)
 
-- **Linux Control Groups** (containers), systemd, lxc, docker, etc
+- **Linux Control Groups**<br/>
+  containers: systemd, lxc, docker
 
-- **Applications**, by grouping the process tree (CPU, memory, disk reads, disk writes, swap, threads, pipes, sockets, etc)
+- **Applications**<br/>
+  by grouping the process tree and reporting CPU, memory, disk reads,
+  disk writes, swap, threads, pipes, sockets - per group
 
    ![apps](https://cloud.githubusercontent.com/assets/2662304/14093565/67c4002c-f557-11e5-86bd-0154f5135def.gif)
 
-- **Users and User Groups resource usage**, by summarizing the process tree per user and group (CPU, memory, disk reads, disk writes, swap, threads, pipes, sockets, etc)
+- **Users and User Groups resource usage**<br/>
+  by summarizing the process tree per user and group,
+  reporting: CPU, memory, disk reads, disk writes, swap, threads, pipes, sockets
 
-- **Apache web servers** mod-status (v2.2, v2.4) and cache log statistics (multiple servers - compatible with lighttpd too)
+- **Apache and lighttpd web servers**<br/>
+   `mod-status` (v2.2, v2.4) and cache log statistics, for multiple servers
 
-- **Nginx web servers** stub-status (multiple servers)
+- **Nginx web servers**<br/>
+  `stub-status`, for multiple servers
 
-- **mySQL databases** (multiple servers, each showing: bandwidth, queries/s, handlers, locks, issues, tmp operations, connections, binlog metrics, threads, innodb metrics, etc)
+- **Tomcat**<br/>
+  accesses, threads, free memory, volume
 
-- **Redis databases** (multiple servers, each showing: operations, hit rate, memory, keys, clients, slaves)
+- **mySQL databases**<br/>
+  multiple servers, each showing: bandwidth, queries/s, handlers, locks, issues,
+  tmp operations, connections, binlog metrics, threads, innodb metrics, and more
 
-- **memcached databases** (multiple servers, each showing: bandwidth, connections, items, etc)
+- **Redis databases**<br/>
+  multiple servers, each showing: operations, hit rate, memory, keys, clients, slaves
 
-- **ISC Bind name servers** (multiple servers, each showing: clients, requests, queries, updates, failures and several per view metrics)
+- **memcached databases**<br/>
+  multiple servers, each showing: bandwidth, connections, items
 
-- **Postfix email servers** message queue (entries, size)
+- **ISC Bind name servers**<br/>
+  multiple servers, each showing: clients, requests, queries, updates, failures and several per view metrics
 
-- **exim email servers** message queue (emails queued)
+- **Postfix email servers**<br/>
+  message queue (entries, size)
 
-- **IPFS** (Bandwidth, Peers)
+- **exim email servers**<br/>
+  message queue (emails queued)
 
-- **Squid proxy servers** (multiple servers, each showing: clients bandwidth and requests, servers bandwidth and requests)
+- **IPFS**<br/>
+  bandwidth, peers
 
-- **Hardware sensors** (temperature, voltage, fans, power, humidity, etc)
+- **Squid proxy servers**<br/>
+  multiple servers, each showing: clients bandwidth and requests, servers bandwidth and requests
 
-- **NUT and APC UPSes** (load, charge, battery voltage, temperature, utility metrics, output metrics)
+- **Hardware sensors**<br/>
+  temperature, voltage, fans, power, humidity
 
-- **Tomcat** (accesses, threads, free memory, volume)
+- **NUT and APC UPSes**<br/>
+  load, charge, battery voltage, temperature, utility metrics, output metrics
 
-- **PHP-FPM** (multiple instances, each reporting connections, requests, performance)
+- **PHP-FPM**<br/>
+  multiple instances, each reporting connections, requests, performance
 
-- **hddtemp** (disk temperatures)
+- **hddtemp**<br/>
+  disk temperatures
 
-- **SNMP devices** can be monitored too (although you will need to configure these)
+- **SNMP devices**<br/>
+  can be monitored too (although you will need to configure these)
 
 And you can extend it, by writing plugins that collect data from any source, using any computer language.
 
diff --git a/ansible/netdata.yml b/ansible/netdata.yml
deleted file mode 100644 (file)
index 4c7e736..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
----
-- name: "Install pre-requisites"    
-  apt:
-    name: "{{ item }}"
-    state: present
-  with_items: 
-    - autoconf
-    - autoconf-archive
-    - autogen
-    - automake
-    - gcc
-    - git
-    - libmnl-dev
-    - make
-    - pkg-config
-    - uuid-dev
-    - zlib1g-dev
-  when: ansible_os_family == "Debian"
-
-- name: "Install pre-requisites"    
-  yum:
-    name: "{{ item }}"
-    state: present
-  with_items: 
-    - autoconf
-    - autoconf-archive
-    - autogen
-    - automake
-    - curl
-    - gcc
-    - git
-    - jq
-    - libmnl-devel
-    - libuuid-devel
-    - make
-    - pkgconfig
-    - zlib-devel
-  when: ansible_os_family == "RedHat"
-
-- name: "Clone repo"
-  git:
-    clone: yes
-    repo: https://github.com/firehol/netdata.git
-    dest: /tmp/netdata
-
-- name: "Installation"
-  shell: cd /tmp/netdata/ && ./netdata-installer.sh --dont-wait --libs-are-really-here
-
-- name: "Clean /tmp"
-  file:
-    path: /tmp/netdata
-    state: absent
-
-- name: "KillAll"
-  shell: killall netdata
-
-- name: "Daemon config"
-  systemd:
-    daemon_reload: yes
-    name: netdata
-    enabled: yes
-    state: started
index 7b4f690bb47ece250d03c249e58f294560d3758f..0e85c486dc95daedb9404d680ac37f42f5a70dfc 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # _update_every is a special variable - it holds the number of seconds
 # between the calls of the _update() function
 ap_update_every=
@@ -7,13 +13,11 @@ ap_priority=6900
 
 declare -A ap_devs=()
 
-export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
-
 # _check is called once, to find out if this chart should be enabled or not
 ap_check() {
        require_cmd iw || return 1
        
-       local ev=$(iw dev | awk '
+       local ev=$(run iw dev | awk '
                BEGIN {
                        i = "";
                        ssid = "";
@@ -43,6 +47,7 @@ ap_check() {
        #  - 1 to disable the chart
 
        [ ${#ap_devs[@]} -gt 0 ] && return 0
+       error "no devices found in AP mode, with 'iw dev'"
        return 1
 }
 
index 2d68d43b277fecae2b1356a4554ea8291522902f..b503e74e3d2cdb8e8acd25e937c23d19aea858e9 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # the URL to download apache status info
 apache_url="http://127.0.0.1:80/server-status?auto"
 apache_curl_opts=
@@ -67,14 +73,14 @@ apache_detect() {
 
        # we will not check of the Conns*
        # keys, since these are apache 2.4 specific
-       [ -z "${apache_key_accesses}"    ] && echo >&2 "apache: missing 'Total Accesses' from apache server: ${*}" && return 1
-       [ -z "${apache_key_kbytes}"      ] && echo >&2 "apache: missing 'Total kBytes' from apache server: ${*}" && return 1
-       [ -z "${apache_key_reqpersec}"   ] && echo >&2 "apache: missing 'ReqPerSec' from apache server: ${*}" && return 1
-       [ -z "${apache_key_bytespersec}" ] && echo >&2 "apache: missing 'BytesPerSec' from apache server: ${*}" && return 1
-       [ -z "${apache_key_bytesperreq}" ] && echo >&2 "apache: missing 'BytesPerReq' from apache server: ${*}" && return 1
-       [ -z "${apache_key_busyworkers}" ] && echo >&2 "apache: missing 'BusyWorkers' from apache server: ${*}" && return 1
-       [ -z "${apache_key_idleworkers}" ] && echo >&2 "apache: missing 'IdleWorkers' from apache server: ${*}" && return 1
-       [ -z "${apache_key_scoreboard}"  ] && echo >&2 "apache: missing 'Scoreboard' from apache server: ${*}" && return 1
+       [ -z "${apache_key_accesses}"    ] && error "missing 'Total Accesses' from apache server: ${*}" && return 1
+       [ -z "${apache_key_kbytes}"      ] && error "missing 'Total kBytes' from apache server: ${*}" && return 1
+       [ -z "${apache_key_reqpersec}"   ] && error "missing 'ReqPerSec' from apache server: ${*}" && return 1
+       [ -z "${apache_key_bytespersec}" ] && error "missing 'BytesPerSec' from apache server: ${*}" && return 1
+       [ -z "${apache_key_bytesperreq}" ] && error "missing 'BytesPerReq' from apache server: ${*}" && return 1
+       [ -z "${apache_key_busyworkers}" ] && error "missing 'BusyWorkers' from apache server: ${*}" && return 1
+       [ -z "${apache_key_idleworkers}" ] && error "missing 'IdleWorkers' from apache server: ${*}" && return 1
+       [ -z "${apache_key_scoreboard}"  ] && error "missing 'Scoreboard' from apache server: ${*}" && return 1
 
        if [ ! -z "${apache_key_connstotal}" \
                -a ! -z "${apache_key_connsasyncwriting}" \
@@ -92,7 +98,7 @@ apache_detect() {
 
 apache_get() {
        local oIFS="${IFS}" ret
-       IFS=$':\n' apache_response=($(curl -Ss ${apache_curl_opts} "${apache_url}"))
+       IFS=$':\n' apache_response=($(run curl -Ss ${apache_curl_opts} "${apache_url}"))
        ret=$?
        IFS="${oIFS}"
 
@@ -130,7 +136,7 @@ apache_get() {
                -o -z "${apache_idleworkers}" \
                ]
                then
-               echo >&2 "apache: empty values got from apache server: ${apache_response[*]}"
+               error "empty values got from apache server: ${apache_response[*]}"
                return 1
        fi
 
@@ -151,7 +157,7 @@ apache_check() {
        apache_get
        if [ $? -ne 0 ]
                then
-               echo >&2 "apache: cannot find stub_status on URL '${apache_url}'. Please set apache_url='http://apache.server:80/server-status?auto' in $confd/apache.conf"
+               error "cannot find stub_status on URL '${apache_url}'. Please set apache_url='http://apache.server:80/server-status?auto' in $confd/apache.conf"
                return 1
        fi
 
index df18aaa2e83535b86c35c59fed3c8a7a5b6034fa..24905afe912ac178c428eaa0d1a799e8427b481d 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 apcupsd_ip=127.0.0.1
 apcupsd_port=3551
 
@@ -12,7 +18,7 @@ apcupsd_timeout=3
 apcupsd_priority=90000
 
 apcupsd_get() {
-       timeout $apcupsd_timeout apcaccess status "$1:$2"
+       run -t $apcupsd_timeout apcaccess status "$1:$2"
 }
 
 apcupsd_check() {
@@ -23,14 +29,14 @@ apcupsd_check() {
 
        require_cmd apcaccess || return 1
 
-       apcupsd_get $apcupsd_ip $apcupsd_port >/dev/null
+       run apcupsd_get $apcupsd_ip $apcupsd_port >/dev/null
        if [ $? -ne 0 ]
                then
-               echo >&2 "apcupsd: ERROR: Cannot get information for apcupsd server."
+               error "cannot get information for apcupsd server."
                return 1
        elif [ $(apcupsd_get $apcupsd_ip $apcupsd_port | awk '/^STATUS.*/{ print $3 }') != "ONLINE" ]
                then
-               echo >&2 "apcupsd: ERROR: UPS not online."
+               error "APC UPS not online."
                return 1
        fi
 
@@ -146,7 +152,7 @@ END {
        print \"SET time = \" time;
        print \"END\"
 }"
-       [ $? -ne 0 ] && echo >&2 "apcupsd: failed to get values" && return 1
+       [ $? -ne 0 ] && error "failed to get values" && return 1
 
        return 0
 }
index 6b2513dcf04f9c0a8f1a9961d891196248305bf3..8e075831a5b51a9f6485ed9a410663914a9b2cef 100755 (executable)
@@ -1,5 +1,10 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
 # THIS PLUGIN IS OBSOLETE
 # USE apps.plugin INSTEAD
 
@@ -19,7 +24,7 @@ cpu_apps_check() {
 
        if [ -z "$cpu_apps_apps" ]
        then
-               echo >&2 "$PROGRAM_NAME: cpu_apps: Please set cpu_apps_apps='command1 command2 ...' in $confd/cpu_apps_apps.conf"
+               error "manual configuration required: please set cpu_apps_apps='command1 command2 ...' in $confd/cpu_apps_apps.conf"
                return 1
        fi
        return 0
index 06f692fa691a78308309534f208cd6e001be49b7..b358ac3e74827455d95ef5753bd1b0735c27e9fa 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # if this chart is called X.chart.sh, then all functions and global variables
 # must start with X_
 
@@ -51,7 +57,7 @@ cpufreq_create() {
 
                id="$( fixid "cpu$cpu" )"
 
-               echo >&2 "charts.d: cpufreq: on file='$file', dir='$dir', cpu='$cpu', id='$id'"
+               debug "file='$file', dir='$dir', cpu='$cpu', id='$id'"
 
                echo "DIMENSION $id '$id' absolute 1 1000"
                echo >>$TMP_DIR/cpufreq.sh "echo \"SET $id = \"\$(< $file )"
@@ -59,7 +65,6 @@ cpufreq_create() {
        echo >>$TMP_DIR/cpufreq.sh "echo END"
 
        [ $cpufreq_source_update -eq 1 ] && echo >>$TMP_DIR/cpufreq.sh "}"
-       # cat >&2 $TMP_DIR/cpufreq.sh
 
        # ok, load the function cpufreq_update() we created
        [ $cpufreq_source_update -eq 1 ] && . $TMP_DIR/cpufreq.sh
index 93f1cf4fddbbc6a7cc22f904b1b47f79d9c4007b..86fde490134e077d0d84e9bcbe40ffe818659623 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # if this chart is called X.chart.sh, then all functions and global variables
 # must start with X_
 
@@ -67,7 +73,7 @@ example_check() {
        #  - 1 to disable the chart
 
        # check something
-       [ "${example_magic_number}" != "12345" ] && echo >&2 "example: you have to set example_magic_number=$example_magic_number in example.conf to start example chart." && return 1
+       [ "${example_magic_number}" != "12345" ] && error "manual configuration required: you have to set example_magic_number=$example_magic_number in example.conf to start example chart." && return 1
 
        # check that we can collect data
        example_get || return 1
@@ -108,7 +114,6 @@ BEGIN example.random2 $1
 SET random = $example_value4
 END
 VALUESEOF
-       # echo >&2 "example_count = $example_count value = $value4"
 
        return 0
 }
index c60ae9460dff2636ffe893a238e4075001268aca..4c70f2c196328295a99c86bb7c0e0d4b995ef3f1 100644 (file)
@@ -1,5 +1,13 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# Contributed by @jsveiga with PR #480
+
+# the exim command to run
 exim_command=
 
 # how frequently to collect queue size
@@ -8,28 +16,15 @@ exim_update_every=5
 exim_priority=60000
 
 exim_check() {
-       if [ -z "$exim_command" -o ! -x "$exim_command" ]
-               then
-               local d=
-               for d in /sbin /usr/sbin /usr/local/sbin
-               do
-                       if [ -x "$d/exim" ]
-                       then
-                               exim_command="$d/exim"
-                               break
-                       fi
-               done
-       fi
-
-       if [ -z "$exim_command" -o ! -x  "$exim_command" ]
-       then
-               echo >&2 "$PROGRAM_NAME: exim: cannot find exim executable. Please set 'exim_command=/path/to/exim' in $confd/exim.conf"
-               return 1
-       fi
+    if [ -z "${exim_command}" ]
+    then
+        require_cmd exim || return 1
+        exim_command="${EXIM_CMD}"
+    fi
 
-       if [ `$exim_command -bpc 2>&1 | grep -c denied` -ne 0 ]
+       if [ $(${exim_command} -bpc 2>&1 | grep -c denied) -ne 0 ]
        then
-               echo >&2 "$PROGRAM_NAME: exim: permission denied. Please set 'queue_list_requires_admin = false' in your exim options file"
+               error "permission denied - please set 'queue_list_requires_admin = false' in your exim options file"
                return 1
        fi
 
@@ -37,16 +32,16 @@ exim_check() {
 }
 
 exim_create() {
-cat <<EOF
+    cat <<EOF
 CHART exim_local.qemails '' "Exim Queue Emails" "emails" queue exim.queued.emails line $((exim_priority + 1)) $exim_update_every
 DIMENSION emails '' absolute 1 1
 EOF
-return 0
+    return 0
 }
 
 exim_update() {
-echo "BEGIN exim_local.qemails $1"
-echo "SET emails = " `$exim_command -bpc`
-echo "END"
-return 0
+    echo "BEGIN exim_local.qemails $1"
+    echo "SET emails = " $(run ${exim_command} -bpc)
+    echo "END"
+    return 0
 }
index 41c3e2478092a7df949fe8cb0c8ca353e0abe00b..15895c5e160a71a845a586ab1f602492b26b3061 100755 (executable)
@@ -1,5 +1,12 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# contributed by @paulfantom with PR #511
+
 # if this chart is called X.chart.sh, then all functions and global variables
 # must start with X_
 hddtemp_host="localhost"
@@ -13,7 +20,8 @@ hddtemp_priority=90000
 
 # _check is called once, to find out if this chart should be enabled or not
 hddtemp_check() {
-       nc $hddtemp_host $hddtemp_port &>/dev/null && return 0 || return 1
+    require_cmd nc || return 1
+       run nc $hddtemp_host $hddtemp_port && return 0 || return 1
 }
 
 # _create is called once, to create the charts
index e6790d8077672ec6331cca81104401fcf47e5999..70d3aec7a2dd2c086295f699780be9ae80f347fb 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 load_average_update_every=5
 load_priority=100
 
index ab95b361c88d178e0035006aa8fbf486261d72e1..3bc65fe24004c9af4e71ebcd88b764f38e5b16eb 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 mem_apps_apps=
 
 # these are required for computing memory in bytes and cpu in seconds
@@ -15,7 +21,7 @@ mem_apps_check() {
 
        if [ -z "$mem_apps_apps" ]
        then
-               echo >&2 "$PROGRAM_NAME: mem_apps: not configured. Please set mem_apps_apps='command1 command2 ...' in $confd/mem_apps_apps.conf"
+               error "manual configuration required: please set mem_apps_apps='command1 command2 ...' in $confd/mem_apps_apps.conf"
                return 1
        fi
        return 0
index 120fec66e32586d45e70847b0ff8444088d1a71f..86feac997c8ac260d3f8ebc9bf23c5fd7d11e4ba 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # http://dev.mysql.com/doc/refman/5.0/en/server-status-variables.html
 #
 # https://dev.mysql.com/doc/refman/5.1/en/show-status.html
@@ -17,9 +23,9 @@ mysql_get() {
        local oIFS="${IFS}"
        mysql_data=()
        IFS=$'\t'$'\n'
-       #arr=($("${@}" -e "SHOW GLOBAL STATUS WHERE value REGEXP '^[0-9]';" | egrep "^(Bytes|Slow_|Que|Handl|Table|Selec|Sort_|Creat|Conne|Abort|Binlo|Threa|Innod|Qcach|Key_|Open)" ))
-       #arr=($("${@}" -N -e "SHOW GLOBAL STATUS;" | egrep "^(Bytes|Slow_|Que|Handl|Table|Selec|Sort_|Creat|Conne|Abort|Binlo|Threa|Innod|Qcach|Key_|Open)[^ ]+\s[0-9]" ))
-       arr=($("${@}" -N -e "SHOW GLOBAL STATUS;" | egrep "^(Bytes|Slow_|Que|Handl|Table|Selec|Sort_|Creat|Conne|Abort|Binlo|Threa|Innod|Qcach|Key_|Open)[^[:space:]]+[[:space:]]+[0-9]+" ))
+       #arr=($(run "${@}" -e "SHOW GLOBAL STATUS WHERE value REGEXP '^[0-9]';" | egrep "^(Bytes|Slow_|Que|Handl|Table|Selec|Sort_|Creat|Conne|Abort|Binlo|Threa|Innod|Qcach|Key_|Open)" ))
+       #arr=($(run "${@}" -N -e "SHOW GLOBAL STATUS;" | egrep "^(Bytes|Slow_|Que|Handl|Table|Selec|Sort_|Creat|Conne|Abort|Binlo|Threa|Innod|Qcach|Key_|Open)[^ ]+\s[0-9]" ))
+       arr=($(run "${@}" -N -e "SHOW GLOBAL STATUS;" | egrep "^(Bytes|Slow_|Que|Handl|Table|Selec|Sort_|Creat|Conne|Abort|Binlo|Threa|Innod|Qcach|Key_|Open)[^[:space:]]+[[:space:]]+[0-9]+" ))
        IFS="${oIFS}"
 
        [ "${#arr[@]}" -lt 3 ] && return 1
@@ -74,13 +80,13 @@ mysql_check() {
                [ -z "${mysql_cmds[$m]}" ] && mysql_cmds[$m]="$mysql_cmd"
                if [ -z "${mysql_cmds[$m]}" ]
                        then
-                       echo >&2 "$PROGRAM_NAME: mysql: cannot get mysql command for '$m'. Please set mysql_cmds[$m]='/path/to/mysql', in $confd/mysql.conf"
+                       error "cannot get mysql command for '$m'. Please set mysql_cmds[$m]='/path/to/mysql', in $confd/mysql.conf"
                fi
 
                mysql_get "${mysql_cmds[$m]}" ${mysql_opts[$m]}
                if [ ! $? -eq 0 ]
                then
-                       echo >&2 "$PROGRAM_NAME: mysql: cannot get global status for '$m'. Please set mysql_opts[$m]='options' to whatever needed to get connected to the mysql server, in $confd/mysql.conf"
+                       error "cannot get global status for '$m'. Please set mysql_opts[$m]='options' to whatever needed to get connected to the mysql server, in $confd/mysql.conf"
                        unset mysql_cmds[$m]
                        unset mysql_opts[$m]
                        unset mysql_ids[$m]
@@ -97,7 +103,7 @@ mysql_check() {
                        mysql_check tryroot "${@}"
                        return $?
                else
-                       echo >&2 "$PROGRAM_NAME: mysql: no mysql servers found. Please set mysql_opts[name]='options' to whatever needed to get connected to the mysql server, in $confd/mysql.conf"
+                       error "no mysql servers found. Please set mysql_opts[name]='options' to whatever needed to get connected to the mysql server, in $confd/mysql.conf"
                        return 1
                fi
        fi
@@ -318,7 +324,7 @@ mysql_update() {
                        unset mysql_ids[$m]
                        unset mysql_opts[$m]
                        unset mysql_cmds[$m]
-                       echo >&2 "$PROGRAM_NAME: mysql: failed to get values for '$m', disabling it."
+                       error "failed to get values for '$m', disabling it."
                        continue
                fi
 
@@ -510,7 +516,7 @@ VALUESEOF
                fi
        done
 
-       [ ${#mysql_ids[@]} -eq 0 ] && echo >&2 "$PROGRAM_NAME: mysql: no mysql servers left active." && return 1
+       [ ${#mysql_ids[@]} -eq 0 ] && error "no mysql servers left active." && return 1
        return 0
 }
 
index a2a9b320fa2766d560a503d31c28f4820b90e986..0ae7d630775af82d3c4580ba0c6fc6cf401ba15e 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # if this chart is called X.chart.sh, then all functions and global variables
 # must start with X_
 
@@ -20,7 +26,7 @@ nginx_reading=0
 nginx_writing=0
 nginx_waiting=0
 nginx_get() {
-       nginx_response=($(curl -Ss ${nginx_curl_opts} "${nginx_url}"))
+       nginx_response=($(run curl -Ss ${nginx_curl_opts} "${nginx_url}"))
        [ $? -ne 0 -o "${#nginx_response[@]}" -eq 0 ] && return 1
 
        if [ "${nginx_response[0]}" != "Active" \
@@ -34,7 +40,7 @@ nginx_get() {
                 -o "${nginx_response[14]}" != "Waiting:" \
           ]
                then
-               echo >&2 "nginx: Invalid response from nginx server: ${nginx_response[*]}"
+               error "Invalid response from nginx server: ${nginx_response[*]}"
                return 1
        fi
 
@@ -55,7 +61,7 @@ nginx_get() {
                -o -z "${nginx_waiting}" \
                ]
                then
-               echo >&2 "nginx: empty values got from nginx server: ${nginx_response[*]}"
+               error "empty values got from nginx server: ${nginx_response[*]}"
                return 1
        fi
 
@@ -68,7 +74,7 @@ nginx_check() {
        nginx_get
        if [ $? -ne 0 ]
                then
-               echo >&2 "nginx: cannot find stub_status on URL '${nginx_url}'. Please set nginx_url='http://nginx.server/stub_status' in $confd/nginx.conf"
+               error "cannot find stub_status on URL '${nginx_url}'. Please set nginx_url='http://nginx.server/stub_status' in $confd/nginx.conf"
                return 1
        fi
 
index 3c8e1c9d0548be6a3058932185f97049a5e290dd..e0b1b4cf981bedbb6e0a8de8c59cecb414d1c77d 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # a space separated list of UPS names
 # if empty, the list returned by 'upsc -l' will be used
 nut_ups=
@@ -15,11 +21,11 @@ nut_priority=90000
 declare -A nut_ids=()
 
 nut_get_all() {
-       timeout $nut_timeout upsc -l
+       run -t $nut_timeout upsc -l
 }
 
 nut_get() {
-       timeout $nut_timeout upsc "$1"
+       run -t $nut_timeout upsc "$1"
 }
 
 nut_check() {
@@ -42,12 +48,12 @@ nut_check() {
                        nut_ids[$x]="$( fixid "$x" )"
                        continue
                fi
-               echo >&2 "nut: ERROR: Cannot get information for NUT UPS '$x'."
+               error "cannot get information for NUT UPS '$x'."
        done
 
        if [ ${#nut_ids[@]} -eq 0 ]
                then
-               echo >&2 "nut: Please set nut_ups='ups_name' in $confd/nut.conf"
+               error "Cannot find UPSes - please set nut_ups='ups_name' in $confd/nut.conf"
                return 1
        fi
 
@@ -179,9 +185,9 @@ END {
        print \"SET temp = \" temp;
        print \"END\"
 }"
-               [ $? -ne 0 ] && unset nut_ids[$i] && echo >&2 "nut: failed to get values for '$i', disabling it."
+               [ $? -ne 0 ] && unset nut_ids[$i] && error "failed to get values for '$i', disabling it."
        done
 
-       [ ${#nut_ids[@]} -eq 0 ] && echo >&2 "nut: no UPSes left active." && return 1
+       [ ${#nut_ids[@]} -eq 0 ] && error "no UPSes left active." && return 1
        return 0
 }
index 779087e353e1bca7aef79da3b340c89a0ebafe0c..2a0249daed4d048082188e325c3894b81111f7d0 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 opensips_opts="fifo get_statistics all"
 opensips_cmd=
 opensips_update_every=5
@@ -7,7 +13,7 @@ opensips_timeout=2
 opensips_priority=80000
 
 opensips_get_stats() {
-       timeout $opensips_timeout "$opensips_cmd" $opensips_opts |\
+       run -t $opensips_timeout "$opensips_cmd" $opensips_opts |\
                grep "^\(core\|dialog\|net\|registrar\|shmem\|siptrace\|sl\|tm\|uri\|usrloc\):[a-zA-Z0-9_-]\+[[:space:]]*[=:]\+[[:space:]]*[0-9]\+[[:space:]]*$" |\
                sed \
                        -e "s|[[:space:]]*[=:]\+[[:space:]]*\([0-9]\+\)[[:space:]]*$|=\1|g" \
@@ -31,7 +37,7 @@ opensips_check() {
        local x="$(opensips_get_stats | grep "^opensips_core_")"
        if [ ! $? -eq 0 -o -z "$x" ]
        then
-               echo >&2 "$PROGRAM_NAME: opensips: cannot get global status. Please set opensips_opts='options' whatever needed to get connected to opensips server, in $confd/opensips.conf"
+               error "cannot get global status. Please set opensips_opts='options' whatever needed to get connected to opensips server, in $confd/opensips.conf"
                return 1
        fi
 
@@ -214,7 +220,7 @@ opensips_update() {
        eval "local $(opensips_get_stats)"
        [ $? -ne 0 ] && return 1
 
-       [ $opensips_command_failed -eq 1 ] && echo >&2 "$PROGRAM_NAME: opensips: failed to get values, disabling." && return 1
+       [ $opensips_command_failed -eq 1 ] && error "failed to get values, disabling." && return 1
 
        # write the result of the work.
        cat <<VALUESEOF
index 976ce91b198d260ebf8fa8ebf709abae6e636bb9..a5ee0ad561637dd3dd30571e966d1863c019ffb4 100755 (executable)
@@ -1,9 +1,13 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
-# if this chart is called X.chart.sh, then all functions and global variables
-# must start with X_
-
-# first, you need open php-fpm status in php-fpm.conf 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# Contributed by @safeie with PR #276
+
+# first, you need open php-fpm status in php-fpm.conf
 # second, you need add status location in nginx.conf
 # you can see, https://easyengine.io/tutorials/php/fpm-status-page/
 
@@ -32,7 +36,7 @@ phpfpm_slow_requests=0
 phpfpm_get() {
        local opts="${1}" url="${2}"
 
-       phpfpm_response=($(curl -Ss ${opts} "${url}"))
+       phpfpm_response=($(run curl -Ss ${opts} "${url}"))
        [ $? -ne 0 -o "${#phpfpm_response[@]}" -eq 0 ] && return 1
 
        if [[ "${phpfpm_response[0]}" != "pool:" \
@@ -46,7 +50,7 @@ phpfpm_get() {
                || "${phpfpm_response[32]}" != "total" \
        ]]
                then
-               echo >&2 "phpfpm: invalid response from phpfpm status server: ${phpfpm_response[*]}"
+               error "invalid response from phpfpm status server: ${phpfpm_response[*]}"
                return 1
        fi
 
@@ -83,7 +87,7 @@ phpfpm_get() {
                || -z "${phpfpm_max_children_reached}" \
        ]]
                then
-               echo >&2 "phpfpm: empty values got from phpfpm status server: ${phpfpm_response[*]}"
+               error "empty values got from phpfpm status server: ${phpfpm_response[*]}"
                return 1
        fi
 
@@ -101,14 +105,14 @@ phpfpm_check() {
        do
                phpfpm_get "${phpfpm_curl_opts[$m]}" "${phpfpm_urls[$m]}"
                if [ $? -ne 0 ]; then
-                       echo >&2 "phpfpm: cannot find status on URL '${phpfpm_url[$m]}'. Please set phpfpm_urls[$m]='http://localhost/status' in $confd/phpfpm.conf"
+                       error "cannot find status on URL '${phpfpm_url[$m]}'. Please set phpfpm_urls[$m]='http://localhost/status' in $confd/phpfpm.conf"
                        unset phpfpm_urls[$m]
                        continue
                fi
        done
        
        if [ ${#phpfpm_urls[@]} -eq 0 ]; then
-               echo >&2 "phpfpm: no phpfpm servers found. Please set phpfpm_urls[name]='url' to whatever needed to get status to the phpfpm server, in $confd/phpfpm.conf"
+               error "no phpfpm servers found. Please set phpfpm_urls[name]='url' to whatever needed to get status to the phpfpm server, in $confd/phpfpm.conf"
                return 1
        fi
        
index 7f07a18682c0f57c255bc9cc9c303befebbd0109..8106414a1f6da024482f348782bb97e2e5ec0abf 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # the postqueue command
 # if empty, it will use the one found in the system path
 postfix_postqueue=
@@ -18,23 +24,11 @@ postfix_check() {
        if [ -z "$postfix_postqueue" -o ! -x "$postfix_postqueue" ]
        then
                postfix_postqueue="`which postqueue 2>/dev/null`"
-               if [ -z "$postfix_postqueue" -o ! -x "$postfix_postqueue" ]
-               then
-                       local d=
-                       for d in /sbin /usr/sbin /usr/local/sbin
-                       do
-                               if [ -x "$d/postqueue" ]
-                               then
-                                       postfix_postqueue="$d/postqueue"
-                                       break
-                               fi
-                       done
-               fi
        fi
 
        if [ -z "$postfix_postqueue" -o ! -x  "$postfix_postqueue" ]
        then
-               echo >&2 "$PROGRAM_NAME: postfix: cannot find postqueue. Please set 'postfix_postqueue=/path/to/postqueue' in $confd/postfix.conf"
+               error "cannot find postqueue. Please set 'postfix_postqueue=/path/to/postqueue' in $confd/postfix.conf"
                return 1
        fi
 
@@ -73,10 +67,10 @@ postfix_update() {
        postfix_q_emails=0
        postfix_q_size=0
 
-       eval "`$postfix_postqueue -p |\
+       eval "$(run $postfix_postqueue -p |\
                grep "^--" |\
                sed -e "s/-- \([0-9]\+\) Kbytes in \([0-9]\+\) Requests.$/local postfix_q_size=\1\nlocal postfix_q_emails=\2/g" |\
-               egrep "^local postfix_q_(emails|size)=[0-9]+$"`"
+               egrep "^local postfix_q_(emails|size)=[0-9]+$")"
 
        # write the result of the work.
        cat <<VALUESEOF
index 9652f896a0822f39cf65f7047fe5f05681daae99..125c925da7f6c031fcbf45bdc78a76cf552bd561 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 # sensors docs
 # https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
 
@@ -43,7 +49,7 @@ sensors_check() {
        #  - 0 to enable the chart
        #  - 1 to disable the chart
 
-       [ -z "$( sensors_find_all_files $sensors_sys_dir )" ] && echo >&2 "$PROGRAM_NAME: sensors: no sensors found in '$sensors_sys_dir'." && return 1
+       [ -z "$( sensors_find_all_files $sensors_sys_dir )" ] && error "no sensors found in '$sensors_sys_dir'." && return 1
        return 0
 }
 
@@ -64,7 +70,7 @@ sensors_check_files() {
                [ $v -ne 0 ] && echo "$f" && continue
                excluded=
 
-               echo >&2 "$PROGRAM_NAME: sensors: $f gives zero values"
+               error "$f gives zero values"
        done
 }
 
@@ -83,7 +89,7 @@ sensors_check_temp_type() {
                v=$(( v + 1 - 1 ))
                [ $v -ne 0 ] && echo "$f" && continue
 
-               echo >&2 "$PROGRAM_NAME: sensors: $f is disabled"
+               error "$f is disabled"
        done
 }
 
@@ -121,7 +127,7 @@ sensors_create() {
 
                id="$( fixid "$device.$subsystem.$dir" )"
 
-               echo >&2 "charts.d: sensors: on path='$path', dir='$dir', device='$device', subsystem='$subsystem', id='$id', name='$name'"
+               debug "path='$path', dir='$dir', device='$device', subsystem='$subsystem', id='$id', name='$name'"
 
                for mode in temperature voltage fans power current energy humidity
                do
@@ -221,7 +227,6 @@ sensors_create() {
        done
 
        [ $sensors_source_update -eq 1 ] && echo >>$TMP_DIR/sensors.sh "}"
-       # cat >&2 $TMP_DIR/sensors.sh
 
        # ok, load the function sensors_update() we created
        [ $sensors_source_update -eq 1 ] && . $TMP_DIR/sensors.sh
index 3e72ba6df507d7442b01a565bcb4f073db80f113..2c19c35d59068f11728cddc6433a786a5bed24fc 100755 (executable)
@@ -1,5 +1,11 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+
 squid_host=
 squid_port=
 squid_url=
@@ -9,7 +15,7 @@ squid_priority=60000
 
 squid_get_stats_internal() {
        local host="$1" port="$2" url="$3"
-       squidclient -h $host -p $port $url
+       run squidclient -h $host -p $port $url
 }
 
 squid_get_stats() {
@@ -29,13 +35,13 @@ squid_autodetect() {
                                squid_host="$host"
                                squid_port="$port"
                                squid_url="$url"
-                               echo >&2 "squid: found squid at '$host:$port' with url '$url'"
+                               debug "found squid at '$host:$port' with url '$url'"
                                return 0
                        fi
                done
        done
 
-       echo >&2 "squid: cannot find squid running in localhost. Please set squid_url='url' and squid_host='IP' and squid_port='PORT' in $confd/squid.conf"
+       error "cannot find squid running in localhost. Please set squid_url='url' and squid_host='IP' and squid_port='PORT' in $confd/squid.conf"
        return 1
 }
 
@@ -53,7 +59,7 @@ squid_check() {
        local x="$(squid_get_stats | grep client_http.requests)"
        if [ ! $? -eq 0 -o -z "$x" ]
        then
-               echo >&2 "squid: cannot fetch URL '$squid_url' by connecting to $squid_host:$squid_port. Please set squid_url='url' and squid_host='host' and squid_port='port' in $confd/squid.conf"
+               error "cannot fetch URL '$squid_url' by connecting to $squid_host:$squid_port. Please set squid_url='url' and squid_host='host' and squid_port='port' in $confd/squid.conf"
                return 1
        fi
 
index cc6baea1ae1f967fe2210086d96ffb13ed8611b9..44a92c86e7bf35f27fb9beb2c324ac056b08720b 100755 (executable)
@@ -1,5 +1,12 @@
 # no need for shebang - this file is loaded from charts.d.plugin
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# Contributed by @jgeromero with PR #277
+
 # Description: Tomcat netdata charts.d plugin
 # Author: Jorge Romero
 
@@ -34,13 +41,13 @@ tomcat_check() {
 
        # check if url, username, passwords are set
        if [ -z "${tomcat_url}" ]; then
-               echo >&2 "tomcat url is unset or set to the empty string"
+               error "tomcat url is unset or set to the empty string"
                return 1
        fi
        if [ -z "${tomcat_user}" ]; then
                # check backwards compatibility
                if [ -z "${tomcatUser}" ]; then         
-                       echo >&2 "tomcat user is unset or set to the empty string"
+               error "tomcat user is unset or set to the empty string"
                        return 1
                else
                        tomcat_user="${tomcatUser}"
@@ -49,7 +56,7 @@ tomcat_check() {
        if [ -z "${tomcat_password}" ]; then
                # check backwards compatibility
                if [ -z "${tomcatPassword}" ]; then
-                       echo >&2 "tomcat password is unset or set to the empty string"
+               error "tomcat password is unset or set to the empty string"
                        return 1
                else
                        tomcat_password="${tomcatPassword}"
@@ -60,8 +67,7 @@ tomcat_check() {
        tomcat_get
        if [ $? -ne 0 ]
                then
-               echo >&2 "tomcat: couldn't get to status page on URL '${tomcat_url}'."\
-               "Please make sure tomcat url, username and password are correct."
+               error "cannot get to status page on URL '${tomcat_url}'. Please make sure tomcat url, username and password are correct."
                return 1
        fi
 
@@ -75,8 +81,8 @@ tomcat_check() {
 tomcat_get() {
        # collect tomcat values
        tomcat_port="$(IFS=/ read -ra a <<< "$tomcat_url"; hostport=${a[2]}; echo "${hostport#*:}")"
-       mapfile -t lines < <(curl -u "$tomcat_user":"$tomcat_password" -Ss ${tomcat_curl_opts} "$tomcat_url" |\
-               xmlstarlet sel \
+       mapfile -t lines < <(run curl -u "$tomcat_user":"$tomcat_password" -Ss ${tomcat_curl_opts} "$tomcat_url" |\
+               run xmlstarlet sel \
                        -t -m "/status/jvm/memory" -v @free \
                        -n -m "/status/connector[@name='\"http-bio-$tomcat_port\"']/threadInfo" -v @currentThreadCount \
                        -n -v @currentThreadsBusy \
index 4b24ac050d717b81cc237acdb2291b75c8671876..c2ef258c9550f95118331ecb32f75742ffeb657a 100644 (file)
@@ -6,6 +6,7 @@ MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
 dist_config_DATA = \
        apps_groups.conf \
        charts.d.conf \
+       fping.conf \
        node.d.conf \
        python.d.conf \
        health_alarm_notify.conf \
diff --git a/conf.d/fping.conf b/conf.d/fping.conf
new file mode 100644 (file)
index 0000000..3bf8047
--- /dev/null
@@ -0,0 +1,32 @@
+# This plugin requires a special version of fping.
+# Get it from https://github.com/ktsaou/fping
+# and build it, like this:
+#
+# cd /usr/src
+# git clone https://github.com/ktsaou/fping.git fping-netdata.git
+# cd fping-netdata.git
+# ./autogen.sh
+# ./configure --prefix=/usr/local
+# make
+# cp src/fping /usr/local/bin/
+# chown root:root /usr/local/bin/fping
+# chmod 4755 /usr/local/bin/fping
+#
+# -----------------------------------------------------------------------------
+# configuration options
+# can be overwritten at /etc/netdata/fping.conf
+
+# the fping binary to use
+# we need one that can output netdata friendly info
+fping="$(which fping || command -v fping)"
+
+# a space separated list of hosts to fping
+# it is best to put hostnames here
+hosts=""
+
+# the time in milliseconds (1 sec = 1000 ms)
+# to ping the hosts - by default 2 pings per iteration
+ping_every="$((update_every * 1000 / 2))"
+
+# how many retries to make if a host does not respond
+retries=1
index be8d1258da90da573112cc84dee5d9a73322aa8b..d4361555bba5ee695d78c0ec32c1ffef8404226c 100644 (file)
@@ -29,6 +29,20 @@ declare -A configs_signatures=(
   ['7cf6402b51e5070f2be3ad6fe059ff89']='charts.d.conf'
   ['a02d14124b19c635c1426cee2e98bac5']='charts.d.conf'
   ['ca026d7c779f0a7cb7787713c5be5c47']='charts.d.conf'
+  ['1f5545b3ff52b3eb75ee05401f67a9bc']='fping.conf'
+  ['2fa8fb929fd597f2ab97b6efc540a043']='health_alarm_notify.conf'
+  ['42ad0e70b1365b6e7244cc305dbaa529']='health_alarm_notify.conf'
+  ['707a63f53f4b32e01d134ae90ba94aad']='health_alarm_notify.conf'
+  ['8fd472a854b0996327e8ed3562161182']='health_alarm_notify.conf'
+  ['a55133f1b0be0a4255057849dd451b09']='health_alarm_notify.conf'
+  ['a89c516a1144435a88decf25509318ac']='health_alarm_notify.conf'
+  ['b68706bb8101ef85192db92f865a5d80']='health_alarm_notify.conf'
+  ['c080e006f544c949baca33cc24a9c126']='health_alarm_notify.conf'
+  ['ce2e8768964a936f58c4c2144aee8a01']='health_alarm_notify.conf'
+  ['e3023092e3b2bbb5351e0fe6682f4fe9']='health_alarm_notify.conf'
+  ['f8dade4484f1b6a48655388502df7d5a']='health_alarm_notify.conf'
+  ['fd3164e6e8cb6726706267eae49aa082']='health_alarm_notify.conf'
+  ['ff1b3d8ae8b2149c711d8da9b7a9c4bd']='health_alarm_notify.conf'
   ['074df527cc70b5f38c0714f08f20e57c']='health.d/apache.conf'
   ['174c21a6ce5de97bda83d502aa47a9f8']='health.d/apache.conf'
   ['3848172053221b95279ba9bf789cd4e0']='health.d/apache.conf'
@@ -152,22 +166,14 @@ declare -A configs_signatures=(
   ['ca08a9b18d38ae0a0f5081a7cdc96863']='health.d/swap.conf'
   ['da29d2ab1ab7b8fda189960c840e5144']='health.d/swap.conf'
   ['b3fc4749b132e55ac0d3a0f92859237e']='health.d/tcp_resets.conf'
-  ['2fa8fb929fd597f2ab97b6efc540a043']='health_alarm_notify.conf'
-  ['42ad0e70b1365b6e7244cc305dbaa529']='health_alarm_notify.conf'
-  ['707a63f53f4b32e01d134ae90ba94aad']='health_alarm_notify.conf'
-  ['8fd472a854b0996327e8ed3562161182']='health_alarm_notify.conf'
-  ['a55133f1b0be0a4255057849dd451b09']='health_alarm_notify.conf'
-  ['a89c516a1144435a88decf25509318ac']='health_alarm_notify.conf'
-  ['b68706bb8101ef85192db92f865a5d80']='health_alarm_notify.conf'
-  ['c080e006f544c949baca33cc24a9c126']='health_alarm_notify.conf'
-  ['ce2e8768964a936f58c4c2144aee8a01']='health_alarm_notify.conf'
-  ['e3023092e3b2bbb5351e0fe6682f4fe9']='health_alarm_notify.conf'
-  ['f8dade4484f1b6a48655388502df7d5a']='health_alarm_notify.conf'
-  ['fd3164e6e8cb6726706267eae49aa082']='health_alarm_notify.conf'
-  ['ff1b3d8ae8b2149c711d8da9b7a9c4bd']='health_alarm_notify.conf'
   ['707a63f53f4b32e01d134ae90ba94aad']='health_email_recipients.conf'
   ['a752e51d923e15add4a11fa8f3be935a']='health_email_recipients.conf'
   ['73a8e10dfe4183aca751e9e2a80dabe3']='node.d.conf'
+  ['6b917300747e7e8314844237e2462261']='python.d/apache_cache.conf'
+  ['e0e96cc47ed61d6492416be5236cd4d3']='python.d/apache_cache.conf'
+  ['5278ebbae19c60db600f0a119cb3664e']='python.d/apache.conf'
+  ['5829812db29598db5857c9f433e96fef']='python.d/apache.conf'
+  ['6bf0de6e3b251b765b10a71d8c5c319d']='python.d/apache.conf'
   ['13141998a5d71308d9c119834c27bfd3']='python.d.conf'
   ['38d1bf04fe9901481dd6febcc0404a86']='python.d.conf'
   ['4b775fb31342f1478b3773d041a72911']='python.d.conf'
@@ -183,11 +189,6 @@ declare -A configs_signatures=(
   ['bba2f3886587f137ea08a6e63dd3d376']='python.d.conf'
   ['d55be5bb5e108da1e7645da007c53cd4']='python.d.conf'
   ['f82924563e41d99cdae5431f0af69155']='python.d.conf'
-  ['5278ebbae19c60db600f0a119cb3664e']='python.d/apache.conf'
-  ['5829812db29598db5857c9f433e96fef']='python.d/apache.conf'
-  ['6bf0de6e3b251b765b10a71d8c5c319d']='python.d/apache.conf'
-  ['6b917300747e7e8314844237e2462261']='python.d/apache_cache.conf'
-  ['e0e96cc47ed61d6492416be5236cd4d3']='python.d/apache_cache.conf'
   ['7830066c46a7e5f9682b8d3f4566b4e5']='python.d/cpufreq.conf'
   ['b5b5a8d6d991fb1cef8d80afa23ba114']='python.d/cpufreq.conf'
   ['dc0d2b96378f290eec3fcf98b89ad824']='python.d/cpufreq.conf'
diff --git a/diagrams/build.sh b/diagrams/build.sh
new file mode 100755 (executable)
index 0000000..53f0ea7
--- /dev/null
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+path=$(dirname "$0")
+cd "${path}" || exit 1
+
+if [ ! -f "plantuml.jar" ]
+then
+       echo >&2 "Please download 'plantuml.jar' from http://plantuml.com/ and put it the same folder with me."
+       exit 1
+fi
+
+for x in *.puml
+do
+       [ "${x}" = "config.puml" ] && continue
+
+       echo >&2 "Working on ${x}..."
+       java -jar plantuml.jar -tpng "${x}"
+       java -jar plantuml.jar -tsvg "${x}"
+       # java -jar plantuml.jar -ttxt "${x}"
+done
diff --git a/diagrams/config.puml b/diagrams/config.puml
new file mode 100644 (file)
index 0000000..0ce0932
--- /dev/null
@@ -0,0 +1,46 @@
+
+skinparam handwritten true
+skinparam monochrome true
+skinparam roundcorner 15
+
+skinparam sequence {
+    ArrowThickness 3
+
+    DividerFontColor Black
+    DividerFontName Comic Sans MS
+    DividerFontSize 15
+    DividerFontStyle Italic
+
+    DelayFontColor Black
+    DelayFontName Comic Sans MS
+    DelayFontSize 15
+    DelayFontStyle Italic
+
+    TitleFontColor Black
+    TitleFontName Comic Sans MS
+    TitleFontStyle Italic
+    TitleFontSize 25
+
+    ArrowColor DeepSkyBlue
+    ArrowFontColor Black
+    ArrowFontName Comic Sans MS
+    ArrowFontStyle Regular
+    ArrowFontSize 19
+
+    ActorBorderColor DeepSkyBlue
+
+    LifeLineBorderColor blue
+    LifeLineBackgroundColor #A9DCDF
+
+    ParticipantBorderColor DeepSkyBlue
+    ParticipantBackgroundColor LightBlue
+    ParticipantFontName Comic Sans MS
+    ParticipantFontSize 20
+    ParticipantFontColor Black
+
+    ActorBackgroundColor aqua
+    ActorFontColor Black
+    ActorFontSize 20
+    ActorFontName Comic Sans MS
+}
+
diff --git a/diagrams/registry.puml b/diagrams/registry.puml
new file mode 100644 (file)
index 0000000..51a337f
--- /dev/null
@@ -0,0 +1,40 @@
+@startuml
+!include config.puml
+
+title netdata registry operation
+actor "web browser" as user
+participant "netdata 1" as n1
+participant "registry 1" as r1
+autonumber "<b>0."
+
+== standard dashboard communication ==
+
+user ->n1 : \
+    hi, give me the dashboard
+
+n1 --> user : \
+    welcome, here it is...
+
+... a few seconds later ...
+
+== registry related communication ==
+
+user -> n1 : \
+    now give me registry information
+
+n1 --> user: \
+    here it is, talk to <b>registry 1</b>
+
+note left of r1 #eee: \
+    only your web browser \n\
+    talks to the registry
+
+user -> r1 : \
+    Hey <b>registry 1</b>, \
+I am accessing <b>netdata 1</b>...
+
+r1 --> user : \
+    nice!, here are other netdata servers \
+you have accessed in the past
+
+@enduml
index c3b97243be54217d2c9a4a86ae076fd32479ce1e..930243c4c4d734f14d166092b64461345fad5a66 100755 (executable)
@@ -15,6 +15,12 @@ fi
 LC_ALL=C
 umask 022
 
+# Be nice on production environments
+renice 19 $$ >/dev/null 2>/dev/null
+
+processors=$(cat /proc/cpuinfo  | grep ^processor | wc -l)
+[ $(( processors )) -lt 1 ] && processors=1
+
 # you can set CFLAGS before running installer
 CFLAGS="${CFLAGS--O3}"
 
@@ -133,7 +139,7 @@ get_git_config_signatures() {
 
     echo >configs.signatures.tmp
 
-    for x in $(find conf.d -name \*.conf)
+    for x in $(find conf.d -name \*.conf | sort)
     do
             x="${x/conf.d\//}"
             echo "${x}"
@@ -427,7 +433,7 @@ if [ -f src/netdata ]
 fi
 
 echo >&2 "Compiling netdata ..."
-run make || exit 1
+run make -j${processors} || exit 1
 
 if [ "${BASH_VERSINFO[0]}" -ge "4" ]
 then
@@ -713,35 +719,35 @@ isnetdata() {
 }
 
 stop_netdata_on_pid() {
-    local pid="$1" ret=0 count=0
+    local pid="${1}" ret=0 count=0
 
-    isnetdata $pid || return 0
+    isnetdata ${pid} || return 0
 
-    printf >&2 "Stopping netdata on pid $pid ..."
-    while [ ! -z "$pid" -a $ret -eq 0 ]
+    printf >&2 "Stopping netdata on pid ${pid} ..."
+    while [ ! -z "$pid" -a ${ret} -eq 0 ]
     do
-        if [ $count -gt 45 ]
+        if [ ${count} -gt 45 ]
             then
-            echo >&2 "Cannot stop the running netdata on pid $pid."
+            echo >&2 "Cannot stop the running netdata on pid ${pid}."
             return 1
         fi
 
         count=$(( count + 1 ))
 
-        run kill $pid 2>/dev/null
+        run kill ${pid} 2>/dev/null
         ret=$?
 
-        test $ret -eq 0 && printf >&2 "." && sleep 2
+        test ${ret} -eq 0 && printf >&2 "." && sleep 2
     done
 
     echo >&2
-    if [ $ret -eq 0 ]
+    if [ ${ret} -eq 0 ]
     then
-        echo >&2 "SORRY! CANNOT STOP netdata ON PID $pid !"
+        echo >&2 "SORRY! CANNOT STOP netdata ON PID ${pid} !"
         return 1
     fi
 
-    echo >&2 "netdata on pid $pid stopped."
+    echo >&2 "netdata on pid ${pid} stopped."
     return 0
 }
 
@@ -755,7 +761,7 @@ stop_all_netdata() {
         $(cat /var/run/netdata/netdata.pid 2>/dev/null) \
         $(pidof netdata 2>/dev/null)
     do
-        stop_netdata_on_pid $p
+        stop_netdata_on_pid ${p}
     done
 }
 
@@ -854,13 +860,13 @@ if [ ! -s "${NETDATA_PREFIX}/etc/netdata/netdata.conf" ]
     ret=$?
 
     # try curl
-    if [ $ret -ne 0 -o ! -s "${NETDATA_PREFIX}/etc/netdata/netdata.conf.new" ]
+    if [ ${ret} -ne 0 -o ! -s "${NETDATA_PREFIX}/etc/netdata/netdata.conf.new" ]
         then
         curl -s -o "${NETDATA_PREFIX}/etc/netdata/netdata.conf.new" "http://localhost:${NETDATA_PORT}/netdata.conf"
         ret=$?
     fi
 
-    if [ $ret -eq 0 -a -s "${NETDATA_PREFIX}/etc/netdata/netdata.conf.new" ]
+    if [ ${ret} -eq 0 -a -s "${NETDATA_PREFIX}/etc/netdata/netdata.conf.new" ]
         then
         mv "${NETDATA_PREFIX}/etc/netdata/netdata.conf.new" "${NETDATA_PREFIX}/etc/netdata/netdata.conf"
         echo >&2 "New configuration saved for you to edit at ${NETDATA_PREFIX}/etc/netdata/netdata.conf"
index 278850e06c39987bc0140920d31e55eac081344e..e5677510023bd7818de52f6025b5b62f3051bd9b 100644 (file)
@@ -22,16 +22,20 @@ Release:    1%{?dist}
 License:       GPL v3+
 Group:         Applications/System
 Source0:       http://firehol.org/download/netdata/releases/v@PACKAGE_VERSION@/%{name}-@PACKAGE_VERSION@.tar.xz
-URL:           http://netdata.firehol.org/
+URL:           http://my-netdata.io/
 BuildRequires: pkgconfig
 BuildRequires: xz
 BuildRequires: zlib-devel
 BuildRequires: libuuid-devel
+Requires: zlib
+Requires: libuuid
 
 # Packages can be found in the EPEL repo
 %if %{with nfacct}
 BuildRequires: libmnl-devel
 BuildRequires: libnetfilter_acct-devel
+Requires: libmnl
+Requires: libnetfilter_acct
 %endif
 
 Requires(pre): /usr/sbin/groupadd
@@ -147,7 +151,9 @@ rm -rf $RPM_BUILD_ROOT
 
 %dir %{_sysconfdir}/%{name}
 %config(noreplace) %{_sysconfdir}/%{name}/*.conf
+#%config(noreplace) %{_sysconfdir}/%{name}/charts.d/*.conf
 %config(noreplace) %{_sysconfdir}/%{name}/health.d/*.conf
+#%config(noreplace) %{_sysconfdir}/%{name}/node.d/*.conf
 %config(noreplace) %{_sysconfdir}/%{name}/python.d/*.conf
 %config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
 
index 6183993b5ba03898cca4e5e982e8fccf2551e922..706f75f4f6140aeb8c685bc84c87609311f71ee1 100644 (file)
@@ -1,5 +1,10 @@
 'use strict';
 
+// netdata
+// real-time performance and health monitoring, done right!
+// (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+// GPL v3+
+
 var url = require('url');
 var http = require('http');
 var util = require('util');
@@ -108,7 +113,8 @@ var netdata = {
                                });
 
                                req.on('error', function(e) {
-                                       service.error('Failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);
+                                       if(netdata.options.DEBUG === true) netdata.debug('Failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message);
+                                       service.error('Failed to make request, message: ' + e.message);
                                        callback(null);
                                });
 
@@ -127,18 +133,33 @@ var netdata = {
                return util.inspect(obj, {depth: 10});
        },
 
+       zeropad2: function(s) {
+               if(typeof s !== 'string')
+                       s = s.toString();
+
+               switch(s.length) {
+                       case 0: return '00';
+                       case 1: return '0' + s;
+                       default: return s;
+               }
+       },
+
+       logdate: function(d) {
+               if(typeof d === 'undefined') d = new Date();
+               return this.zeropad2(d.getFullYear()) + '-' + this.zeropad2(d.getMonth()) + '-' + this.zeropad2(d.getDay())
+                       + ' ' + this.zeropad2(d.getHours()) + ':' + this.zeropad2(d.getMinutes()) + ':' + this.zeropad2(d.getSeconds());
+       },
+
        // show debug info, if debug is enabled
        debug: function(msg) {
                if(this.options.DEBUG === true) {
-                       var now = new Date();
-                       console.error(now.toString() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
+                       console.error(this.logdate() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
                }
        },
 
        // log an error
        error: function(msg) {
-               var now = new Date();
-               console.error(now.toString() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
+               console.error(this.logdate() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString());
        },
 
        // send data to netdata
index 4bc0dc4471fe290794c421527ae4f6da8069853c..326192f8f193fb93f0ccb1dc22aeae35dcefa177 100644 (file)
@@ -13,6 +13,7 @@ dist_plugins_SCRIPTS = \
        cgroup-name.sh \
        charts.d.dryrun-helper.sh \
        charts.d.plugin \
+       fping.plugin \
        node.d.plugin \
        python.d.plugin \
        tc-qos-helper.sh \
index dcb5d4016b78655c61b680bea1874d91e06d2c07..acd44923f9d768f939b4ef0d51e7a5940cb116aa 100755 (executable)
@@ -5,7 +5,7 @@
 # (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
 # GPL v3+
 #
-# Script the send alarm notifications for netdata
+# Script to send alarm notifications for netdata
 #
 # Features:
 #  - multiple notification methods
 #  - telegram.org notifications
 #
 
-me="${0}"
+export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
+export LC_ALL=C
+
+# -----------------------------------------------------------------------------
+
+PROGRAM_NAME="$(basename "${0}")"
+
+logdate() {
+    date "+%Y-%m-%d %H:%M:%S"
+}
+
+log() {
+    local status="${1}"
+    shift
+
+    echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}"
+
+}
+
+warning() {
+    log WARNING "${@}"
+}
+
+error() {
+    log ERROR "${@}"
+}
+
+info() {
+    log INFO "${@}"
+}
+
+fatal() {
+    log FATAL "${@}"
+    exit 1
+}
+
+debug=0
+debug() {
+    [ $debug -eq 1 ] && log DEBUG "${@}"
+}
+
+# -----------------------------------------------------------------------------
 
 # check for BASH v4+ (required for associative arrays)
 [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ] && \
-    echo >&2 "${me}: BASH version 4 or later is required (this is ${BASH_VERSION})." && \
-    exit 1
+    fatal "BASH version 4 or later is required (this is ${BASH_VERSION})."
 
+# -----------------------------------------------------------------------------
 # defaults to allow running this script by hand
+
 NETDATA_CONFIG_DIR="${NETDATA_CONFIG_DIR-/etc/netdata}"
 NETDATA_CACHE_DIR="${NETDATA_CACHE_DIR-/var/cache/netdata}"
 [ -z "${NETDATA_REGISTRY_URL}" ] && NETDATA_REGISTRY_URL="https://registry.my-netdata.io"
@@ -62,14 +104,14 @@ info="${18}"       # a short description of the alarm
 # don't do anything if this is not WARNING, CRITICAL or CLEAR
 if [ "${status}" != "WARNING" -a "${status}" != "CRITICAL" -a "${status}" != "CLEAR" ]
 then
-    echo >&2 "${me}: not sending notification for ${status} on '${chart}.${name}'"
+    info "not sending notification for ${status} on '${chart}.${name}'"
     exit 1
 fi
 
 # don't do anything if this is CLEAR, but it was not WARNING or CRITICAL
 if [ "${old_status}" != "WARNING" -a "${old_status}" != "CRITICAL" -a "${status}" = "CLEAR" ]
 then
-    echo >&2 "${me}: not sending notification for ${status} on '${chart}.${name}' (last status was ${old_status})"
+    info "not sending notification for ${status} on '${chart}.${name}' (last status was ${old_status})"
     exit 1
 fi
 
@@ -262,8 +304,7 @@ fi
 # check that we have at least a method enabled
 if [ "${SEND_EMAIL}" != "YES" -a "${SEND_PUSHOVER}" != "YES" -a "${SEND_TELEGRAM}" != "YES" -a "${SEND_SLACK}" != "YES" ]
     then
-    echo >&2 "All notification methods are disabled. Not sending a notification."
-    exit 1
+    fatal "All notification methods are disabled. Not sending a notification."
 fi
 
 # -----------------------------------------------------------------------------
@@ -372,10 +413,10 @@ send_email() {
 
         if [ $ret -eq 0 ]
         then
-            echo >&2 "${me}: Sent email notification for: ${host} ${chart}.${name} is ${status} to '${to_email}'"
+            info "sent email notification for: ${host} ${chart}.${name} is ${status} to '${to_email}'"
             return 0
         else
-            echo >&2 "${me}: Failed to send email notification for: ${host} ${chart}.${name} is ${status} to '${to_email}' with error code ${ret}."
+            error "failed to send email notification for: ${host} ${chart}.${name} is ${status} to '${to_email}' with error code ${ret}."
             return 1
         fi
     fi
@@ -417,10 +458,10 @@ send_pushover() {
 
             if [ "${httpcode}" == "200" ]
             then
-                echo >&2 "${me}: Sent pushover notification for: ${host} ${chart}.${name} is ${status} to '${user}'"
+                info "sent pushover notification for: ${host} ${chart}.${name} is ${status} to '${user}'"
                 sent=$((sent + 1))
             else
-                echo >&2 "${me}: Failed to send pushover notification for: ${host} ${chart}.${name} is ${status} to '${user}' with HTTP error code ${httpcode}."
+                error "failed to send pushover notification for: ${host} ${chart}.${name} is ${status} to '${user}' with HTTP error code ${httpcode}."
             fi
         done
 
@@ -452,13 +493,13 @@ send_telegram() {
 
             if [ "${httpcode}" == "200" ]
             then
-                echo >&2 "${me}: Sent telegram notification for: ${host} ${chart}.${name} is ${status} to '${chatid}'"
+                info "sent telegram notification for: ${host} ${chart}.${name} is ${status} to '${chatid}'"
                 sent=$((sent + 1))
             elif [ "${httpcode}" == "401" ]
             then
-                echo >&2 "${me}: Failed to send telegram notification for: ${host} ${chart}.${name} is ${status} to '${chatid}': Wrong bot token."
+                error "failed to send telegram notification for: ${host} ${chart}.${name} is ${status} to '${chatid}': Wrong bot token."
             else
-                echo >&2 "${me}: Failed to send telegram notification for: ${host} ${chart}.${name} is ${status} to '${chatid}' with HTTP error code ${httpcode}."
+                error "failed to send telegram notification for: ${host} ${chart}.${name} is ${status} to '${chatid}' with HTTP error code ${httpcode}."
             fi
         done
 
@@ -520,10 +561,10 @@ EOF
         httpcode=$(${curl} --write-out %{http_code} --silent --output /dev/null -X POST --data-urlencode "payload=${payload}" "${webhook}")
         if [ "${httpcode}" == "200" ]
         then
-            echo >&2 "${me}: Sent slack notification for: ${host} ${chart}.${name} is ${status} to '${channel}'"
+            info "sent slack notification for: ${host} ${chart}.${name} is ${status} to '${channel}'"
             sent=$((sent + 1))
         else
-            echo >&2 "${me}: Failed to send slack notification for: ${host} ${chart}.${name} is ${status} to '${channel}', with HTTP error code ${httpcode}."
+            error "failed to send slack notification for: ${host} ${chart}.${name} is ${status} to '${channel}', with HTTP error code ${httpcode}."
         fi
     done
 
index 1c6f564b4c820dab31282939b63bb9ddcbd671c5..9bb3bcabbe7a426d0789ae8658adcfe08260b132 100755 (executable)
@@ -1,17 +1,66 @@
 #!/usr/bin/env bash
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# Script to find a better name for cgroups
+#
+
 export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
 export LC_ALL=C
 
+# -----------------------------------------------------------------------------
+
+PROGRAM_NAME="$(basename "${0}")"
+
+logdate() {
+    date "+%Y-%m-%d %H:%M:%S"
+}
+
+log() {
+    local status="${1}"
+    shift
+
+    echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}"
+
+}
+
+warning() {
+    log WARNING "${@}"
+}
+
+error() {
+    log ERROR "${@}"
+}
+
+info() {
+    log INFO "${@}"
+}
+
+fatal() {
+    log FATAL "${@}"
+    exit 1
+}
+
+debug=0
+debug() {
+    [ $debug -eq 1 ] && log DEBUG "${@}"
+}
+
+# -----------------------------------------------------------------------------
+
 NETDATA_CONFIG_DIR="${NETDATA_CONFIG_DIR-/etc/netdata}"
 CONFIG="${NETDATA_CONFIG_DIR}/cgroups-names.conf"
 CGROUP="${1}"
 NAME=
 
+# -----------------------------------------------------------------------------
+
 if [ -z "${CGROUP}" ]
     then
-    echo >&2 "${0}: called without a cgroup name. Nothing to do."
-    exit 1
+    fatal "called without a cgroup name. Nothing to do."
 fi
 
 if [ -f "${CONFIG}" ]
@@ -19,15 +68,15 @@ if [ -f "${CONFIG}" ]
     NAME="$(grep "^${CGROUP} " "${CONFIG}" | sed "s/[[:space:]]\+/ /g" | cut -d ' ' -f 2)"
     if [ -z "${NAME}" ]
         then
-        echo >&2 "${0}: cannot find cgroup '${CGROUP}' in '${CONFIG}'."
+        info "cannot find cgroup '${CGROUP}' in '${CONFIG}'."
     fi
 #else
-#   echo >&2 "${0}: configuration file '${CONFIG}' is not available."
+#   info "configuration file '${CONFIG}' is not available."
 fi
 
 function get_name_classic {
     local DOCKERID="$1"
-    echo >&2 "Running command: docker ps --filter=id=\"${DOCKERID}\" --format=\"{{.Names}}\""
+    info "Running command: docker ps --filter=id=\"${DOCKERID}\" --format=\"{{.Names}}\""
     NAME="$( docker ps --filter=id="${DOCKERID}" --format="{{.Names}}" )"
     return 0
 }
@@ -36,10 +85,10 @@ function get_name_api {
     local DOCKERID="$1"
     if [ ! -S "/var/run/docker.sock" ]
         then
-        echo >&2 "Can't find /var/run/docker.sock"
+        warning "Can't find /var/run/docker.sock"
         return 1
     fi
-    echo >&2 "Running API command: /containers/${DOCKERID}/json"
+    info "Running API command: /containers/${DOCKERID}/json"
     JSON=$(echo -e "GET /containers/${DOCKERID}/json HTTP/1.0\r\n" | nc -U /var/run/docker.sock | egrep '^{.*')
     NAME=$(echo $JSON | jq -r .Name,.Config.Hostname | grep -v null | head -n1 | sed 's|^/||')
     return 0
@@ -62,10 +111,10 @@ if [ -z "${NAME}" ]
             fi
             if [ -z "${NAME}" ]
                 then
-                echo >&2 "Cannot find the name of docker container '${DOCKERID}'"
+                warning "cannot find the name of docker container '${DOCKERID}'"
                 NAME="${DOCKERID:0:12}"
             else
-                echo >&2 "Docker container '${DOCKERID}' is named '${NAME}'"
+                info "docker container '${DOCKERID}' is named '${NAME}'"
             fi
         fi
     fi
@@ -74,5 +123,5 @@ if [ -z "${NAME}" ]
     [ ${#NAME} -gt 100 ] && NAME="${NAME:0:100}"
 fi
 
-echo >&2 "${0}: cgroup '${CGROUP}' is called '${NAME}'"
+info "cgroup '${CGROUP}' is called '${NAME}'"
 echo "${NAME}"
index df9998ecec1ab838caba4c9c743b9b35efe8d46a..00206f95f96fe9844585b51c8d72b89d4e78396a 100755 (executable)
@@ -1,36 +1,95 @@
 #!/usr/bin/env bash
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# charts.d.plugin allows easy development of BASH plugins
+#
+# if you need to run parallel charts.d processes, link this file to a different name
+# in the same directory, with a .plugin suffix and netdata will start both of them,
+# each will have a different config file and modules configuration directory.
+#
+
+export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
+
 PROGRAM_FILE="$0"
 PROGRAM_NAME="$(basename $0)"
 PROGRAM_NAME="${PROGRAM_NAME/.plugin}"
+MODULE_NAME="main"
 
-# if you need to run parallel charts.d processes
-# just link this files with a different name
-# in the same directory, with a .plugin suffix
-# netdata will start multiple of them
-# each will have a different config file
+# -----------------------------------------------------------------------------
+# create temp dir
 
-echo >&2 "$PROGRAM_NAME: started from '$PROGRAM_FILE' with options: $*"
+debug=0
+TMP_DIR=
+chartsd_cleanup() {
+    if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]
+    then
+        [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..."
+        rm -rf "$TMP_DIR"
+    fi
+    exit 0
+}
+trap chartsd_cleanup EXIT
+trap chartsd_cleanup SIGHUP
+trap chartsd_cleanup INT
 
-if [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ]
+if [ $UID = "0" ]
 then
-    echo >&2
-    echo >&2 "$PROGRAM_NAME: ERROR"
-    echo >&2 "BASH version 4 or later is required."
-    echo >&2 "You are running version: ${BASH_VERSION}"
-    echo >&2 "Please upgrade."
-    echo >&2
-    exit 1
+    TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
+else
+    TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
 fi
 
+logdate() {
+    date "+%Y-%m-%d %H:%M:%S"
+}
+
+log() {
+    local status="${1}"
+    shift
+
+    echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${MODULE_NAME}: ${*}"
+
+}
+
+warning() {
+    log WARNING "${@}"
+}
+
+error() {
+    log ERROR "${@}"
+}
+
+info() {
+    log INFO "${@}"
+}
+
+fatal() {
+    log FATAL "${@}"
+    echo "DISABLE"
+    exit 1
+}
+
+debug() {
+    [ $debug -eq 1 ] && log DEBUG "${@}"
+}
+
+# -----------------------------------------------------------------------------
 # check a few commands
+
 require_cmd() {
-    which "$1" >/dev/null
-    if [ $? -ne 0 ]
+    local x=$(which "${1}" 2>/dev/null || command -v "${1}" 2>/dev/null)
+    if [ -z "${x}" -o ! -x "${x}" ]
         then
-        echo >&2 "$PROGRAM_NAME: ERROR: Command '$1' is not found in the system path."
+        warning "command '${1}' is not found in ${PATH}."
+        eval "${1^^}_CMD=\"\""
         return 1
     fi
+
+    eval "${1^^}_CMD=\"${x}\""
     return 0
 }
 
@@ -43,9 +102,17 @@ require_cmd grep || exit 1
 require_cmd egrep || exit 1
 require_cmd mktemp || exit 1
 require_cmd awk || exit 1
+require_cmd timeout || exit 1
+require_cmd curl || exit 1
+
+# -----------------------------------------------------------------------------
+
+[ $(( ${BASH_VERSINFO[0]} )) -lt 4 ] && fatal "BASH version 4 or later is required, but found version: ${BASH_VERSION}. Please upgrade."
+
+info "started from '$PROGRAM_FILE' with options: $*"
 
 # -----------------------------------------------------------------------------
-# insternal defaults
+# internal defaults
 # netdata exposes a few environment variables for us
 
 pluginsd="${NETDATA_PLUGINS_DIR}"
@@ -97,7 +164,6 @@ enable_all_charts="yes"
 # -----------------------------------------------------------------------------
 # parse parameters
 
-debug=0
 check=0
 chart_only=
 while [ ! -z "$1" ]
@@ -143,9 +209,7 @@ do
         continue
     fi
 
-    echo >&2 "Cannot understand parameter $1. Aborting."
-    echo "DISABLE"
-    exit 1
+    fatal "Cannot understand parameter $1. Aborting."
 done
 
 
@@ -173,17 +237,13 @@ mysleep="sleep"
 if [ -f "$myconfig" ]
     then
     . "$myconfig"
-    if [ $? -ne 0 ]
-    then
-        echo >&2 "$PROGRAM_NAME: cannot load $myconfig"
-        echo "DISABLE"
-        exit 1
-    fi
+    [ $? -ne 0 ] && fatal "cannot load $myconfig"
+
     time_divisor=$((time_divisor))
     [ $time_divisor -lt 10 ] && time_divisor=10
     [ $time_divisor -gt 100 ] && time_divisor=100
 else
-    echo >&2 "$PROGRAM_NAME: configuration file '$myconfig' not found. Using defaults."
+    info "configuration file '$myconfig' not found. Using defaults."
 fi
 
 # we check for the timeout command, after we load our
@@ -204,12 +264,7 @@ update_every=$(( update_every + 1 - 1)) # makes sure it is a number
 test $update_every -eq 0 && update_every=1 # if it is zero, make it 1
 
 # check the charts.d directory
-if [ ! -d "$chartsd" ]
-    then
-    echo >&2 "$PROGRAM_NAME: cannot find charts directory '$chartsd'"
-    echo "DISABLE"
-fi
-
+[ ! -d "$chartsd" ] && fatal "cannot find charts directory '$chartsd'"
 
 # -----------------------------------------------------------------------------
 # library functions
@@ -221,6 +276,35 @@ fixid() {
         tr "[A-Z]" "[a-z]"
 }
 
+run() {
+    local ret pid="${BASHPID}" t
+
+    if [ "z${1}" = "z-t" -a "${2}" != "0" ]
+    then
+        t="${2}"
+        shift 2
+        timeout ${t} "${@}" 2>"${TMP_DIR}/run.${pid}"
+        ret=$?
+    else
+        "${@}" 2>"${TMP_DIR}/run.${pid}"
+        ret=$?
+    fi
+
+    if [ ${ret} -ne 0 ]
+    then
+        {
+            printf "$(logdate): ${PROGRAM_NAME}: ${status}: ${MODULE_NAME}: command '"
+            printf "%q " "${@}"
+            printf "' failed:\n --- BEGIN TRACE ---\n"
+            cat "${TMP_DIR}/run.${pid}"
+            printf " --- END TRACE ---\n"
+        } >&2
+    fi
+    rm "${TMP_DIR}/run.${pid}"
+
+    return ${ret}
+}
+
 # convert any floating point number
 # to integer, give a multiplier
 # the result is stored in ${FLOAT2INT_RESULT}
@@ -230,8 +314,6 @@ float2int() {
     local f m="$2" a b l v=($1)
     f=${v[0]}
 
-    # echo >&2 "value='${1}' f='${f}', m='${m}'"
-
     # the length of the multiplier - 1
     l=$(( ${#m} - 1 ))
 
@@ -277,7 +359,6 @@ float2int() {
 
     # store the result
     FLOAT2INT_RESULT=$(( (a * m) + b ))
-    #echo >&2 "FLOAT2INT_RESULT='${FLOAT2INT_RESULT}'"
 }
 
 
@@ -286,7 +367,7 @@ float2int() {
 
 all_charts() {
     cd "$chartsd"
-    [ $? -ne 0 ] && echo >&2 "$PROGRAM_NAME: Cannot cd to $chartsd" && return 1
+    [ $? -ne 0 ] && error "cannot cd to $chartsd" && return 1
 
     ls *.chart.sh | sed "s/\.chart\.sh$//g"
 }
@@ -316,6 +397,8 @@ all_enabled_charts() {
 
     for chart in $( all_charts )
     do
+        MODULE_NAME="${chart}"
+
         eval "enabled=\$$chart"
         if [ -z "${enabled}" ]
             then
@@ -327,35 +410,38 @@ all_enabled_charts() {
 
         if [ ! "${enabled}" = "${required}" ]
         then
-            echo >&2 "$PROGRAM_NAME: '$chart' is NOT enabled. Add a line with $chart=$required in $myconfig to enable it (or remove the line that disables it)."
+            info "is disabled. Add a line with $chart=$required in $myconfig to enable it (or remove the line that disables it)."
         else
-            [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' is enabled."
+            debug "is enabled for auto-detection."
             local charts="$charts $chart"
         fi
     done
+    MODULE_NAME="main"
 
     local charts2=
     for chart in $charts
     do
+        MODULE_NAME="${chart}"
+
         # check the enabled charts
         local check="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()" )"
         if [ -z "$check" ]
         then
-            echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_check() function. Disabling it."
+            error "module '$chart' does not seem to have a $chart$charts_check() function. Disabling it."
             continue
         fi
 
         local create="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()" )"
         if [ -z "$create" ]
         then
-            echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_create() function. Disabling it."
+            error "module '$chart' does not seem to have a $chart$charts_create() function. Disabling it."
             continue
         fi
 
         local update="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()" )"
         if [ -z "$update" ]
         then
-            echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_update() function. Disabling it."
+            error "module '$chart' does not seem to have a $chart$charts_update() function. Disabling it."
             continue
         fi
 
@@ -364,7 +450,7 @@ all_enabled_charts() {
         #then
         #   if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
         #   then
-        #       echo >&2 "$PROGRAM_NAME: chart's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
+        #       error "module's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
         #       continue
         #   fi
         #fi
@@ -374,19 +460,19 @@ all_enabled_charts() {
         #   "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null
         #   if [ $? -ne 0 ]
         #   then
-        #       echo >&2 "$PROGRAM_NAME: chart's $chart did not pass the dry run check. This means it uses global variables not starting with $chart. Disabling it."
+        #       error "module's $chart did not pass the dry run check. This means it uses global variables not starting with $chart. Disabling it."
         #       continue
         #   fi
         #fi
 
         local charts2="$charts2 $chart"
     done
+    MODULE_NAME="main"
 
     echo $charts2
-    [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: enabled charts: $charts2"
+    debug "enabled charts: $charts2"
 }
 
-
 # -----------------------------------------------------------------------------
 # load the charts
 
@@ -394,19 +480,22 @@ suffix_update_every="_update_every"
 active_charts=
 for chart in $( all_enabled_charts )
 do
-    [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart: '$chartsd/$chart.chart.sh'"
+    MODULE_NAME="${chart}"
+
+    debug "loading module: '$chartsd/$chart.chart.sh'"
+
     . "$chartsd/$chart.chart.sh"
 
-    if [ -f "$confd/charts.d/$chart.conf" ]
+    if [ -f "$confd/$PROGRAM_NAME/$chart.conf" ]
     then
-        [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/charts.d/$chart.conf'"
-        . "$confd/charts.d/$chart.conf"
+        debug "loading module configuration: '$confd/$PROGRAM_NAME/$chart.conf'"
+        . "$confd/$PROGRAM_NAME/$chart.conf"
     elif [ -f "$confd/$chart.conf" ]
     then
-        [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/$chart.conf'"
+        debug "loading module configuration: '$confd/$chart.conf'"
         . "$confd/$chart.conf"
     else
-        echo >&2 "$PROGRAM_NAME: $chart: configuration file '$confd/charts.d/$chart.conf' not found. Using defaults."
+        warning "configuration file '$confd/$PROGRAM_NAME/$chart.conf' not found. Using defaults."
     fi
 
     eval "dt=\$$chart$suffix_update_every"
@@ -419,13 +508,14 @@ do
     $chart$charts_check
     if [ $? -eq 0 ]
     then
-        [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' activated"
+        debug "module '$chart' activated"
         active_charts="$active_charts $chart"
     else
-        echo >&2 "$PROGRAM_NAME: chart '$chart' check() function reports failure."
+        error "module's '$chart' check() function reports failure."
     fi
 done
-[ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
+MODULE_NAME="main"
+debug "activated modules: $active_charts"
 
 
 # -----------------------------------------------------------------------------
@@ -438,7 +528,7 @@ test $debug -eq 1 && debug_time=tellwork
 # if we only need a specific chart, remove all the others
 if [ ! -z "${chart_only}" ]
 then
-    [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: requested to run only for: '${chart_only}'"
+    debug "requested to run only for: '${chart_only}'"
     check_charts=
     for chart in $active_charts
     do
@@ -450,41 +540,19 @@ then
     done
     active_charts="$check_charts"
 fi
-[ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
+debug "activated charts: $active_charts"
 
 # stop if we just need a pre-check
 if [ $check -eq 1 ]
 then
-    echo >&2 "CHECK RESULT"
-    echo >&2 "Will run the charts: $active_charts"
+    info "CHECK RESULT"
+    info "Will run the charts: $active_charts"
     exit 0
 fi
 
 # -----------------------------------------------------------------------------
-# create temp dir
-
-TMP_DIR=
-chartsd_cleanup() {
-    cd /tmp
-    if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]
-    then
-        [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..."
-        rm -rf "$TMP_DIR"
-    fi
-    exit 0
-}
-trap chartsd_cleanup EXIT
-trap chartsd_cleanup SIGHUP
-trap chartsd_cleanup INT
 
-if [ $UID = "0" ]
-then
-    TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
-else
-    TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
-fi
-
-cd "$TMP_DIR" || exit 1
+cd "${TMP_DIR}" || exit 1
 
 # -----------------------------------------------------------------------------
 # create charts
@@ -492,28 +560,26 @@ cd "$TMP_DIR" || exit 1
 run_charts=
 for chart in $active_charts
 do
-    [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: Calling '$chart$charts_create()'..."
+    MODULE_NAME="${chart}"
+
+    debug "calling '$chart$charts_create()'..."
     $chart$charts_create
     if [ $? -eq 0 ]
     then
         run_charts="$run_charts $chart"
-        [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' has initialized."
+        debug "'$chart' initialized."
     else
-        echo >&2 "$PROGRAM_NAME: chart '$chart' function '$chart$charts_create()' reports failure."
+        error "module's '$chart' function '$chart$charts_create()' reports failure."
     fi
 done
-[ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: run_charts='$run_charts'"
+MODULE_NAME="main"
+debug "run_charts='$run_charts'"
 
 
 # -----------------------------------------------------------------------------
 # update dimensions
 
-if [ -z "$run_charts" ]
-    then
-    echo >&2 "$PROGRAM_NAME: No charts to collect data from."
-    echo "DISABLE"
-    exit 1
-fi
+[ -z "$run_charts" ] && fatal "No charts to collect data from."
 
 declare -A charts_last_update=() charts_update_every=() charts_next_update=() charts_run_counter=() charts_serial_failures=()
 global_update() {
@@ -552,12 +618,12 @@ global_update() {
 
         for chart in "${now_charts[@]}"
         do
-            #echo >&2 "                              DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}"
+            MODULE_NAME="${chart}"
+
             if [ ${now_ms} -ge ${charts_next_update[$chart]} ]
             then
                 last_ms=${charts_last_update[$chart]}
                 dt=$(( (now_ms - last_ms) ))
-                #echo >&2 "                              DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}, dt: ${dt}"
 
                 charts_last_update[$chart]=${now_ms}
 
@@ -576,7 +642,6 @@ global_update() {
                 fi
 
                 exec_start_ms=$now_ms
-                #echo >&2 "                              EXEC: $chart$charts_update $dt"
                 $chart$charts_update $dt
                 ret=$?
 
@@ -596,9 +661,9 @@ global_update() {
 
                     if [ ${charts_serial_failures[$chart]} -gt 10 ]
                         then
-                        echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reported failure ${charts_serial_failures[$chart]} times. Disabling it."
+                        error "module's '$chart' update() function reported failure ${charts_serial_failures[$chart]} times. Disabling it."
                     else
-                        echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Will keep trying for a while."
+                        error "module's '$chart' update() function reports failure. Will keep trying for a while."
                         next_charts+=($chart)
                     fi
                 fi
@@ -606,6 +671,7 @@ global_update() {
                 next_charts+=($chart)
             fi
         done
+        MODULE_NAME="${chart}"
 
         # wait the time you are required to
         next_ms=$((now_ms + (update_every * 1000 * 100) ))
@@ -625,18 +691,17 @@ global_update() {
                 millis="0${millis}"
             fi
 
-            [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: sleeping for ${seconds}.${millis} seconds."
+            debug "sleeping for ${seconds}.${millis} seconds."
             ${mysleep} ${seconds}.${millis}
         else
-            [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: sleeping for ${update_every} seconds."
+            debug "sleeping for ${update_every} seconds."
             ${mysleep} $update_every
         fi
 
         test ${now_ms} -ge ${exit_at} && exit 0
     done
 
-    echo >&2 "$PROGRAM_NAME: Nothing left to do. Disabling charts.d.plugin."
-    echo "DISABLE"
+    fatal "nothing left to do, exiting..."
 }
 
 global_update
diff --git a/plugins.d/fping.plugin b/plugins.d/fping.plugin
new file mode 100755 (executable)
index 0000000..32e1b71
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# This plugin requires a special version of fping.
+# Get it from https://github.com/ktsaou/fping
+# and build it, like this:
+#
+# cd /usr/src
+# git clone https://github.com/ktsaou/fping.git fping-netdata.git
+# cd fping-netdata.git
+# ./autogen.sh
+# ./configure --prefix=/usr/local
+# make
+# cp src/fping /usr/local/bin/
+# chown root:root /usr/local/bin/fping
+# chmod 4755 /usr/local/bin/fping
+#
+# Then, create /etc/netdata/fping.conf
+# and set the configuration options given below
+
+export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
+export LC_ALL=C
+
+# -----------------------------------------------------------------------------
+
+PROGRAM_NAME="$(basename "${0}")"
+
+logdate() {
+    date "+%Y-%m-%d %H:%M:%S"
+}
+
+log() {
+    local status="${1}"
+    shift
+
+    echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}"
+
+}
+
+warning() {
+    log WARNING "${@}"
+}
+
+error() {
+    log ERROR "${@}"
+}
+
+info() {
+    log INFO "${@}"
+}
+
+fatal() {
+    log FATAL "${@}"
+       echo "DISABLE"
+    exit 1
+}
+
+debug=0
+debug() {
+    [ $debug -eq 1 ] && log DEBUG "${@}"
+}
+
+# -----------------------------------------------------------------------------
+
+# the frequency to send info to netdata
+# passed by netdata as the first parameter
+update_every="${1-1}"
+
+# the netdata configuration directory
+# passed by netdata as an environment variable
+NETDATA_CONFIG_DIR="${NETDATA_CONFIG_DIR-/etc/netdata}"
+
+# -----------------------------------------------------------------------------
+# configuration options
+# can be overwritten at /etc/netdata/fping.conf
+
+# the fping binary to use
+# we need one that can output netdata friendly info
+fping="$(which fping || command -v fping)"
+
+# a space separated list of hosts to fping
+hosts=""
+
+# the time in milliseconds (1 sec = 1000 ms)
+# to ping the hosts - by default 2 pings per iteration
+ping_every="$((update_every * 1000 / 2))"
+
+# how many retries to make if a host does not respond
+retries=1
+
+# -----------------------------------------------------------------------------
+# load the configuration file
+
+if [ ! -f "${NETDATA_CONFIG_DIR}/fping.conf" ]
+then
+       fatal "configuration file '${NETDATA_CONFIG_DIR}/fping.conf' not found - nothing to do."
+fi
+
+source "${NETDATA_CONFIG_DIR}/fping.conf"
+
+if [ -z "${hosts}" ]
+then
+       fatal "no hosts configued in '${NETDATA_CONFIG_DIR}/fping.conf' - nothing to do."
+fi
+
+if [ -z "${fping}" -o ! -x "${fping}" ]
+then
+       fatal "command '${fping}' is not executable - cannot proceed."
+fi
+
+# the fping options we will use
+options=( -N -l -R -Q ${update_every} -p ${ping_every} -r ${retries} ${hosts} )
+
+# execute fping
+exec "${fping}" "${options[@]}"
+
+# if we cannot execute fping, stop
+fatal "command '${fping} ${options[@]}' failed to be executed."
index df96ecb207e05a266ed22a9c04b0e6999e9c1582..8b7047fcbbbfdb5423cb10593a084576fca7e9e2 100755 (executable)
@@ -8,6 +8,11 @@
 // Then, the second line, finds nodejs or node or js in the system path
 // and executes it with the shell parameters.
 
+// netdata
+// real-time performance and health monitoring, done right!
+// (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+// GPL v3+
+
 // --------------------------------------------------------------------------------------------------------------------
 
 'use strict';
index 9caef85f720cfeae0a8fb0bf54ed9efbd31898cc..ffe995f7d316616e1fc963d97078e9afad26f1b7 100755 (executable)
@@ -1,11 +1,59 @@
 #!/usr/bin/env bash
 
+# netdata
+# real-time performance and health monitoring, done right!
+# (C) 2016 Costa Tsaousis <costa@tsaousis.gr>
+# GPL v3+
+#
+# This script is a helper to allow netdata collect tc data
+# parsing tc output has been implemented in C, inside netdata
+# This script allows setting names to dimensions.
+
 export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
+export LC_ALL=C
 
 PROGRAM_FILE="$0"
 PROGRAM_NAME="$(basename $0)"
 PROGRAM_NAME="${PROGRAM_NAME/.plugin}"
 
+# -----------------------------------------------------------------------------
+
+logdate() {
+    date "+%Y-%m-%d %H:%M:%S"
+}
+
+log() {
+    local status="${1}"
+    shift
+
+    echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}"
+
+}
+
+warning() {
+    log WARNING "${@}"
+}
+
+error() {
+    log ERROR "${@}"
+}
+
+info() {
+    log INFO "${@}"
+}
+
+fatal() {
+    log FATAL "${@}"
+    exit 1
+}
+
+debug=0
+debug() {
+    [ $debug -eq 1 ] && log DEBUG "${@}"
+}
+
+# -----------------------------------------------------------------------------
+
 plugins_dir="${NETDATA_PLUGINS_DIR}"
 [ -z "$plugins_dir" ] && plugins_dir="$( dirname $PROGRAM_FILE )"
 
@@ -39,8 +87,7 @@ loopsleepms() {
 
 if [ -z "${tc}" -o ! -x "${tc}" ]
     then
-    echo >&2 "${PROGRAM_NAME}: Cannot find command 'tc' in this system."
-    exit 1
+    fatal "cannot find command 'tc' in this system."
 fi
 
 devices=
index ecefba0a4fa185b38cb4e02b5a1c8b4f07651dc4..203751afdb051d39ab98c5f5b883c3dc772d017f 100644 (file)
@@ -24,7 +24,7 @@ def log_msg(msg_type, *args):
         LOG_COUNTER -= 1
     now = time()
     if LOG_COUNTER >= 0:
-        timestamp = strftime('%y-%m-%d %X')
+        timestamp = strftime('%Y-%m-%d %X')
         msg = "%s: %s %s: %s" % (timestamp, PROGRAM, str(msg_type), " ".join(args))
         WRITE(msg + "\n")
         FLUSH()
@@ -33,7 +33,7 @@ def log_msg(msg_type, *args):
     if NEXT_CHECK <= now:
         NEXT_CHECK = now - (now % LOG_INTERVAL) + LOG_INTERVAL
         if LOG_COUNTER < 0:
-            timestamp = strftime('%y-%m-%d %X')
+            timestamp = strftime('%Y-%m-%d %X')
             msg = "%s: Prevented %s log messages from displaying" % (timestamp, str(0 - LOG_COUNTER))
             WRITE(msg + "\n")
             FLUSH()
index e1925ff5e9ad95c12f1fca49c3c249a67830aa3c..ce348d79479ab1b3dd7564ba5f98733a999648a8 100644 (file)
@@ -224,7 +224,7 @@ int sleep_usec(unsigned long long usec) {
 
     while (nanosleep(&req, &rem) == -1) {
         if (likely(errno == EINTR)) {
-            info("nanosleep() interrupted (while sleeping for %llu microseconds).", usec);
+            debug(D_SYSTEM, "nanosleep() interrupted (while sleeping for %llu microseconds).", usec);
             req.tv_sec = rem.tv_sec;
             req.tv_nsec = rem.tv_nsec;
         } else {
@@ -1121,7 +1121,7 @@ long get_system_cpus(void) {
 
     procfile_close(ff);
 
-    info("System has %d processors.", processors);
+    debug(D_SYSTEM, "System has %d processors.", processors);
     return processors;
 }
 
@@ -1152,7 +1152,7 @@ pid_t get_system_pid_max(void) {
     }
 
     procfile_close(ff);
-    info("System supports %d pids.", pid_max);
+    debug(D_SYSTEM, "System supports %d pids.", pid_max);
     return pid_max;
 }
 
index 9ffa8c8bc4efb7b43de058a0cae064c183f43145..b9721824f75f083f6f4475dd716b13ce7ffbb290 100644 (file)
@@ -64,6 +64,7 @@
 #include <time.h>
 #include <unistd.h>
 #include <uuid/uuid.h>
+#include <mntent.h>
 
 #ifdef STORAGE_WITH_MATH
 #include <math.h>
index 1c34405d826a89902580af25316b532ae9480f86..5bf6a1e0a0fffddcb69c669fce54ea9761c041c7 100644 (file)
@@ -138,7 +138,7 @@ void oom_score_adj(int score) {
     if(!done)
         error("Cannot adjust my Out-Of-Memory score to %d.", score);
     else
-        info("Adjusted my Out-Of-Memory score to %d.", score);
+        debug(D_SYSTEM, "Adjusted my Out-Of-Memory score to %d.", score);
 }
 
 int sched_setscheduler_idle(void) {
@@ -151,7 +151,7 @@ int sched_setscheduler_idle(void) {
     if(i != 0)
         error("Cannot adjust my scheduling priority to IDLE.");
     else
-        info("Adjusted my scheduling priority to IDLE.");
+        debug(D_SYSTEM, "Adjusted my scheduling priority to IDLE.");
 
     return i;
 #else
@@ -214,14 +214,14 @@ int become_daemon(int dont_fork, const char *user)
     // 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.");
+        else debug(D_SYSTEM, "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);
+        else debug(D_SYSTEM, "Successfully became user '%s'.", user);
     }
 
     if(pidfd != -1) {
index 596b143a49d3b7d55fa82e90d0f2a1117a2869f6..53fbcdca9129575e12d5f40783245d81cb5fc1fc 100644 (file)
@@ -6,6 +6,7 @@ struct health_options {
     const char *health_default_exec;
     const char *health_default_recipient;
     const char *log_filename;
+    size_t log_entries_written;
     FILE *log_fp;
 };
 
@@ -13,6 +14,7 @@ static struct health_options health = {
     .health_default_exec = PLUGINS_DIR "/alarm-notify.sh",
     .health_default_recipient = "root",
     .log_filename = VARLIB_DIR "/health/alarm_log.db",
+    .log_entries_written = 0,
     .log_fp = NULL
 };
 
@@ -30,11 +32,11 @@ static inline int health_alarm_log_open(void) {
 
     if(health.log_fp) {
         if (setvbuf(health.log_fp, NULL, _IOLBF, 0) != 0)
-            error("Cannot set line buffering on health log file.");
+            error("Health: cannot set line buffering on health log file.");
         return 0;
     }
 
-    error("Cannot open health log file '%s'. Health data will be lost in case of netdata or server crash.", health.log_filename);
+    error("Health: cannot open health log file '%s'. Health data will be lost in case of netdata or server crash.", health.log_filename);
     return -1;
 }
 
@@ -45,51 +47,310 @@ static inline void health_alarm_log_close(void) {
     }
 }
 
-static inline void health_log_recreate(void) {
-    if(health.log_fp != NULL) {
+static inline void health_log_rotate(void) {
+    static size_t rotate_every = 0;
+
+    if(unlikely(rotate_every == 0)) {
+        rotate_every = (size_t)config_get_number("health", "rotate log every lines", 2000);
+        if(rotate_every < 100) rotate_every = 100;
+    }
+
+    if(unlikely(health.log_entries_written > rotate_every)) {
         health_alarm_log_close();
 
+        char old_filename[FILENAME_MAX + 1];
+        snprintfz(old_filename, FILENAME_MAX, "%s.old", health.log_filename);
+
+        if(unlink(old_filename) == -1 && errno != ENOENT)
+            error("Health: cannot remove old alarms log file '%s'", old_filename);
+
+        if(link(health.log_filename, old_filename) == -1 && errno != ENOENT)
+            error("Health: cannot move file '%s' to '%s'.", health.log_filename, old_filename);
+
+        if(unlink(health.log_filename) == -1 && errno != ENOENT)
+            error("Health: cannot remove old alarms log file '%s'", health.log_filename);
+
         // open it with truncate
         health.log_fp = fopen(health.log_filename, "w");
-        if(health.log_fp) fclose(health.log_fp);
-        else error("Cannot truncate health log '%s'", health.log_filename);
+
+        if(health.log_fp)
+            fclose(health.log_fp);
+        else
+            error("Health: cannot truncate health log '%s'", health.log_filename);
 
         health.log_fp = NULL;
 
+        health.log_entries_written = 0;
         health_alarm_log_open();
     }
 }
 
 static inline void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae) {
-    (void)host;
-    (void)ae;
-    
-/*    if(likely(health.log_fp)) {
-        if(unlikely(fprintf(health.log_fp, "A\t%s\t%08x\t%08x\t%08x\t%08x\t%08x\t%08x\t%s\t%s\t%s\t%s\t%s\t%08x\n",
-            host->hostname,
-            ae->unique_id,
-            ae->alarm_id,
-            ae->alarm_event_id,
-            (uint32_t)ae->when,
-            (uint32_t)ae->duration,
-            (uint32_t)ae->non_clear_duration,
-            (uint32_t)ae->exec_run_timestamp,
-            ae->name,
-            ae->chart,
-            ae->family,
-            ae->exec,
-            ae->recipient
-            ) < 0))
+    health_log_rotate();
+
+    if(likely(health.log_fp)) {
+        if(unlikely(fprintf(health.log_fp
+                , "%c\t%s"
+                  "\t%08x\t%08x\t%08x\t%08x\t%08x"
+                  "\t%08x\t%08x\t%08x"
+                  "\t%08x\t%08x\t%08x"
+                  "\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s"
+                  "\t%d\t%d\t%d\t%d"
+                  "\t%Lf\t%Lf"
+                  "\n"
+                , (ae->flags & HEALTH_ENTRY_FLAG_SAVED)?'U':'A'
+                , host->hostname
+
+                , ae->unique_id
+                , ae->alarm_id
+                , ae->alarm_event_id
+                , ae->updated_by_id
+                , ae->updates_id
+
+                , (uint32_t)ae->when
+                , (uint32_t)ae->duration
+                , (uint32_t)ae->non_clear_duration
+                , (uint32_t)ae->flags
+                , (uint32_t)ae->exec_run_timestamp
+                , (uint32_t)ae->delay_up_to_timestamp
+
+                , (ae->name)?ae->name:""
+                , (ae->chart)?ae->chart:""
+                , (ae->family)?ae->family:""
+                , (ae->exec)?ae->exec:""
+                , (ae->recipient)?ae->recipient:""
+                , (ae->source)?ae->source:""
+                , (ae->units)?ae->units:""
+                , (ae->info)?ae->info:""
+
+                , ae->exec_code
+                , ae->new_status
+                , ae->old_status
+                , ae->delay
+
+                , (long double)ae->new_value
+                , (long double)ae->old_value
+        ) < 0))
             error("Health: failed to save alarm log entry. Health data may be lost in case of abnormal restart.");
+        else {
+            ae->flags |= HEALTH_ENTRY_FLAG_SAVED;
+            health.log_entries_written++;
+        }
     }
-*/
+}
+
+static inline ssize_t health_alarm_log_read(RRDHOST *host, FILE *fp, const char *filename) {
+    static uint32_t max_unique_id = 0, max_alarm_id = 0;
+    ssize_t loaded = -1, updated = -1, errored = -1, duplicate = -1;
+
+    errno = 0;
+
+    char *s, *buf = mallocz(65536 + 1);
+    size_t line = 0, len = 0;
+    loaded = updated = errored = duplicate = 0;
+
+    pthread_rwlock_rdlock(&host->health_log.alarm_log_rwlock);
+
+    while((s = fgets_trim_len(buf, 65536, fp, &len))) {
+        health.log_entries_written++;
+        line++;
+
+        int max_entries = 30, entries = 0;
+        char *pointers[max_entries];
+
+        pointers[entries++] = s++;
+        while(*s) {
+            if(unlikely(*s == '\t')) {
+                *s = '\0';
+                pointers[entries++] = ++s;
+                if(entries >= max_entries) {
+                    error("Health: line %zu of file '%s' has more than %d entries. Ignoring excessive entries.", line, filename, max_entries);
+                    break;
+                }
+            }
+            else s++;
+        }
+
+        if(likely(*pointers[0] == 'U' || *pointers[0] == 'A')) {
+            ALARM_ENTRY *ae = NULL;
+
+            if(entries < 26) {
+                error("Health: line %zu of file '%s' should have at least 26 entries, but it has %d. Ignoring it.", line, filename, entries);
+                errored++;
+                continue;
+            }
+
+            // check that we have valid ids
+            uint32_t unique_id = (uint32_t)strtoul(pointers[2], NULL, 16);
+            if(!unique_id) {
+                error("Health: line %zu of file '%s' states alarm entry with invalid unique id %u (%s). Ignoring it.", line, filename, unique_id, pointers[2]);
+                errored++;
+                continue;
+            }
+
+            uint32_t alarm_id = (uint32_t)strtoul(pointers[3], NULL, 16);
+            if(!alarm_id) {
+                error("Health: line %zu of file '%s' states alarm entry for invalid alarm id %u (%s). Ignoring it.", line, filename, alarm_id, pointers[3]);
+                errored++;
+                continue;
+            }
+
+            if(unlikely(*pointers[0] == 'A')) {
+                // make sure it is properly numbered
+                if(unlikely(host->health_log.alarms && unique_id < host->health_log.alarms->unique_id)) {
+                    error("Health: line %zu of file '%s' has alarm log entry with %u in wrong order. Ignoring it.", line, filename, unique_id);
+                    errored++;
+                    continue;
+                }
+
+                ae = callocz(1, sizeof(ALARM_ENTRY));
+            }
+            else if(unlikely(*pointers[0] == 'U')) {
+                // find the original
+                for(ae = host->health_log.alarms; ae; ae = ae->next) {
+                    if(unlikely(unique_id == ae->unique_id)) {
+                        if(unlikely(*pointers[0] == 'A')) {
+                            error("Health: line %zu of file '%s' adds duplicate alarm log entry with unique id %u. Using the later."
+                                  , line, filename, unique_id);
+                            *pointers[0] = 'U';
+                            duplicate++;
+                        }
+                        break;
+                    }
+                    else if(unlikely(unique_id > ae->unique_id)) {
+                        // no need to continue
+                        // the linked list is sorted
+                        ae = NULL;
+                        break;
+                    }
+                }
+
+                // if not found, skip this line
+                if(!ae) {
+                    // error("Health: line %zu of file '%s' updates alarm log entry with unique id %u, but it is not found.", line, filename, unique_id);
+                    continue;
+                }
+            }
+
+            // check for a possible host missmatch
+            //if(strcmp(pointers[1], host->hostname))
+            //    error("Health: line %zu of file '%s' provides an alarm for host '%s' but this is named '%s'.", line, filename, pointers[1], host->hostname);
+
+            ae->unique_id               = unique_id;
+            ae->alarm_id                = alarm_id;
+            ae->alarm_event_id          = (uint32_t)strtoul(pointers[4], NULL, 16);
+            ae->updated_by_id           = (uint32_t)strtoul(pointers[5], NULL, 16);
+            ae->updates_id              = (uint32_t)strtoul(pointers[6], NULL, 16);
+
+            ae->when                    = (uint32_t)strtoul(pointers[7], NULL, 16);
+            ae->duration                = (uint32_t)strtoul(pointers[8], NULL, 16);
+            ae->non_clear_duration      = (uint32_t)strtoul(pointers[9], NULL, 16);
+
+            ae->flags                   = (uint32_t)strtoul(pointers[10], NULL, 16);
+            ae->flags |= HEALTH_ENTRY_FLAG_SAVED;
+
+            ae->exec_run_timestamp      = (uint32_t)strtoul(pointers[11], NULL, 16);
+            ae->delay_up_to_timestamp   = (uint32_t)strtoul(pointers[12], NULL, 16);
+
+            if(unlikely(ae->name)) freez(ae->name);
+            ae->name = strdupz(pointers[13]);
+            ae->hash_name = simple_hash(ae->name);
+
+            if(unlikely(ae->chart)) freez(ae->chart);
+            ae->chart = strdupz(pointers[14]);
+            ae->hash_chart = simple_hash(ae->chart);
+
+            if(unlikely(ae->family)) freez(ae->family);
+            ae->family = strdupz(pointers[15]);
+
+            if(unlikely(ae->exec)) freez(ae->exec);
+            ae->exec = strdupz(pointers[16]);
+            if(!*ae->exec) { freez(ae->exec); ae->exec = NULL; }
+
+            if(unlikely(ae->recipient)) freez(ae->recipient);
+            ae->recipient = strdupz(pointers[17]);
+            if(!*ae->recipient) { freez(ae->recipient); ae->recipient = NULL; }
+
+            if(unlikely(ae->source)) freez(ae->source);
+            ae->source = strdupz(pointers[18]);
+            if(!*ae->source) { freez(ae->source); ae->source = NULL; }
+
+            if(unlikely(ae->units)) freez(ae->units);
+            ae->units = strdupz(pointers[19]);
+            if(!*ae->units) { freez(ae->units); ae->units = NULL; }
+
+            if(unlikely(ae->info)) freez(ae->info);
+            ae->info = strdupz(pointers[20]);
+            if(!*ae->info) { freez(ae->info); ae->info = NULL; }
+
+            ae->exec_code   = atoi(pointers[21]);
+            ae->new_status  = atoi(pointers[22]);
+            ae->old_status  = atoi(pointers[23]);
+            ae->delay       = atoi(pointers[24]);
+
+            ae->new_value   = strtold(pointers[25], NULL);
+            ae->old_value   = strtold(pointers[26], NULL);
+
+            // add it to host if not already there
+            if(unlikely(*pointers[0] == 'A')) {
+                ae->next = host->health_log.alarms;
+                host->health_log.alarms = ae;
+                loaded++;
+            }
+            else updated++;
+
+            if(unlikely(ae->unique_id > max_unique_id))
+                max_unique_id = ae->unique_id;
+
+            if(unlikely(ae->alarm_id >= max_alarm_id))
+                max_alarm_id = ae->alarm_id;
+        }
+        else {
+            error("Health: line %zu of file '%s' is invalid (unrecognized entry type '%s').", line, filename, pointers[0]);
+            errored++;
+        }
+    }
+
+    pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock);
+
+    freez(buf);
+
+    if(!max_unique_id) max_unique_id = (uint32_t)time(NULL);
+    if(!max_alarm_id)  max_alarm_id  = (uint32_t)time(NULL);
+
+    host->health_log.next_log_id = max_unique_id + 1;
+    host->health_log.next_alarm_id = max_alarm_id + 1;
+
+    debug(D_HEALTH, "Health: loaded file '%s' with %zd new alarm entries, updated %zd alarms, errors %zd entries, duplicate %zd", filename, loaded, updated, errored, duplicate);
+    return loaded;
 }
 
 static inline void health_alarm_log_load(RRDHOST *host) {
-    (void)host;
+    health_alarm_log_close();
 
+    char filename[FILENAME_MAX + 1];
+    snprintfz(filename, FILENAME_MAX, "%s.old", health.log_filename);
+    FILE *fp = fopen(filename, "r");
+    if(!fp)
+        error("Health: cannot open health file: %s", filename);
+    else {
+        health_alarm_log_read(host, fp, filename);
+        fclose(fp);
+    }
+
+    health.log_entries_written = 0;
+    fp = fopen(health.log_filename, "r");
+    if(!fp)
+        error("Health: cannot open health file: %s", health.log_filename);
+    else {
+        health_alarm_log_read(host, fp, health.log_filename);
+        fclose(fp);
+    }
+
+    health_alarm_log_open();
 }
 
+
 // ----------------------------------------------------------------------------
 // health alarm log management
 
@@ -152,8 +413,8 @@ static inline void health_alarm_log(RRDHOST *host,
     ALARM_ENTRY *t;
     for(t = host->health_log.alarms ; t ; t = t->next) {
         if(t != ae && t->alarm_id == ae->alarm_id) {
-            if(!(t->notifications & HEALTH_ENTRY_NOTIFICATIONS_UPDATED) && !t->updated_by_id) {
-                t->notifications |= HEALTH_ENTRY_NOTIFICATIONS_UPDATED;
+            if(!(t->flags & HEALTH_ENTRY_FLAG_UPDATED) && !t->updated_by_id) {
+                t->flags |= HEALTH_ENTRY_FLAG_UPDATED;
                 t->updated_by_id = ae->unique_id;
                 ae->updates_id = t->unique_id;
 
@@ -163,10 +424,9 @@ static inline void health_alarm_log(RRDHOST *host,
 
                 health_alarm_log_save(host, t);
             }
-            else {
-                // no need to continue
-                break;
-            }
+
+            // no need to continue
+            break;
         }
     }
     pthread_rwlock_unlock(&host->health_log.alarm_log_rwlock);
@@ -1353,6 +1613,16 @@ static inline int health_parse_db_lookup(
     return 1;
 }
 
+static inline char *tabs2spaces(char *s) {
+    char *t = s;
+    while(*t) {
+        if(unlikely(*t == '\t')) *t = ' ';
+        t++;
+    }
+
+    return s;
+}
+
 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);
@@ -1405,10 +1675,8 @@ int health_readfile(const char *path, const char *filename) {
     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] == '\\') {
@@ -1445,7 +1713,6 @@ int health_readfile(const char *path, const char *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)) {
@@ -1460,7 +1727,7 @@ int health_readfile(const char *path, const char *filename) {
 
             rc = callocz(1, sizeof(RRDCALC));
             rc->next_event_id = 1;
-            rc->name = strdupz(value);
+            rc->name = tabs2spaces(strdupz(value));
             rc->hash = simple_hash(rc->name);
             rc->source = health_source_file(line, path, filename);
             rc->green = NAN;
@@ -1483,7 +1750,7 @@ int health_readfile(const char *path, const char *filename) {
                 rrdcalctemplate_free(&localhost, rt);
 
             rt = callocz(1, sizeof(RRDCALCTEMPLATE));
-            rt->name = strdupz(value);
+            rt->name = tabs2spaces(strdupz(value));
             rt->hash_name = simple_hash(rt->name);
             rt->source = health_source_file(line, path, filename);
             rt->green = NAN;
@@ -1497,12 +1764,12 @@ int health_readfile(const char *path, const char *filename) {
             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').",
+                        error("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->chart = tabs2spaces(strdupz(value));
                 rc->hash_chart = simple_hash(rc->chart);
             }
             else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
@@ -1512,14 +1779,14 @@ int health_readfile(const char *path, const char *filename) {
             }
             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'.",
+                    error("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'.",
+                    error("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);
                 }
             }
@@ -1527,7 +1794,7 @@ int health_readfile(const char *path, const char *filename) {
                 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'.",
+                    error("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);
                 }
             }
@@ -1561,43 +1828,43 @@ int health_readfile(const char *path, const char *filename) {
             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').",
+                        error("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);
+                rc->exec = tabs2spaces(strdupz(value));
             }
             else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
                 if(rc->recipient) {
                     if(strcmp(rc->recipient, 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').",
+                        error("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->recipient, value, value);
 
                     freez(rc->recipient);
                 }
-                rc->recipient = strdupz(value);
+                rc->recipient = tabs2spaces(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').",
+                        error("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);
+                rc->units = tabs2spaces(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').",
+                        error("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);
+                rc->info = tabs2spaces(strdupz(value));
                 strip_quotes(rc->info);
             }
             else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
@@ -1612,12 +1879,12 @@ int health_readfile(const char *path, const char *filename) {
             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').",
+                        error("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->context = tabs2spaces(strdupz(value));
                 rt->hash_context = simple_hash(rt->context);
             }
             else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
@@ -1627,14 +1894,14 @@ int health_readfile(const char *path, const char *filename) {
             }
             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'.",
+                    error("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'.",
+                    error("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);
                 }
             }
@@ -1642,7 +1909,7 @@ int health_readfile(const char *path, const char *filename) {
                 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'.",
+                    error("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);
                 }
             }
@@ -1676,43 +1943,43 @@ int health_readfile(const char *path, const char *filename) {
             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').",
+                        error("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);
+                rt->exec = tabs2spaces(strdupz(value));
             }
             else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
                 if(rt->recipient) {
                     if(strcmp(rt->recipient, 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').",
+                        error("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->recipient, value, value);
 
                     freez(rt->recipient);
                 }
-                rt->recipient = strdupz(value);
+                rt->recipient = tabs2spaces(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').",
+                        error("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);
+                rt->units = tabs2spaces(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').",
+                        error("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);
+                rt->info = tabs2spaces(strdupz(value));
                 strip_quotes(rt->info);
             }
             else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
@@ -1798,7 +2065,16 @@ void health_init(void) {
         return;
     }
 
+    char *pathname = config_get("health", "health db directory", VARLIB_DIR "/health");
+    if(mkdir(pathname, 0770) == -1 && errno != EEXIST)
+        fatal("Cannot create directory '%s'.", pathname);
+
+    char filename[FILENAME_MAX + 1];
+    snprintfz(filename, FILENAME_MAX, "%s/health-log.db", pathname);
+    health.log_filename = config_get("health", "health db file", filename);
+
     health_alarm_log_load(&localhost);
+    health_alarm_log_open();
 
     char *path = health_config_dir();
 
@@ -1865,10 +2141,10 @@ static inline void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae, R
                    ae->name,
                    ae->chart,
                    ae->family,
-                   (ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_PROCESSED)?"true":"false",
-                   (ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_UPDATED)?"true":"false",
+                   (ae->flags & HEALTH_ENTRY_FLAG_PROCESSED)?"true":"false",
+                   (ae->flags & HEALTH_ENTRY_FLAG_UPDATED)?"true":"false",
                    (unsigned long)ae->exec_run_timestamp,
-                   (ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_EXEC_FAILED)?"true":"false",
+                   (ae->flags & HEALTH_ENTRY_FLAG_EXEC_FAILED)?"true":"false",
                    ae->exec?ae->exec:health.health_default_exec,
                    ae->recipient?ae->recipient:health.health_default_recipient,
                    ae->exec_code,
@@ -2085,7 +2361,7 @@ void health_reload(void) {
     ALARM_ENTRY *t;
     for(t = localhost.health_log.alarms ; t ; t = t->next) {
         if(t->new_status != RRDCALC_STATUS_REMOVED)
-            t->notifications |= HEALTH_ENTRY_NOTIFICATIONS_UPDATED;
+            t->flags |= HEALTH_ENTRY_FLAG_UPDATED;
     }
 
     // reset all thresholds to all charts
@@ -2121,25 +2397,36 @@ static inline int rrdcalc_value2status(calculated_number n) {
 }
 
 static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
-    ae->notifications |= HEALTH_ENTRY_NOTIFICATIONS_PROCESSED;
+    ae->flags |= HEALTH_ENTRY_FLAG_PROCESSED;
+
+    if(unlikely(ae->new_status < RRDCALC_STATUS_CLEAR)) {
+        // do not send notifications for internal statuses
+        goto done;
+    }
 
     // find the previous notification for the same alarm
+    // which we have run the exec script
     ALARM_ENTRY *t;
     for(t = ae->next; t ;t = t->next) {
-        if(t->alarm_id == ae->alarm_id && t->notifications & HEALTH_ENTRY_NOTIFICATIONS_EXEC_RUN)
+        if(t->alarm_id == ae->alarm_id && t->flags & HEALTH_ENTRY_FLAG_EXEC_RUN)
             break;
     }
 
-    if(t && t->new_status == ae->new_status) {
-        // don't send the same notification again
-        info("Health not sending again notification for alarm '%s.%s' status %s", ae->chart, ae->name, rrdcalc_status2string(ae->new_status));
-        goto done;
+    if(likely(t)) {
+        // we have executed this alarm notification in the past
+        if (t && t->new_status == ae->new_status) {
+            // don't send the same notification again
+            debug(D_HEALTH, "Health not sending again notification for alarm '%s.%s' status %s", ae->chart, ae->name,
+                 rrdcalc_status2string(ae->new_status));
+            goto done;
+        }
     }
-
-    if((ae->old_status == RRDCALC_STATUS_UNDEFINED && ae->new_status == RRDCALC_STATUS_UNINITIALIZED)
-        || (ae->old_status == RRDCALC_STATUS_UNINITIALIZED && ae->new_status == RRDCALC_STATUS_CLEAR)) {
-        info("Health not sending notification for first initialization of alarm '%s.%s' status %s", ae->chart, ae->name, rrdcalc_status2string(ae->new_status));
-        goto done;
+    else {
+        // we have not executed this alarm notification in the past
+        if(unlikely(ae->old_status == RRDCALC_STATUS_UNINITIALIZED && ae->new_status == RRDCALC_STATUS_CLEAR)) {
+            debug(D_HEALTH, "Health not sending notification for first initialization of alarm '%s.%s' status %s", ae->chart, ae->name, rrdcalc_status2string(ae->new_status));
+            goto done;
+        }
     }
 
     char buffer[FILENAME_MAX + 1];
@@ -2173,7 +2460,7 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
               ae->info?ae->info:""
     );
 
-    ae->notifications |= HEALTH_ENTRY_NOTIFICATIONS_EXEC_RUN;
+    ae->flags |= HEALTH_ENTRY_FLAG_EXEC_RUN;
     ae->exec_run_timestamp = time(NULL);
 
     debug(D_HEALTH, "executing command '%s'", buffer);
@@ -2189,7 +2476,7 @@ static inline void health_alarm_execute(RRDHOST *host, ALARM_ENTRY *ae) {
     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;
+        ae->flags |= HEALTH_ENTRY_FLAG_EXEC_FAILED;
 
 done:
     health_alarm_log_save(host, ae);
@@ -2197,7 +2484,7 @@ done:
 }
 
 static inline void health_process_notifications(RRDHOST *host, ALARM_ENTRY *ae) {
-    info("Health alarm '%s.%s' = %0.2Lf - changed status from %s to %s",
+    debug(D_HEALTH, "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),
@@ -2217,8 +2504,8 @@ static inline void health_alarm_log_process(RRDHOST *host) {
     ALARM_ENTRY *ae;
     for(ae = host->health_log.alarms; ae && ae->unique_id >= stop_at_id ; ae = ae->next) {
         if(unlikely(
-            !(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_PROCESSED) &&
-            !(ae->notifications & HEALTH_ENTRY_NOTIFICATIONS_UPDATED)
+            !(ae->flags & HEALTH_ENTRY_FLAG_PROCESSED) &&
+            !(ae->flags & HEALTH_ENTRY_FLAG_UPDATED)
             )) {
 
             if(unlikely(ae->unique_id < first_waiting))
index 9d5834fcafefb8f69c178415e52651d7b79664ce..cebea49ba9b6ed7e9bfd1f4e5340fc843195cb1a 100644 (file)
@@ -264,10 +264,11 @@ typedef struct 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
+#define HEALTH_ENTRY_FLAG_PROCESSED    0x00000001
+#define HEALTH_ENTRY_FLAG_UPDATED      0x00000002
+#define HEALTH_ENTRY_FLAG_EXEC_RUN     0x00000004
+#define HEALTH_ENTRY_FLAG_EXEC_FAILED  0x00000008
+#define HEALTH_ENTRY_FLAG_SAVED        0x10000000
 
 typedef struct alarm_entry {
     uint32_t unique_id;
@@ -300,7 +301,7 @@ typedef struct alarm_entry {
     int old_status;
     int new_status;
 
-    uint32_t notifications;
+    uint32_t flags;
 
     int delay;
     time_t delay_up_to_timestamp;
index b7ec64bcb9463eb31b2eab07c37685c7f6a6d5ee..86716987aeba7eab4dbab471732d5dcafa4a9da3 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -200,7 +200,7 @@ int error_log_limit(int reset) {
 
 void log_date(FILE *out)
 {
-        char outstr[24];
+        char outstr[26];
         time_t t;
         struct tm *tmp, tmbuf;
 
@@ -208,7 +208,7 @@ void log_date(FILE *out)
         tmp = localtime_r(&t, &tmbuf);
 
         if (tmp == NULL) return;
-        if (unlikely(strftime(outstr, sizeof(outstr), "%y-%m-%d %H:%M:%S", tmp) == 0)) return;
+        if (unlikely(strftime(outstr, sizeof(outstr), "%Y-%m-%d %H:%M:%S", tmp) == 0)) return;
 
         fprintf(out, "%s: ", outstr);
 }
@@ -222,7 +222,7 @@ void debug_int( const char *file, const char *function, const unsigned long line
 
     log_date(stdout);
     va_start( args, fmt );
-    printf("DEBUG (%04lu@%-10.10s:%-15.15s): %s: ", line, file, function, program_name);
+    printf("%s: DEBUG (%04lu@%-10.10s:%-15.15s): ", program_name, line, file, function);
     vprintf(fmt, args);
     va_end( args );
     putchar('\n');
@@ -249,8 +249,8 @@ void info_int( const char *file, const char *function, const unsigned long line,
     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);
+    if(debug_flags) fprintf(stderr, "%s: INFO: (%04lu@%-10.10s:%-15.15s):", program_name, line, file, function);
+    else            fprintf(stderr, "%s: INFO: ", program_name);
     vfprintf( stderr, fmt, args );
     va_end( args );
 
@@ -298,8 +298,8 @@ void error_int( const char *prefix, const char *file, const char *function, cons
     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);
+    if(debug_flags) fprintf(stderr, "%s: %s: (%04lu@%-10.10s:%-15.15s): ", program_name, prefix, line, file, function);
+    else            fprintf(stderr, "%s: %s: ", program_name, prefix);
     vfprintf( stderr, fmt, args );
     va_end( args );
 
@@ -325,8 +325,8 @@ void fatal_int( const char *file, const char *function, const unsigned long line
     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);
+    if(debug_flags) fprintf(stderr, "%s: FATAL: (%04lu@%-10.10s:%-15.15s): ", program_name, line, file, function);
+    else            fprintf(stderr, "%s: FATAL: ", program_name);
     vfprintf( stderr, fmt, args );
     va_end( args );
 
index 3a022fc93959cba965940bb66b21c9ad6943fac5..b7323fcf47bd5e73ebe3542e32ddb378ff10f536 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -25,6 +25,7 @@
 #define D_REGISTRY          0x00200000
 #define D_VARIABLES         0x00400000
 #define D_HEALTH            0x00800000
+#define D_SYSTEM            0x80000000
 
 //#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS)
 //#define DEBUG 0xffffffff
index 3e6aa5046845b29fbf552c01c2364829c060db24..1a81993873f9092590cc2c38ba6b73f8182d5a4c 100644 (file)
@@ -7,7 +7,7 @@ void netdata_cleanup_and_exit(int ret) {
 
     error_log_limit_unlimited();
 
-    info("Called: netdata_cleanup_and_exit()");
+    debug(D_EXIT, "Called: netdata_cleanup_and_exit()");
 #ifdef NETDATA_INTERNAL_CHECKS
     rrdset_free_all();
 #else
@@ -169,7 +169,7 @@ void kill_childs()
     }
 
     if(tc_child_pid) {
-        info("Killing tc-qos-helper procees");
+        debug(D_EXIT, "Killing tc-qos-helper procees");
         if(killpid(tc_child_pid, SIGTERM) != -1)
             waitid(P_PID, (id_t) tc_child_pid, &info, WEXITED);
     }
@@ -460,7 +460,8 @@ int main(int argc, char **argv)
         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...");
+                error("Cannot request unlimited core dumps for debugging... Proceeding anyway...");
+
             prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
         }
 
@@ -520,7 +521,7 @@ int main(int argc, char **argv)
 
         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);
+            error("Invalid history entries %d given. Defaulting to %d.", rrd_default_history_entries, RRD_DEFAULT_HISTORY_ENTRIES);
             rrd_default_history_entries = RRD_DEFAULT_HISTORY_ENTRIES;
         }
         else {
@@ -531,7 +532,7 @@ int main(int argc, char **argv)
 
         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);
+            error("Invalid data collection frequency (update every) %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);
@@ -635,7 +636,7 @@ int main(int argc, char **argv)
     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...");
+            error("Cannot request unlimited core dumps for debugging... Proceeding anyway...");
         prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
     }
 #endif /* NETDATA_INTERNAL_CHECKS */
@@ -655,7 +656,7 @@ int main(int argc, char **argv)
         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);
+            debug(D_SYSTEM, "Successfully set pthread stacksize to %zu bytes", wanted_stacksize);
     }
 
     // ------------------------------------------------------------------------
@@ -692,7 +693,7 @@ int main(int argc, char **argv)
         if(st->enabled) {
             st->thread = mallocz(sizeof(pthread_t));
 
-            info("Starting thread %s.", st->name);
+            debug(D_SYSTEM, "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);
@@ -700,7 +701,7 @@ int main(int argc, char **argv)
             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 debug(D_SYSTEM, "Not starting thread %s.", st->name);
     }
 
     // ------------------------------------------------------------------------
@@ -716,7 +717,7 @@ int main(int argc, char **argv)
     while(1) {
         pause();
         if(netdata_exit) {
-            info("Exit main loop of netdata.");
+            debug(D_EXIT, "Exit main loop of netdata.");
             netdata_cleanup_and_exit(0);
             exit(0);
         }
index ad8d7596f5ea9383ea3584e9f9f57e71ab101fa8..8448b7311d18049b185f686b2b92a2abb00fa527 100644 (file)
@@ -138,7 +138,7 @@ FILE *mypopen(const char *command, pid_t *pidptr)
             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());
+    debug(D_CHILDS, "executing command: '%s' on pid %d.", command, getpid());
     execl("/bin/sh", "sh", "-c", command, NULL);
     exit(1);
 }
index 459d5a133b29fd2e5024e20b7b37ab92b1c6f2df..6a5bf0a35cf969b153b3c073e5263debf0ec5857 100644 (file)
@@ -6,13 +6,19 @@
 #define DISK_TYPE_PARTITION 2
 #define DISK_TYPE_CONTAINER 3
 
+#ifndef NETDATA_RELOAD_MOUNTINFO_EVERY
+#define NETDATA_RELOAD_MOUNTINFO_EVERY 10
+#endif
+
 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;
+    uint32_t mount_point_hash;
 
     // disk options caching
     int configured;
@@ -31,6 +37,152 @@ static struct disk {
 
 static struct mountinfo *disk_mountinfo_root = NULL;
 
+static inline void mountinfo_reload(int force) {
+    static time_t last_loaded = 0;
+    time_t now = time(NULL);
+
+    if(force || now - last_loaded >= NETDATA_RELOAD_MOUNTINFO_EVERY) {
+//#ifdef NETDATA_INTERNAL_CHECKS
+//        info("Reloading mountinfo");
+//#endif
+
+        // 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();
+
+        last_loaded = now;
+    }
+}
+
+static inline void do_disk_space_stats(struct disk *d, const char *mount_point, const char *mount_source, const char *disk, const char *family, int update_every, unsigned long long dt) {
+    int do_space, do_inodes;
+
+    if(d) {
+        // verify we collected the metrics for the right disk.
+        // if not the mountpoint has changed.
+
+        struct stat buff_stat;
+        if(stat(mount_point, &buff_stat) == -1) {
+            error("Failed to stat() for '%s' (disk '%s')", mount_point, disk);
+            return;
+        }
+        else if(major(buff_stat.st_dev) != d->major || minor(buff_stat.st_dev) != d->minor) {
+            error("Disk '%s' (disk '%s') switched major:minor", mount_point, disk);
+            freez(d->mount_point);
+            d->mount_point = NULL;
+            d->mount_point_hash = 0;
+            return;
+        }
+
+        do_space = d->do_space;
+        do_inodes = d->do_inodes;
+    }
+    else {
+        char var_name[4096 + 1];
+        snprintfz(var_name, 4096, "plugin:proc:/proc/diskstats:%s", mount_point);
+
+        int def_space = CONFIG_ONDEMAND_ONDEMAND;
+
+        if(unlikely(strncmp(mount_point, "/run/user/", 10) == 0))
+            def_space = CONFIG_ONDEMAND_NO;
+
+        // 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;
+
+        do_space = config_get_boolean_ondemand(var_name, "space usage", ddo_space);
+        do_inodes = config_get_boolean_ondemand(var_name, "inodes usage", ddo_inodes);
+    }
+
+    if(do_space == CONFIG_ONDEMAND_NO && do_inodes == CONFIG_ONDEMAND_NO)
+        return;
+
+    struct statvfs buff_statvfs;
+    if (statvfs(mount_point, &buff_statvfs) < 0) {
+        error("Failed statvfs() for '%s' (disk '%s')", mount_point, disk);
+        return;
+    }
+
+    // taken from get_fs_usage() found in coreutils
+    unsigned long bsize = (buff_statvfs.f_frsize) ? buff_statvfs.f_frsize : buff_statvfs.f_bsize;
+
+    fsblkcnt_t bavail         = buff_statvfs.f_bavail;
+    fsblkcnt_t btotal         = buff_statvfs.f_blocks;
+    fsblkcnt_t bavail_root    = buff_statvfs.f_bfree;
+    fsblkcnt_t breserved_root = bavail_root - bavail;
+    fsblkcnt_t bused;
+    if(likely(btotal >= bavail_root))
+        bused = btotal - bavail_root;
+    else
+        bused = bavail_root - btotal;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+    if(unlikely(btotal != bavail + breserved_root + bused))
+        error("Disk block statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mount_point, disk, (unsigned long long)btotal, (unsigned long long)bavail, (unsigned long long)breserved_root, (unsigned long long)bused);
+#endif
+
+    // --------------------------------------------------------------------------
+
+    fsfilcnt_t favail         = buff_statvfs.f_favail;
+    fsfilcnt_t ftotal         = buff_statvfs.f_files;
+    fsfilcnt_t favail_root    = buff_statvfs.f_ffree;
+    fsfilcnt_t freserved_root = favail_root - favail;
+    fsfilcnt_t fused          = ftotal - favail_root;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+    if(unlikely(btotal != bavail + breserved_root + bused))
+        error("Disk inode statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mount_point, disk, (unsigned long long)ftotal, (unsigned long long)favail, (unsigned long long)freserved_root, (unsigned long long)fused);
+#endif
+
+    // --------------------------------------------------------------------------
+
+    RRDSET *st;
+
+    if(do_space == CONFIG_ONDEMAND_YES || (do_space == CONFIG_ONDEMAND_ONDEMAND && (bavail || breserved_root || bused))) {
+        st = rrdset_find_bytype("disk_space", disk);
+        if(!st) {
+            char title[4096 + 1];
+            snprintfz(title, 4096, "Disk Space Usage for %s [%s]", family, mount_source);
+            st = rrdset_create("disk_space", disk, NULL, family, "disk.space", title, "GB", 2023, update_every, RRDSET_TYPE_STACKED);
+
+            rrddim_add(st, "avail", NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+            rrddim_add(st, "used" , NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+            rrddim_add(st, "reserved_for_root", "reserved for root", bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+        }
+        else rrdset_next_usec(st, dt);
+
+        rrddim_set(st, "avail", bavail);
+        rrddim_set(st, "used", bused);
+        rrddim_set(st, "reserved_for_root", breserved_root);
+        rrdset_done(st);
+    }
+
+    // --------------------------------------------------------------------------
+
+    if(do_inodes == CONFIG_ONDEMAND_YES || (do_inodes == CONFIG_ONDEMAND_ONDEMAND && (favail || freserved_root || fused))) {
+        st = rrdset_find_bytype("disk_inodes", disk);
+        if(!st) {
+            char title[4096 + 1];
+            snprintfz(title, 4096, "Disk Files (inodes) Usage for %s [%s]", family, mount_source);
+            st = rrdset_create("disk_inodes", disk, NULL, family, "disk.inodes", title, "Inodes", 2024, update_every, RRDSET_TYPE_STACKED);
+
+            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", favail);
+        rrddim_set(st, "used", fused);
+        rrddim_set(st, "reserved_for_root", freserved_root);
+        rrdset_done(st);
+    }
+}
+
 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] = "";
@@ -114,22 +266,21 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
 
     // 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();
+/*    if(unlikely(!mi)) {
+        mountinfo_reload(1);
 
         // search again for this disk
         mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor);
     }
-
-    if(mi)
+*/
+    if(mi) {
         d->mount_point = strdupz(mi->mount_point);
-        // no need to check for NULL
-    else
+        d->mount_point_hash = mi->mount_point_hash;
+    }
+    else {
         d->mount_point = NULL;
+        d->mount_point_hash = 0;
+    }
 
     // ------------------------------------------------------------------------
     // find the disk sector size
@@ -189,8 +340,6 @@ static inline int select_positive_option(int option1, int option2) {
 
 int do_proc_diskstats(int update_every, unsigned long long dt) {
     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,
@@ -245,27 +394,29 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
     uint32_t lines = procfile_lines(ff), l;
     uint32_t words;
 
+    // this is smart enough not to reload it every time
+    mountinfo_reload(0);
+
     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,
+        unsigned long       major = 0, minor = 0;
+
+        collected_number    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;
+                            queued_ios = 0, busy_ms = 0, backlog_ms = 0;
 
-        unsigned long long  last_reads = 0,  last_readsectors = 0,  last_readms = 0,
+        collected_number    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);
+        major           = strtoul(procfile_lineword(ff, l, 0), NULL, 10);
+        minor           = strtoul(procfile_lineword(ff, l, 1), NULL, 10);
         disk            = procfile_lineword(ff, l, 2);
 
         // # of reads completed # of writes completed
@@ -372,7 +523,6 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
 
                         if(d->mount_point)
                             def_performance = select_positive_option(def_performance, global_enable_performance_for_virtual_mountpoints);
-
                         break;
                 }
 
@@ -626,74 +776,42 @@ int do_proc_diskstats(int update_every, unsigned long long dt) {
             }
         }
 
+/*
         // --------------------------------------------------------------------------
         // space metrics
 
         if(d->mount_point && (d->do_space || d->do_inodes) ) {
-            // collect space metrics using statvfs
+            do_disk_space_stats(d, d->mount_point, disk, disk, family, update_every, dt);
+        }
+*/
+    }
 
-            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;
+    // --------------------------------------------------------------------------
+    // space metrics for non-block devices
+
+    struct mountinfo *mi;
+    for(mi = disk_mountinfo_root; mi ;mi = mi->next) {
+        if(unlikely(mi->flags & (MOUNTINFO_IS_DUMMY|MOUNTINFO_IS_BIND|MOUNTINFO_IS_SAME_DEV|MOUNTINFO_NO_STAT|MOUNTINFO_NO_SIZE)))
+            continue;
+
+/*
+        // skip the ones with block devices
+        int skip = 0;
+        struct disk *d;
+        for(d = disk_root; d ;d = d->next) {
+            if(unlikely(d->mount_point && mi->mount_point_hash == d->mount_point_hash && strcmp(mi->mount_point, d->mount_point))) {
+                skip = 1;
+                break;
+            }
+        }
 
-                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;
+        if(unlikely(skip))
+            continue;
 
-                // verify we collected the metrics for the right disk.
-                // if not the mountpoint has changed.
+        // fprintf(stderr, "Will process mount point '%s', source '%s', filesystem '%s'\n", mi->mount_point, mi->mount_source, mi->filesystem);
+*/
 
-                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);
-                        }
-                    }
-                }
-            }
-        }
+        do_disk_space_stats(NULL, mi->mount_point, mi->mount_source, mi->persistent_id, mi->mount_point , update_every, dt);
     }
 
     return 0;
index 51aea7aee1a9b7a653dba8f15f16d6777fae5c4f..22be1aa630a51718bf49b8d5f1aa608afae620e1 100644 (file)
@@ -1,5 +1,48 @@
 #include "common.h"
 
+// ----------------------------------------------------------------------------
+// taken from gnulib/mountlist.c
+
+#ifndef ME_REMOTE
+/* A file system is "remote" if its Fs_name contains a ':'
+   or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')
+   or Fs_name is equal to "-hosts" (used by autofs to mount remote fs).  */
+# define ME_REMOTE(Fs_name, Fs_type)            \
+    (strchr (Fs_name, ':') != NULL              \
+     || ((Fs_name)[0] == '/'                    \
+         && (Fs_name)[1] == '/'                 \
+         && (strcmp (Fs_type, "smbfs") == 0     \
+             || strcmp (Fs_type, "cifs") == 0)) \
+     || (strcmp("-hosts", Fs_name) == 0))
+#endif
+
+#define ME_DUMMY_0(Fs_name, Fs_type)            \
+  (strcmp (Fs_type, "autofs") == 0              \
+   || strcmp (Fs_type, "proc") == 0             \
+   || strcmp (Fs_type, "subfs") == 0            \
+   /* for Linux 2.6/3.x */                      \
+   || strcmp (Fs_type, "debugfs") == 0          \
+   || strcmp (Fs_type, "devpts") == 0           \
+   || strcmp (Fs_type, "fusectl") == 0          \
+   || strcmp (Fs_type, "mqueue") == 0           \
+   || strcmp (Fs_type, "rpc_pipefs") == 0       \
+   || strcmp (Fs_type, "sysfs") == 0            \
+   /* FreeBSD, Linux 2.4 */                     \
+   || strcmp (Fs_type, "devfs") == 0            \
+   /* for NetBSD 3.0 */                         \
+   || strcmp (Fs_type, "kernfs") == 0           \
+   /* for Irix 6.5 */                           \
+   || strcmp (Fs_type, "ignore") == 0)
+
+/* Historically, we have marked as "dummy" any file system of type "none",
+   but now that programs like du need to know about bind-mounted directories,
+   we grant an exception to any with "bind" in its list of mount options.
+   I.e., those are *not* dummy entries.  */
+# define ME_DUMMY(Fs_name, Fs_type)            \
+  (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0)
+
+// ----------------------------------------------------------------------------
+
 // 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) {
@@ -72,6 +115,7 @@ void mountinfo_free(struct mountinfo *mi) {
     freez(mi->root);
     freez(mi->mount_point);
     freez(mi->mount_options);
+    freez(mi->persistent_id);
 
 /*
     if(mi->optional_fields_count) {
@@ -138,14 +182,6 @@ struct mountinfo *mountinfo_read() {
 
         mi = mallocz(sizeof(struct mountinfo));
 
-        if(unlikely(!root))
-            root = last = mi;
-        else
-            last->next = mi;
-
-        last = mi;
-        mi->next = NULL;
-
         unsigned long w = 0;
         mi->id = strtoul(procfile_lineword(ff, l, w), NULL, 10); w++;
         mi->parentid = strtoul(procfile_lineword(ff, l, w), NULL, 10); w++;
@@ -155,12 +191,15 @@ struct mountinfo *mountinfo_read() {
 
         if(!*minor) {
             error("Cannot parse major:minor on '%s' at line %lu of '%s'", major, l + 1, filename);
+            freez(mi);
             continue;
         }
 
         *minor = '\0';
         minor++;
 
+        mi->flags = 0;
+
         mi->major = strtoul(major, NULL, 10);
         mi->minor = strtoul(minor, NULL, 10);
 
@@ -170,6 +209,10 @@ struct mountinfo *mountinfo_read() {
         mi->mount_point = strdupz_decoding_octal(procfile_lineword(ff, l, w)); w++;
         mi->mount_point_hash = simple_hash(mi->mount_point);
 
+        mi->persistent_id = strdupz(mi->mount_point);
+        netdata_fix_chart_id(mi->persistent_id);
+        mi->persistent_id_hash = simple_hash(mi->persistent_id);
+
         mi->mount_options = strdupz(procfile_lineword(ff, l, w)); w++;
 
         // count the optional fields
@@ -214,29 +257,124 @@ struct mountinfo *mountinfo_read() {
             mi->mount_source_hash = simple_hash(mi->mount_source);
 
             mi->super_options = strdupz(procfile_lineword(ff, l, w)); w++;
+
+            if(ME_DUMMY(mi->mount_source, mi->filesystem))
+                mi->flags |= MOUNTINFO_IS_DUMMY;
+
+            if(ME_REMOTE(mi->mount_source, mi->filesystem))
+                mi->flags |= MOUNTINFO_IS_REMOTE;
+
+            // mark as BIND the duplicates (i.e. same filesystem + same source)
+            {
+                struct stat buf;
+                if(unlikely(stat(mi->mount_point, &buf) == -1)) {
+                    mi->st_dev = 0;
+                    mi->flags |= MOUNTINFO_NO_STAT;
+                }
+                else {
+                    mi->st_dev = buf.st_dev;
+
+                    struct mountinfo *mt;
+                    for(mt = root; mt; mt = mt->next) {
+                        if(unlikely(mt->st_dev == mi->st_dev && !(mi->flags & MOUNTINFO_NO_STAT))) {
+                            if(strlen(mi->mount_point) < strlen(mt->mount_point))
+                                mt->flags |= MOUNTINFO_IS_SAME_DEV;
+                            else
+                                mi->flags |= MOUNTINFO_IS_SAME_DEV;
+                        }
+                    }
+                }
+            }
         }
         else {
             mi->filesystem = NULL;
+            mi->filesystem_hash = 0;
+
             mi->mount_source = NULL;
+            mi->mount_source_hash = 0;
+
             mi->super_options = NULL;
+
+            mi->st_dev = 0;
         }
 
+        // check if it has size
+        {
+            struct statvfs buff_statvfs;
+            if(statvfs(mi->mount_point, &buff_statvfs) < 0) {
+                mi->flags |= MOUNTINFO_NO_STAT;
+            }
+            else if(!buff_statvfs.f_blocks /* || !buff_statvfs.f_files */) {
+                mi->flags |= MOUNTINFO_NO_SIZE;
+            }
+        }
+
+        // link it
+        if(unlikely(!root))
+            root = mi;
+        else
+            last->next = mi;
+
+        last = mi;
+        mi->next = NULL;
+
 /*
-        info("MOUNTINFO: %u %u %u:%u root '%s', mount point '%s', mount options '%s', filesystem '%s', mount source '%s', super options '%s'",
+#ifdef NETDATA_INTERNAL_CHECKS
+        fprintf(stderr, "MOUNTINFO: %ld %ld %lu:%lu root '%s', persistent id '%s', mount point '%s', mount options '%s', filesystem '%s', mount source '%s', super options '%s'%s%s%s%s%s%s\n",
              mi->id,
              mi->parentid,
              mi->major,
              mi->minor,
              mi->root,
-             mi->mount_point,
-             mi->mount_options,
-             mi->filesystem,
-             mi->mount_source,
-             mi->super_options
+             mi->persistent_id,
+                (mi->mount_point)?mi->mount_point:"",
+                (mi->mount_options)?mi->mount_options:"",
+                (mi->filesystem)?mi->filesystem:"",
+                (mi->mount_source)?mi->mount_source:"",
+                (mi->super_options)?mi->super_options:"",
+                (mi->flags & MOUNTINFO_IS_DUMMY)?" DUMMY":"",
+                (mi->flags & MOUNTINFO_IS_BIND)?" BIND":"",
+                (mi->flags & MOUNTINFO_IS_REMOTE)?" REMOTE":"",
+                (mi->flags & MOUNTINFO_NO_STAT)?" NOSTAT":"",
+                (mi->flags & MOUNTINFO_NO_SIZE)?" NOSIZE":"",
+                (mi->flags & MOUNTINFO_IS_SAME_DEV)?" SAMEDEV":""
         );
+#endif
 */
     }
 
+/* find if the mount options have "bind" in them
+    {
+        FILE *fp = setmntent(MOUNTED, "r");
+        if (fp != NULL) {
+            struct mntent mntbuf;
+            struct mntent *mnt;
+            char buf[4096 + 1];
+
+            while ((mnt = getmntent_r(fp, &mntbuf, buf, 4096))) {
+                char *bind = hasmntopt(mnt, "bind");
+                if(bind) {
+                    struct mountinfo *mi;
+                    for(mi = root; mi ; mi = mi->next) {
+                        if(strcmp(mnt->mnt_dir, mi->mount_point) == 0) {
+                            fprintf(stderr, "Mount point '%s' is BIND\n", mi->mount_point);
+                            mi->flags |= MOUNTINFO_IS_BIND;
+                            break;
+                        }
+                    }
+
+#ifdef NETDATA_INTERNAL_CHECKS
+                    if(!mi) {
+                        error("Mount point '%s' not found in /proc/self/mountinfo", mnt->mnt_dir);
+                    }
+#endif
+                }
+            }
+            endmntent(fp);
+        }
+    }
+*/
+
     procfile_close(ff);
     return root;
 }
index c2d9688c19e836e221f7e0f445fc961abbad3b69..d47b6112562f70dca15331a1828d1e5bffa2aba8 100644 (file)
@@ -1,12 +1,22 @@
 #ifndef NETDATA_PROC_SELF_MOUNTINFO_H
 #define NETDATA_PROC_SELF_MOUNTINFO_H 1
 
+#define MOUNTINFO_IS_DUMMY      0x00000001
+#define MOUNTINFO_IS_REMOTE     0x00000002
+#define MOUNTINFO_IS_BIND       0x00000004
+#define MOUNTINFO_IS_SAME_DEV   0x00000008
+#define MOUNTINFO_NO_STAT       0x00000010
+#define MOUNTINFO_NO_SIZE       0x00000020
+
 struct mountinfo {
     long id;                // mount ID: unique identifier of the mount (may be reused after umount(2)).
     long parentid;          // parent ID: ID of parent mount (or of self for the top of the mount tree).
     unsigned long major;    // major:minor: value of st_dev for files on filesystem (see stat(2)).
     unsigned long minor;
 
+    char *persistent_id;    // a calculated persistent id for the mount point
+    uint32_t persistent_id_hash;
+
     char *root;             // root: root of the mount within the filesystem.
     uint32_t root_hash;
 
@@ -27,6 +37,10 @@ struct mountinfo {
 
     char *super_options;    // super options: per-superblock options.
 
+    uint32_t flags;
+
+    dev_t st_dev;           // id of device as given by stat()
+
     struct mountinfo *next;
 };
 
index a0fb629ada069cf3becf5da0a356f1c118ab1f58..bce7c348fa0339933f98c20070fb8f1890eb5d8c 100644 (file)
@@ -743,8 +743,7 @@ static inline void registry_log_recreate_nolock(void) {
 }
 
 int registry_log_load(void) {
-    char *s, buf[4096 + 1];
-    size_t line = -1;
+    ssize_t line = -1;
 
     // closing the log is required here
     // otherwise we will append to it the values we read
@@ -755,8 +754,10 @@ int registry_log_load(void) {
     if(!fp)
         error("Registry: cannot open registry file: %s", registry.log_filename);
     else {
+        char *s, buf[4096 + 1];
         line = 0;
         size_t len = 0;
+
         while ((s = fgets_trim_len(buf, 4096, fp, &len))) {
             line++;
 
@@ -766,7 +767,7 @@ int registry_log_load(void) {
 
                     // 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);
+                        error("Registry: log line %zd is wrong (len = %zu).", line, len);
                         continue;
                     }
                     s[1] = s[10] = s[47] = s[84] = '\0';
@@ -781,7 +782,7 @@ int registry_log_load(void) {
                     char *url = name;
                     while(*url && *url != '\t') url++;
                     if(!*url) {
-                        error("Registry: log line %zu does not have a url.", line);
+                        error("Registry: log line %zd does not have a url.", line);
                         continue;
                     }
                     *url++ = '\0';
@@ -800,7 +801,7 @@ int registry_log_load(void) {
                     break;
 
                 default:
-                    error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s);
+                    error("Registry: ignoring line %zd of filename '%s': %s.", line, registry.log_filename, s);
                     break;
             }
         }
@@ -1471,7 +1472,7 @@ int registry_save(void) {
     // 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);
+        error("Registry: cannot move file '%s' to '%s'. Saving registry DB failed!", registry.db_filename, old_filename);
 
     else {
         // remove the database (it is saved in .old)
index 474b5915db753f3becf23798bdf789d868249206..b2c1a05583ab535f4ed22a7164ad164e4e913c3b 100644 (file)
@@ -1158,7 +1158,7 @@ inline static void rrdr_free(RRDR *r)
     freez(r);
 }
 
-inline void rrdr_done(RRDR *r)
+static inline void rrdr_done(RRDR *r)
 {
     r->rows = r->c + 1;
     r->c = 0;
index 2485f20565fd6252f5dcd3c660a67477ff6b601a..c16acfa00e88b8631d3dfe82c7a609d03fbdc3f5 100644 (file)
@@ -14,7 +14,7 @@ int web_enable_gzip = 1, web_gzip_level = 3, web_gzip_strategy = Z_DEFAULT_STRAT
 struct web_client *web_clients = NULL;
 unsigned long long web_clients_count = 0;
 
-inline int web_client_crock_socket(struct web_client *w) {
+static 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;
@@ -29,7 +29,7 @@ inline int web_client_crock_socket(struct web_client *w) {
     return 0;
 }
 
-inline int web_client_uncrock_socket(struct web_client *w) {
+static 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;
index 68df1eb9a8bc195d9b8934dff04a66b382b2a9e5..494593224293569dbcb1f09745c86e48e1133a4d 100644 (file)
@@ -697,8 +697,9 @@ netdataDashboard.context = {
 \r
     // ------------------------------------------------------------------------\r
     // RETROSHARE\r
+\r
     'retroshare.bandwidth': {\r
-        info: 'Shows inbound and outbound traffic.',\r
+        info: 'RetroShare inbound and outbound traffic.',\r
         mainheads: [\r
             netdataDashboard.gaugeChart('Received', '12%', 'bandwidth_down_kb'),\r
             netdataDashboard.gaugeChart('Sent', '12%', 'bandwidth_up_kb')\r
@@ -706,7 +707,7 @@ netdataDashboard.context = {
     },\r
 \r
     'retroshare.peers': {\r
-        info: 'Shows the number of (connected) friends.',\r
+        info: 'Number of (connected) RetroShare friends.',\r
         mainheads: [\r
             function(id) {\r
                 return  '<div data-netdata="' + id + '"'\r
@@ -725,6 +726,19 @@ netdataDashboard.context = {
     },\r
 \r
     'retroshare.dht': {\r
-        info: 'Shows statistics about RetroShare\'s DHT. These values are estimated!'\r
+        info: 'Statistics about RetroShare\'s DHT. These values are estimated!'\r
+    },\r
+\r
+    // ------------------------------------------------------------------------\r
+    // fping\r
+\r
+    'fping.loss': {\r
+        colors: NETDATA.colors[1],\r
+        height: 0.5\r
+    },\r
+\r
+    'fping.packets': {\r
+        height: 0.5\r
     }\r
+\r
 };\r
index de881d30f2230ef451bee86b74210b60efb25854..0f83ca1d74b4af5be486c47c2045df4e020d91ff 100644 (file)
         });
 
         NETDATA.requiredJs.push({
-            url: NETDATA.serverDefault + 'dashboard_info.js?v20161002-1',
+            url: NETDATA.serverDefault + 'dashboard_info.js?v20161016-1',
             async: false,
             isAlreadyLoaded: function() { return false; }
         });
index c2ed74d44c459d6bf332bf97ff75e9e89bfac3e3..ad424abad8726d93f94bdb0412d08bf9e823b608 100644 (file)
@@ -3,7 +3,7 @@
     "info": {
         "title": "NetData API",
         "description": "Real time data collection and graphs...",
-        "version": "1.2.1_master"
+        "version": "1.4.1_master"
     },
     "host": "registry.my-netdata.io",
     "schemes": [
@@ -90,7 +90,7 @@
                     {
                         "name": "after",
                         "in": "query",
-                        "description": "This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds, to the last collected timestamp. Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.",
+                        "description": "This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds (negative, relative to parameter: before). Netdata will assume it is a relative number if it is less that 3 years (in seconds). Netdata will adapt this parameter to the boundaries of the round robin database. The default is the beginning of the round robin database (i.e. by default netdata will attempt to return data for the entire database).",
                         "required": true,
                         "type": "number",
                         "format": "integer",
                     {
                         "name": "before",
                         "in": "query",
-                        "description": "This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds, to the last collected timestamp. Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.",
+                        "description": "This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds). Netdata will adapt this parameter to the boundaries of the round robin database. The default is zero (i.e. the timestamp of the last value collected).",
                         "required": false,
                         "type": "number",
                         "format": "integer",
                     {
                         "name": "points",
                         "in": "query",
-                        "description": "The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the round robin database for this chart for the given duration, all the available collected values for the given duration are returned.",
+                        "description": "The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the round robin database for this chart for the given duration, all the available collected values for the given duration will be returned.",
                         "required": true,
                         "type": "number",
                         "format": "integer",
                     {
                         "name": "group",
                         "in": "query",
-                        "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. Two methods are supported, \"max\" and \"average\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimesions to return the most extreme value in either direction).",
+                        "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimesions to return the most extreme value in either direction).",
                         "required": true,
                         "type": "string",
                         "enum": [
+                            "min",
                             "max",
-                            "average"
+                            "average",
+                            "sum",
+                            "incremental-sum"
                         ],
                         "default": "average",
                         "allowEmptyValue": false
                                 "null2zero",
                                 "objectrows",
                                 "google_json",
-                                "percentage"
+                                "percentage",
+                                "unaligned"
                             ],
                             "collectionFormat": "pipes"
                         },
                         "allowEmptyValue": false,
                         "default": "system.cpu"
                     },
+                    {
+                        "name": "alarm",
+                        "in": "query",
+                        "description": "the name of an alarm linked to the chart",
+                        "required": false,
+                        "type": "string",
+                        "format": "any text",
+                        "allowEmptyValue": true
+                    },
                     {
                         "name": "dimension",
                         "in": "query",
                         "format": "integer",
                         "default": 0
                     },
-                    {
-                        "name": "points",
-                        "in": "query",
-                        "description": "The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the round robin database for this chart for the given duration, all the available collected values for the given duration are returned.",
-                        "required": true,
-                        "type": "number",
-                        "format": "integer",
-                        "allowEmptyValue": false,
-                        "default": 20
-                    },
                     {
                         "name": "group",
                         "in": "query",
-                        "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. Two methods are supported, \"max\" and \"average\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimesions to return the most extreme value in either direction).",
+                        "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods are supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimesions to return the most extreme value in either direction).",
                         "required": true,
                         "type": "string",
                         "enum": [
+                            "min",
                             "max",
-                            "average"
+                            "average",
+                            "sum",
+                            "incremental-sum"
                         ],
                         "default": "average",
                         "allowEmptyValue": false
                         "items": {
                             "type": "string",
                             "enum": [
-                                "nonzero",
-                                "flip",
                                 "abs",
                                 "absolute",
                                 "absolute-sum",
                                 "null2zero",
-                                "percentage"
+                                "percentage",
+                                "unaligned"
                             ],
                             "collectionFormat": "pipes"
                         },
index 370ebd2461e6d1c74cc2763f395908b612120f17..8c96207908960336e74f4a48cb7e139b4fd2843a 100644 (file)
@@ -2,7 +2,7 @@ swagger: '2.0'
 info:
   title: NetData API
   description: 'Real time data collection and graphs...'
-  version: 1.2.1_master
+  version: 1.4.1_master
 host: registry.my-netdata.io
 schemes:
   - http
@@ -66,7 +66,7 @@ paths:
           allowEmptyValue: false
         - name: after
           in: query
-          description: 'This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds (relative to parameter: before). Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.'
+          description: 'This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds (negative, relative to parameter: before). Netdata will assume it is a relative number if it is less that 3 years (in seconds). Netdata will adapt this parameter to the boundaries of the round robin database. The default is the beginning of the round robin database (i.e. by default netdata will attempt to return data for the entire database).'
           required: true
           type: number
           format: integer
@@ -74,14 +74,14 @@ paths:
           default: -600
         - name: before
           in: query
-          description: 'This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds, to the last collected timestamp. Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.'
+          description: 'This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds). Netdata will adapt this parameter to the boundaries of the round robin database. The default is zero (i.e. the timestamp of the last value collected).'
           required: false
           type: number
           format: integer
           default: 0
         - name: points
           in: query
-          description: 'The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the round robin database for this chart for the given duration, all the available collected values for the given duration are returned.'
+          description: 'The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the round robin database for this chart for the given duration, all the available collected values for the given duration will be returned.'
           required: true
           type: number
           format: integer