]> arthur.barton.de Git - netdata.git/commitdiff
Merge remote-tracking branch 'upstream/master' into health
authorCosta Tsaousis <costa@tsaousis.gr>
Thu, 11 Aug 2016 17:33:30 +0000 (20:33 +0300)
committerCosta Tsaousis <costa@tsaousis.gr>
Thu, 11 Aug 2016 17:33:30 +0000 (20:33 +0300)
21 files changed:
README.md
conf.d/Makefile.am
conf.d/apps_groups.conf
conf.d/python.d.conf
conf.d/python.d/dovecot.conf [new file with mode: 0644]
conf.d/python.d/hddtemp.conf
conf.d/python.d/ipfs.conf [new file with mode: 0644]
configs.signatures
plugins.d/python.d.plugin
python.d/Makefile.am
python.d/dovecot.chart.py [new file with mode: 0644]
python.d/hddtemp.chart.py
python.d/ipfs.chart.py [new file with mode: 0644]
python.d/memcached.chart.py
python.d/mysql.chart.py
python.d/nginx.chart.py
python.d/python_modules/base.py
python.d/tomcat.chart.py
system/netdata.service.in
web/dashboard.js
web/index.html

index 049a133d524acad73142cbb93bc88e332ba9213e..47ed5bfecfe7880905da1f1ee16e9eeeca6a04aa 100644 (file)
--- a/README.md
+++ b/README.md
@@ -107,17 +107,25 @@ This is what it currently monitors (most with zero configuration):
 
 - **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)
 
-- **Apache web server** mod-status (v2.2, v2.4)
+- **Apache web server** mod-status (v2.2, v2.4) and cache log statistics (multiple servers)
 
-- **Nginx web server** stub-status
+- **Nginx web server** stub-status (multiple servers)
 
 - **mySQL databases** (multiple servers, each showing: bandwidth, queries/s, handlers, locks, issues, tmp operations, connections, binlog metrics, threads, innodb metrics, etc)
 
+- **Redis databases** (multiple servers, each showing: operations, hit rate, memory, keys, clients, slaves)
+
+- **memcached databases** (multiple servers, each showing: bandwidth, connections, items, etc)
+
 - **ISC Bind name server** (multiple servers, each showing: clients, requests, queries, updates, failures and several per view metrics)
 
 - **Postfix email server** message queue (entries, size)
 
-- **Squid proxy server** (clients bandwidth and requests, servers bandwidth and requests) 
+- **exim email server** message queue (emails queued)
+
+- **IPFS** (Bandwidth, Peers)
+
+- **Squid proxy server** (multiple servers, each showing: clients bandwidth and requests, servers bandwidth and requests)
 
 - **Hardware sensors** (temperature, voltage, fans, power, humidity, etc)
 
@@ -127,6 +135,8 @@ This is what it currently monitors (most with zero configuration):
 
 - **PHP-FPM** (multiple instances, each reporting connections, requests, performance)
 
+- **hddtemp** (disk temperatures)
+
 - **SNMP devices** 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.
@@ -160,4 +170,3 @@ It should run on **any Linux** system. It has been tested on:
 ## Documentation
 
 Check the **[netdata wiki](https://github.com/firehol/netdata/wiki)**.
-
index 84b6c47a1ab1a06601352661b05ded1e53fd7b27..6be4945e6c440189345c98af54af5d5ba4d7568b 100644 (file)
@@ -22,9 +22,11 @@ dist_pythonconfig_DATA = \
        python.d/apache.conf \
        python.d/apache_cache.conf \
        python.d/cpufreq.conf \
+       python.d/dovecot.conf \
        python.d/example.conf \
        python.d/exim.conf \
        python.d/hddtemp.conf \
+       python.d/ipfs.conf \
        python.d/memcached.conf \
        python.d/mysql.conf \
        python.d/nginx.conf \
index 42bd58d267c051e1fbfd75596c84c0da9220b3ec..0a6f55cd790c89b286fcb4dce631f262deb43e81 100644 (file)
@@ -216,3 +216,4 @@ crsproxy: crsproxy
 sidekiq: *sidekiq*
 java: java
 chat: irssi
+ipfs: ipfs
index eebf241b0c303d0f67612361918b59569c667855..18558fbd2294f1ae33308ede95949d0a4583b319 100644 (file)
@@ -21,6 +21,7 @@ enabled: yes
 example: no
 # exim: yes
 # hddtemp: yes
+# ipfs: yes
 # mysql: yes
 # nginx: yes
 # phpfpm: yes
diff --git a/conf.d/python.d/dovecot.conf b/conf.d/python.d/dovecot.conf
new file mode 100644 (file)
index 0000000..917c527
--- /dev/null
@@ -0,0 +1,89 @@
+# netdata python.d.plugin configuration for dovecot
+#
+# This file is in YaML format. Generally the format is:
+#
+# name: value
+#
+# There are 2 sections:
+#  - global variables
+#  - one or more JOBS
+#
+# JOBS allow you to collect values from multiple sources.
+# Each source will have its own set of charts.
+#
+# JOB parameters have to be indented (using spaces only, example below).
+
+# ----------------------------------------------------------------------
+# Global Variables
+# These variables set the defaults for all JOBs, however each JOB
+# may define its own, overriding the defaults.
+
+# update_every sets the default data collection frequency.
+# If unset, the python.d.plugin default is used.
+# update_every: 1
+
+# priority controls the order of charts at the netdata dashboard.
+# Lower numbers move the charts towards the top of the page.
+# If unset, the default for python.d.plugin is used.
+# priority: 60000
+
+# retries sets the number of retries to be made in case of failures.
+# If unset, the default for python.d.plugin is used.
+# Attempts to restore the service are made once every update_every
+# and only if the module has collected values in the past.
+# retries: 5
+
+# ----------------------------------------------------------------------
+# JOBS (data collection sources)
+#
+# The default JOBS share the same *name*. JOBS with the same name
+# are mutually exclusive. Only one of them will be allowed running at
+# any time. This allows autodetection to try several alternatives and
+# pick the one that works.
+#
+# Any number of jobs is supported.
+#
+# All python.d.plugin JOBS (for all its modules) support a set of
+# predefined parameters. These are:
+#
+# job_name:
+#     name: myname     # the JOB's name as it will appear at the
+#                      # dashboard (by default is the job_name)
+#                      # JOBs sharing a name are mutually exclusive
+#     update_every: 1  # the JOB's data collection frequency
+#     priority: 60000  # the JOB's order on the dashboard
+#     retries: 5       # the JOB's number of restoration attempts
+#
+# Additionally to the above, dovecot also supports the following:
+#
+#     socket: 'path/to/dovecot/stats'
+#
+#  or
+#     host: 'IP or HOSTNAME' # the host to connect to
+#     port: PORT             # the port to connect to
+#
+#
+
+# ----------------------------------------------------------------------
+# AUTO-DETECTION JOBS
+# only one of them will run (they have the same name)
+
+localhost:
+  name     : 'local'
+  host     : 'localhost'
+  port     : 24242
+
+localipv4:
+  name     : 'local'
+  host     : '127.0.0.1'
+  port     : 24242
+
+localipv6:
+  name     : 'local'
+  host     : '::1'
+  port     : 24242
+
+localsocket:
+  name     : 'local'
+  socket   : '/var/run/dovecot/stats'
+
index 34790e7c6027753531ee5875bca6517463e2af59..f3cb667a5e5b0256b636d89dd003a7a475cabfd4 100644 (file)
 #     port: PORT             # the port to connect to
 #
 
+# By default this module will try to autodetect number of disks.
+# However this can be overridden by setting variable `disk_count` to
+# desired number of disks. Example for two disks:
+#
+# disk_count: 2
+#
+
 # ----------------------------------------------------------------------
 # AUTO-DETECTION JOBS
 # only one of them will run (they have the same name)
@@ -76,5 +83,5 @@ localipv4:
 
 localipv6:
   name: 'local'
-  host: '127.0.0.1'
+  host: '::1'
   port: 7634
diff --git a/conf.d/python.d/ipfs.conf b/conf.d/python.d/ipfs.conf
new file mode 100644 (file)
index 0000000..e039026
--- /dev/null
@@ -0,0 +1,67 @@
+# netdata python.d.plugin configuration for ipfs
+#
+# This file is in YaML format. Generally the format is:
+#
+# name: value
+#
+# There are 2 sections:
+#  - global variables
+#  - one or more JOBS
+#
+# JOBS allow you to collect values from multiple sources.
+# Each source will have its own set of charts.
+#
+# JOB parameters have to be indented (using spaces only, example below).
+
+# ----------------------------------------------------------------------
+# Global Variables
+# These variables set the defaults for all JOBs, however each JOB
+# may define its own, overriding the defaults.
+
+# update_every sets the default data collection frequency.
+# If unset, the python.d.plugin default is used.
+# update_every: 1
+
+# priority controls the order of charts at the netdata dashboard.
+# Lower numbers move the charts towards the top of the page.
+# If unset, the default for python.d.plugin is used.
+# priority: 60000
+
+# retries sets the number of retries to be made in case of failures.
+# If unset, the default for python.d.plugin is used.
+# Attempts to restore the service are made once every update_every
+# and only if the module has collected values in the past.
+# retries: 5
+
+# ----------------------------------------------------------------------
+# JOBS (data collection sources)
+#
+# The default JOBS share the same *name*. JOBS with the same name
+# are mutually exclusive. Only one of them will be allowed running at
+# any time. This allows autodetection to try several alternatives and
+# pick the one that works.
+#
+# Any number of jobs is supported.
+#
+# All python.d.plugin JOBS (for all its modules) support a set of
+# predefined parameters. These are:
+#
+# job_name:
+#     name: myname     # the JOB's name as it will appear at the
+#                      # dashboard (by default is the job_name)
+#                      # JOBs sharing a name are mutually exclusive
+#     update_every: 1  # the JOB's data collection frequency
+#     priority: 60000  # the JOB's order on the dashboard
+#     retries: 5       # the JOB's number of restoration attempts
+#
+# Additionally to the above, ipfs also supports the following:
+#
+#     url: 'URL'       # URL to the IPFS API
+#
+# ----------------------------------------------------------------------
+# AUTO-DETECTION JOBS
+# only one of them will run (they have the same name)
+
+localhost:
+  name : 'local'
+  url  : 'http://localhost:5001'
index dc7e90996dc8a76e1bd54e1d1227e4891084ed6a..26d1a277de1f746602b92cc981a416f4d36d9f47 100644 (file)
@@ -3,6 +3,7 @@ declare -A configs_signatures=(
        ['18ee1c6197a4381b1c1631ef6129824f']='apps_groups.conf'
        ['2f4a85fedecce1bf425fa1039f6b021e']='apps_groups.conf'
        ['3af522d65b50a5e447607ffb28c81ff5']='apps_groups.conf'
+       ['3b1bfa40a4ff6a200bb2fc00bc51a664']='apps_groups.conf'
        ['4a448831776de8acf2e0bdc4cc994cb4']='apps_groups.conf'
        ['5bf51bb24fb41db9b1e448bd060d3f8c']='apps_groups.conf'
        ['636d032928ea0f4741eab264fb49c099']='apps_groups.conf'
@@ -42,6 +43,7 @@ declare -A configs_signatures=(
        ['b27f10a38a95edbbec20f44a4728b7c4']='python.d.conf'
        ['b32164929eda7449a9677044e11151bf']='python.d.conf'
        ['b8969be5b3ceb4a99477937119bd4323']='python.d.conf'
+       ['bba2f3886587f137ea08a6e63dd3d376']='python.d.conf'
        ['d55be5bb5e108da1e7645da007c53cd4']='python.d.conf'
        ['f82924563e41d99cdae5431f0af69155']='python.d.conf'
        ['7830066c46a7e5f9682b8d3f4566b4e5']='python.d/cpufreq.conf'
@@ -55,8 +57,12 @@ declare -A configs_signatures=(
        ['73125ae64d5c6e9361944cd9bd14844e']='python.d/exim.conf'
        ['a94af1c808aafdf00537d85ff2197ec8']='python.d/exim.conf'
        ['2a0794fd43eadf30a51805bc9ba2c64d']='python.d/hddtemp.conf'
+       ['47180421d580baeaedf8c0ef3d647fb5']='python.d/hddtemp.conf'
        ['731a1fcfe9b2da1b9d685056a59541b8']='python.d/hddtemp.conf'
        ['d74dc63fbe631dab9a2ff1b0f5d71719']='python.d/hddtemp.conf'
+       ['0e59bc11d0a869ea0247c04c08c8d72e']='python.d/ipfs.conf'
+       ['70105b1744a8e13f49083d7f1981aea2']='python.d/ipfs.conf'
+       ['97f337eb96213f3ede05e522e3743a6c']='python.d/memcached.conf'
        ['1ea8e8ef1fa8a3a0fcdfba236f4cb195']='python.d/mysql.conf'
        ['5379cdc26d7725e2b0d688d785816cef']='python.d/mysql.conf'
        ['7deb236ec68a512b9bdd18e6a51d76f7']='python.d/mysql.conf'
index 085d4fc7cfe9beb83a5c4fc6741c770957e3b448..1301777bee9debbd70108b093a4116297b8d459c 100755 (executable)
@@ -353,7 +353,7 @@ class PythonCharts(object):
                         pass
             except AttributeError as e:
                 self._stop(job)
-                msg.error(job.chart_name, "cannot find check() function.")
+                msg.error(job.chart_name, "cannot find check() function or it thrown unhandled exception.")
                 msg.debug(str(e))
             except (UnboundLocalError, Exception) as e:
                 msg.error(job.chart_name, str(e))
@@ -390,7 +390,7 @@ class PythonCharts(object):
                     # sys.stdout.flush()
                     i += 1
             except AttributeError:
-                msg.error(job.chart_name, "cannot find create() function.")
+                msg.error(job.chart_name, "cannot find create() function or it thrown unhandled exception.")
                 self._stop(job)
             except (UnboundLocalError, Exception) as e:
                 msg.error(job.chart_name, str(e))
index 51ebad096cbc181bcd5837edfd69452fdee0549b..57d89bad251ed1510bcd6d573f83c92db5d68052 100644 (file)
@@ -11,9 +11,11 @@ dist_python_SCRIPTS = \
        apache.chart.py \
        apache_cache.chart.py \
        cpufreq.chart.py \
+       dovecot.chart.py \
        example.chart.py \
        exim.chart.py \
        hddtemp.chart.py \
+       ipfs.chart.py \
        memcached.chart.py \
        mysql.chart.py \
        nginx.chart.py \
@@ -78,4 +80,4 @@ dist_pythonyaml3_DATA = \
     python_modules/pyyaml3/scanner.py \
     python_modules/pyyaml3/serializer.py \
     python_modules/pyyaml3/tokens.py \
-    $(NULL)
\ No newline at end of file
+    $(NULL)
diff --git a/python.d/dovecot.chart.py b/python.d/dovecot.chart.py
new file mode 100644 (file)
index 0000000..4ec1acd
--- /dev/null
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+# Description: dovecot netdata python.d module
+# Author: Pawel Krupa (paulfantom)
+
+from base import SocketService
+
+# default module values (can be overridden per job in `config`)
+# update_every = 2
+priority = 60000
+retries = 60
+
+# charts order (can be overridden if you want less charts, or different order)
+ORDER = ['sessions', 'commands',
+         'faults',
+         'context_switches',
+         'disk', 'bytes', 'syscalls',
+         'lookup', 'cache',
+         'auth', 'auth_cache']
+
+CHARTS = {
+    'sessions': {
+        'options': [None, "logins and sessions", 'number', 'IMAP', 'dovecot.imap', 'line'],
+        'lines': [
+            ['num_logins', 'logins', 'absolute'],
+            ['num_connected_sessions', 'active sessions', 'absolute']
+        ]},
+    'commands': {
+        'options': [None, "commands", "commands", 'IMAP', 'dovecot.imap', 'line'],
+        'lines': [
+            ['num_cmds', 'commands', 'absolute']
+        ]},
+    'faults': {
+        'options': [None, "faults", "faults", 'Faults', 'dovecot.faults', 'line'],
+        'lines': [
+            ['min_faults', 'minor', 'absolute'],
+            ['maj_faults', 'major', 'absolute']
+        ]},
+    'context_switches': {
+        'options': [None, "context switches", '', 'Context Switches', 'dovecot.context_switches', 'line'],
+        'lines': [
+            ['vol_cs', 'volountary', 'absolute'],
+            ['invol_cs', 'involountary', 'absolute']
+        ]},
+    'disk': {
+        'options': [None, "disk", 'bytes/s', 'Reads and Writes', 'dovecot.read_write', 'line'],
+        'lines': [
+            ['disk_input', 'read', 'incremental'],
+            ['disk_output', 'write', 'incremental']
+        ]},
+    'bytes': {
+        'options': [None, "bytes", 'bytes/s', 'Reads and Writes', 'dovecot.read_write', 'line'],
+        'lines': [
+            ['read_bytes', 'read', 'incremental'],
+            ['write_bytes', 'write', 'incremental']
+        ]},
+    'syscalls': {
+        'options': [None, "number of syscalls", 'syscalls/s', 'Reads and Writes', 'dovecot.read_write', 'line'],
+        'lines': [
+            ['read_count', 'read', 'incremental'],
+            ['write_count', 'write', 'incremental']
+        ]},
+    'lookup': {
+        'options': [None, "lookups", 'number/s', 'Mail', 'dovecot.mail', 'line'],
+        'lines': [
+            ['mail_lookup_path', 'path', 'incremental'],
+            ['mail_lookup_attr', 'attr', 'incremental']
+        ]},
+    'cache': {
+        'options': [None, "hits", 'hits/s', 'Mail', 'dovecot.mail', 'line'],
+        'lines': [
+            ['mail_cache_hits', 'hits', 'incremental']
+        ]},
+    'auth': {
+        'options': [None, "attempts", 'attempts', 'Authentication', 'dovecot.auth', 'stacked'],
+        'lines': [
+            ['auth_successes', 'success', 'absolute'],
+            ['auth_failures', 'failure', 'absolute']
+        ]},
+    'auth_cache': {
+        'options': [None, "cache", 'number', 'Authentication', 'dovecot.auth', 'stacked'],
+        'lines': [
+            ['auth_cache_hits', 'hit', 'absolute'],
+            ['auth_cache_misses', 'miss', 'absolute']
+        ]}
+}
+
+
+class Service(SocketService):
+    def __init__(self, configuration=None, name=None):
+        SocketService.__init__(self, configuration=configuration, name=name)
+        self.request = "EXPORT\tglobal\r\n"
+        self.host = None  # localhost
+        self.port = None  # 24242
+        # self._keep_alive = True
+        self.unix_socket = "/var/run/dovecot/stats"
+        self.order = ORDER
+        self.definitions = CHARTS
+
+    def _get_data(self):
+        """
+        Format data received from socket
+        :return: dict
+        """
+        try:
+            raw = self._get_raw_data()
+        except (ValueError, AttributeError):
+            return None
+
+        data = raw.split('\n')[:2]
+        desc = data[0].split('\t')
+        vals = data[1].split('\t')
+        # ret = dict(zip(desc, vals))
+        ret = {}
+        for i in range(len(desc)):
+            try:
+                #d = str(desc[i])
+                #if d in ('user_cpu', 'sys_cpu', 'clock_time'):
+                #    val = float(vals[i])
+                #else:
+                #    val = int(vals[i])
+                #ret[d] = val
+                ret[str(desc[i])] = int(vals[i])
+            except ValueError:
+                pass
+        if len(ret) == 0:
+            return None
+        return ret
index ebbe1cea9b0b4a7e3aae7baed15ce70125465c16..beef64161645ff47363f3f4109ace960fe4e3514 100644 (file)
@@ -65,7 +65,6 @@ class Service(SocketService):
         Get data from TCP/IP socket
         :return: dict
         """
-        self.disk_count = self._get_disk_count()
         try:
             raw = self._get_raw_data().split("|")[:-1]
         except AttributeError:
@@ -96,6 +95,14 @@ class Service(SocketService):
         except (KeyError, TypeError) as e:
             self.info("No excluded disks")
             self.debug(str(e))
+
+        try:
+            self.disk_count = int(self.configuration['disk_count'])
+        except (KeyError, TypeError) as e:
+            self.info("Autodetecting number of disks")
+            self.disk_count = self._get_disk_count()
+            self.debug(str(e))
+
         data = self._get_data()
         if data is None:
             return False
diff --git a/python.d/ipfs.chart.py b/python.d/ipfs.chart.py
new file mode 100644 (file)
index 0000000..b0b2a96
--- /dev/null
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+# Description: IPFS netdata python.d module
+# Authors: Pawel Krupa (paulfantom), davidak
+
+from base import UrlService
+import json
+
+# default module values (can be overridden per job in `config`)
+# update_every = 2
+priority = 60000
+retries = 60
+
+# default job configuration (overridden by python.d.plugin)
+# config = {'local': {
+#     'update_every': update_every,
+#     'retries': retries,
+#     'priority': priority,
+#     'url': 'http://localhost:5001'
+# }}
+
+# charts order (can be overridden if you want less charts, or different order)
+ORDER = ['bandwidth', 'peers']
+
+CHARTS = {
+    'bandwidth': {
+        'options': [None, 'IPFS Bandwidth', 'kbits/s', 'Bandwidth', 'ipfs.bandwidth', 'line'],
+        'lines': [
+            ["in", None, "absolute", 8, 1000],
+            ["out", None, "absolute", -8, 1000]
+        ]},
+    'peers': {
+        'options': [None, 'IPFS Peers', 'peers', 'Peers', 'ipfs.peers', 'line'],
+        'lines': [
+            ["peers", None, 'absolute']
+        ]}
+}
+
+
+class Service(UrlService):
+    def __init__(self, configuration=None, name=None):
+        UrlService.__init__(self, configuration=configuration, name=name)
+        try:
+            self.baseurl = str(self.configuration['url'])
+        except (KeyError, TypeError):
+            self.baseurl = "http://localhost:5001"
+        self.order = ORDER
+        self.definitions = CHARTS
+
+    def _get_bandwidth(self):
+        """
+        Format data received from http request
+        :return: int, int
+        """
+        self.url = self.baseurl + "/api/v0/stats/bw"
+        try:
+            raw = self._get_raw_data()
+        except AttributeError:
+            return None
+
+        try:
+            parsed = json.loads(raw)
+            bw_in = int(parsed['RateIn'])
+            bw_out = int(parsed['RateOut'])
+        except:
+            return None
+
+        return bw_in, bw_out
+
+    def _get_peers(self):
+        """
+        Format data received from http request
+        :return: int
+        """
+        self.url = self.baseurl + "/api/v0/swarm/peers"
+        try:
+            raw = self._get_raw_data()
+        except AttributeError:
+            return None
+
+        try:
+            parsed = json.loads(raw)
+            peers = len(parsed['Strings'])
+        except:
+            return None
+
+        return peers
+
+    def _get_data(self):
+        """
+        Get data from API
+        :return: dict
+        """
+        try:
+            peers = self._get_peers()
+            bandwidth_in, bandwidth_out = self._get_bandwidth()
+        except:
+            return None
+        data = {}
+        if peers is not None:
+            data['peers'] = peers
+        if bandwidth_in is not None and bandwidth_out is not None:
+            data['in'] = bandwidth_in
+            data['out'] = bandwidth_out
+
+        if len(data) == 0:
+            return None
+        return data
index 12e0fb9b57d645ba560409508381987eadc0a21e..e99186f3fd1aeac1863469842ac86862664461f5 100644 (file)
@@ -23,17 +23,17 @@ ORDER = ['net', 'connections', 'items', 'evicted_reclaimed', 'get', 'get_rate',
 
 CHARTS = {
     'net': {
-        'options': [None, 'Network', 'bytes', 'Network', 'memcached.net', 'line'],
+        'options': [None, 'Network', 'kilobytes/s', 'Network', 'memcached.net', 'line'],
         'lines': [
-            ['bytes_read', 'read', 'absolute'],
-            ['bytes_written', 'written', 'absolute']
+            ['bytes_read', 'read', 'incremental', 1, 1024],
+            ['bytes_written', 'written', 'incremental', 1, 1024]
         ]},
     'connections': {
-        'options': [None, 'Connections', 'connections', 'Cluster', 'memcached.cluster', 'line'],
+        'options': [None, 'Connections', 'connections/s', 'Cluster', 'memcached.cluster', 'line'],
         'lines': [
-            ['curr_connections', 'current', 'absolute'],
-            ['rejected_connections', 'rejected', 'absolute'],
-            ['total_connections', 'total', 'absolute']
+            ['curr_connections', 'current', 'incremental'],
+            ['rejected_connections', 'rejected', 'incremental'],
+            ['total_connections', 'total', 'incremental']
         ]},
     'items': {
         'options': [None, 'Items', 'items', 'Cluster', 'memcached.cluster', 'line'],
@@ -42,7 +42,7 @@ CHARTS = {
             ['total_items', 'total', 'absolute']
         ]},
     'evicted_reclaimed': {
-        'options': [None, 'Items', 'items', 'Evicted & Reclaimed', 'memcached.evicted_reclaimed', 'line'],
+        'options': [None, 'Items', 'items', 'Evicted and Reclaimed', 'memcached.evicted_reclaimed', 'line'],
         'lines': [
             ['evictions', 'evicted', 'absolute'],
             ['reclaimed', 'reclaimed', 'absolute']
@@ -108,6 +108,7 @@ class Service(SocketService):
         self.request = "stats\r\n"
         self.host = "localhost"
         self.port = 11211
+        self._keep_alive = True
         self.unix_socket = None
         self.order = ORDER
         self.definitions = CHARTS
@@ -144,6 +145,12 @@ class Service(SocketService):
         else:
             return data
 
+    def _check_raw_data(self, data):
+        if data.endswith('END\r\n'):
+            return True
+        else:
+            return False
+
     def check(self):
         """
         Parse configuration, check if memcached is available
index 5c996d7768d2545308ede774884c8287f6137d0c..d84f322429b5234e923b96c1a3a0e47558e9f8a0 100644 (file)
@@ -39,7 +39,7 @@ retries = 60
 #}
 
 # query executed on MySQL server
-QUERY = "SHOW GLOBAL STATUS"
+QUERY = "SHOW GLOBAL STATUS;"
 
 ORDER = ['net',
          'queries',
@@ -351,6 +351,10 @@ class Service(SimpleService):
                                               host=self.configuration['host'],
                                               port=self.configuration['port'],
                                               connect_timeout=self.update_every)
+        except MySQLdb.OperationalError as e:
+            self.error("Cannot establish connection to MySQL.")
+            self.debug(str(e))
+            raise RuntimeError
         except Exception as e:
             self.error("problem connecting to server:", e)
             raise RuntimeError
@@ -369,11 +373,18 @@ class Service(SimpleService):
             cursor = self.connection.cursor()
             cursor.execute(QUERY)
             raw_data = cursor.fetchall()
+        except MySQLdb.OperationalError as e:
+            self.debug("Reconnecting due to", str(e))
+            self._connect()
+            cursor = self.connection.cursor()
+            cursor.execute(QUERY)
+            raw_data = cursor.fetchall()
         except Exception as e:
             self.error("cannot execute query.", e)
             self.connection.close()
             self.connection = None
             return None
+
         data = dict(raw_data)
         try:
             data["Thread_cache_misses"] = int(data["Threads_created"] * 10000 / float(data["Connections"]))
index 83b1892a9ea9e2117f184eb221661196f603f310..07196173871821c511d6dd51e587a59a94a99608 100644 (file)
@@ -63,11 +63,11 @@ class Service(UrlService):
         try:
             raw = self._get_raw_data().split(" ")
             return {'active': int(raw[2]),
-                    'requests': int(raw[7]),
+                    'requests': int(raw[9]),
                     'reading': int(raw[11]),
                     'writing': int(raw[13]),
                     'waiting': int(raw[15]),
-                    'accepts': int(raw[8]),
-                    'handled': int(raw[9])}
+                    'accepts': int(raw[7]),
+                    'handled': int(raw[8])}
         except (ValueError, AttributeError):
             return None
index 0eaf5cd613b98211752e54d0aa5fa81ab09cdda4..87c55830c32e3293d0ae1639149c815cb1a0c790 100644 (file)
@@ -412,7 +412,7 @@ class UrlService(SimpleService):
 
     def __add_openers(self):
         # TODO add error handling
-        opener = urllib2.build_opener()
+        self.opener = urllib2.build_opener()
 
         # Proxy handling
         # TODO currently self.proxies isn't parsed from configuration file
@@ -439,9 +439,10 @@ class UrlService(SimpleService):
         if self.user is not None and self.password is not None:
             passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
             passman.add_password(None, self.url, self.user, self.password)
-            opener.add_handler(urllib2.HTTPBasicAuthHandler(passman))
+            self.opener.add_handler(urllib2.HTTPBasicAuthHandler(passman))
+            self.debug("Enabling HTTP basic auth")
 
-        urllib2.install_opener(opener)
+        #urllib2.install_opener(opener)
 
     def _get_raw_data(self):
         """
@@ -450,7 +451,8 @@ class UrlService(SimpleService):
         """
         raw = None
         try:
-            f = urllib2.urlopen(self.url, timeout=self.update_every * 2)
+            f = self.opener.open(self.url, timeout=self.update_every * 2)
+            # f = urllib2.urlopen(self.url, timeout=self.update_every * 2)
         except Exception as e:
             self.error(str(e))
             return None
@@ -488,7 +490,8 @@ class UrlService(SimpleService):
 
         self.__add_openers()
 
-        if self._get_data() is None or len(self._get_data()) == 0:
+        test = self._get_data()
+        if test is None or len(test) == 0:
             return False
         else:
             return True
@@ -543,8 +546,13 @@ class SocketService(SimpleService):
                         self._disconnect()
             else:
                 # connect to unix socket
-                self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
-                self._sock.connect(self.unix_socket)
+                try:
+                    self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+                    self._sock.connect(self.unix_socket)
+                except socket.error:
+                    self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+                    self._sock.connect(self.unix_socket)
+
         except Exception as e:
             self.error(str(e),
                        "Cannot create socket with following configuration: host:", str(self.host),
index f791d27f28dbedf5b3b976b258d6854391c47f84..266f2737648fac46bedc4d32049ff3125663d254 100644 (file)
@@ -20,12 +20,12 @@ CHARTS = {
     'accesses': {
         'options': [None, "tomcat requests", "requests/s", "statistics", "tomcat.accesses", "area"],
         'lines': [
-            ["accesses"]
+            ["accesses", None, 'incremental']
         ]},
     'volume': {
         'options': [None, "tomcat volume", "KB/s", "volume", "tomcat.volume", "area"],
         'lines': [
-            ["volume", None, 'incremental']
+            ["volume", None, 'incremental', 1, 1024]
         ]},
     'threads': {
         'options': [None, "tomcat threads", "current threads", "statistics", "tomcat.threads", "line"],
@@ -36,7 +36,7 @@ CHARTS = {
     'jvm': {
         'options': [None, "JVM Free Memory", "MB", "statistics", "tomcat.jvm", "area"],
         'lines': [
-            ["jvm", None, "absolute"]
+            ["jvm", None, "absolute", 1, 1048576]
         ]}
 }
 
@@ -66,7 +66,8 @@ class Service(UrlService):
         if self.port == 0:
             self.port = 80
 
-        if self._get_data() is None or len(self._get_data()) == 0:
+        test = self._get_data()
+        if test is None or len(test) == 0:
             return False
         else:
             return True
@@ -81,13 +82,12 @@ class Service(UrlService):
             try:
                 data = ET.fromstring(raw)
             except ET.ParseError as e:
-                #if e.code == errors.codes[errors.XML_ERROR_JUNK_AFTER_DOC_ELEMENT]:
+                # if e.code == errors.codes[errors.XML_ERROR_JUNK_AFTER_DOC_ELEMENT]:
                 if e.code == 9:
-                    # cut rest of invalid string
-                    pos = 0
-                    for i in range(e.position[0] - 1):
-                        pos += raw.find('\n', pos)
-                    raw = raw[:47604 + pos + e.position[0] - 1]
+                    end = raw.find('</status>')
+                    end += 9
+                    raw = raw[:end]
+                    self.debug(raw)
                     data = ET.fromstring(raw)
                 else:
                     raise Exception(e)
index 65e33cec29f476c92e7e5f0960e724a231815991..0dd6eba38d9f18aa4f3d8ee922a3b00390c2e495 100644 (file)
@@ -5,13 +5,23 @@ After=network.target httpd.service squid.service nfs-server.service mysqld.servi
 [Service]
 Type=forking
 WorkingDirectory=/tmp
-User=root
-Group=root
-PIDFile=@localstatedir_POST@/run/netdata.pid
-ExecStart=@sbindir_POST@/netdata -P @localstatedir_POST@/run/netdata.pid
+User=netdata
+Group=netdata
+RuntimeDirectory=netdata
+PIDFile=@localstatedir_POST@/run/netdata/netdata.pid
+ExecStart=@sbindir_POST@/netdata -P @localstatedir_POST@/run/netdata/netdata.pid
 KillMode=mixed
 KillSignal=SIGTERM
 TimeoutStopSec=30
 
+#Hardening
+AmbientCapabilities=CAP_DAC_READ_SEARCH CAP_SYS_PTRACE
+CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_SYS_PTRACE
+PrivateTmp=true
+ProtectSystem=full
+ProtectHome=read-only
+#NoNewPrivileges=true is implicitly set by the MemoryDenyWriteExecute=true
+MemoryDenyWriteExecute=true
+
 [Install]
 WantedBy=multi-user.target
index 5a51b0222841132683c9394c5bf19801d6947295..0b994589908179211189ee6cd49bec375ef22b64 100644 (file)
@@ -15,6 +15,7 @@
 // var netdataNoRegistry = true;               // Don't update the registry for this access
 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
 //                                         the URLs from the registry
+// var netdataShowHelp = true;                 // enable/disable help
 //
 // You can also set the default netdata server, using the following.
 // When this variable is not set, we assume the page is hosted on your
        else
                NETDATA.themes.current = NETDATA.themes.white;
 
+       if(typeof netdataShowHelp === 'undefined')
+               netdataShowHelp = true;
+
        NETDATA.colors = NETDATA.themes.current.colors;
 
        // these are the colors Google Charts are using
 
                        destroy_on_hide: false,         // destroy charts when they are not visible
 
-                       show_help: true,                        // when enabled the charts will show some help
+                       show_help: netdataShowHelp,     // when enabled the charts will show some help
                        show_help_delay_show_ms: 500,
                        show_help_delay_hide_ms: 0,
 
                        try {
                                // console.log('localStorage: loading "' + key.toString() + '"');
                                ret = localStorage.getItem(key.toString());
+                               // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
                                if(ret === null || ret === 'undefined') {
                                        // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
                                        localStorage.setItem(key.toString(), JSON.stringify(def));
                force_before_ms: null,  // the timespan to sync all other charts
                force_after_ms: null,
 
+               callback: null,
+
                // set a new master
                setMaster: function(state, after, before) {
                        if(NETDATA.options.current.sync_pan_and_zoom === false)
                        this.force_after_ms = after;
                        this.force_before_ms = before;
                        NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
+
+                       if(typeof this.callback === 'function')
+                               this.callback(true, after, before);
                },
 
                // clear the master
                        this.force_after_ms = null;
                        this.force_before_ms = null;
                        NETDATA.options.auto_refresher_stop_until = 0;
+
+                       if(typeof this.callback === 'function')
+                               this.callback(false, 0, 0);
                },
 
                // is the given state the master of the global
                NETDATA.parseDom(NETDATA.chartRefresher);
 
                // Registry initialization
-               setTimeout(NETDATA.registry.init, 3000);
+               setTimeout(NETDATA.registry.init, 1000);
        };
 
        // ----------------------------------------------------------------------------------------------------------------
index f8832b674372411dfa8f07df24d41a82aece86ec..59629ae1cf4888d7dbab6a985810221200f85b6a 100644 (file)
                padding-top: 50px;
        }
 
+       .loadOverlay {
+               position: absolute;
+               top: 0px;
+               left: 0px;
+               width: 100%;
+               height:100%;
+               z-index: 2000;
+               font-size: 10vh;
+               font-family: sans-serif;
+               padding: 40vh 0 40vh 0;
+               font-weight: bold;
+               text-align: center;
+       }
+
        .modal-wide .modal-dialog {
                width: 80%;
        }
        }
        </style>
 
-       <!-- you can set your netdata server globally, by ucommenting this -->
-       <!-- you can also give a different server per chart, with the attribute: data-host="http://netdata.server:19999" -->
-       <!-- <script> netdataServer = "http://box:19999"; </script> -->
-
        <!-- check which theme to use -->
-       <script>
+       <script type="text/javascript">
+               // --------------------------------------------------------------------
+               // urlOptions
+
+               var urlOptions = {
+                       hash: '#',
+                       theme: null,
+                       help: null,
+                       pan_and_zoom: false,
+                       after: 0,
+                       before: 0,
+                       nowelcome: 0,
+                       hasProperty: function(property) {
+                               // console.log('checking property ' + property + ' of type ' + typeof(this[property]));
+                               return typeof this[property] !== 'undefined';
+                       }
+               };
+
+               function netdataPanAndZoomCallback(status, after, before) {
+                       urlOptions.pan_and_zoom = status;
+                       urlOptions.after = after;
+                       urlOptions.before = before;
+                       netdataHashUpdate();
+               }
+
+               function netdataHashUpdate() {
+                       history.replaceState(null, document.title, netdataHash());
+               }
+
+               function netdataHash() {
+                       var hash = urlOptions.hash;
+
+                       if(urlOptions.pan_and_zoom === true) {
+                               hash += ';after='  + urlOptions.after.toString() +
+                                       ';before=' + urlOptions.before.toString();
+                       }
+
+                       if(urlOptions.theme !== null)
+                               hash += ';theme=' + urlOptions.theme.toString();
+
+                       if(urlOptions.help !== null)
+                               hash += ';help=' + urlOptions.help.toString();
+
+                       return hash;
+               }
+
+               function netdataHashParse() {
+                       var variables = document.location.hash.split(';');
+                       var len = variables.length;
+                       while(len--) {
+                               if(len !== 0) {
+                                       var p = variables[len].split('=');
+                                       if(urlOptions.hasProperty(p[0]) && typeof p[1] !== 'undefined')
+                                               urlOptions[p[0]] = p[1];
+                               }
+                               else {
+                                       if(variables[len].length > 0)
+                                               urlOptions.hash = variables[len];
+                               }
+                       }
+
+                       if(urlOptions.before > 0 && urlOptions.after > 0)
+                               urlOptions.pan_and_zoom = true;
+
+                       // console.log(urlOptions);
+               }
+
+               netdataHashParse();
+
+               // --------------------------------------------------------------------
+               // check options that should be processed before loading netdata.js
+               
                function loadLocalStorage(name) {
                        var ret = null;
 
                        if(typeof ret === 'undefined' || ret === null)
                                return null;
 
+                       // console.log('loaded: ' + name.toString() + ' = ' + ret.toString());
+
                        return ret;
                }
 
                function saveLocalStorage(name, value) {
+                       // console.log('saving: ' + name.toString() + ' = ' + value.toString());
                        try {
                                if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
                                        localStorage.setItem(name, value.toString());
                                return ret;
                }
 
-               var netdataTheme = getTheme('slate');
-
                function setTheme(theme) {
                        if(theme === netdataTheme) return false;
-
                        return saveLocalStorage('netdataTheme', theme);
                }
 
+               var netdataTheme = getTheme('slate');
+               var netdataShowHelp = true;
+
+               if(urlOptions.theme !== null) {
+                       setTheme(urlOptions.theme);
+                       netdataTheme = urlOptions.theme;
+               }
+               else
+                       urlOptions.theme = netdataTheme;
+
+               if(urlOptions.help !== null) {
+                       saveLocalStorage('options.show_help', urlOptions.help);
+                       netdataShowHelp = urlOptions.help;
+               }
+               else {
+                       urlOptions.help = loadLocalStorage('options.show_help');
+               }
+
+               // --------------------------------------------------------------------
+               // registry call back to render my-netdata menu
+
                var netdataRegistryCallback = function(machines_array) {
                        var el = '';
                        var a1 = '';
        </script>
 
        <!-- load the dashboard manager - it will do the rest -->
-       <script type="text/javascript" src="dashboard.js?v40"></script>
+       <script type="text/javascript" src="dashboard.js?v41"></script>
 </head>
-
 <body data-spy="scroll" data-target="#sidebar">
+       <div id="loadOverlay" class="loadOverlay" style="background-color: #888; color: #888;">
+               netdata<br/><div style="font-size: 3vh;">Real-time performance monitoring, done right!</div>
+       </div>
+       <script type="text/javascript">
+               // change the loadOverlay colors ASAP to match the theme
+               document.getElementById('loadOverlay').style = (urlOptions.theme === 'slate')?"background-color:#272b30; color: #373b40;":"background-color:#fff; color: #ddd;";
+       </script>
        <nav class="navbar navbar-default navbar-fixed-top" role="banner">
                <div class="container">
                        <nav id="mynetdata_nav" class="collapse navbar-collapse navbar-left hidden-sm hidden-xs" role="navigation" style="padding-right: 20px;">
                </div>
        </div>
 
-<script>
+<script type="text/javascript">
 var this_is_demo = null;
 function isdemo() {
        if(this_is_demo !== null) return this_is_demo;
@@ -1021,6 +1129,31 @@ function isdemo() {
 if(isdemo()) {
        document.getElementById('masthead').style.display = 'block';
 }
+
+function netdataURL(url) {
+       if(typeof url === 'undefined')
+               url = document.location.toString();
+
+       if(url.indexOf('#') !== -1)
+               url = url.substring(0, url.indexOf('#'));
+
+       var hash = netdataHash();
+
+       // console.log('netdataURL: ' + url + hash);
+
+       return url + hash;
+}
+
+function netdataReload(url) {
+       var t = netdataURL(url);
+       // console.log('netdataReload: ' + t);
+       document.location = t;
+
+       // since we play with hash
+       // this is needed to reload the page
+       location.reload();
+}
+
 var gotoServerValidateRemaining = 0;
 var gotoServerMiddleClick = false;
 var gotoServerStop = false;
@@ -1031,25 +1164,26 @@ function gotoServerValidateUrl(id, guid, url) {
                        // to allow the user walk through all its servers.
                        penaldy = 500;
 
+       var finalURL = netdataURL(url);
+
        setTimeout(function() {
-               document.getElementById('gotoServerList').innerHTML += '<tr><td style="padding-left: 20px;"><a href="' + url + '" target="_blank">' + url + '</a></td><td style="padding-left: 30px;"><code id="' + guid + '-' + id + '-status">checking...</code></td></tr>';
+               document.getElementById('gotoServerList').innerHTML += '<tr><td style="padding-left: 20px;"><a href="' + finalURL + '" target="_blank">' + url + '</a></td><td style="padding-left: 30px;"><code id="' + guid + '-' + id + '-status">checking...</code></td></tr>';
 
                NETDATA.registry.hello(url, function(data) {
                        if (data) {
                                // console.log('OK ' + id + ' URL: ' + url);
                                document.getElementById(guid + '-' + id + '-status').innerHTML = "OK";
-                               var hash = document.location.hash || '';
 
                                if(!gotoServerStop) {
                                        gotoServerStop = true;
 
                                        if(gotoServerMiddleClick) {
-                                               window.open(url + hash, '_blank');
+                                               window.open(finalURL, '_blank');
                                                gotoServerMiddleClick = false;
-                                               document.getElementById('gotoServerResponse').innerHTML = '<b>Opening new window to ' + NETDATA.registry.machines[guid].name + '<br/><a href="' + url + hash + '">' + url + hash + '</a></b><br/>(check your pop-up blocker if it fails)';
+                                               document.getElementById('gotoServerResponse').innerHTML = '<b>Opening new window to ' + NETDATA.registry.machines[guid].name + '<br/><a href="' + finalURL + '">' + url + '</a></b><br/>(check your pop-up blocker if it fails)';
                                        }
                                        else
-                                               document.location = url + hash;
+                                               document.location = finalURL;
                                }
                        }
                        else {
@@ -1372,6 +1506,11 @@ var menuData = {
                info: undefined
        },
 
+       'ipfs': {
+               title: 'IPFS',
+               info: undefined
+       },
+
        'phpfpm': {
                title: 'PHP-FPM',
                info: undefined,
@@ -1880,6 +2019,7 @@ function enrichChartData(chart) {
                case 'phpfpm':
                case 'postfix':
                case 'redis':
+               case 'ipfs':
                case 'squid':
                case 'snmp':
                case 'tomcat':
@@ -2209,6 +2349,8 @@ function downloadAllCharts(netdata_url) {
                netdata_url = NETDATA.serverDefault;
 
        NETDATA.pause(function() {
+               $("#loadOverlay").css("display","none");
+               $("#loadOverlay").css("display","none");
 
                // download all the charts the server knows
                NETDATA.chartRegistry.downloadAll(netdata_url, function(data) {
@@ -2352,21 +2494,6 @@ function notifyForUpdate(force) {
 
 // ----------------------------------------------------------------------------
 
-function getUrlParameter(sParam) {
-    var sPageURL = decodeURIComponent(window.location.search.substring(1)),
-        sURLVariables = sPageURL.split('&'),
-        sParameterName,
-        i;
-
-    for (i = 0; i < sURLVariables.length; i++) {
-        sParameterName = sURLVariables[i].split('=');
-
-        if (sParameterName[0] === sParam) {
-            return sParameterName[1] === undefined ? true : sParameterName[1];
-        }
-    }
-}
-
 function finalizePage() {
        // resize all charts - without starting the background thread
        // this has to be done while NETDATA is paused
@@ -2374,14 +2501,9 @@ function finalizePage() {
        // the Dom elements are initially zero-sized
        NETDATA.parseDom();
 
-       var before = 0, after = 0, nowelcome = 0;
-       after = getUrlParameter('force_after_ms');
-       before = getUrlParameter('force_before_ms');
-       nowelcome = (getUrlParameter('nowelcome') === true)?true:false;
-
-       if(before > 0 && after > 0) {
-               nowelcome = true;
-               NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], after, before);
+       if(urlOptions.pan_and_zoom === true) {
+               urlOptions.nowelcome = true;
+               NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], urlOptions.after, urlOptions.before);
        }
 
        // ------------------------------------------------------------------------
@@ -2519,11 +2641,14 @@ function finalizePage() {
        $(".chart-message").shorten();
        // ------------------------------------------------------------------------
 
+       // callback for us to track PanAndZoom operations
+       NETDATA.globalPanAndZoom.callback = netdataPanAndZoomCallback;
+
        // let it run (update the charts)
        NETDATA.unpause();
 
        // check if we have to jump to a specific section
-       scrollToId(location.hash.replace('#',''));
+       scrollToId(urlOptions.hash.replace('#',''));
 
        /* activate bootstrap sidebar (affix) */
        $('#sidebar').affix({
@@ -2549,8 +2674,14 @@ function finalizePage() {
        // change the URL based on the current position of the screen
        $('#sidebar').on('activate.bs.scrollspy', function (e) {
                var el = $(e.target);
-               if(el.find('ul').size() == 0)
-                       history.replaceState(null, document.title, el.find('a').attr('href'));
+               if(el.find('ul').size() == 0) {
+                       var hash = el.find('a').attr('href');
+                       if(typeof hash === 'string' && hash.substring(0, 1) == '#') {
+                               urlOptions.hash = hash;
+                               // console.log(urlOptions.hash);
+                               netdataHashUpdate();
+                       }
+               }
        });
 
        document.getElementById('footer').style.display = 'block';
@@ -2604,13 +2735,18 @@ function finalizePage() {
        $('#stop_updates_when_focus_is_lost').change(function() { NETDATA.setOption('stop_updates_when_focus_is_lost', $(this).prop('checked')); });
        $('#smooth_plot').change(function()                     { NETDATA.setOption('smooth_plot', $(this).prop('checked')); });
        $('#pan_and_zoom_data_padding').change(function()       { NETDATA.setOption('pan_and_zoom_data_padding', $(this).prop('checked')); });
-       $('#show_help').change(function()                       { NETDATA.setOption('show_help', $(this).prop('checked')); location.reload(); });
+       $('#show_help').change(function()                       {
+               urlOptions.help = $(this).prop('checked');
+               NETDATA.setOption('show_help', urlOptions.help);
+               netdataReload();
+       });
 
        // this has to be the last
        // it reloads the page
        $('#netdata_theme_control').change(function() {
-               if(setTheme($(this).prop('checked')?'slate':'white'))
-                       location.reload();
+               urlOptions.theme = $(this).prop('checked')?'slate':'white';
+               if(setTheme(urlOptions.theme))
+                       netdataReload();
        });
 
        $('#updateModal').on('shown.bs.modal', function() {
@@ -2622,7 +2758,7 @@ function finalizePage() {
        });
 
        if(isdemo()) {
-               if(!nowelcome) {
+               if(!urlOptions.nowelcome) {
                        setTimeout(function() {
                                $('#welcomeModal').modal();
                        }, 1000);
@@ -2648,10 +2784,10 @@ function resetDashboardOptions() {
 
        NETDATA.resetOptions();
        if(setTheme('slate'))
-               location.reload();
+               netdataReload();
 
        if(help !== NETDATA.options.current.show_help)
-               location.reload();
+               netdataReload();
 }
 
 downloadAllCharts();