summaryrefslogtreecommitdiffstats
path: root/daemon
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 11:08:07 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 11:08:07 +0000
commitc69cb8cc094cc916adbc516b09e944cd3d137c01 (patch)
treef2878ec41fb6d0e3613906c6722fc02b934eeb80 /daemon
parentInitial commit. (diff)
downloadnetdata-upstream/1.29.3.tar.xz
netdata-upstream/1.29.3.zip
Adding upstream version 1.29.3.upstream/1.29.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--daemon/Makefile.am23
-rw-r--r--daemon/README.md554
-rwxr-xr-xdaemon/anonymous-statistics.sh.in104
-rw-r--r--daemon/buildinfo.c229
-rw-r--r--daemon/buildinfo.h8
-rw-r--r--daemon/commands.c726
-rw-r--r--daemon/commands.h81
-rw-r--r--daemon/common.c19
-rw-r--r--daemon/common.h101
-rw-r--r--daemon/config/README.md229
-rw-r--r--daemon/daemon.c501
-rw-r--r--daemon/daemon.h18
-rw-r--r--daemon/get-kubernetes-labels.sh.in41
-rw-r--r--daemon/global_statistics.c950
-rw-r--r--daemon/global_statistics.h23
-rw-r--r--daemon/main.c1532
-rw-r--r--daemon/main.h48
-rw-r--r--daemon/signals.c285
-rw-r--r--daemon/signals.h13
-rwxr-xr-xdaemon/system-info.sh406
-rw-r--r--daemon/unit_test.c2235
-rw-r--r--daemon/unit_test.h19
22 files changed, 8145 insertions, 0 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
new file mode 100644
index 0000000..d3102f6
--- /dev/null
+++ b/daemon/Makefile.am
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = \
+ anonymous-statistics.sh \
+ $(NULL)
+
+include $(top_srcdir)/build/subst.inc
+SUFFIXES = .in
+
+dist_noinst_DATA = \
+ README.md \
+ config/README.md \
+ anonymous-statistics.sh.in \
+ get-kubernetes-labels.sh.in \
+ $(NULL)
+
+dist_plugins_SCRIPTS = \
+ anonymous-statistics.sh \
+ system-info.sh \
+ get-kubernetes-labels.sh \
+ $(NULL)
diff --git a/daemon/README.md b/daemon/README.md
new file mode 100644
index 0000000..ec1f1c7
--- /dev/null
+++ b/daemon/README.md
@@ -0,0 +1,554 @@
+<!--
+title: "Netdata daemon"
+date: 2020-04-29
+custom_edit_url: https://github.com/netdata/netdata/edit/master/daemon/README.md
+-->
+
+# Netdata daemon
+
+## Starting netdata
+
+- You can start Netdata by executing it with `/usr/sbin/netdata` (the installer will also start it).
+
+- You can stop Netdata by killing it with `killall netdata`. You can stop and start Netdata at any point. When
+ exiting, the [database engine](/database/engine/README.md) saves metrics to `/var/cache/netdata/dbengine/` so that
+ it can continue when started again.
+
+Access to the web site, for all graphs, is by default on port `19999`, so go to:
+
+```sh
+http://127.0.0.1:19999/
+```
+
+You can get the running config file at any time, by accessing `http://127.0.0.1:19999/netdata.conf`.
+
+### Starting Netdata at boot
+
+In the `system` directory you can find scripts and configurations for the
+various distros.
+
+#### systemd
+
+The installer already installs `netdata.service` if it detects a systemd system.
+
+To install `netdata.service` by hand, run:
+
+```sh
+# stop Netdata
+killall netdata
+
+# copy netdata.service to systemd
+cp system/netdata.service /etc/systemd/system/
+
+# let systemd know there is a new service
+systemctl daemon-reload
+
+# enable Netdata at boot
+systemctl enable netdata
+
+# start Netdata
+systemctl start netdata
+```
+
+#### init.d
+
+In the system directory you can find `netdata-lsb`. Copy it to the proper place according to your distribution
+documentation. For Ubuntu, this can be done via running the following commands as root.
+
+```sh
+# copy the Netdata startup file to /etc/init.d
+cp system/netdata-lsb /etc/init.d/netdata
+
+# make sure it is executable
+chmod +x /etc/init.d/netdata
+
+# enable it
+update-rc.d netdata defaults
+```
+
+#### openrc (gentoo)
+
+In the `system` directory you can find `netdata-openrc`. Copy it to the proper
+place according to your distribution documentation.
+
+#### CentOS / Red Hat Enterprise Linux
+
+For older versions of RHEL/CentOS that don't have systemd, an init script is included in the system directory. This can
+be installed by running the following commands as root.
+
+```sh
+# copy the Netdata startup file to /etc/init.d
+cp system/netdata-init-d /etc/init.d/netdata
+
+# make sure it is executable
+chmod +x /etc/init.d/netdata
+
+# enable it
+chkconfig --add netdata
+```
+
+_There have been some recent work on the init script, see PR
+<https://github.com/netdata/netdata/pull/403>_
+
+#### other systems
+
+You can start Netdata by running it from `/etc/rc.local` or equivalent.
+
+## Command line options
+
+Normally you don't need to supply any command line arguments to netdata.
+
+If you do though, they override the configuration equivalent options.
+
+To get a list of all command line parameters supported, run:
+
+```sh
+netdata -h
+```
+
+The program will print the supported command line parameters.
+
+The command line options of the Netdata 1.10.0 version are the following:
+
+```sh
+ ^
+ |.-. .-. .-. .-. . netdata
+ | '-' '-' '-' '-' real-time performance monitoring, done right!
+ +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--->
+
+ Copyright (C) 2016-2020, Netdata, Inc. <info@netdata.cloud>
+ Released under GNU General Public License v3 or later.
+ All rights reserved.
+
+ Home Page : https://netdata.cloud
+ Source Code: https://github.com/netdata/netdata
+ Docs : https://learn.netdata.cloud
+ Support : https://github.com/netdata/netdata/issues
+ License : https://github.com/netdata/netdata/blob/master/LICENSE.md
+
+ Twitter : https://twitter.com/linuxnetdata
+ Facebook : https://www.facebook.com/linuxnetdata/
+
+
+ SYNOPSIS: netdata [options]
+
+ Options:
+
+ -c filename Configuration file to load.
+ Default: /etc/netdata/netdata.conf
+
+ -D Do not fork. Run in the foreground.
+ Default: run in the background
+
+ -h Display this help message.
+
+ -P filename File to save a pid while running.
+ Default: do not save pid to a file
+
+ -i IP The IP address to listen to.
+ Default: all IP addresses IPv4 and IPv6
+
+ -p port API/Web port to use.
+ Default: 19999
+
+ -s path Prefix for /proc and /sys (for containers).
+ Default: no prefix
+
+ -t seconds The internal clock of netdata.
+ Default: 1
+
+ -u username Run as user.
+ Default: netdata
+
+ -v Print netdata version and exit.
+
+ -V Print netdata version and exit.
+
+ -W options See Advanced options below.
+
+
+ Advanced options:
+
+ -W stacksize=N Set the stacksize (in bytes).
+
+ -W debug_flags=N Set runtime tracing to debug.log.
+
+ -W unittest Run internal unittests and exit.
+
+ -W createdataset=N Create a DB engine dataset of N seconds and exit.
+
+ -W set section option value
+ set netdata.conf option from the command line.
+
+ -W simple-pattern pattern string
+ Check if string matches pattern and exit.
+
+ -W "claim -token=TOKEN -rooms=ROOM1,ROOM2 url=https://app.netdata.cloud"
+ Claim the agent to the workspace rooms pointed to by TOKEN and ROOM*.
+
+ Signals netdata handles:
+
+ - HUP Close and reopen log files.
+ - USR1 Save internal DB to disk.
+ - USR2 Reload health configuration.
+```
+
+You can send commands during runtime via [netdatacli](/cli/README.md).
+
+## Log files
+
+Netdata uses 3 log files:
+
+1. `error.log`
+2. `access.log`
+3. `debug.log`
+
+Any of them can be disabled by setting it to `/dev/null` or `none` in `netdata.conf`. By default `error.log` and
+`access.log` are enabled. `debug.log` is only enabled if debugging/tracing is also enabled (Netdata needs to be compiled
+with debugging enabled).
+
+Log files are stored in `/var/log/netdata/` by default.
+
+### error.log
+
+The `error.log` is the `stderr` of the `netdata` daemon and all external plugins
+run by `netdata`.
+
+So if any process, in the Netdata process tree, writes anything to its standard error,
+it will appear in `error.log`.
+
+For most Netdata programs (including standard external plugins shipped by netdata), the following lines may appear:
+
+| tag | description |
+|:-:|:----------|
+| `INFO` | Something important the user should know. |
+| `ERROR` | Something that might disable a part of netdata.<br/>The log line includes `errno` (if it is not zero). |
+| `FATAL` | Something prevented a program from running.<br/>The log line includes `errno` (if it is not zero) and the program exited. |
+
+So, when auto-detection of data collection fail, `ERROR` lines are logged and the relevant modules are disabled, but the
+program continues to run.
+
+When a Netdata program cannot run at all, a `FATAL` line is logged.
+
+### access.log
+
+The `access.log` logs web requests. The format is:
+
+```txt
+DATE: ID: (sent/all = SENT_BYTES/ALL_BYTES bytes PERCENT_COMPRESSION%, prep/sent/total PREP_TIME/SENT_TIME/TOTAL_TIME ms): ACTION CODE URL
+```
+
+where:
+
+- `ID` is the client ID. Client IDs are auto-incremented every time a client connects to netdata.
+- `SENT_BYTES` is the number of bytes sent to the client, without the HTTP response header.
+- `ALL_BYTES` is the number of bytes of the response, before compression.
+- `PERCENT_COMPRESSION` is the percentage of traffic saved due to compression.
+- `PREP_TIME` is the time in milliseconds needed to prepared the response.
+- `SENT_TIME` is the time in milliseconds needed to sent the response to the client.
+- `TOTAL_TIME` is the total time the request was inside Netdata (from the first byte of the request to the last byte
+ of the response).
+- `ACTION` can be `filecopy`, `options` (used in CORS), `data` (API call).
+
+### debug.log
+
+See [debugging](#debugging).
+
+## OOM Score
+
+Netdata runs with `OOMScore = 1000`. This means Netdata will be the first to be killed when your server runs out of
+memory.
+
+You can set Netdata OOMScore in `netdata.conf`, like this:
+
+```conf
+[global]
+ OOM score = 1000
+```
+
+Netdata logs its OOM score when it starts:
+
+```sh
+# grep OOM /var/log/netdata/error.log
+2017-10-15 03:47:31: netdata INFO : Adjusted my Out-Of-Memory (OOM) score from 0 to 1000.
+```
+
+### OOM score and systemd
+
+Netdata will not be able to lower its OOM Score below zero, when it is started as the `netdata` user (systemd case).
+
+To allow Netdata control its OOM Score in such cases, you will need to edit `netdata.service` and set:
+
+```sh
+[Service]
+# The minimum Netdata Out-Of-Memory (OOM) score.
+# Netdata (via [global].OOM score in netdata.conf) can only increase the value set here.
+# To decrease it, set the minimum here and set the same or a higher value in netdata.conf.
+# Valid values: -1000 (never kill netdata) to 1000 (always kill netdata).
+OOMScoreAdjust=-1000
+```
+
+Run `systemctl daemon-reload` to reload these changes.
+
+The above, sets and OOMScore for Netdata to `-1000`, so that Netdata can increase it via `netdata.conf`.
+
+If you want to control it entirely via systemd, you can set in `netdata.conf`:
+
+```conf
+[global]
+ OOM score = keep
+```
+
+Using the above, whatever OOM Score you have set at `netdata.service` will be maintained by netdata.
+
+## Netdata process scheduling policy
+
+By default Netdata runs with the `idle` process scheduling policy, so that it uses CPU resources, only when there is
+idle CPU to spare. On very busy servers (or weak servers), this can lead to gaps on the charts.
+
+You can set Netdata scheduling policy in `netdata.conf`, like this:
+
+```conf
+[global]
+ process scheduling policy = idle
+```
+
+You can use the following:
+
+| policy | description |
+| :-----------------------: | :---------- |
+| `idle` | use CPU only when there is spare - this is lower than nice 19 - it is the default for Netdata and it is so low that Netdata will run in "slow motion" under extreme system load, resulting in short (1-2 seconds) gaps at the charts. |
+| `other`<br/>or<br/>`nice` | this is the default policy for all processes under Linux. It provides dynamic priorities based on the `nice` level of each process. Check below for setting this `nice` level for netdata. |
+| `batch` | This policy is similar to `other` in that it schedules the thread according to its dynamic priority (based on the `nice` value). The difference is that this policy will cause the scheduler to always assume that the thread is CPU-intensive. Consequently, the scheduler will apply a small scheduling penalty with respect to wake-up behavior, so that this thread is mildly disfavored in scheduling decisions. |
+| `fifo` | `fifo` can be used only with static priorities higher than 0, which means that when a `fifo` threads becomes runnable, it will always immediately preempt any currently running `other`, `batch`, or `idle` thread. `fifo` is a simple scheduling algorithm without time slicing. |
+| `rr` | a simple enhancement of `fifo`. Everything described above for `fifo` also applies to `rr`, except that each thread is allowed to run only for a maximum time quantum. |
+| `keep`<br/>or<br/>`none` | do not set scheduling policy, priority or nice level - i.e. keep running with whatever it is set already (e.g. by systemd). |
+
+For more information see `man sched`.
+
+### scheduling priority for `rr` and `fifo`
+
+Once the policy is set to one of `rr` or `fifo`, the following will appear:
+
+```conf
+[global]
+ process scheduling priority = 0
+```
+
+These priorities are usually from 0 to 99. Higher numbers make the process more
+important.
+
+### nice level for policies `other` or `batch`
+
+When the policy is set to `other`, `nice`, or `batch`, the following will appear:
+
+```conf
+[global]
+ process nice level = 19
+```
+
+## scheduling settings and systemd
+
+Netdata will not be able to set its scheduling policy and priority to more important values when it is started as the
+`netdata` user (systemd case).
+
+You can set these settings at `/etc/systemd/system/netdata.service`:
+
+```sh
+[Service]
+# By default Netdata switches to scheduling policy idle, which makes it use CPU, only
+# when there is spare available.
+# Valid policies: other (the system default) | batch | idle | fifo | rr
+#CPUSchedulingPolicy=other
+
+# This sets the maximum scheduling priority Netdata can set (for policies: rr and fifo).
+# Netdata (via [global].process scheduling priority in netdata.conf) can only lower this value.
+# Priority gets values 1 (lowest) to 99 (highest).
+#CPUSchedulingPriority=1
+
+# For scheduling policy 'other' and 'batch', this sets the lowest niceness of netdata.
+# Netdata (via [global].process nice level in netdata.conf) can only increase the value set here.
+#Nice=0
+```
+
+Run `systemctl daemon-reload` to reload these changes.
+
+Now, tell Netdata to keep these settings, as set by systemd, by editing
+`netdata.conf` and setting:
+
+```conf
+[global]
+ process scheduling policy = keep
+```
+
+Using the above, whatever scheduling settings you have set at `netdata.service`
+will be maintained by netdata.
+
+### Example 1: Netdata with nice -1 on non-systemd systems
+
+On a system that is not based on systemd, to make Netdata run with nice level -1 (a little bit higher to the default for
+all programs), edit `netdata.conf` and set:
+
+```conf
+[global]
+ process scheduling policy = other
+ process nice level = -1
+```
+
+then execute this to restart netdata:
+
+```sh
+sudo service netdata restart
+```
+
+#### Example 2: Netdata with nice -1 on systemd systems
+
+On a system that is based on systemd, to make Netdata run with nice level -1 (a little bit higher to the default for all
+programs), edit `netdata.conf` and set:
+
+```conf
+[global]
+ process scheduling policy = keep
+```
+
+edit /etc/systemd/system/netdata.service and set:
+
+```sh
+[Service]
+CPUSchedulingPolicy=other
+Nice=-1
+```
+
+then execute:
+
+```sh
+sudo systemctl daemon-reload
+sudo systemctl restart netdata
+```
+
+## Virtual memory
+
+You may notice that netdata's virtual memory size, as reported by `ps` or `/proc/pid/status` (or even netdata's
+applications virtual memory chart) is unrealistically high.
+
+For example, it may be reported to be 150+MB, even if the resident memory size is just 25MB. Similar values may be
+reported for Netdata plugins too.
+
+Check this for example: A Netdata installation with default settings on Ubuntu
+16.04LTS. The top chart is **real memory used**, while the bottom one is
+**virtual memory**:
+
+![image](https://cloud.githubusercontent.com/assets/2662304/19013772/5eb7173e-87e3-11e6-8f2b-a2ccfeb06faf.png)
+
+### Why does this happen?
+
+The system memory allocator allocates virtual memory arenas, per thread running. On Linux systems this defaults to 16MB
+per thread on 64 bit machines. So, if you get the difference between real and virtual memory and divide it by 16MB you
+will roughly get the number of threads running.
+
+The system does this for speed. Having a separate memory arena for each thread, allows the threads to run in parallel in
+multi-core systems, without any locks between them.
+
+This behaviour is system specific. For example, the chart above when running
+Netdata on Alpine Linux (that uses **musl** instead of **glibc**) is this:
+
+![image](https://cloud.githubusercontent.com/assets/2662304/19013807/7cf5878e-87e4-11e6-9651-082e68701eab.png)
+
+### Can we do anything to lower it?
+
+Since Netdata already uses minimal memory allocations while it runs (i.e. it adapts its memory on start, so that while
+repeatedly collects data it does not do memory allocations), it already instructs the system memory allocator to
+minimize the memory arenas for each thread. We have also added [2 configuration
+options](https://github.com/netdata/netdata/blob/5645b1ee35248d94e6931b64a8688f7f0d865ec6/src/main.c#L410-L418) to allow
+you tweak these settings: `glibc malloc arena max for plugins` and `glibc malloc arena max for netdata`.
+
+However, even if we instructed the memory allocator to use just one arena, it
+seems it allocates an arena per thread.
+
+Netdata also supports `jemalloc` and `tcmalloc`, however both behave exactly the
+same to the glibc memory allocator in this aspect.
+
+### Is this a problem?
+
+No, it is not.
+
+Linux reserves real memory (physical RAM) in pages (on x86 machines pages are 4KB each). So even if the system memory
+allocator is allocating huge amounts of virtual memory, only the 4KB pages that are actually used are reserving physical
+RAM. The **real memory** chart on Netdata application section, shows the amount of physical memory these pages occupy(it
+accounts the whole pages, even if parts of them are actually used).
+
+## Debugging
+
+When you compile Netdata with debugging:
+
+1. compiler optimizations for your CPU are disabled (Netdata will run somewhat slower)
+
+2. a lot of code is added all over netdata, to log debug messages to `/var/log/netdata/debug.log`. However, nothing is
+ printed by default. Netdata allows you to select which sections of Netdata you want to trace. Tracing is activated
+ via the config option `debug flags`. It accepts a hex number, to enable or disable specific sections. You can find
+ the options supported at [log.h](https://raw.githubusercontent.com/netdata/netdata/master/libnetdata/log/log.h).
+ They are the `D_*` defines. The value `0xffffffffffffffff` will enable all possible debug flags.
+
+Once Netdata is compiled with debugging and tracing is enabled for a few sections, the file `/var/log/netdata/debug.log`
+will contain the messages.
+
+> Do not forget to disable tracing (`debug flags = 0`) when you are done tracing. The file `debug.log` can grow too
+> fast.
+
+### compiling Netdata with debugging
+
+To compile Netdata with debugging, use this:
+
+```sh
+# step into the Netdata source directory
+cd /usr/src/netdata.git
+
+# run the installer with debugging enabled
+CFLAGS="-O1 -ggdb -DNETDATA_INTERNAL_CHECKS=1" ./netdata-installer.sh
+```
+
+The above will compile and install Netdata with debugging info embedded. You can now use `debug flags` to set the
+section(s) you need to trace.
+
+### debugging crashes
+
+We have made the most to make Netdata crash free. If however, Netdata crashes on your system, it would be very helpful
+to provide stack traces of the crash. Without them, is will be almost impossible to find the issue (the code base is
+quite large to find such an issue by just observing it).
+
+To provide stack traces, **you need to have Netdata compiled with debugging**. There is no need to enable any tracing
+(`debug flags`).
+
+Then you need to be in one of the following 2 cases:
+
+1. Netdata crashes and you have a core dump
+
+2. you can reproduce the crash
+
+If you are not on these cases, you need to find a way to be (i.e. if your system does not produce core dumps, check your
+distro documentation to enable them).
+
+### Netdata crashes and you have a core dump
+
+> you need to have Netdata compiled with debugging info for this to work (check above)
+
+Run the following command and post the output on a github issue.
+
+```sh
+gdb $(which netdata) /path/to/core/dump
+```
+
+### you can reproduce a Netdata crash on your system
+
+> you need to have Netdata compiled with debugging info for this to work (check above)
+
+Install the package `valgrind` and run:
+
+```sh
+valgrind $(which netdata) -D
+```
+
+Netdata will start and it will be a lot slower. Now reproduce the crash and `valgrind` will dump on your console the
+stack trace. Open a new github issue and post the output.
+
+[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdaemon%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/daemon/anonymous-statistics.sh.in b/daemon/anonymous-statistics.sh.in
new file mode 100755
index 0000000..f0d9c10
--- /dev/null
+++ b/daemon/anonymous-statistics.sh.in
@@ -0,0 +1,104 @@
+#!/usr/bin/env sh
+
+# Valid actions:
+
+# - FATAL - netdata exited due to a fatal condition
+# ACTION_RESULT -- program name and thread tag
+# ACTION_DATA -- fmt, args passed to fatal
+# - START - netdata started
+# ACTION_DATA -- nan
+# - EXIT - installation action
+# ACTION_DATA -- ret value of
+
+ACTION="${1}"
+ACTION_RESULT="${2}"
+ACTION_DATA="${3}"
+ACTION_DATA=$(echo "${ACTION_DATA}" | tr '"' "'")
+
+# -------------------------------------------------------------------------------------------------
+# check opt-out
+
+if [ -f "@configdir_POST@/.opt-out-from-anonymous-statistics" ] || [ ! "${DO_NOT_TRACK:-0}" -eq 0 ] || [ -n "$DO_NOT_TRACK" ]; then
+ exit 0
+fi
+
+# Shorten version for easier reporting
+NETDATA_VERSION=$(echo "${NETDATA_VERSION}" | sed 's/-.*//g' | tr -d 'v')
+
+# -------------------------------------------------------------------------------------------------
+# send the anonymous statistics to GA
+# https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
+# The maximum index for a cd parameter is 20 so we have effectively run out.
+if [ -n "$(command -v curl 2> /dev/null)" ]; then
+ curl -X POST -Ss --max-time 2 \
+ --data "v=1" \
+ --data "tid=UA-64295674-3" \
+ --data "aip=1" \
+ --data "ds=shell" \
+ --data-urlencode "cid=${NETDATA_REGISTRY_UNIQUE_ID}" \
+ --data-urlencode "cs=${NETDATA_REGISTRY_UNIQUE_ID}" \
+ --data "t=event" \
+ --data "ni=1" \
+ --data "an=anonymous-statistics" \
+ --data-urlencode "av=${NETDATA_VERSION}" \
+ --data-urlencode "ec=${ACTION}" \
+ --data-urlencode "ea=${ACTION_RESULT}" \
+ --data-urlencode "el=${ACTION_DATA}" \
+ --data-urlencode "cd1=${NETDATA_HOST_OS_NAME}" \
+ --data-urlencode "cd2=${NETDATA_HOST_OS_ID}" \
+ --data-urlencode "cd3=${NETDATA_HOST_OS_ID_LIKE}" \
+ --data-urlencode "cd4=${NETDATA_HOST_OS_VERSION}" \
+ --data-urlencode "cd5=${NETDATA_HOST_OS_VERSION_ID}" \
+ --data-urlencode "cd6=${NETDATA_HOST_OS_DETECTION}" \
+ --data-urlencode "cd7=${NETDATA_SYSTEM_KERNEL_NAME}" \
+ --data-urlencode "cd8=${NETDATA_SYSTEM_KERNEL_VERSION}" \
+ --data-urlencode "cd9=${NETDATA_SYSTEM_ARCHITECTURE}" \
+ --data-urlencode "cd10=${NETDATA_SYSTEM_VIRTUALIZATION}" \
+ --data-urlencode "cd11=${NETDATA_SYSTEM_VIRT_DETECTION}" \
+ --data-urlencode "cd12=${NETDATA_SYSTEM_CONTAINER}" \
+ --data-urlencode "cd13=${NETDATA_SYSTEM_CONTAINER_DETECTION}" \
+ --data-urlencode "cd14=${NETDATA_CONTAINER_OS_NAME}" \
+ --data-urlencode "cd15=${NETDATA_CONTAINER_OS_ID}" \
+ --data-urlencode "cd16=${NETDATA_CONTAINER_OS_ID_LIKE}" \
+ --data-urlencode "cd17=${NETDATA_CONTAINER_OS_VERSION}" \
+ --data-urlencode "cd18=${NETDATA_CONTAINER_OS_VERSION_ID}" \
+ --data-urlencode "cd19=${NETDATA_CONTAINER_OS_DETECTION}" \
+ --data-urlencode "cd20=${NETDATA_HOST_IS_K8S_NODE}" \
+ "https://www.google-analytics.com/collect" > /dev/null 2>&1
+else
+ wget -q -O - --timeout=1 "https://www.google-analytics.com/collect?\
+&v=1\
+&tid=UA-64295674-3\
+&aip=1\
+&ds=shell\
+&cid=${NETDATA_REGISTRY_UNIQUE_ID}\
+&cs=${NETDATA_REGISTRY_UNIQUE_ID}\
+&t=event\
+&ni=1\
+&an=anonymous-statistics\
+&av=${NETDATA_VERSION}\
+&ec=${ACTION}\
+&ea=${ACTION_RESULT}\
+&el=${ACTION_DATA}\
+&cd1=${NETDATA_HOST_OS_NAME}\
+&cd2=${NETDATA_HOST_OS_ID}\
+&cd3=${NETDATA_HOST_OS_ID_LIKE}\
+&cd4=${NETDATA_HOST_OS_VERSION}\
+&cd5=${NETDATA_HOST_OS_VERSION_ID}\
+&cd6=${NETDATA_HOST_OS_DETECTION}\
+&cd7=${NETDATA_SYSTEM_KERNEL_NAME}\
+&cd8=${NETDATA_SYSTEM_KERNEL_VERSION}\
+&cd9=${NETDATA_SYSTEM_ARCHITECTURE}\
+&cd10=${NETDATA_SYSTEM_VIRTUALIZATION}\
+&cd11=${NETDATA_SYSTEM_VIRT_DETECTION}\
+&cd12=${NETDATA_SYSTEM_CONTAINER}\
+&cd13=${NETDATA_SYSTEM_CONTAINER_DETECTION}\
+&cd14=${NETDATA_CONTAINER_OS_NAME} \
+&cd15=${NETDATA_CONTAINER_OS_ID} \
+&cd16=${NETDATA_CONTAINER_OS_ID_LIKE} \
+&cd17=${NETDATA_CONTAINER_OS_VERSION} \
+&cd18=${NETDATA_CONTAINER_OS_VERSION_ID} \
+&cd19=${NETDATA_CONTAINER_OS_DETECTION} \
+&cd20=${NETDATA_HOST_IS_K8S_NODE} \
+" > /dev/null 2>&1
+fi
diff --git a/daemon/buildinfo.c b/daemon/buildinfo.c
new file mode 100644
index 0000000..de02a72
--- /dev/null
+++ b/daemon/buildinfo.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <stdio.h>
+#include "./config.h"
+
+// Optional features
+
+#ifdef ENABLE_ACLK
+#define FEAT_CLOUD "YES"
+#else
+#ifdef DISABLE_CLOUD
+#define FEAT_CLOUD "NO (by user request e.g. '--disable-cloud')"
+#else
+#define FEAT_CLOUD "NO"
+#endif
+#endif
+
+#ifdef ENABLE_DBENGINE
+#define FEAT_DBENGINE "YES"
+#else
+#define FEAT_DBENGINE "NO"
+#endif
+
+#if defined(HAVE_X509_VERIFY_PARAM_set1_host) && HAVE_X509_VERIFY_PARAM_set1_host == 1
+#define FEAT_TLS_HOST_VERIFY "YES"
+#else
+#define FEAT_TLS_HOST_VERIFY "NO"
+#endif
+
+#ifdef ENABLE_HTTPS
+#define FEAT_NATIVE_HTTPS "YES"
+#else
+#define FEAT_NATIVE_HTTPS "NO"
+#endif
+
+// Optional libraries
+
+#ifdef ENABLE_JSONC
+#define FEAT_JSONC "YES"
+#else
+#define FEAT_JSONC "NO"
+#endif
+
+#ifdef ENABLE_JEMALLOC
+#define FEAT_JEMALLOC "YES"
+#else
+#define FEAT_JEMALLOC "NO"
+#endif
+
+#ifdef ENABLE_TCMALLOC
+#define FEAT_TCMALLOC "YES"
+#else
+#define FEAT_TCMALLOC "NO"
+#endif
+
+#ifdef HAVE_CAPABILITY
+#define FEAT_LIBCAP "YES"
+#else
+#define FEAT_LIBCAP "NO"
+#endif
+
+#ifdef ACLK_NO_LIBMOSQ
+#define FEAT_MOSQUITTO "NO"
+#else
+#define FEAT_MOSQUITTO "YES"
+#endif
+
+#ifdef ACLK_NO_LWS
+#define FEAT_LWS "NO"
+#else
+#ifdef ENABLE_ACLK
+#include <libwebsockets.h>
+#endif
+#ifdef BUNDLED_LWS
+#define FEAT_LWS "YES static"
+#else
+#define FEAT_LWS "YES shared-lib"
+#endif
+#endif
+
+#ifdef NETDATA_WITH_ZLIB
+#define FEAT_ZLIB "YES"
+#else
+#define FEAT_ZLIB "NO"
+#endif
+
+#ifdef STORAGE_WITH_MATH
+#define FEAT_LIBM "YES"
+#else
+#define FEAT_LIBM "NO"
+#endif
+
+#ifdef HAVE_CRYPTO
+#define FEAT_CRYPTO "YES"
+#else
+#define FEAT_CRYPTO "NO"
+#endif
+
+// Optional plugins
+
+#ifdef ENABLE_APPS_PLUGIN
+#define FEAT_APPS_PLUGIN "YES"
+#else
+#define FEAT_APPS_PLUGIN "NO"
+#endif
+
+#ifdef HAVE_FREEIPMI
+#define FEAT_IPMI "YES"
+#else
+#define FEAT_IPMI "NO"
+#endif
+
+#ifdef HAVE_CUPS
+#define FEAT_CUPS "YES"
+#else
+#define FEAT_CUPS "NO"
+#endif
+
+#ifdef HAVE_LIBMNL
+#define FEAT_NFACCT "YES"
+#else
+#define FEAT_NFACCT "NO"
+#endif
+
+#ifdef HAVE_LIBXENSTAT
+#define FEAT_XEN "YES"
+#else
+#define FEAT_XEN "NO"
+#endif
+
+#ifdef HAVE_XENSTAT_VBD_ERROR
+#define FEAT_XEN_VBD_ERROR "YES"
+#else
+#define FEAT_XEN_VBD_ERROR "NO"
+#endif
+
+#ifdef HAVE_LIBBPF
+#define FEAT_EBPF "YES"
+#else
+#define FEAT_EBPF "NO"
+#endif
+
+#ifdef HAVE_SETNS
+#define FEAT_CGROUP_NET "YES"
+#else
+#define FEAT_CGROUP_NET "NO"
+#endif
+
+#ifdef ENABLE_PERF_PLUGIN
+#define FEAT_PERF "YES"
+#else
+#define FEAT_PERF "NO"
+#endif
+
+#ifdef ENABLE_SLABINFO
+#define FEAT_SLABINFO "YES"
+#else
+#define FEAT_SLABINFO "NO"
+#endif
+
+// Optional Exporters
+
+#ifdef HAVE_KINESIS
+#define FEAT_KINESIS "YES"
+#else
+#define FEAT_KINESIS "NO"
+#endif
+
+#ifdef ENABLE_EXPORTING_PUBSUB
+#define FEAT_PUBSUB "YES"
+#else
+#define FEAT_PUBSUB "NO"
+#endif
+
+#ifdef HAVE_MONGOC
+#define FEAT_MONGO "YES"
+#else
+#define FEAT_MONGO "NO"
+#endif
+
+#ifdef ENABLE_PROMETHEUS_REMOTE_WRITE
+#define FEAT_REMOTE_WRITE "YES"
+#else
+#define FEAT_REMOTE_WRITE "NO"
+#endif
+
+
+void print_build_info(void) {
+ printf("Configure options: %s\n", CONFIGURE_COMMAND);
+
+ printf("Features:\n");
+ printf(" dbengine: %s\n", FEAT_DBENGINE);
+ printf(" Native HTTPS: %s\n", FEAT_NATIVE_HTTPS);
+ printf(" Netdata Cloud: %s\n", FEAT_CLOUD);
+ printf(" TLS Host Verification: %s\n", FEAT_TLS_HOST_VERIFY);
+
+ printf("Libraries:\n");
+ printf(" jemalloc: %s\n", FEAT_JEMALLOC);
+ printf(" JSON-C: %s\n", FEAT_JSONC);
+ printf(" libcap: %s\n", FEAT_LIBCAP);
+ printf(" libcrypto: %s\n", FEAT_CRYPTO);
+ printf(" libm: %s\n", FEAT_LIBM);
+#if defined(ENABLE_ACLK)
+ printf(" LWS: %s v%d.%d.%d\n", FEAT_LWS, LWS_LIBRARY_VERSION_MAJOR, LWS_LIBRARY_VERSION_MINOR, LWS_LIBRARY_VERSION_PATCH);
+#else
+ printf(" LWS: %s\n", FEAT_LWS);
+#endif
+ printf(" mosquitto: %s\n", FEAT_MOSQUITTO);
+ printf(" tcalloc: %s\n", FEAT_TCMALLOC);
+ printf(" zlib: %s\n", FEAT_ZLIB);
+
+ printf("Plugins:\n");
+ printf(" apps: %s\n", FEAT_APPS_PLUGIN);
+ printf(" cgroup Network Tracking: %s\n", FEAT_CGROUP_NET);
+ printf(" CUPS: %s\n", FEAT_CUPS);
+ printf(" EBPF: %s\n", FEAT_EBPF);
+ printf(" IPMI: %s\n", FEAT_IPMI);
+ printf(" NFACCT: %s\n", FEAT_NFACCT);
+ printf(" perf: %s\n", FEAT_PERF);
+ printf(" slabinfo: %s\n", FEAT_SLABINFO);
+ printf(" Xen: %s\n", FEAT_XEN);
+ printf(" Xen VBD Error Tracking: %s\n", FEAT_XEN_VBD_ERROR);
+
+ printf("Exporters:\n");
+ printf(" AWS Kinesis: %s\n", FEAT_KINESIS);
+ printf(" GCP PubSub: %s\n", FEAT_PUBSUB);
+ printf(" MongoDB: %s\n", FEAT_MONGO);
+ printf(" Prometheus Remote Write: %s\n", FEAT_REMOTE_WRITE);
+};
diff --git a/daemon/buildinfo.h b/daemon/buildinfo.h
new file mode 100644
index 0000000..76912ea
--- /dev/null
+++ b/daemon/buildinfo.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_BUILDINFO_H
+#define NETDATA_BUILDINFO_H 1
+
+extern void print_build_info(void);
+
+#endif // NETDATA_BUILDINFO_H
diff --git a/daemon/commands.c b/daemon/commands.c
new file mode 100644
index 0000000..eac392e
--- /dev/null
+++ b/daemon/commands.c
@@ -0,0 +1,726 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common.h"
+#include "../database/engine/rrdenginelib.h"
+
+static uv_thread_t thread;
+static uv_loop_t* loop;
+static uv_async_t async;
+static struct completion completion;
+static uv_pipe_t server_pipe;
+
+char cmd_prefix_by_status[] = {
+ CMD_PREFIX_INFO,
+ CMD_PREFIX_ERROR,
+ CMD_PREFIX_ERROR
+};
+
+static int command_server_initialized = 0;
+static int command_thread_error;
+static int command_thread_shutdown;
+static unsigned clients = 0;
+
+struct command_context {
+ /* embedded client pipe structure at address 0 */
+ uv_pipe_t client;
+
+ uv_work_t work;
+ uv_write_t write_req;
+ cmd_t idx;
+ char *args;
+ char *message;
+ cmd_status_t status;
+ char command_string[MAX_COMMAND_LENGTH];
+ unsigned command_string_size;
+};
+
+/* Forward declarations */
+static cmd_status_t cmd_help_execute(char *args, char **message);
+static cmd_status_t cmd_reload_health_execute(char *args, char **message);
+static cmd_status_t cmd_save_database_execute(char *args, char **message);
+static cmd_status_t cmd_reopen_logs_execute(char *args, char **message);
+static cmd_status_t cmd_exit_execute(char *args, char **message);
+static cmd_status_t cmd_fatal_execute(char *args, char **message);
+static cmd_status_t cmd_reload_claiming_state_execute(char *args, char **message);
+static cmd_status_t cmd_reload_labels_execute(char *args, char **message);
+static cmd_status_t cmd_read_config_execute(char *args, char **message);
+static cmd_status_t cmd_write_config_execute(char *args, char **message);
+static cmd_status_t cmd_ping_execute(char *args, char **message);
+
+static command_info_t command_info_array[] = {
+ {"help", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY}, // show help menu
+ {"reload-health", cmd_reload_health_execute, CMD_TYPE_ORTHOGONAL}, // reload health configuration
+ {"save-database", cmd_save_database_execute, CMD_TYPE_ORTHOGONAL}, // save database for memory mode save
+ {"reopen-logs", cmd_reopen_logs_execute, CMD_TYPE_ORTHOGONAL}, // Close and reopen log files
+ {"shutdown-agent", cmd_exit_execute, CMD_TYPE_EXCLUSIVE}, // exit cleanly
+ {"fatal-agent", cmd_fatal_execute, CMD_TYPE_HIGH_PRIORITY}, // exit with fatal error
+ {"reload-claiming-state", cmd_reload_claiming_state_execute, CMD_TYPE_ORTHOGONAL}, // reload claiming state
+ {"reload-labels", cmd_reload_labels_execute, CMD_TYPE_ORTHOGONAL}, // reload the labels
+ {"read-config", cmd_read_config_execute, CMD_TYPE_CONCURRENT},
+ {"write-config", cmd_write_config_execute, CMD_TYPE_ORTHOGONAL},
+ {"ping", cmd_ping_execute, CMD_TYPE_ORTHOGONAL}
+};
+
+/* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */
+static uv_mutex_t command_lock_array[CMD_TOTAL_COMMANDS];
+/* Commands of type CMD_TYPE_EXCLUSIVE are writers */
+static uv_rwlock_t exclusive_rwlock;
+/*
+ * Locking order:
+ * 1. exclusive_rwlock
+ * 2. command_lock_array[]
+ */
+
+/* Forward declarations */
+static void cmd_lock_exclusive(unsigned index);
+static void cmd_lock_orthogonal(unsigned index);
+static void cmd_lock_idempotent(unsigned index);
+static void cmd_lock_high_priority(unsigned index);
+
+static command_lock_t *cmd_lock_by_type[] = {
+ cmd_lock_exclusive,
+ cmd_lock_orthogonal,
+ cmd_lock_idempotent,
+ cmd_lock_high_priority
+};
+
+/* Forward declarations */
+static void cmd_unlock_exclusive(unsigned index);
+static void cmd_unlock_orthogonal(unsigned index);
+static void cmd_unlock_idempotent(unsigned index);
+static void cmd_unlock_high_priority(unsigned index);
+
+static command_lock_t *cmd_unlock_by_type[] = {
+ cmd_unlock_exclusive,
+ cmd_unlock_orthogonal,
+ cmd_unlock_idempotent,
+ cmd_unlock_high_priority
+};
+
+static cmd_status_t cmd_help_execute(char *args, char **message)
+{
+ (void)args;
+
+ *message = mallocz(MAX_COMMAND_LENGTH);
+ strncpyz(*message,
+ "\nThe commands are (arguments are in brackets):\n"
+ "help\n"
+ " Show this help menu.\n"
+ "reload-health\n"
+ " Reload health configuration.\n"
+ "reload-labels\n"
+ " Reload all labels.\n"
+ "save-database\n"
+ " Save internal DB to disk for memory mode save.\n"
+ "reopen-logs\n"
+ " Close and reopen log files.\n"
+ "shutdown-agent\n"
+ " Cleanup and exit the netdata agent.\n"
+ "fatal-agent\n"
+ " Log the state and halt the netdata agent.\n"
+ "reload-claiming-state\n"
+ " Reload agent claiming state from disk.\n"
+ "ping\n"
+ " Return with 'pong' if agent is alive.\n",
+ MAX_COMMAND_LENGTH - 1);
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_reload_health_execute(char *args, char **message)
+{
+ (void)args;
+ (void)message;
+
+ error_log_limit_unlimited();
+ info("COMMAND: Reloading HEALTH configuration.");
+ health_reload();
+ error_log_limit_reset();
+
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_save_database_execute(char *args, char **message)
+{
+ (void)args;
+ (void)message;
+
+ error_log_limit_unlimited();
+ info("COMMAND: Saving databases.");
+ rrdhost_save_all();
+ info("COMMAND: Databases saved.");
+ error_log_limit_reset();
+
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_reopen_logs_execute(char *args, char **message)
+{
+ (void)args;
+ (void)message;
+
+ error_log_limit_unlimited();
+ info("COMMAND: Reopening all log files.");
+ reopen_all_log_files();
+ error_log_limit_reset();
+
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_exit_execute(char *args, char **message)
+{
+ (void)args;
+ (void)message;
+
+ error_log_limit_unlimited();
+ info("COMMAND: Cleaning up to exit.");
+ netdata_cleanup_and_exit(0);
+ exit(0);
+
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_fatal_execute(char *args, char **message)
+{
+ (void)args;
+ (void)message;
+
+ fatal("COMMAND: netdata now exits.");
+
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_reload_claiming_state_execute(char *args, char **message)
+{
+ (void)args;
+ (void)message;
+#if defined(DISABLE_CLOUD) || !defined(ENABLE_ACLK)
+ info("The claiming feature has been explicitly disabled");
+ *message = strdupz("This agent cannot be claimed, it was built without support for Cloud");
+ return CMD_STATUS_FAILURE;
+#endif
+ error_log_limit_unlimited();
+ info("COMMAND: Reloading Agent Claiming configuration.");
+ load_claiming_state();
+ registry_update_cloud_base_url();
+ rrdpush_claimed_id(localhost);
+ error_log_limit_reset();
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_reload_labels_execute(char *args, char **message)
+{
+ (void)args;
+ info("COMMAND: reloading host labels.");
+ reload_host_labels();
+
+ BUFFER *wb = buffer_create(10);
+
+ rrdhost_rdlock(localhost);
+ netdata_rwlock_rdlock(&localhost->labels.labels_rwlock);
+ struct label *l = localhost->labels.head;
+ while (l != NULL) {
+ buffer_sprintf(wb,"Label [source id=%s]: \"%s\" -> \"%s\"\n", translate_label_source(l->label_source), l->key, l->value);
+ l = l->next;
+ }
+ netdata_rwlock_unlock(&localhost->labels.labels_rwlock);
+ rrdhost_unlock(localhost);
+
+ (*message)=strdupz(buffer_tostring(wb));
+ buffer_free(wb);
+
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_read_config_execute(char *args, char **message)
+{
+ size_t n = strlen(args);
+ char *separator = strchr(args,'|');
+ if (separator == NULL)
+ return CMD_STATUS_FAILURE;
+ char *separator2 = strchr(separator + 1,'|');
+ if (separator2 == NULL)
+ return CMD_STATUS_FAILURE;
+
+ char *temp = callocz(n + 1, 1);
+ strcpy(temp, args);
+ size_t offset = separator - args;
+ temp[offset] = 0;
+ size_t offset2 = separator2 - args;
+ temp[offset2] = 0;
+
+ const char *conf_file = temp; /* "cloud" is cloud.conf, otherwise netdata.conf */
+ struct config *tmp_config = strcmp(conf_file, "cloud") ? &netdata_config : &cloud_config;
+
+ char *value = appconfig_get(tmp_config, temp + offset + 1, temp + offset2 + 1, NULL);
+ if (value == NULL)
+ {
+ error("Cannot execute read-config conf_file=%s section=%s / key=%s because no value set", conf_file,
+ temp + offset + 1, temp + offset2 + 1);
+ freez(temp);
+ return CMD_STATUS_FAILURE;
+ }
+ else
+ {
+ (*message) = strdupz(value);
+ freez(temp);
+ return CMD_STATUS_SUCCESS;
+ }
+
+}
+
+static cmd_status_t cmd_write_config_execute(char *args, char **message)
+{
+ UNUSED(message);
+ info("write-config %s", args);
+ size_t n = strlen(args);
+ char *separator = strchr(args,'|');
+ if (separator == NULL)
+ return CMD_STATUS_FAILURE;
+ char *separator2 = strchr(separator + 1,'|');
+ if (separator2 == NULL)
+ return CMD_STATUS_FAILURE;
+ char *separator3 = strchr(separator2 + 1,'|');
+ if (separator3 == NULL)
+ return CMD_STATUS_FAILURE;
+ char *temp = callocz(n + 1, 1);
+ strcpy(temp, args);
+ size_t offset = separator - args;
+ temp[offset] = 0;
+ size_t offset2 = separator2 - args;
+ temp[offset2] = 0;
+ size_t offset3 = separator3 - args;
+ temp[offset3] = 0;
+
+ const char *conf_file = temp; /* "cloud" is cloud.conf, otherwise netdata.conf */
+ struct config *tmp_config = strcmp(conf_file, "cloud") ? &netdata_config : &cloud_config;
+
+ appconfig_set(tmp_config, temp + offset + 1, temp + offset2 + 1, temp + offset3 + 1);
+ info("write-config conf_file=%s section=%s key=%s value=%s",conf_file, temp + offset + 1, temp + offset2 + 1,
+ temp + offset3 + 1);
+ freez(temp);
+ return CMD_STATUS_SUCCESS;
+}
+
+static cmd_status_t cmd_ping_execute(char *args, char **message)
+{
+ (void)args;
+
+ *message = strdupz("pong");
+
+ return CMD_STATUS_SUCCESS;
+}
+
+static void cmd_lock_exclusive(unsigned index)
+{
+ (void)index;
+
+ uv_rwlock_wrlock(&exclusive_rwlock);
+}
+
+static void cmd_lock_orthogonal(unsigned index)
+{
+ uv_rwlock_rdlock(&exclusive_rwlock);
+ uv_mutex_lock(&command_lock_array[index]);
+}
+
+static void cmd_lock_idempotent(unsigned index)
+{
+ (void)index;
+
+ uv_rwlock_rdlock(&exclusive_rwlock);
+}
+
+static void cmd_lock_high_priority(unsigned index)
+{
+ (void)index;
+}
+
+static void cmd_unlock_exclusive(unsigned index)
+{
+ (void)index;
+
+ uv_rwlock_wrunlock(&exclusive_rwlock);
+}
+
+static void cmd_unlock_orthogonal(unsigned index)
+{
+ uv_rwlock_rdunlock(&exclusive_rwlock);
+ uv_mutex_unlock(&command_lock_array[index]);
+}
+
+static void cmd_unlock_idempotent(unsigned index)
+{
+ (void)index;
+
+ uv_rwlock_rdunlock(&exclusive_rwlock);
+}
+
+static void cmd_unlock_high_priority(unsigned index)
+{
+ (void)index;
+}
+
+static void pipe_close_cb(uv_handle_t* handle)
+{
+ /* Also frees command context */
+ freez(handle);
+}
+
+static void pipe_write_cb(uv_write_t* req, int status)
+{
+ (void)status;
+ uv_pipe_t *client = req->data;
+
+ uv_close((uv_handle_t *)client, pipe_close_cb);
+ --clients;
+ info("Command Clients = %u\n", clients);
+}
+
+static inline void add_char_to_command_reply(char *reply_string, unsigned *reply_string_size, char character)
+{
+ reply_string[(*reply_string_size)++] = character;
+}
+
+static inline void add_string_to_command_reply(char *reply_string, unsigned *reply_string_size, char *str)
+{
+ unsigned len;
+
+ len = strlen(str);
+ strncpyz(reply_string + *reply_string_size, str, len);
+ *reply_string_size += len;
+}
+
+static void send_command_reply(struct command_context *cmd_ctx, cmd_status_t status, char *message)
+{
+ int ret;
+ char reply_string[MAX_COMMAND_LENGTH] = {'\0', };
+ char exit_status_string[MAX_EXIT_STATUS_LENGTH + 1] = {'\0', };
+ unsigned reply_string_size = 0;
+ uv_buf_t write_buf;
+ uv_stream_t *client = (uv_stream_t *)(uv_pipe_t *)cmd_ctx;
+
+ snprintfz(exit_status_string, MAX_EXIT_STATUS_LENGTH, "%u", status);
+ add_char_to_command_reply(reply_string, &reply_string_size, CMD_PREFIX_EXIT_CODE);
+ add_string_to_command_reply(reply_string, &reply_string_size, exit_status_string);
+ add_char_to_command_reply(reply_string, &reply_string_size, '\0');
+
+ if (message) {
+ add_char_to_command_reply(reply_string, &reply_string_size, cmd_prefix_by_status[status]);
+ add_string_to_command_reply(reply_string, &reply_string_size, message);
+ }
+
+ cmd_ctx->write_req.data = client;
+ write_buf.base = reply_string;
+ write_buf.len = reply_string_size;
+ ret = uv_write(&cmd_ctx->write_req, (uv_stream_t *)client, &write_buf, 1, pipe_write_cb);
+ if (ret) {
+ error("uv_write(): %s", uv_strerror(ret));
+ }
+ info("COMMAND: Sending reply: \"%s\"", reply_string);
+}
+
+cmd_status_t execute_command(cmd_t idx, char *args, char **message)
+{
+ cmd_status_t status;
+ cmd_type_t type = command_info_array[idx].type;
+
+ cmd_lock_by_type[type](idx);
+ status = command_info_array[idx].func(args, message);
+ cmd_unlock_by_type[type](idx);
+
+ return status;
+}
+
+static void after_schedule_command(uv_work_t *req, int status)
+{
+ struct command_context *cmd_ctx = req->data;
+
+ (void)status;
+
+ send_command_reply(cmd_ctx, cmd_ctx->status, cmd_ctx->message);
+ if (cmd_ctx->message)
+ freez(cmd_ctx->message);
+}
+
+static void schedule_command(uv_work_t *req)
+{
+ struct command_context *cmd_ctx = req->data;
+
+ cmd_ctx->status = execute_command(cmd_ctx->idx, cmd_ctx->args, &cmd_ctx->message);
+}
+
+/* This will alter the state of the command_info_array.cmd_str
+*/
+static void parse_commands(struct command_context *cmd_ctx)
+{
+ char *message = NULL, *pos, *lstrip, *rstrip;
+ cmd_t i;
+ cmd_status_t status;
+
+ status = CMD_STATUS_FAILURE;
+
+ /* Skip white-space characters */
+ for (pos = cmd_ctx->command_string ; isspace(*pos) && ('\0' != *pos) ; ++pos) {;}
+ for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
+ if (!strncmp(pos, command_info_array[i].cmd_str, strlen(command_info_array[i].cmd_str))) {
+ if (CMD_EXIT == i) {
+ /* musl C does not like libuv workqueues calling exit() */
+ execute_command(CMD_EXIT, NULL, NULL);
+ }
+ for (lstrip=pos + strlen(command_info_array[i].cmd_str); isspace(*lstrip) && ('\0' != *lstrip); ++lstrip) {;}
+ for (rstrip=lstrip+strlen(lstrip)-1; rstrip>lstrip && isspace(*rstrip); *(rstrip--) = 0 );
+
+ cmd_ctx->work.data = cmd_ctx;
+ cmd_ctx->idx = i;
+ cmd_ctx->args = lstrip;
+ cmd_ctx->message = NULL;
+
+ fatal_assert(0 == uv_queue_work(loop, &cmd_ctx->work, schedule_command, after_schedule_command));
+ break;
+ }
+ }
+ if (CMD_TOTAL_COMMANDS == i) {
+ /* no command found */
+ message = strdupz("Illegal command. Please type \"help\" for instructions.");
+ send_command_reply(cmd_ctx, status, message);
+ freez(message);
+ }
+}
+
+static void pipe_read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
+{
+ struct command_context *cmd_ctx = (struct command_context *)client;
+
+ if (0 == nread) {
+ info("%s: Zero bytes read by command pipe.", __func__);
+ } else if (UV_EOF == nread) {
+ info("EOF found in command pipe.");
+ parse_commands(cmd_ctx);
+ } else if (nread < 0) {
+ error("%s: %s", __func__, uv_strerror(nread));
+ }
+
+ if (nread < 0) { /* stop stream due to EOF or error */
+ (void)uv_read_stop((uv_stream_t *)client);
+ } else if (nread) {
+ size_t to_copy;
+
+ to_copy = MIN(nread, MAX_COMMAND_LENGTH - 1 - cmd_ctx->command_string_size);
+ memcpy(cmd_ctx->command_string + cmd_ctx->command_string_size, buf->base, to_copy);
+ cmd_ctx->command_string_size += to_copy;
+ cmd_ctx->command_string[cmd_ctx->command_string_size] = '\0';
+ }
+ if (buf && buf->len) {
+ freez(buf->base);
+ }
+
+ if (nread < 0 && UV_EOF != nread) {
+ uv_close((uv_handle_t *)client, pipe_close_cb);
+ --clients;
+ info("Command Clients = %u\n", clients);
+ }
+}
+
+static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
+{
+ (void)handle;
+
+ buf->base = mallocz(suggested_size);
+ buf->len = suggested_size;
+}
+
+static void connection_cb(uv_stream_t *server, int status)
+{
+ int ret;
+ uv_pipe_t *client;
+ struct command_context *cmd_ctx;
+ fatal_assert(status == 0);
+
+ /* combined allocation of client pipe and command context */
+ cmd_ctx = mallocz(sizeof(*cmd_ctx));
+ client = (uv_pipe_t *)cmd_ctx;
+ ret = uv_pipe_init(server->loop, client, 1);
+ if (ret) {
+ error("uv_pipe_init(): %s", uv_strerror(ret));
+ freez(cmd_ctx);
+ return;
+ }
+ ret = uv_accept(server, (uv_stream_t *)client);
+ if (ret) {
+ error("uv_accept(): %s", uv_strerror(ret));
+ uv_close((uv_handle_t *)client, pipe_close_cb);
+ return;
+ }
+
+ ++clients;
+ info("Command Clients = %u\n", clients);
+ /* Start parsing a new command */
+ cmd_ctx->command_string_size = 0;
+ cmd_ctx->command_string[0] = '\0';
+
+ ret = uv_read_start((uv_stream_t*)client, alloc_cb, pipe_read_cb);
+ if (ret) {
+ error("uv_read_start(): %s", uv_strerror(ret));
+ uv_close((uv_handle_t *)client, pipe_close_cb);
+ --clients;
+ info("Command Clients = %u\n", clients);
+ return;
+ }
+}
+
+static void async_cb(uv_async_t *handle)
+{
+ uv_stop(handle->loop);
+}
+
+static void command_thread(void *arg)
+{
+ int ret;
+ uv_fs_t req;
+
+ (void) arg;
+ loop = mallocz(sizeof(uv_loop_t));
+ ret = uv_loop_init(loop);
+ if (ret) {
+ error("uv_loop_init(): %s", uv_strerror(ret));
+ command_thread_error = ret;
+ goto error_after_loop_init;
+ }
+ loop->data = NULL;
+
+ ret = uv_async_init(loop, &async, async_cb);
+ if (ret) {
+ error("uv_async_init(): %s", uv_strerror(ret));
+ command_thread_error = ret;
+ goto error_after_async_init;
+ }
+ async.data = NULL;
+
+ ret = uv_pipe_init(loop, &server_pipe, 0);
+ if (ret) {
+ error("uv_pipe_init(): %s", uv_strerror(ret));
+ command_thread_error = ret;
+ goto error_after_pipe_init;
+ }
+ (void)uv_fs_unlink(loop, &req, PIPENAME, NULL);
+ uv_fs_req_cleanup(&req);
+ ret = uv_pipe_bind(&server_pipe, PIPENAME);
+ if (ret) {
+ error("uv_pipe_bind(): %s", uv_strerror(ret));
+ command_thread_error = ret;
+ goto error_after_pipe_bind;
+ }
+ ret = uv_listen((uv_stream_t *)&server_pipe, SOMAXCONN, connection_cb);
+ if (ret) {
+ /* Fallback to backlog of 1 */
+ info("uv_listen() failed with backlog = %d, falling back to backlog = 1.", SOMAXCONN);
+ ret = uv_listen((uv_stream_t *)&server_pipe, 1, connection_cb);
+ }
+ if (ret) {
+ error("uv_listen(): %s", uv_strerror(ret));
+ command_thread_error = ret;
+ goto error_after_uv_listen;
+ }
+
+ command_thread_error = 0;
+ command_thread_shutdown = 0;
+ /* wake up initialization thread */
+ complete(&completion);
+
+ while (command_thread_shutdown == 0) {
+ uv_run(loop, UV_RUN_DEFAULT);
+ }
+ /* cleanup operations of the event loop */
+ info("Shutting down command event loop.");
+ uv_close((uv_handle_t *)&async, NULL);
+ uv_close((uv_handle_t*)&server_pipe, NULL);
+ uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
+
+ info("Shutting down command loop complete.");
+ fatal_assert(0 == uv_loop_close(loop));
+ freez(loop);
+
+ return;
+
+error_after_uv_listen:
+error_after_pipe_bind:
+ uv_close((uv_handle_t*)&server_pipe, NULL);
+error_after_pipe_init:
+ uv_close((uv_handle_t *)&async, NULL);
+error_after_async_init:
+ uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
+ fatal_assert(0 == uv_loop_close(loop));
+error_after_loop_init:
+ freez(loop);
+
+ /* wake up initialization thread */
+ complete(&completion);
+}
+
+static void sanity_check(void)
+{
+ /* The size of command_info_array must be CMD_TOTAL_COMMANDS elements */
+ BUILD_BUG_ON(CMD_TOTAL_COMMANDS != sizeof(command_info_array) / sizeof(command_info_array[0]));
+}
+
+void commands_init(void)
+{
+ cmd_t i;
+ int error;
+
+ sanity_check();
+ if (command_server_initialized)
+ return;
+
+ info("Initializing command server.");
+ for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
+ fatal_assert(0 == uv_mutex_init(&command_lock_array[i]));
+ }
+ fatal_assert(0 == uv_rwlock_init(&exclusive_rwlock));
+
+ init_completion(&completion);
+ error = uv_thread_create(&thread, command_thread, NULL);
+ if (error) {
+ error("uv_thread_create(): %s", uv_strerror(error));
+ goto after_error;
+ }
+ /* wait for worker thread to initialize */
+ wait_for_completion(&completion);
+ destroy_completion(&completion);
+ uv_thread_set_name_np(thread, "DAEMON_COMMAND");
+
+ if (command_thread_error) {
+ error = uv_thread_join(&thread);
+ if (error) {
+ error("uv_thread_create(): %s", uv_strerror(error));
+ }
+ goto after_error;
+ }
+
+ command_server_initialized = 1;
+ return;
+
+after_error:
+ error("Failed to initialize command server. The netdata cli tool will be unable to send commands.");
+}
+
+void commands_exit(void)
+{
+ cmd_t i;
+
+ if (!command_server_initialized)
+ return;
+
+ command_thread_shutdown = 1;
+ info("Shutting down command server.");
+ /* wake up event loop */
+ fatal_assert(0 == uv_async_send(&async));
+ fatal_assert(0 == uv_thread_join(&thread));
+
+ for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
+ uv_mutex_destroy(&command_lock_array[i]);
+ }
+ uv_rwlock_destroy(&exclusive_rwlock);
+ info("Command server has stopped.");
+ command_server_initialized = 0;
+}
diff --git a/daemon/commands.h b/daemon/commands.h
new file mode 100644
index 0000000..bd4aabf
--- /dev/null
+++ b/daemon/commands.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_COMMANDS_H
+#define NETDATA_COMMANDS_H 1
+
+#ifdef _WIN32
+# define PIPENAME "\\\\?\\pipe\\netdata-cli"
+#else
+# define PIPENAME "/tmp/netdata-ipc"
+#endif
+
+#define MAX_COMMAND_LENGTH 4096
+#define MAX_EXIT_STATUS_LENGTH 23 /* Can't ever be bigger than "X-18446744073709551616" */
+
+typedef enum cmd {
+ CMD_HELP = 0,
+ CMD_RELOAD_HEALTH,
+ CMD_SAVE_DATABASE,
+ CMD_REOPEN_LOGS,
+ CMD_EXIT,
+ CMD_FATAL,
+ CMD_RELOAD_CLAIMING_STATE,
+ CMD_RELOAD_LABELS,
+ CMD_READ_CONFIG,
+ CMD_WRITE_CONFIG,
+ CMD_PING,
+ CMD_TOTAL_COMMANDS
+} cmd_t;
+
+typedef enum cmd_status {
+ CMD_STATUS_SUCCESS = 0,
+ CMD_STATUS_FAILURE,
+ CMD_STATUS_BUSY
+} cmd_status_t;
+
+#define CMD_PREFIX_INFO 'O' /* Following string should go to cli stdout */
+#define CMD_PREFIX_ERROR 'E' /* Following string should go to cli stderr */
+#define CMD_PREFIX_EXIT_CODE 'X' /* Following string is cli integer exit code */
+
+typedef enum cmd_type {
+ /*
+ * No other command is allowed to run at the same time (except for CMD_TYPE_HIGH_PRIORITY).
+ */
+ CMD_TYPE_EXCLUSIVE = 0,
+ /*
+ * Other commands are allowed to run concurrently (except for CMD_TYPE_EXCLUSIVE) but calls to this command are
+ * serialized.
+ */
+ CMD_TYPE_ORTHOGONAL,
+ /*
+ * Other commands are allowed to run concurrently (except for CMD_TYPE_EXCLUSIVE) as are calls to this command.
+ */
+ CMD_TYPE_CONCURRENT,
+ /*
+ * Those commands are always allowed to run.
+ */
+ CMD_TYPE_HIGH_PRIORITY
+} cmd_type_t;
+
+/**
+ * Executes a command and returns the status.
+ *
+ * @param args a string that may contain additional parameters to be parsed
+ * @param message allocate and return a message if need be (up to MAX_COMMAND_LENGTH bytes)
+ * @return CMD_FAILURE or CMD_SUCCESS
+ */
+typedef cmd_status_t (command_action_t) (char *args, char **message);
+
+typedef struct command_info {
+ char *cmd_str; // the command string
+ command_action_t *func; // the function that executes the command
+ cmd_type_t type; // Concurrency control information for the command
+} command_info_t;
+
+typedef void (command_lock_t) (unsigned index);
+
+cmd_status_t execute_command(cmd_t idx, char *args, char **message);
+extern void commands_init(void);
+extern void commands_exit(void);
+
+#endif //NETDATA_COMMANDS_H
diff --git a/daemon/common.c b/daemon/common.c
new file mode 100644
index 0000000..45d5fa3
--- /dev/null
+++ b/daemon/common.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common.h"
+
+char *netdata_configured_hostname = NULL;
+char *netdata_configured_user_config_dir = CONFIG_DIR;
+char *netdata_configured_stock_config_dir = LIBCONFIG_DIR;
+char *netdata_configured_log_dir = LOG_DIR;
+char *netdata_configured_primary_plugins_dir = NULL;
+char *netdata_configured_web_dir = WEB_DIR;
+char *netdata_configured_cache_dir = CACHE_DIR;
+char *netdata_configured_varlib_dir = VARLIB_DIR;
+char *netdata_configured_lock_dir = NULL;
+char *netdata_configured_home_dir = VARLIB_DIR;
+char *netdata_configured_host_prefix = NULL;
+char *netdata_configured_timezone = NULL;
+int netdata_ready;
+int netdata_cloud_setting;
+
diff --git a/daemon/common.h b/daemon/common.h
new file mode 100644
index 0000000..68af957
--- /dev/null
+++ b/daemon/common.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_COMMON_H
+#define NETDATA_COMMON_H 1
+
+#include "../libnetdata/libnetdata.h"
+
+// ----------------------------------------------------------------------------
+// shortcuts for the default netdata configuration
+
+#define config_load(filename, overwrite_used, section) appconfig_load(&netdata_config, filename, overwrite_used, section)
+#define config_get(section, name, default_value) appconfig_get(&netdata_config, section, name, default_value)
+#define config_get_number(section, name, value) appconfig_get_number(&netdata_config, section, name, value)
+#define config_get_float(section, name, value) appconfig_get_float(&netdata_config, section, name, value)
+#define config_get_boolean(section, name, value) appconfig_get_boolean(&netdata_config, section, name, value)
+#define config_get_boolean_ondemand(section, name, value) appconfig_get_boolean_ondemand(&netdata_config, section, name, value)
+#define config_get_duration(section, name, value) appconfig_get_duration(&netdata_config, section, name, value)
+
+#define config_set(section, name, default_value) appconfig_set(&netdata_config, section, name, default_value)
+#define config_set_default(section, name, value) appconfig_set_default(&netdata_config, section, name, value)
+#define config_set_number(section, name, value) appconfig_set_number(&netdata_config, section, name, value)
+#define config_set_float(section, name, value) appconfig_set_float(&netdata_config, section, name, value)
+#define config_set_boolean(section, name, value) appconfig_set_boolean(&netdata_config, section, name, value)
+
+#define config_exists(section, name) appconfig_exists(&netdata_config, section, name)
+#define config_move(section_old, name_old, section_new, name_new) appconfig_move(&netdata_config, section_old, name_old, section_new, name_new)
+
+#define config_generate(buffer, only_changed) appconfig_generate(&netdata_config, buffer, only_changed)
+
+// ----------------------------------------------------------------------------
+// netdata include files
+
+#include "global_statistics.h"
+
+// the netdata database
+#include "database/rrd.h"
+
+// the netdata webserver(s)
+#include "web/server/web_server.h"
+
+// streaming metrics between netdata servers
+#include "streaming/rrdpush.h"
+
+// health monitoring and alarm notifications
+#include "health/health.h"
+
+// the netdata registry
+// the registry is actually an API feature
+#include "registry/registry.h"
+
+// backends for archiving the metrics
+#include "backends/backends.h"
+// the new exporting engine for archiving the metrics
+#include "exporting/exporting_engine.h"
+
+// the netdata API
+#include "web/api/web_api_v1.h"
+
+// all data collection plugins
+#include "collectors/all.h"
+
+// netdata unit tests
+#include "unit_test.h"
+
+// netdata agent claiming
+#include "claim/claim.h"
+
+// netdata agent cloud link
+#include "aclk/legacy/agent_cloud_link.h"
+
+// global GUID map functions
+
+// netdata agent spawn server
+#include "spawn/spawn.h"
+
+// the netdata deamon
+#include "daemon.h"
+#include "main.h"
+#include "signals.h"
+#include "commands.h"
+
+// global netdata daemon variables
+extern char *netdata_configured_hostname;
+extern char *netdata_configured_user_config_dir;
+extern char *netdata_configured_stock_config_dir;
+extern char *netdata_configured_log_dir;
+extern char *netdata_configured_primary_plugins_dir;
+extern char *netdata_configured_web_dir;
+extern char *netdata_configured_cache_dir;
+extern char *netdata_configured_varlib_dir;
+extern char *netdata_configured_lock_dir;
+extern char *netdata_configured_home_dir;
+extern char *netdata_configured_host_prefix;
+extern char *netdata_configured_timezone;
+extern int netdata_zero_metrics_enabled;
+extern int netdata_anonymous_statistics_enabled;
+
+extern int netdata_ready;
+extern int netdata_cloud_setting;
+
+#endif /* NETDATA_COMMON_H */
diff --git a/daemon/config/README.md b/daemon/config/README.md
new file mode 100644
index 0000000..a1e2b04
--- /dev/null
+++ b/daemon/config/README.md
@@ -0,0 +1,229 @@
+<!--
+title: "Daemon configuration"
+description: "The Netdata Agent's daemon is installed preconfigured to collect thousands of metrics every second, but is highly configurable for real-world workloads."
+custom_edit_url: https://github.com/netdata/netdata/edit/master/daemon/config/README.md
+-->
+
+# Daemon configuration
+
+<details markdown="1"><summary>The daemon configuration file is read from `/etc/netdata/netdata.conf`.</summary>
+Depending on your installation method, Netdata will have been installed either directly under `/`, or under `/opt/netdata`. The paths mentioned here and in the documentation in general assume that your installation is under `/`. If it is not, you will find the exact same paths under `/opt/netdata` as well. (i.e. `/etc/netdata` will be `/opt/netdata/etc/netdata`).</details>
+
+This config file **is not needed by default**. Netdata works fine out of the box without it. But it does allow you to adapt the general behavior of Netdata, in great detail. You can find all these settings, with their default values, by accessing the URL `https://netdata.server.hostname:19999/netdata.conf`. For example check the configuration file of [netdata.firehol.org](http://netdata.firehol.org/netdata.conf). HTTP access to this file is limited by default to private IPs, via the [web server access lists](/web/server/README.md#access-lists).
+
+`netdata.conf` has sections stated with `[section]`. You will see the following sections:
+
+1. `[global]` to [configure](#global-section-options) the [Netdata daemon](/daemon/README.md).
+2. `[web]` to [configure the web server](/web/server/README.md).
+3. `[plugins]` to [configure](#plugins-section-options) which [collectors](/collectors/README.md) to use and PATH
+ settings.
+4. `[health]` to [configure](#health-section-options) general settings for [health monitoring](/health/README.md)
+5. `[registry]` for the [Netdata registry](/registry/README.md).
+6. `[backend]` to set up [streaming and replication](/streaming/README.md) options.
+7. `[statsd]` for the general settings of the [stats.d.plugin](/collectors/statsd.plugin/README.md).
+8. `[plugin:NAME]` sections for each collector plugin, under the comment [Per plugin configuration](#per-plugin-configuration).
+9. `[CHART_NAME]` sections for each chart defined, under the comment [Per chart configuration](#per-chart-configuration).
+
+The configuration file is a `name = value` dictionary. Netdata will not complain if you set options unknown to it. When you check the running configuration by accessing the URL `/netdata.conf` on your Netdata server, Netdata will add a comment on settings it does not currently use.
+
+## Applying changes
+
+After `netdata.conf` has been modified, Netdata needs to be restarted for changes to apply:
+
+```bash
+sudo service netdata restart
+```
+
+If the above does not work, try the following:
+
+```bash
+sudo killall netdata; sleep 10; sudo netdata
+```
+
+Please note that your data history will be lost if you have modified `history` parameter in section `[global]`.
+
+## Sections
+
+### [global] section options
+
+| setting|default|info|||
+|:-----:|:-----:|:---|---|---|
+| process scheduling policy|`keep`|See [Netdata process scheduling policy](/daemon/README.md#netdata-process-scheduling-policy)|||
+| OOM score|`1000`|See [OOM score](../#oom-score)|||
+| glibc malloc arena max for plugins|`1`|See [Virtual memory](/daemon/README.md#virtual-memory).|||
+| glibc malloc arena max for Netdata|`1`|See [Virtual memory](/daemon/README.md#virtual-memory).|||
+| hostname|auto-detected|The hostname of the computer running Netdata.|||
+| history|`3996`| Used with `memory mode = save/map/ram/alloc`, not the default `memory mode = dbengine`. This number reflects the number of entries the `netdata` daemon will by default keep in memory for each chart dimension. This setting can also be configured per chart. Check [Memory Requirements](/database/README.md) for more information. |||
+| update every|`1`|The frequency in seconds, for data collection. For more information see the [performance guide](/docs/guides/configure/performance.md).|||
+| config directory|`/etc/netdata`|The directory configuration files are kept.|||
+| stock config directory|`/usr/lib/netdata/conf.d`||||
+| log directory|`/var/log/netdata`|The directory in which the [log files](/daemon/README.md#log-files) are kept.|||
+| web files directory|`/usr/share/netdata/web`|The directory the web static files are kept.|||
+| cache directory|`/var/cache/netdata`|The directory the memory database will be stored if and when Netdata exits. Netdata will re-read the database when it will start again, to continue from the same point.|||
+| lib directory|`/var/lib/netdata`|Contains the alarm log and the Netdata instance guid.|||
+| home directory|`/var/cache/netdata`|Contains the db files for the collected metrics|||
+| plugins directory|`"/usr/libexec/netdata/plugins.d" "/etc/netdata/custom-plugins.d"`|The directory plugin programs are kept. This setting supports multiple directories, space separated. If any directory path contains spaces, enclose it in single or double quotes.|||
+| memory mode | `dbengine` | `dbengine`: The default for long-term metrics storage with efficient RAM and disk usage. Can be extended with `page cache size` and `dbengine disk space`. <br />`save`: Netdata will save its round robin database on exit and load it on startup. <br />`map`: Cache files will be updated in real-time. Not ideal for systems with high load or slow disks (check `man mmap`). <br />`ram`: The round-robin database will be temporary and it will be lost when Netdata exits. <br />`none`: Disables the database at this host, and disables health monitoring entirely, as that requires a database of metrics. |
+| page cache size | 32 | Determines the amount of RAM in MiB that is dedicated to caching Netdata metric values. |||
+| dbengine disk space | 256 | Determines the amount of disk space in MiB that is dedicated to storing Netdata metric values and all related metadata describing them. |||
+| dbengine multihost disk space | 256 | Same functionality as `dbengine disk space`, but includes support for storing metrics streamed to a parent node by its children. Can be used in single-node environments as well. |||
+| host access prefix||This is used in docker environments where /proc, /sys, etc have to be accessed via another path. You may also have to set SYS_PTRACE capability on the docker for this work. Check [issue 43](https://github.com/netdata/netdata/issues/43).|
+| memory deduplication (ksm)|`yes`|When set to `yes`, Netdata will offer its in-memory round robin database to kernel same page merging (KSM) for deduplication. For more information check [Memory Deduplication - Kernel Same Page Merging - KSM](/database/README.md#ksm)|||
+| TZ environment variable|`:/etc/localtime`|Where to find the timezone|||
+| timezone|auto-detected|The timezone retrieved from the environment variable|||
+| debug flags|`0x0000000000000000`|Bitmap of debug options to enable. For more information check [Tracing Options](/daemon/README.md#debugging).|||
+| debug log|`/var/log/netdata/debug.log`|The filename to save debug information. This file will not be created if debugging is not enabled. You can also set it to `syslog` to send the debug messages to syslog, or `none` to disable this log. For more information check [Tracing Options](/daemon/README.md#debugging).|||
+| error log|`/var/log/netdata/error.log`|The filename to save error messages for Netdata daemon and all plugins (`stderr` is sent here for all Netdata programs, including the plugins). You can also set it to `syslog` to send the errors to syslog, or `none` to disable this log.|||
+| access log|`/var/log/netdata/access.log`|The filename to save the log of web clients accessing Netdata charts. You can also set it to `syslog` to send the access log to syslog, or `none` to disable this log.|||
+| errors flood protection period|`1200`|Length of period (in sec) during which the number of errors should not exceed the `errors to trigger flood protection`.|||
+| errors to trigger flood protection|`200`|Number of errors written to the log in `errors flood protection period` sec before flood protection is activated.|||
+| run as user|`netdata`|The user Netdata will run as.|||
+| pthread stack size|auto-detected||||
+| cleanup obsolete charts after seconds|`3600`|See [monitoring ephemeral containers](/collectors/cgroups.plugin/README.md#monitoring-ephemeral-containers), also sets the timeout for cleaning up obsolete dimensions|||
+| gap when lost iterations above|`1`||||
+| cleanup orphan hosts after seconds|`3600`|How long to wait until automatically removing from the DB a remote Netdata host (child) that is no longer sending data.|||
+| delete obsolete charts files|`yes`|See [monitoring ephemeral containers](/collectors/cgroups.plugin/README.md#monitoring-ephemeral-containers), also affects the deletion of files for obsolete dimensions|||
+| delete orphan hosts files|`yes`|Set to `no` to disable non-responsive host removal.|||
+| enable zero metrics|`no`|Set to `yes` to show charts when all their metrics are zero.|||
+
+### [web] section options
+
+Refer to the [web server documentation](/web/server/README.md)
+
+### [plugins] section options
+
+In this section you will see be a boolean (`yes`/`no`) option for each plugin (e.g. tc, cgroups, apps, proc etc.). Note that the configuration options in this section for the orchestrator plugins `python.d`, `charts.d` and `node.d` control **all the modules** written for that orchestrator. For instance, setting `python.d = no` means that all Python modules under `collectors/python.d.plugin` will be disabled.
+
+Additionally, there will be the following options:
+
+| setting|default|info|
+|:-----:|:-----:|:---|
+| PATH environment variable|`auto-detected`||
+| PYTHONPATH environment variable||Used to set a custom python path|
+| enable running new plugins|`yes`|When set to `yes`, Netdata will enable detected plugins, even if they are not configured explicitly. Setting this to `no` will only enable plugins explicitly configured in this file with a `yes`|
+| check for new plugins every|60|The time in seconds to check for new plugins in the plugins directory. This allows having other applications dynamically creating plugins for Netdata.|
+| checks|`no`|This is a debugging plugin for the internal latency|
+
+### [health] section options
+
+This section controls the general behavior of the health monitoring capabilities of Netdata.
+
+Specific alarms are configured in per-collector config files under the `health.d` directory. For more info, see [health
+monitoring](/health/README.md).
+
+[Alarm notifications](/health/notifications/README.md) are configured in `health_alarm_notify.conf`.
+
+| setting|default|info|
+|:-----:|:-----:|:---|
+| enabled|`yes`|Set to `no` to disable all alarms and notifications|
+| in memory max health log entries|1000|Size of the alarm history held in RAM|
+| script to execute on alarm|`/usr/libexec/netdata/plugins.d/alarm-notify.sh`|The script that sends alarm notifications. Note that in versions before 1.16, the plugins.d directory may be installed in a different location in certain OSs (e.g. under `/usr/lib/netdata`).|
+| stock health configuration directory|`/usr/lib/netdata/conf.d/health.d`|Contains the stock alarm configuration files for each collector|
+| health configuration directory|`/etc/netdata/health.d`|The directory containing the user alarm configuration files, to override the stock configurations|
+| run at least every seconds|`10`|Controls how often all alarm conditions should be evaluated.|
+| postpone alarms during hibernation for seconds|`60`|Prevents false alarms. May need to be increased if you get alarms during hibernation.|
+| rotate log every lines|2000|Controls the number of alarm log entries stored in `<lib directory>/health-log.db`, where `<lib directory>` is the one configured in the [\[global\] section](#global-section-options)|
+
+### [registry] section options
+
+To understand what this section is and how it should be configured, please refer to the [registry documentation](/registry/README.md).
+
+### [backend]
+
+Refer to the [streaming and replication](/streaming/README.md) documentation.
+
+## Per-plugin configuration
+
+The configuration options for plugins appear in sections following the pattern `[plugin:NAME]`.
+
+### Internal plugins
+
+Most internal plugins will provide additional options. Check [Internal Plugins](/collectors/README.md) for more
+information.
+
+Please note, that by default Netdata will enable monitoring metrics for disks, memory, and network only when they are not zero. If they are constantly zero they are ignored. Metrics that will start having values, after Netdata is started, will be detected and charts will be automatically added to the dashboard (a refresh of the dashboard is needed for them to appear though). Use `yes` instead of `auto` in plugin configuration sections to enable these charts permanently. You can also set the `enable zero metrics` option to `yes` in the `[global]` section which enables charts with zero metrics for all internal Netdata plugins.
+
+### External plugins
+
+External plugins will have only 2 options at `netdata.conf`:
+
+| setting | default | info |
+| :-----:|:-----:|:---|
+| update every | the value of `[global].update every` setting|The frequency in seconds the plugin should collect values. For more information check the [performance guide](/docs/guides/configure/performance.md).|
+| command options | _empty_ | Additional command line options to pass to the plugin.|
+
+External plugins that need additional configuration may support a dedicated file in `/etc/netdata`. Check their documentation.
+
+## Per-chart configuration
+
+In this area of `netdata.conf` you can find configuration options for individual charts. They appear in sections
+following the pattern `[NAME]`.
+
+Using the settings and values under these sections, you can control all aspects of a specific chart. You can change its
+title, make it appear higher in Netdata's [menu](/web/gui/README.md#metrics-menus), tweak its dimensions, and much more.
+
+To find the name of a given chart, and thus the name of its section in `netdata.conf`, look at the top-left corner of a
+chart:
+
+![Finding the unique ID of a
+chart](https://user-images.githubusercontent.com/1153921/67443082-43b16e80-f5b8-11e9-8d33-d6ee052c6678.png)
+
+Every per-chart configuration section has several common settings, which are listed in the table just below. Beneath
+that is information about lines that begin with `dim`, which affect a chart's dimensions.
+
+| Setting | Function |
+| :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `history` | Override the `history` setting in the [[global] options](#global-section-options) for this particular chart. Should be less than or equal to the global `history` setting. |
+| `enabled` | A boolean (`yes` or `no`) that explicitly enables or disables the chart in question. |
+| `cache directory` | The directory where cache files for this plugin, if needed, are stored. |
+| `chart type` | Defines what type of chart to display. It can be `line`, `area`, or `stacked`. If empty or missing, `line` will be used. |
+| `type` | Uniquely identify which [metrics menu](/web/gui/README.md#metrics-menus) on the Netdata dashboard this chart should appear under. Some examples include `system` (**System**), `disk` (**Disks**), `net` (**Network Interfaces**), and `netdata` (**Netdata Monitoring**). |
+| `family` | Change the chart's [family](/web/README.md#families) from its default. For example, you could force a disk space chart to collect metrics for family `sdb` instead of family `sda`. |
+| `units` | Text for the label of the vertical axis of the chart. This means all dimensions should have the same unit of measurement. |
+| `context` | Change the default [context](/web/README.md#contexts) of the chart. Changing this setting will affect what metrics and metrics the chart displays, and which alarms are attached to it. |
+| `priority` | Define where the chart should appear on the Netdata dashboard. Lower values equal higher priority, so a priority of `1` will place the chart highest, while a priority of `9999999` would place the chart at the bottom of the Netdata dashboard. |
+| `name` | The name of the chart that appears in the top-left corner, after the chart's title. You can also use this name when writing [health entities](/health/REFERENCE.md#health-entity-reference). |
+| `title` | The text that appears above the chart in the Netdata dashboard. |
+
+### Dimension settings
+
+You may notice some settings that begin with `dim` beneath the ones defined in the table above. These settings determine
+which dimensions appear on the given chart and how Netdata calculates them.
+
+Each dimension setting has the following structure: `dim [DIMENSION ID] [OPTION] = [VALUE]`. The available options are `name`, `algorithm`, `multiplier`, and `divisor`.
+
+| Setting | Function |
+| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `name` | The name of the dimension as it will appear in the legend of the chart. If left empty, or is missing, Netdata will use the `[DIMENSION ID]` instead. |
+| `algorithm` | Can be `absolute`, `incremental`, `percentage-of-absolute-row`, or `percentage-of-incremental-row`. If this setting is empty, invalid, or missing, Netdata will use `absolute`. See the list beneath this table for descriptions of what each algorithm does. |
+| `multiplier` | An integer value by which to multiply the collected value. If empty or missing, Netdata will use `1`. This setting is often used with the value `1024` to convert metabytes to kilobytes, kilobytes to bytes, and so on. |
+| `divisor` | An integer value by which to divide the collected value. If empty or missing, Netdata will use `1`. This setting is often used with the value `1024` to convert bytes to kilobytes, kilobytes to megabytes, and so on. |
+
+Here are the options for the `algorithm` setting:
+
+- `absolute`: The value is drawn as-is (interpolated to second boundary).
+- `incremental`: To be used when the value always increases over time, such as the I/O on a disk. Netdata takes the
+ difference between the current metric and the past metric to calculate a per-second figure.
+- `percentage-of-absolute-row`: The % of this value compared to the total of all dimensions.
+- `percentage-of-incremental-row`: The % of this value compared to the incremental total of all dimensions.
+
+For example, the `system.io` chart has the following default settings:
+
+```conf
+ # dim in name = in
+ # dim in algorithm = incremental
+ # dim in multiplier = 1
+ # dim in divisor = 1
+ # dim out name = out
+ # dim out algorithm = incremental
+ # dim out multiplier = -1
+ # dim out divisor = 1
+```
+
+These `dim` settings produce two dimensions, `in` and `out`, both of which use the `incremental` algorithm. By
+multiplying the value of `out` by -1, Netdata creates the negative values seen in the following area chart:
+
+![The system.io chart on a macOS
+laptop](https://user-images.githubusercontent.com/1153921/69286708-2cfb3900-0bb1-11ea-9fcd-dd8fbb2adf11.png)
+
+[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdaemon%2Fconfig%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/daemon/daemon.c b/daemon/daemon.c
new file mode 100644
index 0000000..8319110
--- /dev/null
+++ b/daemon/daemon.c
@@ -0,0 +1,501 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common.h"
+#include <sched.h>
+
+char pidfile[FILENAME_MAX + 1] = "";
+char claimingdirectory[FILENAME_MAX + 1];
+char exepath[FILENAME_MAX + 1];
+
+void get_netdata_execution_path(void)
+{
+ int ret;
+ size_t exepath_size = 0;
+ struct passwd *passwd = NULL;
+ char *user = NULL;
+
+ passwd = getpwuid(getuid());
+ user = (passwd && passwd->pw_name) ? passwd->pw_name : "";
+
+ exepath_size = sizeof(exepath) - 1;
+ ret = uv_exepath(exepath, &exepath_size);
+ if (0 != ret) {
+ error("uv_exepath(\"%s\", %u) (user: %s) failed (%s).", exepath, (unsigned)exepath_size, user,
+ uv_strerror(ret));
+ fatal("Cannot start netdata without getting execution path.");
+ }
+ exepath[exepath_size] = '\0';
+}
+
+static void chown_open_file(int fd, uid_t uid, gid_t gid) {
+ if(fd == -1) return;
+
+ struct stat buf;
+
+ if(fstat(fd, &buf) == -1) {
+ error("Cannot fstat() fd %d", fd);
+ return;
+ }
+
+ if((buf.st_uid != uid || buf.st_gid != gid) && S_ISREG(buf.st_mode)) {
+ if(fchown(fd, uid, gid) == -1)
+ error("Cannot fchown() fd %d.", fd);
+ }
+}
+
+void create_needed_dir(const char *dir, uid_t uid, gid_t gid)
+{
+ // attempt to create the directory
+ if(mkdir(dir, 0755) == 0) {
+ // we created it
+
+ // chown it to match the required user
+ if(chown(dir, uid, gid) == -1)
+ error("Cannot chown directory '%s' to %u:%u", dir, (unsigned int)uid, (unsigned int)gid);
+ }
+ else if(errno != EEXIST)
+ // log an error only if the directory does not exist
+ error("Cannot create directory '%s'", dir);
+}
+
+void clean_directory(char *dirname)
+{
+ DIR *dir = opendir(dirname);
+ if(!dir) return;
+
+ int dir_fd = dirfd(dir);
+ struct dirent *de = NULL;
+
+ while((de = readdir(dir)))
+ if(de->d_type == DT_REG)
+ if (unlinkat(dir_fd, de->d_name, 0))
+ error("Cannot delete %s/%s", dirname, de->d_name);
+
+ closedir(dir);
+}
+
+int become_user(const char *username, int pid_fd) {
+ int am_i_root = (getuid() == 0)?1:0;
+
+ struct passwd *pw = getpwnam(username);
+ if(!pw) {
+ error("User %s is not present.", username);
+ return -1;
+ }
+
+ uid_t uid = pw->pw_uid;
+ gid_t gid = pw->pw_gid;
+
+ create_needed_dir(netdata_configured_cache_dir, uid, gid);
+ create_needed_dir(netdata_configured_varlib_dir, uid, gid);
+ create_needed_dir(netdata_configured_lock_dir, uid, gid);
+ create_needed_dir(claimingdirectory, uid, gid);
+
+ clean_directory(netdata_configured_lock_dir);
+
+ if(pidfile[0]) {
+ if(chown(pidfile, uid, gid) == -1)
+ error("Cannot chown '%s' to %u:%u", pidfile, (unsigned int)uid, (unsigned int)gid);
+ }
+
+ int ngroups = (int)sysconf(_SC_NGROUPS_MAX);
+ gid_t *supplementary_groups = NULL;
+ if(ngroups > 0) {
+ supplementary_groups = mallocz(sizeof(gid_t) * ngroups);
+ if(getgrouplist(username, gid, supplementary_groups, &ngroups) == -1) {
+ if(am_i_root)
+ error("Cannot get supplementary groups of user '%s'.", username);
+
+ ngroups = 0;
+ }
+ }
+
+ chown_open_file(STDOUT_FILENO, uid, gid);
+ chown_open_file(STDERR_FILENO, uid, gid);
+ chown_open_file(stdaccess_fd, uid, gid);
+ chown_open_file(pid_fd, uid, gid);
+
+ if(supplementary_groups && ngroups > 0) {
+ if(setgroups((size_t)ngroups, supplementary_groups) == -1) {
+ if(am_i_root)
+ error("Cannot set supplementary groups for user '%s'", username);
+ }
+ ngroups = 0;
+ }
+
+ if(supplementary_groups)
+ freez(supplementary_groups);
+
+#ifdef __APPLE__
+ if(setregid(gid, gid) != 0) {
+#else
+ if(setresgid(gid, gid, gid) != 0) {
+#endif /* __APPLE__ */
+ error("Cannot switch to user's %s group (gid: %u).", username, gid);
+ return -1;
+ }
+
+#ifdef __APPLE__
+ if(setreuid(uid, uid) != 0) {
+#else
+ if(setresuid(uid, uid, uid) != 0) {
+#endif /* __APPLE__ */
+ error("Cannot switch to user %s (uid: %u).", username, uid);
+ return -1;
+ }
+
+ if(setgid(gid) != 0) {
+ error("Cannot switch to user's %s group (gid: %u).", username, gid);
+ return -1;
+ }
+ if(setegid(gid) != 0) {
+ error("Cannot effectively switch to user's %s group (gid: %u).", username, gid);
+ return -1;
+ }
+ if(setuid(uid) != 0) {
+ error("Cannot switch to user %s (uid: %u).", username, uid);
+ return -1;
+ }
+ if(seteuid(uid) != 0) {
+ error("Cannot effectively switch to user %s (uid: %u).", username, uid);
+ return -1;
+ }
+
+ return(0);
+}
+
+#ifndef OOM_SCORE_ADJ_MAX
+#define OOM_SCORE_ADJ_MAX (1000)
+#endif
+#ifndef OOM_SCORE_ADJ_MIN
+#define OOM_SCORE_ADJ_MIN (-1000)
+#endif
+
+static void oom_score_adj(void) {
+ char buf[30 + 1];
+ long long int old_score, wanted_score = OOM_SCORE_ADJ_MAX, final_score = 0;
+
+ // read the existing score
+ if(read_single_signed_number_file("/proc/self/oom_score_adj", &old_score)) {
+ error("Out-Of-Memory (OOM) score setting is not supported on this system.");
+ return;
+ }
+
+ if(old_score != 0)
+ wanted_score = old_score;
+
+ // check the environment
+ char *s = getenv("OOMScoreAdjust");
+ if(!s || !*s) {
+ snprintfz(buf, 30, "%d", (int)wanted_score);
+ s = buf;
+ }
+
+ // check netdata.conf configuration
+ s = config_get(CONFIG_SECTION_GLOBAL, "OOM score", s);
+ if(s && *s && (isdigit(*s) || *s == '-' || *s == '+'))
+ wanted_score = atoll(s);
+ else if(s && !strcmp(s, "keep")) {
+ info("Out-Of-Memory (OOM) kept as-is (running with %d)", (int) old_score);
+ return;
+ }
+ else {
+ info("Out-Of-Memory (OOM) score not changed due to non-numeric setting: '%s' (running with %d)", s, (int)old_score);
+ return;
+ }
+
+ if(wanted_score < OOM_SCORE_ADJ_MIN) {
+ error("Wanted Out-Of-Memory (OOM) score %d is too small. Using %d", (int)wanted_score, (int)OOM_SCORE_ADJ_MIN);
+ wanted_score = OOM_SCORE_ADJ_MIN;
+ }
+
+ if(wanted_score > OOM_SCORE_ADJ_MAX) {
+ error("Wanted Out-Of-Memory (OOM) score %d is too big. Using %d", (int)wanted_score, (int)OOM_SCORE_ADJ_MAX);
+ wanted_score = OOM_SCORE_ADJ_MAX;
+ }
+
+ if(old_score == wanted_score) {
+ info("Out-Of-Memory (OOM) score is already set to the wanted value %d", (int)old_score);
+ return;
+ }
+
+ int written = 0;
+ int fd = open("/proc/self/oom_score_adj", O_WRONLY);
+ if(fd != -1) {
+ snprintfz(buf, 30, "%d", (int)wanted_score);
+ ssize_t len = strlen(buf);
+ if(len > 0 && write(fd, buf, (size_t)len) == len) written = 1;
+ close(fd);
+
+ if(written) {
+ if(read_single_signed_number_file("/proc/self/oom_score_adj", &final_score))
+ error("Adjusted my Out-Of-Memory (OOM) score to %d, but cannot verify it.", (int)wanted_score);
+ else if(final_score == wanted_score)
+ info("Adjusted my Out-Of-Memory (OOM) score from %d to %d.", (int)old_score, (int)final_score);
+ else
+ error("Adjusted my Out-Of-Memory (OOM) score from %d to %d, but it has been set to %d.", (int)old_score, (int)wanted_score, (int)final_score);
+ }
+ else
+ error("Failed to adjust my Out-Of-Memory (OOM) score to %d. Running with %d. (systemd systems may change it via netdata.service)", (int)wanted_score, (int)old_score);
+ }
+ else
+ error("Failed to adjust my Out-Of-Memory (OOM) score. Cannot open /proc/self/oom_score_adj for writing.");
+}
+
+static void process_nice_level(void) {
+#ifdef HAVE_NICE
+ int nice_level = (int)config_get_number(CONFIG_SECTION_GLOBAL, "process nice level", 19);
+ if(nice(nice_level) == -1) error("Cannot set netdata CPU nice level to %d.", nice_level);
+ else debug(D_SYSTEM, "Set netdata nice level to %d.", nice_level);
+#endif // HAVE_NICE
+};
+
+#define SCHED_FLAG_NONE 0x00
+#define SCHED_FLAG_PRIORITY_CONFIGURABLE 0x01 // the priority is user configurable
+#define SCHED_FLAG_KEEP_AS_IS 0x04 // do not attempt to set policy, priority or nice()
+#define SCHED_FLAG_USE_NICE 0x08 // use nice() after setting this policy
+
+struct sched_def {
+ char *name;
+ int policy;
+ int priority;
+ uint8_t flags;
+} scheduler_defaults[] = {
+
+ // the order of array members is important!
+ // the first defined is the default used by netdata
+
+ // the available members are important too!
+ // these are all the possible scheduling policies supported by netdata
+
+#ifdef SCHED_IDLE
+ { "idle", SCHED_IDLE, 0, SCHED_FLAG_NONE },
+#endif
+
+#ifdef SCHED_OTHER
+ { "other", SCHED_OTHER, 0, SCHED_FLAG_USE_NICE },
+ { "nice", SCHED_OTHER, 0, SCHED_FLAG_USE_NICE },
+#endif
+
+#ifdef SCHED_RR
+ { "rr", SCHED_RR, 0, SCHED_FLAG_PRIORITY_CONFIGURABLE },
+#endif
+
+#ifdef SCHED_FIFO
+ { "fifo", SCHED_FIFO, 0, SCHED_FLAG_PRIORITY_CONFIGURABLE },
+#endif
+
+#ifdef SCHED_BATCH
+ { "batch", SCHED_BATCH, 0, SCHED_FLAG_USE_NICE },
+#endif
+
+ // do not change the scheduling priority
+ { "keep", 0, 0, SCHED_FLAG_KEEP_AS_IS },
+ { "none", 0, 0, SCHED_FLAG_KEEP_AS_IS },
+
+ // array termination
+ { NULL, 0, 0, 0 }
+};
+
+
+#ifdef HAVE_SCHED_GETSCHEDULER
+static void sched_getscheduler_report(void) {
+ int sched = sched_getscheduler(0);
+ if(sched == -1) {
+ error("Cannot get my current process scheduling policy.");
+ return;
+ }
+ else {
+ int i;
+ for(i = 0 ; scheduler_defaults[i].name ; i++) {
+ if(scheduler_defaults[i].policy == sched) {
+ if(scheduler_defaults[i].flags & SCHED_FLAG_PRIORITY_CONFIGURABLE) {
+ struct sched_param param;
+ if(sched_getparam(0, &param) == -1) {
+ error("Cannot get the process scheduling priority for my policy '%s'", scheduler_defaults[i].name);
+ return;
+ }
+ else {
+ info("Running with process scheduling policy '%s', priority %d", scheduler_defaults[i].name, param.sched_priority);
+ }
+ }
+ else if(scheduler_defaults[i].flags & SCHED_FLAG_USE_NICE) {
+ #ifdef HAVE_GETPRIORITY
+ int n = getpriority(PRIO_PROCESS, 0);
+ info("Running with process scheduling policy '%s', nice level %d", scheduler_defaults[i].name, n);
+ #else // !HAVE_GETPRIORITY
+ info("Running with process scheduling policy '%s'", scheduler_defaults[i].name);
+ #endif // !HAVE_GETPRIORITY
+ }
+ else {
+ info("Running with process scheduling policy '%s'", scheduler_defaults[i].name);
+ }
+
+ return;
+ }
+ }
+ }
+}
+#else // !HAVE_SCHED_GETSCHEDULER
+static void sched_getscheduler_report(void) {
+#ifdef HAVE_GETPRIORITY
+ info("Running with priority %d", getpriority(PRIO_PROCESS, 0));
+#endif // HAVE_GETPRIORITY
+}
+#endif // !HAVE_SCHED_GETSCHEDULER
+
+#ifdef HAVE_SCHED_SETSCHEDULER
+
+static void sched_setscheduler_set(void) {
+
+ if(scheduler_defaults[0].name) {
+ const char *name = scheduler_defaults[0].name;
+ int policy = scheduler_defaults[0].policy, priority = scheduler_defaults[0].priority;
+ uint8_t flags = scheduler_defaults[0].flags;
+ int found = 0;
+
+ // read the configuration
+ name = config_get(CONFIG_SECTION_GLOBAL, "process scheduling policy", name);
+ int i;
+ for(i = 0 ; scheduler_defaults[i].name ; i++) {
+ if(!strcmp(name, scheduler_defaults[i].name)) {
+ found = 1;
+ policy = scheduler_defaults[i].policy;
+ priority = scheduler_defaults[i].priority;
+ flags = scheduler_defaults[i].flags;
+
+ if(flags & SCHED_FLAG_KEEP_AS_IS)
+ goto report;
+
+ if(flags & SCHED_FLAG_PRIORITY_CONFIGURABLE)
+ priority = (int)config_get_number(CONFIG_SECTION_GLOBAL, "process scheduling priority", priority);
+
+#ifdef HAVE_SCHED_GET_PRIORITY_MIN
+ errno = 0;
+ if(priority < sched_get_priority_min(policy)) {
+ error("scheduler %s (%d) priority %d is below the minimum %d. Using the minimum.", name, policy, priority, sched_get_priority_min(policy));
+ priority = sched_get_priority_min(policy);
+ }
+#endif
+#ifdef HAVE_SCHED_GET_PRIORITY_MAX
+ errno = 0;
+ if(priority > sched_get_priority_max(policy)) {
+ error("scheduler %s (%d) priority %d is above the maximum %d. Using the maximum.", name, policy, priority, sched_get_priority_max(policy));
+ priority = sched_get_priority_max(policy);
+ }
+#endif
+ break;
+ }
+ }
+
+ if(!found) {
+ error("Unknown scheduling policy '%s' - falling back to nice", name);
+ goto fallback;
+ }
+
+ const struct sched_param param = {
+ .sched_priority = priority
+ };
+
+ errno = 0;
+ i = sched_setscheduler(0, policy, &param);
+ if(i != 0) {
+ error("Cannot adjust netdata scheduling policy to %s (%d), with priority %d. Falling back to nice.", name, policy, priority);
+ }
+ else {
+ info("Adjusted netdata scheduling policy to %s (%d), with priority %d.", name, policy, priority);
+ if(!(flags & SCHED_FLAG_USE_NICE))
+ goto report;
+ }
+ }
+
+fallback:
+ process_nice_level();
+
+report:
+ sched_getscheduler_report();
+}
+#else // !HAVE_SCHED_SETSCHEDULER
+static void sched_setscheduler_set(void) {
+ process_nice_level();
+}
+#endif // !HAVE_SCHED_SETSCHEDULER
+
+int become_daemon(int dont_fork, const char *user)
+{
+ if(!dont_fork) {
+ int i = fork();
+ if(i == -1) {
+ perror("cannot fork");
+ exit(1);
+ }
+ if(i != 0) {
+ exit(0); // the parent
+ }
+
+ // become session leader
+ if (setsid() < 0) {
+ perror("Cannot become session leader.");
+ exit(2);
+ }
+
+ // fork() again
+ i = fork();
+ if(i == -1) {
+ perror("cannot fork");
+ exit(1);
+ }
+ if(i != 0) {
+ exit(0); // the parent
+ }
+ }
+
+ // generate our pid file
+ int pidfd = -1;
+ if(pidfile[0]) {
+ pidfd = open(pidfile, O_WRONLY | O_CREAT, 0644);
+ if(pidfd >= 0) {
+ if(ftruncate(pidfd, 0) != 0)
+ error("Cannot truncate pidfile '%s'.", pidfile);
+
+ char b[100];
+ sprintf(b, "%d\n", getpid());
+ ssize_t i = write(pidfd, b, strlen(b));
+ if(i <= 0)
+ error("Cannot write pidfile '%s'.", pidfile);
+ }
+ else error("Failed to open pidfile '%s'.", pidfile);
+ }
+
+ // Set new file permissions
+ umask(0007);
+
+ // adjust my Out-Of-Memory score
+ oom_score_adj();
+
+ // never become a problem
+ sched_setscheduler_set();
+
+ // Set claiming directory based on user config directory with correct ownership
+ snprintfz(claimingdirectory, FILENAME_MAX, "%s/cloud.d", netdata_configured_varlib_dir);
+
+ if(user && *user) {
+ if(become_user(user, pidfd) != 0) {
+ error("Cannot become user '%s'. Continuing as we are.", user);
+ }
+ else debug(D_SYSTEM, "Successfully became user '%s'.", user);
+ }
+ else {
+ create_needed_dir(netdata_configured_cache_dir, getuid(), getgid());
+ create_needed_dir(netdata_configured_varlib_dir, getuid(), getgid());
+ create_needed_dir(netdata_configured_lock_dir, getuid(), getgid());
+ create_needed_dir(claimingdirectory, getuid(), getgid());
+
+ clean_directory(netdata_configured_lock_dir);
+ }
+
+ if(pidfd != -1)
+ close(pidfd);
+
+ return(0);
+}
diff --git a/daemon/daemon.h b/daemon/daemon.h
new file mode 100644
index 0000000..bec3df9
--- /dev/null
+++ b/daemon/daemon.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_DAEMON_H
+#define NETDATA_DAEMON_H 1
+
+extern int become_user(const char *username, int pid_fd);
+
+extern int become_daemon(int dont_fork, const char *user);
+
+extern void netdata_cleanup_and_exit(int i);
+extern void send_statistics(const char *action, const char *action_result, const char *action_data);
+
+extern void get_netdata_execution_path(void);
+
+extern char pidfile[];
+extern char exepath[];
+
+#endif /* NETDATA_DAEMON_H */
diff --git a/daemon/get-kubernetes-labels.sh.in b/daemon/get-kubernetes-labels.sh.in
new file mode 100644
index 0000000..5aa89ab
--- /dev/null
+++ b/daemon/get-kubernetes-labels.sh.in
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+# Checks if netdata is running in a kubernetes pod and fetches that pod's labels
+
+if [ -z "${KUBERNETES_SERVICE_HOST}" ] || [ -z "${KUBERNETES_PORT_443_TCP_PORT}" ] || [ -z "${MY_POD_NAMESPACE}" ] || [ -z "${MY_POD_NAME}" ]; then
+ exit 0
+fi
+
+if ! command -v jq > /dev/null 2>&1; then
+ echo "jq command not available. Please install jq to get host labels for kubernetes pods."
+ exit 1
+fi
+
+TOKEN="$(< /var/run/secrets/kubernetes.io/serviceaccount/token)"
+HEADER="Authorization: Bearer $TOKEN"
+HOST="$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT"
+
+URL="https://$HOST/api/v1/namespaces/$MY_POD_NAMESPACE/pods/$MY_POD_NAME"
+if ! POD_DATA=$(curl -sSk -H "$HEADER" "$URL" 2>&1); then
+ echo "error on curl '${URL}': ${POD_DATA}."
+ exit 1
+fi
+
+URL="https://$HOST/api/v1/namespaces/kube-system"
+if ! KUBE_SYSTEM_NS_DATA=$(curl -sSk -H "$HEADER" "$URL" 2>&1); then
+ echo "error on curl '${URL}': ${KUBE_SYSTEM_NS_DATA}."
+ exit 1
+fi
+
+if ! POD_LABELS=$(jq -r '.metadata.labels' <<< "$POD_DATA" | grep ':' | tr -d '," ' 2>&1); then
+ echo "error on 'jq' parse pod data: ${POD_LABELS}."
+ exit 1
+fi
+
+if ! KUBE_SYSTEM_NS_UID=$(jq -r '.metadata.uid' <<< "$KUBE_SYSTEM_NS_DATA" 2>&1); then
+ echo "error on 'jq' parse kube_system_ns: ${KUBE_SYSTEM_NS_UID}."
+ exit 1
+fi
+
+echo -e "$POD_LABELS\nk8s_cluster_id:$KUBE_SYSTEM_NS_UID"
+exit 0
diff --git a/daemon/global_statistics.c b/daemon/global_statistics.c
new file mode 100644
index 0000000..7e78355
--- /dev/null
+++ b/daemon/global_statistics.c
@@ -0,0 +1,950 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common.h"
+
+#define GLOBAL_STATS_RESET_WEB_USEC_MAX 0x01
+
+
+static struct global_statistics {
+ volatile uint16_t connected_clients;
+
+ volatile uint64_t web_requests;
+ volatile uint64_t web_usec;
+ volatile uint64_t web_usec_max;
+ volatile uint64_t bytes_received;
+ volatile uint64_t bytes_sent;
+ volatile uint64_t content_size;
+ volatile uint64_t compressed_content_size;
+
+ volatile uint64_t web_client_count;
+
+ volatile uint64_t rrdr_queries_made;
+ volatile uint64_t rrdr_db_points_read;
+ volatile uint64_t rrdr_result_points_generated;
+} global_statistics = {
+ .connected_clients = 0,
+ .web_requests = 0,
+ .web_usec = 0,
+ .bytes_received = 0,
+ .bytes_sent = 0,
+ .content_size = 0,
+ .compressed_content_size = 0,
+ .web_client_count = 1,
+
+ .rrdr_queries_made = 0,
+ .rrdr_db_points_read = 0,
+ .rrdr_result_points_generated = 0,
+};
+
+#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
+#else
+netdata_mutex_t global_statistics_mutex = NETDATA_MUTEX_INITIALIZER;
+
+static inline void global_statistics_lock(void) {
+ netdata_mutex_lock(&global_statistics_mutex);
+}
+
+static inline void global_statistics_unlock(void) {
+ netdata_mutex_unlock(&global_statistics_mutex);
+}
+#endif
+
+
+void rrdr_query_completed(uint64_t db_points_read, uint64_t result_points_generated) {
+#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
+ __atomic_fetch_add(&global_statistics.rrdr_queries_made, 1, __ATOMIC_SEQ_CST);
+ __atomic_fetch_add(&global_statistics.rrdr_db_points_read, db_points_read, __ATOMIC_SEQ_CST);
+ __atomic_fetch_add(&global_statistics.rrdr_result_points_generated, result_points_generated, __ATOMIC_SEQ_CST);
+#else
+ #warning NOT using atomic operations - using locks for global statistics
+ if (web_server_is_multithreaded)
+ global_statistics_lock();
+
+ global_statistics.rrdr_queries_made++;
+ global_statistics.rrdr_db_points_read += db_points_read;
+ global_statistics.rrdr_result_points_generated += result_points_generated;
+
+ if (web_server_is_multithreaded)
+ global_statistics_unlock();
+#endif
+}
+
+void finished_web_request_statistics(uint64_t dt,
+ uint64_t bytes_received,
+ uint64_t bytes_sent,
+ uint64_t content_size,
+ uint64_t compressed_content_size) {
+#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
+ uint64_t old_web_usec_max = global_statistics.web_usec_max;
+ while(dt > old_web_usec_max)
+ __atomic_compare_exchange(&global_statistics.web_usec_max, &old_web_usec_max, &dt, 1, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
+
+ __atomic_fetch_add(&global_statistics.web_requests, 1, __ATOMIC_SEQ_CST);
+ __atomic_fetch_add(&global_statistics.web_usec, dt, __ATOMIC_SEQ_CST);
+ __atomic_fetch_add(&global_statistics.bytes_received, bytes_received, __ATOMIC_SEQ_CST);
+ __atomic_fetch_add(&global_statistics.bytes_sent, bytes_sent, __ATOMIC_SEQ_CST);
+ __atomic_fetch_add(&global_statistics.content_size, content_size, __ATOMIC_SEQ_CST);
+ __atomic_fetch_add(&global_statistics.compressed_content_size, compressed_content_size, __ATOMIC_SEQ_CST);
+#else
+#warning NOT using atomic operations - using locks for global statistics
+ if (web_server_is_multithreaded)
+ global_statistics_lock();
+
+ if (dt > global_statistics.web_usec_max)
+ global_statistics.web_usec_max = dt;
+
+ global_statistics.web_requests++;
+ global_statistics.web_usec += dt;
+ global_statistics.bytes_received += bytes_received;
+ global_statistics.bytes_sent += bytes_sent;
+ global_statistics.content_size += content_size;
+ global_statistics.compressed_content_size += compressed_content_size;
+
+ if (web_server_is_multithreaded)
+ global_statistics_unlock();
+#endif
+}
+
+uint64_t web_client_connected(void) {
+#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
+ __atomic_fetch_add(&global_statistics.connected_clients, 1, __ATOMIC_SEQ_CST);
+ uint64_t id = __atomic_fetch_add(&global_statistics.web_client_count, 1, __ATOMIC_SEQ_CST);
+#else
+ if (web_server_is_multithreaded)
+ global_statistics_lock();
+
+ global_statistics.connected_clients++;
+ uint64_t id = global_statistics.web_client_count++;
+
+ if (web_server_is_multithreaded)
+ global_statistics_unlock();
+#endif
+
+ return id;
+}
+
+void web_client_disconnected(void) {
+#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
+ __atomic_fetch_sub(&global_statistics.connected_clients, 1, __ATOMIC_SEQ_CST);
+#else
+ if (web_server_is_multithreaded)
+ global_statistics_lock();
+
+ global_statistics.connected_clients--;
+
+ if (web_server_is_multithreaded)
+ global_statistics_unlock();
+#endif
+}
+
+
+static inline void global_statistics_copy(struct global_statistics *gs, uint8_t options) {
+#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
+ gs->connected_clients = __atomic_fetch_add(&global_statistics.connected_clients, 0, __ATOMIC_SEQ_CST);
+ gs->web_requests = __atomic_fetch_add(&global_statistics.web_requests, 0, __ATOMIC_SEQ_CST);
+ gs->web_usec = __atomic_fetch_add(&global_statistics.web_usec, 0, __ATOMIC_SEQ_CST);
+ gs->web_usec_max = __atomic_fetch_add(&global_statistics.web_usec_max, 0, __ATOMIC_SEQ_CST);
+ gs->bytes_received = __atomic_fetch_add(&global_statistics.bytes_received, 0, __ATOMIC_SEQ_CST);
+ gs->bytes_sent = __atomic_fetch_add(&global_statistics.bytes_sent, 0, __ATOMIC_SEQ_CST);
+ gs->content_size = __atomic_fetch_add(&global_statistics.content_size, 0, __ATOMIC_SEQ_CST);
+ gs->compressed_content_size = __atomic_fetch_add(&global_statistics.compressed_content_size, 0, __ATOMIC_SEQ_CST);
+ gs->web_client_count = __atomic_fetch_add(&global_statistics.web_client_count, 0, __ATOMIC_SEQ_CST);
+
+ gs->rrdr_queries_made = __atomic_fetch_add(&global_statistics.rrdr_queries_made, 0, __ATOMIC_SEQ_CST);
+ gs->rrdr_db_points_read = __atomic_fetch_add(&global_statistics.rrdr_db_points_read, 0, __ATOMIC_SEQ_CST);
+ gs->rrdr_result_points_generated = __atomic_fetch_add(&global_statistics.rrdr_result_points_generated, 0, __ATOMIC_SEQ_CST);
+
+ if(options & GLOBAL_STATS_RESET_WEB_USEC_MAX) {
+ uint64_t n = 0;
+ __atomic_compare_exchange(&global_statistics.web_usec_max, &gs->web_usec_max, &n, 1, __ATOMIC_SEQ_CST,
+ __ATOMIC_SEQ_CST);
+ }
+#else
+ global_statistics_lock();
+
+ memcpy(gs, (const void *)&global_statistics, sizeof(struct global_statistics));
+
+ if (options & GLOBAL_STATS_RESET_WEB_USEC_MAX)
+ global_statistics.web_usec_max = 0;
+
+ global_statistics_unlock();
+#endif
+}
+
+void global_statistics_charts(void) {
+ static unsigned long long old_web_requests = 0,
+ old_web_usec = 0,
+ old_content_size = 0,
+ old_compressed_content_size = 0;
+
+ static collected_number compression_ratio = -1,
+ average_response_time = -1;
+
+ struct global_statistics gs;
+ struct rusage me, thread;
+
+ global_statistics_copy(&gs, GLOBAL_STATS_RESET_WEB_USEC_MAX);
+ getrusage(RUSAGE_THREAD, &thread);
+ getrusage(RUSAGE_SELF, &me);
+
+ {
+ static RRDSET *st_cpu_thread = NULL;
+ static RRDDIM *rd_cpu_thread_user = NULL,
+ *rd_cpu_thread_system = NULL;
+
+#ifdef __FreeBSD__
+ if (unlikely(!st_cpu_thread)) {
+ st_cpu_thread = rrdset_create_localhost(
+ "netdata"
+ , "plugin_freebsd_cpu"
+ , NULL
+ , "freebsd"
+ , NULL
+ , "NetData FreeBSD Plugin CPU usage"
+ , "milliseconds/s"
+ , "netdata"
+ , "stats"
+ , 132000
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_STACKED
+ );
+#else
+ if (unlikely(!st_cpu_thread)) {
+ st_cpu_thread = rrdset_create_localhost(
+ "netdata"
+ , "plugin_proc_cpu"
+ , NULL
+ , "proc"
+ , NULL
+ , "NetData Proc Plugin CPU usage"
+ , "milliseconds/s"
+ , "netdata"
+ , "stats"
+ , 132000
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_STACKED
+ );
+#endif
+
+ rd_cpu_thread_user = rrddim_add(st_cpu_thread, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ rd_cpu_thread_system = rrddim_add(st_cpu_thread, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_cpu_thread);
+
+ rrddim_set_by_pointer(st_cpu_thread, rd_cpu_thread_user, thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec);
+ rrddim_set_by_pointer(st_cpu_thread, rd_cpu_thread_system, thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec);
+ rrdset_done(st_cpu_thread);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_cpu = NULL;
+ static RRDDIM *rd_cpu_user = NULL,
+ *rd_cpu_system = NULL;
+
+ if (unlikely(!st_cpu)) {
+ st_cpu = rrdset_create_localhost(
+ "netdata"
+ , "server_cpu"
+ , NULL
+ , "netdata"
+ , NULL
+ , "NetData CPU usage"
+ , "milliseconds/s"
+ , "netdata"
+ , "stats"
+ , 130000
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_cpu_user = rrddim_add(st_cpu, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ rd_cpu_system = rrddim_add(st_cpu, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_cpu);
+
+ rrddim_set_by_pointer(st_cpu, rd_cpu_user, me.ru_utime.tv_sec * 1000000ULL + me.ru_utime.tv_usec);
+ rrddim_set_by_pointer(st_cpu, rd_cpu_system, me.ru_stime.tv_sec * 1000000ULL + me.ru_stime.tv_usec);
+ rrdset_done(st_cpu);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_clients = NULL;
+ static RRDDIM *rd_clients = NULL;
+
+ if (unlikely(!st_clients)) {
+ st_clients = rrdset_create_localhost(
+ "netdata"
+ , "clients"
+ , NULL
+ , "netdata"
+ , NULL
+ , "NetData Web Clients"
+ , "connected clients"
+ , "netdata"
+ , "stats"
+ , 130200
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_clients = rrddim_add(st_clients, "clients", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_clients);
+
+ rrddim_set_by_pointer(st_clients, rd_clients, gs.connected_clients);
+ rrdset_done(st_clients);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_reqs = NULL;
+ static RRDDIM *rd_requests = NULL;
+
+ if (unlikely(!st_reqs)) {
+ st_reqs = rrdset_create_localhost(
+ "netdata"
+ , "requests"
+ , NULL
+ , "netdata"
+ , NULL
+ , "NetData Web Requests"
+ , "requests/s"
+ , "netdata"
+ , "stats"
+ , 130300
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_requests = rrddim_add(st_reqs, "requests", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_reqs);
+
+ rrddim_set_by_pointer(st_reqs, rd_requests, (collected_number) gs.web_requests);
+ rrdset_done(st_reqs);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_bytes = NULL;
+ static RRDDIM *rd_in = NULL,
+ *rd_out = NULL;
+
+ if (unlikely(!st_bytes)) {
+ st_bytes = rrdset_create_localhost(
+ "netdata"
+ , "net"
+ , NULL
+ , "netdata"
+ , NULL
+ , "NetData Network Traffic"
+ , "kilobits/s"
+ , "netdata"
+ , "stats"
+ , 130000
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_in = rrddim_add(st_bytes, "in", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_bytes, "out", NULL, -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_bytes);
+
+ rrddim_set_by_pointer(st_bytes, rd_in, (collected_number) gs.bytes_received);
+ rrddim_set_by_pointer(st_bytes, rd_out, (collected_number) gs.bytes_sent);
+ rrdset_done(st_bytes);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_duration = NULL;
+ static RRDDIM *rd_average = NULL,
+ *rd_max = NULL;
+
+ if (unlikely(!st_duration)) {
+ st_duration = rrdset_create_localhost(
+ "netdata"
+ , "response_time"
+ , NULL
+ , "netdata"
+ , NULL
+ , "NetData API Response Time"
+ , "milliseconds/request"
+ , "netdata"
+ , "stats"
+ , 130400
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_average = rrddim_add(st_duration, "average", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_max = rrddim_add(st_duration, "max", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_duration);
+
+ uint64_t gweb_usec = gs.web_usec;
+ uint64_t gweb_requests = gs.web_requests;
+
+ uint64_t web_usec = (gweb_usec >= old_web_usec) ? gweb_usec - old_web_usec : 0;
+ uint64_t web_requests = (gweb_requests >= old_web_requests) ? gweb_requests - old_web_requests : 0;
+
+ old_web_usec = gweb_usec;
+ old_web_requests = gweb_requests;
+
+ if (web_requests)
+ average_response_time = (collected_number) (web_usec / web_requests);
+
+ if (unlikely(average_response_time != -1))
+ rrddim_set_by_pointer(st_duration, rd_average, average_response_time);
+ else
+ rrddim_set_by_pointer(st_duration, rd_average, 0);
+
+ rrddim_set_by_pointer(st_duration, rd_max, ((gs.web_usec_max)?(collected_number)gs.web_usec_max:average_response_time));
+ rrdset_done(st_duration);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_compression = NULL;
+ static RRDDIM *rd_savings = NULL;
+
+ if (unlikely(!st_compression)) {
+ st_compression = rrdset_create_localhost(
+ "netdata"
+ , "compression_ratio"
+ , NULL
+ , "netdata"
+ , NULL
+ , "NetData API Responses Compression Savings Ratio"
+ , "percentage"
+ , "netdata"
+ , "stats"
+ , 130500
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_savings = rrddim_add(st_compression, "savings", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_compression);
+
+ // since we don't lock here to read the global statistics
+ // read the smaller value first
+ unsigned long long gcompressed_content_size = gs.compressed_content_size;
+ unsigned long long gcontent_size = gs.content_size;
+
+ unsigned long long compressed_content_size = gcompressed_content_size - old_compressed_content_size;
+ unsigned long long content_size = gcontent_size - old_content_size;
+
+ old_compressed_content_size = gcompressed_content_size;
+ old_content_size = gcontent_size;
+
+ if (content_size && content_size >= compressed_content_size)
+ compression_ratio = ((content_size - compressed_content_size) * 100 * 1000) / content_size;
+
+ if (compression_ratio != -1)
+ rrddim_set_by_pointer(st_compression, rd_savings, compression_ratio);
+
+ rrdset_done(st_compression);
+ }
+
+ // ----------------------------------------------------------------
+
+ if(gs.rrdr_queries_made) {
+ static RRDSET *st_rrdr_queries = NULL;
+ static RRDDIM *rd_queries = NULL;
+
+ if (unlikely(!st_rrdr_queries)) {
+ st_rrdr_queries = rrdset_create_localhost(
+ "netdata"
+ , "queries"
+ , NULL
+ , "queries"
+ , NULL
+ , "NetData API Queries"
+ , "queries/s"
+ , "netdata"
+ , "stats"
+ , 130500
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_queries = rrddim_add(st_rrdr_queries, "queries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_rrdr_queries);
+
+ rrddim_set_by_pointer(st_rrdr_queries, rd_queries, (collected_number)gs.rrdr_queries_made);
+
+ rrdset_done(st_rrdr_queries);
+ }
+
+ // ----------------------------------------------------------------
+
+ if(gs.rrdr_db_points_read || gs.rrdr_result_points_generated) {
+ static RRDSET *st_rrdr_points = NULL;
+ static RRDDIM *rd_points_read = NULL;
+ static RRDDIM *rd_points_generated = NULL;
+
+ if (unlikely(!st_rrdr_points)) {
+ st_rrdr_points = rrdset_create_localhost(
+ "netdata"
+ , "db_points"
+ , NULL
+ , "queries"
+ , NULL
+ , "NetData API Points"
+ , "points/s"
+ , "netdata"
+ , "stats"
+ , 130501
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_points_read = rrddim_add(st_rrdr_points, "read", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_points_generated = rrddim_add(st_rrdr_points, "generated", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_rrdr_points);
+
+ rrddim_set_by_pointer(st_rrdr_points, rd_points_read, (collected_number)gs.rrdr_db_points_read);
+ rrddim_set_by_pointer(st_rrdr_points, rd_points_generated, (collected_number)gs.rrdr_result_points_generated);
+
+ rrdset_done(st_rrdr_points);
+ }
+
+ // ----------------------------------------------------------------
+
+#ifdef ENABLE_DBENGINE
+ RRDHOST *host;
+ unsigned long long stats_array[RRDENG_NR_STATS] = {0};
+ unsigned long long local_stats_array[RRDENG_NR_STATS];
+ unsigned dbengine_contexts = 0, counted_multihost_db = 0, i;
+
+ rrd_rdlock();
+ rrdhost_foreach_read(host) {
+ if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE && !rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) {
+ if (&multidb_ctx == host->rrdeng_ctx) {
+ if (counted_multihost_db)
+ continue; /* Only count multi-host DB once */
+ counted_multihost_db = 1;
+ }
+ ++dbengine_contexts;
+ /* get localhost's DB engine's statistics */
+ rrdeng_get_37_statistics(host->rrdeng_ctx, local_stats_array);
+ for (i = 0 ; i < RRDENG_NR_STATS ; ++i) {
+ /* aggregate statistics across hosts */
+ stats_array[i] += local_stats_array[i];
+ }
+ }
+ }
+ rrd_unlock();
+
+ if (dbengine_contexts) {
+ /* deduplicate global statistics by getting the ones from the last context */
+ stats_array[30] = local_stats_array[30];
+ stats_array[31] = local_stats_array[31];
+ stats_array[32] = local_stats_array[32];
+ stats_array[34] = local_stats_array[34];
+ stats_array[36] = local_stats_array[36];
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_compression = NULL;
+ static RRDDIM *rd_savings = NULL;
+
+ if (unlikely(!st_compression)) {
+ st_compression = rrdset_create_localhost(
+ "netdata"
+ , "dbengine_compression_ratio"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData DB engine data extents' compression savings ratio"
+ , "percentage"
+ , "netdata"
+ , "stats"
+ , 130502
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_savings = rrddim_add(st_compression, "savings", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_compression);
+
+ unsigned long long ratio;
+ unsigned long long compressed_content_size = stats_array[12];
+ unsigned long long content_size = stats_array[11];
+
+ if (content_size) {
+ // allow negative savings
+ ratio = ((content_size - compressed_content_size) * 100 * 1000) / content_size;
+ } else {
+ ratio = 0;
+ }
+ rrddim_set_by_pointer(st_compression, rd_savings, ratio);
+
+ rrdset_done(st_compression);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_pg_cache_hit_ratio = NULL;
+ static RRDDIM *rd_hit_ratio = NULL;
+
+ if (unlikely(!st_pg_cache_hit_ratio)) {
+ st_pg_cache_hit_ratio = rrdset_create_localhost(
+ "netdata"
+ , "page_cache_hit_ratio"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData DB engine page cache hit ratio"
+ , "percentage"
+ , "netdata"
+ , "stats"
+ , 130503
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_hit_ratio = rrddim_add(st_pg_cache_hit_ratio, "ratio", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_pg_cache_hit_ratio);
+
+ static unsigned long long old_hits = 0;
+ static unsigned long long old_misses = 0;
+ unsigned long long hits = stats_array[7];
+ unsigned long long misses = stats_array[8];
+ unsigned long long hits_delta;
+ unsigned long long misses_delta;
+ unsigned long long ratio;
+
+ hits_delta = hits - old_hits;
+ misses_delta = misses - old_misses;
+ old_hits = hits;
+ old_misses = misses;
+
+ if (hits_delta + misses_delta) {
+ ratio = (hits_delta * 100 * 1000) / (hits_delta + misses_delta);
+ } else {
+ ratio = 0;
+ }
+ rrddim_set_by_pointer(st_pg_cache_hit_ratio, rd_hit_ratio, ratio);
+
+ rrdset_done(st_pg_cache_hit_ratio);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_pg_cache_pages = NULL;
+ static RRDDIM *rd_descriptors = NULL;
+ static RRDDIM *rd_populated = NULL;
+ static RRDDIM *rd_dirty = NULL;
+ static RRDDIM *rd_backfills = NULL;
+ static RRDDIM *rd_evictions = NULL;
+ static RRDDIM *rd_used_by_collectors = NULL;
+
+ if (unlikely(!st_pg_cache_pages)) {
+ st_pg_cache_pages = rrdset_create_localhost(
+ "netdata"
+ , "page_cache_stats"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData dbengine page cache statistics"
+ , "pages"
+ , "netdata"
+ , "stats"
+ , 130504
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_descriptors = rrddim_add(st_pg_cache_pages, "descriptors", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_populated = rrddim_add(st_pg_cache_pages, "populated", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_dirty = rrddim_add(st_pg_cache_pages, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_backfills = rrddim_add(st_pg_cache_pages, "backfills", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_evictions = rrddim_add(st_pg_cache_pages, "evictions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_used_by_collectors = rrddim_add(st_pg_cache_pages, "used_by_collectors", NULL, 1, 1,
+ RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_pg_cache_pages);
+
+ rrddim_set_by_pointer(st_pg_cache_pages, rd_descriptors, (collected_number)stats_array[27]);
+ rrddim_set_by_pointer(st_pg_cache_pages, rd_populated, (collected_number)stats_array[3]);
+ rrddim_set_by_pointer(st_pg_cache_pages, rd_dirty, (collected_number)stats_array[0] + stats_array[4]);
+ rrddim_set_by_pointer(st_pg_cache_pages, rd_backfills, (collected_number)stats_array[9]);
+ rrddim_set_by_pointer(st_pg_cache_pages, rd_evictions, (collected_number)stats_array[10]);
+ rrddim_set_by_pointer(st_pg_cache_pages, rd_used_by_collectors, (collected_number)stats_array[0]);
+ rrdset_done(st_pg_cache_pages);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_long_term_pages = NULL;
+ static RRDDIM *rd_total = NULL;
+ static RRDDIM *rd_insertions = NULL;
+ static RRDDIM *rd_deletions = NULL;
+ static RRDDIM *rd_flushing_pressure_deletions = NULL;
+
+ if (unlikely(!st_long_term_pages)) {
+ st_long_term_pages = rrdset_create_localhost(
+ "netdata"
+ , "dbengine_long_term_page_stats"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData dbengine long-term page statistics"
+ , "pages"
+ , "netdata"
+ , "stats"
+ , 130505
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_total = rrddim_add(st_long_term_pages, "total", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_insertions = rrddim_add(st_long_term_pages, "insertions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_deletions = rrddim_add(st_long_term_pages, "deletions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_flushing_pressure_deletions = rrddim_add(st_long_term_pages, "flushing_pressure_deletions", NULL, -1,
+ 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_long_term_pages);
+
+ rrddim_set_by_pointer(st_long_term_pages, rd_total, (collected_number)stats_array[2]);
+ rrddim_set_by_pointer(st_long_term_pages, rd_insertions, (collected_number)stats_array[5]);
+ rrddim_set_by_pointer(st_long_term_pages, rd_deletions, (collected_number)stats_array[6]);
+ rrddim_set_by_pointer(st_long_term_pages, rd_flushing_pressure_deletions,
+ (collected_number)stats_array[36]);
+ rrdset_done(st_long_term_pages);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_io_stats = NULL;
+ static RRDDIM *rd_reads = NULL;
+ static RRDDIM *rd_writes = NULL;
+
+ if (unlikely(!st_io_stats)) {
+ st_io_stats = rrdset_create_localhost(
+ "netdata"
+ , "dbengine_io_throughput"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData DB engine I/O throughput"
+ , "MiB/s"
+ , "netdata"
+ , "stats"
+ , 130506
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL);
+ rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_io_stats);
+
+ rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[17]);
+ rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[15]);
+ rrdset_done(st_io_stats);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_io_stats = NULL;
+ static RRDDIM *rd_reads = NULL;
+ static RRDDIM *rd_writes = NULL;
+
+ if (unlikely(!st_io_stats)) {
+ st_io_stats = rrdset_create_localhost(
+ "netdata"
+ , "dbengine_io_operations"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData DB engine I/O operations"
+ , "operations/s"
+ , "netdata"
+ , "stats"
+ , 130507
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_reads = rrddim_add(st_io_stats, "reads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_writes = rrddim_add(st_io_stats, "writes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_io_stats);
+
+ rrddim_set_by_pointer(st_io_stats, rd_reads, (collected_number)stats_array[18]);
+ rrddim_set_by_pointer(st_io_stats, rd_writes, (collected_number)stats_array[16]);
+ rrdset_done(st_io_stats);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_errors = NULL;
+ static RRDDIM *rd_fs_errors = NULL;
+ static RRDDIM *rd_io_errors = NULL;
+ static RRDDIM *pg_cache_over_half_dirty_events = NULL;
+
+ if (unlikely(!st_errors)) {
+ st_errors = rrdset_create_localhost(
+ "netdata"
+ , "dbengine_global_errors"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData DB engine errors"
+ , "errors/s"
+ , "netdata"
+ , "stats"
+ , 130508
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_io_errors = rrddim_add(st_errors, "io_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_fs_errors = rrddim_add(st_errors, "fs_errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ pg_cache_over_half_dirty_events = rrddim_add(st_errors, "pg_cache_over_half_dirty_events", NULL, 1, 1,
+ RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_errors);
+
+ rrddim_set_by_pointer(st_errors, rd_io_errors, (collected_number)stats_array[30]);
+ rrddim_set_by_pointer(st_errors, rd_fs_errors, (collected_number)stats_array[31]);
+ rrddim_set_by_pointer(st_errors, pg_cache_over_half_dirty_events, (collected_number)stats_array[34]);
+ rrdset_done(st_errors);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_fd = NULL;
+ static RRDDIM *rd_fd_current = NULL;
+ static RRDDIM *rd_fd_max = NULL;
+
+ if (unlikely(!st_fd)) {
+ st_fd = rrdset_create_localhost(
+ "netdata"
+ , "dbengine_global_file_descriptors"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData DB engine File Descriptors"
+ , "descriptors"
+ , "netdata"
+ , "stats"
+ , 130509
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_fd_current = rrddim_add(st_fd, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_fd_max = rrddim_add(st_fd, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_fd);
+
+ rrddim_set_by_pointer(st_fd, rd_fd_current, (collected_number)stats_array[32]);
+ /* Careful here, modify this accordingly if the File-Descriptor budget ever changes */
+ rrddim_set_by_pointer(st_fd, rd_fd_max, (collected_number)rlimit_nofile.rlim_cur / 4);
+ rrdset_done(st_fd);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_ram_usage = NULL;
+ static RRDDIM *rd_cached = NULL;
+ static RRDDIM *rd_pinned = NULL;
+ static RRDDIM *rd_metadata = NULL;
+
+ collected_number cached_pages, pinned_pages, API_producers, populated_pages, metadata, pages_on_disk,
+ page_cache_descriptors;
+
+ if (unlikely(!st_ram_usage)) {
+ st_ram_usage = rrdset_create_localhost(
+ "netdata"
+ , "dbengine_ram"
+ , NULL
+ , "dbengine"
+ , NULL
+ , "NetData DB engine RAM usage"
+ , "MiB"
+ , "netdata"
+ , "stats"
+ , 130510
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_cached = rrddim_add(st_ram_usage, "cache", NULL, 1, 256, RRD_ALGORITHM_ABSOLUTE);
+ rd_pinned = rrddim_add(st_ram_usage, "collectors", NULL, 1, 256, RRD_ALGORITHM_ABSOLUTE);
+ rd_metadata = rrddim_add(st_ram_usage, "metadata", NULL, 1, 1048576, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_ram_usage);
+
+ API_producers = (collected_number)stats_array[0];
+ pages_on_disk = (collected_number)stats_array[2];
+ populated_pages = (collected_number)stats_array[3];
+ page_cache_descriptors = (collected_number)stats_array[27];
+
+ if (API_producers * 2 > populated_pages) {
+ pinned_pages = API_producers;
+ } else{
+ pinned_pages = API_producers * 2;
+ }
+ cached_pages = populated_pages - pinned_pages;
+
+ metadata = page_cache_descriptors * sizeof(struct page_cache_descr);
+ metadata += pages_on_disk * sizeof(struct rrdeng_page_descr);
+ /* This is an empirical estimation for Judy array indexing and extent structures */
+ metadata += pages_on_disk * 58;
+
+ rrddim_set_by_pointer(st_ram_usage, rd_cached, cached_pages);
+ rrddim_set_by_pointer(st_ram_usage, rd_pinned, pinned_pages);
+ rrddim_set_by_pointer(st_ram_usage, rd_metadata, metadata);
+ rrdset_done(st_ram_usage);
+ }
+ }
+#endif
+
+}
diff --git a/daemon/global_statistics.h b/daemon/global_statistics.h
new file mode 100644
index 0000000..9dd7db5
--- /dev/null
+++ b/daemon/global_statistics.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_GLOBAL_STATISTICS_H
+#define NETDATA_GLOBAL_STATISTICS_H 1
+
+#include "common.h"
+
+// ----------------------------------------------------------------------------
+// global statistics
+
+extern void rrdr_query_completed(uint64_t db_points_read, uint64_t result_points_generated);
+
+extern void finished_web_request_statistics(uint64_t dt,
+ uint64_t bytes_received,
+ uint64_t bytes_sent,
+ uint64_t content_size,
+ uint64_t compressed_content_size);
+
+extern uint64_t web_client_connected(void);
+extern void web_client_disconnected(void);
+extern void global_statistics_charts(void);
+
+#endif /* NETDATA_GLOBAL_STATISTICS_H */
diff --git a/daemon/main.c b/daemon/main.c
new file mode 100644
index 0000000..7c002ac
--- /dev/null
+++ b/daemon/main.c
@@ -0,0 +1,1532 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common.h"
+#include "buildinfo.h"
+
+int netdata_zero_metrics_enabled;
+int netdata_anonymous_statistics_enabled;
+
+struct config netdata_config = {
+ .first_section = NULL,
+ .last_section = NULL,
+ .mutex = NETDATA_MUTEX_INITIALIZER,
+ .index = {
+ .avl_tree = {
+ .root = NULL,
+ .compar = appconfig_section_compare
+ },
+ .rwlock = AVL_LOCK_INITIALIZER
+ }
+};
+
+void netdata_cleanup_and_exit(int ret) {
+ // enabling this, is wrong
+ // because the threads will be cancelled while cleaning up
+ // netdata_exit = 1;
+
+ error_log_limit_unlimited();
+ info("EXIT: netdata prepares to exit with code %d...", ret);
+
+ send_statistics("EXIT", ret?"ERROR":"OK","-");
+
+ // cleanup/save the database and exit
+ info("EXIT: cleaning up the database...");
+ rrdhost_cleanup_all();
+
+ if(!ret) {
+ // exit cleanly
+
+ // stop everything
+ info("EXIT: stopping static threads...");
+ cancel_main_threads();
+
+ // free the database
+ info("EXIT: freeing database memory...");
+#ifdef ENABLE_DBENGINE
+ rrdeng_prepare_exit(&multidb_ctx);
+#endif
+ rrdhost_free_all();
+#ifdef ENABLE_DBENGINE
+ rrdeng_exit(&multidb_ctx);
+#endif
+ }
+
+ // unlink the pid
+ if(pidfile[0]) {
+ info("EXIT: removing netdata PID file '%s'...", pidfile);
+ if(unlink(pidfile) != 0)
+ error("EXIT: cannot unlink pidfile '%s'.", pidfile);
+ }
+
+#ifdef ENABLE_HTTPS
+ security_clean_openssl();
+#endif
+ info("EXIT: all done - netdata is now exiting - bye bye...");
+ exit(ret);
+}
+
+struct netdata_static_thread static_threads[] = {
+
+ NETDATA_PLUGIN_HOOK_CHECKS
+ NETDATA_PLUGIN_HOOK_FREEBSD
+ NETDATA_PLUGIN_HOOK_MACOS
+
+ // linux internal plugins
+ NETDATA_PLUGIN_HOOK_LINUX_PROC
+ NETDATA_PLUGIN_HOOK_LINUX_DISKSPACE
+ NETDATA_PLUGIN_HOOK_LINUX_CGROUPS
+ NETDATA_PLUGIN_HOOK_LINUX_TC
+
+ NETDATA_PLUGIN_HOOK_IDLEJITTER
+ NETDATA_PLUGIN_HOOK_STATSD
+
+#ifdef ENABLE_ACLK
+ NETDATA_ACLK_HOOK
+#endif
+
+ // common plugins for all systems
+ {"BACKENDS", NULL, NULL, 1, NULL, NULL, backends_main},
+ {"EXPORTING", NULL, NULL, 1, NULL, NULL, exporting_main},
+ {"WEB_SERVER[static1]", NULL, NULL, 0, NULL, NULL, socket_listen_main_static_threaded},
+ {"STREAM", NULL, NULL, 0, NULL, NULL, rrdpush_sender_thread},
+
+ NETDATA_PLUGIN_HOOK_PLUGINSD
+ NETDATA_PLUGIN_HOOK_HEALTH
+
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL}
+};
+
+void web_server_threading_selection(void) {
+ web_server_mode = web_server_mode_id(config_get(CONFIG_SECTION_WEB, "mode", web_server_mode_name(web_server_mode)));
+
+ int static_threaded = (web_server_mode == WEB_SERVER_MODE_STATIC_THREADED);
+
+ int i;
+ for (i = 0; static_threads[i].name; i++) {
+ if (static_threads[i].start_routine == socket_listen_main_static_threaded)
+ static_threads[i].enabled = static_threaded;
+ }
+}
+
+int make_dns_decision(const char *section_name, const char *config_name, const char *default_value, SIMPLE_PATTERN *p)
+{
+ char *value = config_get(section_name,config_name,default_value);
+ if(!strcmp("yes",value))
+ return 1;
+ if(!strcmp("no",value))
+ return 0;
+ if(strcmp("heuristic",value))
+ error("Invalid configuration option '%s' for '%s'/'%s'. Valid options are 'yes', 'no' and 'heuristic'. Proceeding with 'heuristic'",
+ value, section_name, config_name);
+
+ return simple_pattern_is_potential_name(p);
+}
+
+void web_server_config_options(void)
+{
+ web_client_timeout =
+ (int)config_get_number(CONFIG_SECTION_WEB, "disconnect idle clients after seconds", web_client_timeout);
+ web_client_first_request_timeout =
+ (int)config_get_number(CONFIG_SECTION_WEB, "timeout for first request", web_client_first_request_timeout);
+ web_client_streaming_rate_t =
+ config_get_number(CONFIG_SECTION_WEB, "accept a streaming request every seconds", web_client_streaming_rate_t);
+
+ respect_web_browser_do_not_track_policy =
+ config_get_boolean(CONFIG_SECTION_WEB, "respect do not track policy", respect_web_browser_do_not_track_policy);
+ web_x_frame_options = config_get(CONFIG_SECTION_WEB, "x-frame-options response header", "");
+ if(!*web_x_frame_options)
+ web_x_frame_options = NULL;
+
+ web_allow_connections_from =
+ simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow connections from", "localhost *"),
+ NULL, SIMPLE_PATTERN_EXACT);
+ web_allow_connections_dns =
+ make_dns_decision(CONFIG_SECTION_WEB, "allow connections by dns", "heuristic", web_allow_connections_from);
+ web_allow_dashboard_from =
+ simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"),
+ NULL, SIMPLE_PATTERN_EXACT);
+ web_allow_dashboard_dns =
+ make_dns_decision(CONFIG_SECTION_WEB, "allow dashboard by dns", "heuristic", web_allow_dashboard_from);
+ web_allow_badges_from =
+ simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), NULL, SIMPLE_PATTERN_EXACT);
+ web_allow_badges_dns =
+ make_dns_decision(CONFIG_SECTION_WEB, "allow badges by dns", "heuristic", web_allow_badges_from);
+ web_allow_registry_from =
+ simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT);
+ web_allow_registry_dns = make_dns_decision(CONFIG_SECTION_REGISTRY, "allow by dns", "heuristic",
+ web_allow_registry_from);
+ web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"),
+ NULL, SIMPLE_PATTERN_EXACT);
+ web_allow_streaming_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow streaming by dns", "heuristic",
+ web_allow_streaming_from);
+ // Note the default is not heuristic, the wildcards could match DNS but the intent is ip-addresses.
+ web_allow_netdataconf_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow netdata.conf from",
+ "localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.*"
+ " 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.*"
+ " 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.*"
+ " 172.31.* UNKNOWN"), NULL, SIMPLE_PATTERN_EXACT);
+ web_allow_netdataconf_dns =
+ make_dns_decision(CONFIG_SECTION_WEB, "allow netdata.conf by dns", "no", web_allow_netdataconf_from);
+ web_allow_mgmt_from =
+ simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow management from", "localhost"),
+ NULL, SIMPLE_PATTERN_EXACT);
+ web_allow_mgmt_dns =
+ make_dns_decision(CONFIG_SECTION_WEB, "allow management by dns","heuristic",web_allow_mgmt_from);
+
+
+#ifdef NETDATA_WITH_ZLIB
+ web_enable_gzip = config_get_boolean(CONFIG_SECTION_WEB, "enable gzip compression", web_enable_gzip);
+
+ char *s = config_get(CONFIG_SECTION_WEB, "gzip compression strategy", "default");
+ if(!strcmp(s, "default"))
+ web_gzip_strategy = Z_DEFAULT_STRATEGY;
+ else if(!strcmp(s, "filtered"))
+ web_gzip_strategy = Z_FILTERED;
+ else if(!strcmp(s, "huffman only"))
+ web_gzip_strategy = Z_HUFFMAN_ONLY;
+ else if(!strcmp(s, "rle"))
+ web_gzip_strategy = Z_RLE;
+ else if(!strcmp(s, "fixed"))
+ web_gzip_strategy = Z_FIXED;
+ else {
+ error("Invalid compression strategy '%s'. Valid strategies are 'default', 'filtered', 'huffman only', 'rle' and 'fixed'. Proceeding with 'default'.", s);
+ web_gzip_strategy = Z_DEFAULT_STRATEGY;
+ }
+
+ web_gzip_level = (int)config_get_number(CONFIG_SECTION_WEB, "gzip compression level", 3);
+ if(web_gzip_level < 1) {
+ error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 1 (fastest compression).", web_gzip_level);
+ web_gzip_level = 1;
+ }
+ else if(web_gzip_level > 9) {
+ error("Invalid compression level %d. Valid levels are 1 (fastest) to 9 (best ratio). Proceeding with level 9 (best compression).", web_gzip_level);
+ web_gzip_level = 9;
+ }
+#endif /* NETDATA_WITH_ZLIB */
+}
+
+
+// killpid kills pid with SIGTERM.
+int killpid(pid_t pid) {
+ int ret;
+ debug(D_EXIT, "Request to kill pid %d", pid);
+
+ errno = 0;
+ ret = kill(pid, SIGTERM);
+ if (ret == -1) {
+ switch(errno) {
+ case ESRCH:
+ // We wanted the process to exit so just let the caller handle.
+ return ret;
+
+ case EPERM:
+ error("Cannot kill pid %d, but I do not have enough permissions.", pid);
+ break;
+
+ default:
+ error("Cannot kill pid %d, but I received an error.", pid);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void cancel_main_threads() {
+ error_log_limit_unlimited();
+
+ int i, found = 0;
+ usec_t max = 5 * USEC_PER_SEC, step = 100000;
+ for (i = 0; static_threads[i].name != NULL ; i++) {
+ if(static_threads[i].enabled == NETDATA_MAIN_THREAD_RUNNING) {
+ info("EXIT: Stopping main thread: %s", static_threads[i].name);
+ netdata_thread_cancel(*static_threads[i].thread);
+ found++;
+ }
+ }
+
+ netdata_exit = 1;
+
+ while(found && max > 0) {
+ max -= step;
+ info("Waiting %d threads to finish...", found);
+ sleep_usec(step);
+ found = 0;
+ for (i = 0; static_threads[i].name != NULL ; i++) {
+ if (static_threads[i].enabled != NETDATA_MAIN_THREAD_EXITED)
+ found++;
+ }
+ }
+
+ if(found) {
+ for (i = 0; static_threads[i].name != NULL ; i++) {
+ if (static_threads[i].enabled != NETDATA_MAIN_THREAD_EXITED)
+ error("Main thread %s takes too long to exit. Giving up...", static_threads[i].name);
+ }
+ }
+ else
+ info("All threads finished.");
+}
+
+struct option_def option_definitions[] = {
+ // opt description arg name default value
+ { 'c', "Configuration file to load.", "filename", CONFIG_DIR "/" CONFIG_FILENAME},
+ { 'D', "Do not fork. Run in the foreground.", NULL, "run in the background"},
+ { 'd', "Fork. Run in the background.", NULL, "run in the background"},
+ { 'h', "Display this help message.", NULL, NULL},
+ { 'P', "File to save a pid while running.", "filename", "do not save pid to a file"},
+ { 'i', "The IP address to listen to.", "IP", "all IP addresses IPv4 and IPv6"},
+ { 'p', "API/Web port to use.", "port", "19999"},
+ { 's', "Prefix for /proc and /sys (for containers).", "path", "no prefix"},
+ { 't', "The internal clock of netdata.", "seconds", "1"},
+ { 'u', "Run as user.", "username", "netdata"},
+ { 'v', "Print netdata version and exit.", NULL, NULL},
+ { 'V', "Print netdata version and exit.", NULL, NULL},
+ { 'W', "See Advanced options below.", "options", NULL},
+};
+
+int help(int exitcode) {
+ FILE *stream;
+ if(exitcode == 0)
+ stream = stdout;
+ else
+ stream = stderr;
+
+ int num_opts = sizeof(option_definitions) / sizeof(struct option_def);
+ int i;
+ int max_len_arg = 0;
+
+ // Compute maximum argument length
+ for( i = 0; i < num_opts; i++ ) {
+ if(option_definitions[i].arg_name) {
+ int len_arg = (int)strlen(option_definitions[i].arg_name);
+ if(len_arg > max_len_arg) max_len_arg = len_arg;
+ }
+ }
+
+ if(max_len_arg > 30) max_len_arg = 30;
+ if(max_len_arg < 20) max_len_arg = 20;
+
+ fprintf(stream, "%s", "\n"
+ " ^\n"
+ " |.-. .-. .-. .-. . netdata \n"
+ " | '-' '-' '-' '-' real-time performance monitoring, done right! \n"
+ " +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--->\n"
+ "\n"
+ " Copyright (C) 2016-2020, Netdata, Inc. <info@netdata.cloud>\n"
+ " Released under GNU General Public License v3 or later.\n"
+ " All rights reserved.\n"
+ "\n"
+ " Home Page : https://netdata.cloud\n"
+ " Source Code: https://github.com/netdata/netdata\n"
+ " Docs : https://learn.netdata.cloud\n"
+ " Support : https://github.com/netdata/netdata/issues\n"
+ " License : https://github.com/netdata/netdata/blob/master/LICENSE.md\n"
+ "\n"
+ " Twitter : https://twitter.com/linuxnetdata\n"
+ " Facebook : https://www.facebook.com/linuxnetdata/\n"
+ "\n"
+ "\n"
+ );
+
+ fprintf(stream, " SYNOPSIS: netdata [options]\n");
+ fprintf(stream, "\n");
+ fprintf(stream, " Options:\n\n");
+
+ // Output options description.
+ for( i = 0; i < num_opts; i++ ) {
+ fprintf(stream, " -%c %-*s %s", option_definitions[i].val, max_len_arg, option_definitions[i].arg_name ? option_definitions[i].arg_name : "", option_definitions[i].description);
+ if(option_definitions[i].default_value) {
+ fprintf(stream, "\n %c %-*s Default: %s\n", ' ', max_len_arg, "", option_definitions[i].default_value);
+ } else {
+ fprintf(stream, "\n");
+ }
+ fprintf(stream, "\n");
+ }
+
+ fprintf(stream, "\n Advanced options:\n\n"
+ " -W stacksize=N Set the stacksize (in bytes).\n\n"
+ " -W debug_flags=N Set runtime tracing to debug.log.\n\n"
+ " -W unittest Run internal unittests and exit.\n\n"
+#ifdef ENABLE_DBENGINE
+ " -W createdataset=N Create a DB engine dataset of N seconds and exit.\n\n"
+ " -W stresstest=A,B,C,D,E,F\n"
+ " Run a DB engine stress test for A seconds,\n"
+ " with B writers and C readers, with a ramp up\n"
+ " time of D seconds for writers, a page cache\n"
+ " size of E MiB, an optional disk space limit"
+ " of F MiB and exit.\n\n"
+#endif
+ " -W set section option value\n"
+ " set netdata.conf option from the command line.\n\n"
+ " -W simple-pattern pattern string\n"
+ " Check if string matches pattern and exit.\n\n"
+ " -W \"claim -token=TOKEN -rooms=ROOM1,ROOM2\"\n"
+ " Claim the agent to the workspace rooms pointed to by TOKEN and ROOM*.\n\n"
+ );
+
+ fprintf(stream, "\n Signals netdata handles:\n\n"
+ " - HUP Close and reopen log files.\n"
+ " - USR1 Save internal DB to disk.\n"
+ " - USR2 Reload health configuration.\n"
+ "\n"
+ );
+
+ fflush(stream);
+ return exitcode;
+}
+
+// TODO: Remove this function with the nix major release.
+void remove_option(int opt_index, int *argc, char **argv) {
+ int i;
+
+ // remove the options.
+ do {
+ *argc = *argc - 1;
+ for(i = opt_index; i < *argc; i++) {
+ argv[i] = argv[i+1];
+ }
+ i = opt_index;
+ } while(argv[i][0] != '-' && opt_index >= *argc);
+}
+
+static const char *verify_required_directory(const char *dir) {
+ if(chdir(dir) == -1)
+ fatal("Cannot cd to directory '%s'", dir);
+
+ DIR *d = opendir(dir);
+ if(!d)
+ fatal("Cannot examine the contents of directory '%s'", dir);
+ closedir(d);
+
+ return dir;
+}
+
+#ifdef ENABLE_HTTPS
+static void security_init(){
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/ssl/key.pem",netdata_configured_user_config_dir);
+ security_key = config_get(CONFIG_SECTION_WEB, "ssl key", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s/ssl/cert.pem",netdata_configured_user_config_dir);
+ security_cert = config_get(CONFIG_SECTION_WEB, "ssl certificate", filename);
+
+ tls_version = config_get(CONFIG_SECTION_WEB, "tls version", "1.3");
+ tls_ciphers = config_get(CONFIG_SECTION_WEB, "tls ciphers", "none");
+
+ security_openssl_library();
+}
+#endif
+
+static void log_init(void) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/debug.log", netdata_configured_log_dir);
+ stdout_filename = config_get(CONFIG_SECTION_GLOBAL, "debug log", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s/error.log", netdata_configured_log_dir);
+ stderr_filename = config_get(CONFIG_SECTION_GLOBAL, "error log", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s/access.log", netdata_configured_log_dir);
+ stdaccess_filename = config_get(CONFIG_SECTION_GLOBAL, "access log", filename);
+
+ char deffacility[8];
+ snprintfz(deffacility,7,"%s","daemon");
+ facility_log = config_get(CONFIG_SECTION_GLOBAL, "facility log", deffacility);
+
+ error_log_throttle_period = config_get_number(CONFIG_SECTION_GLOBAL, "errors flood protection period", error_log_throttle_period);
+ error_log_errors_per_period = (unsigned long)config_get_number(CONFIG_SECTION_GLOBAL, "errors to trigger flood protection", (long long int)error_log_errors_per_period);
+ error_log_errors_per_period_backup = error_log_errors_per_period;
+
+ setenv("NETDATA_ERRORS_THROTTLE_PERIOD", config_get(CONFIG_SECTION_GLOBAL, "errors flood protection period" , ""), 1);
+ setenv("NETDATA_ERRORS_PER_PERIOD", config_get(CONFIG_SECTION_GLOBAL, "errors to trigger flood protection", ""), 1);
+}
+
+char *initialize_lock_directory_path(char *prefix)
+{
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/lock", prefix);
+
+ return config_get(CONFIG_SECTION_GLOBAL, "lock directory", filename);
+}
+
+static void backwards_compatible_config() {
+ // move [global] options to the [web] section
+ config_move(CONFIG_SECTION_GLOBAL, "http port listen backlog",
+ CONFIG_SECTION_WEB, "listen backlog");
+
+ config_move(CONFIG_SECTION_GLOBAL, "bind socket to IP",
+ CONFIG_SECTION_WEB, "bind to");
+
+ config_move(CONFIG_SECTION_GLOBAL, "bind to",
+ CONFIG_SECTION_WEB, "bind to");
+
+ config_move(CONFIG_SECTION_GLOBAL, "port",
+ CONFIG_SECTION_WEB, "default port");
+
+ config_move(CONFIG_SECTION_GLOBAL, "default port",
+ CONFIG_SECTION_WEB, "default port");
+
+ config_move(CONFIG_SECTION_GLOBAL, "disconnect idle web clients after seconds",
+ CONFIG_SECTION_WEB, "disconnect idle clients after seconds");
+
+ config_move(CONFIG_SECTION_GLOBAL, "respect web browser do not track policy",
+ CONFIG_SECTION_WEB, "respect do not track policy");
+
+ config_move(CONFIG_SECTION_GLOBAL, "web x-frame-options header",
+ CONFIG_SECTION_WEB, "x-frame-options response header");
+
+ config_move(CONFIG_SECTION_GLOBAL, "enable web responses gzip compression",
+ CONFIG_SECTION_WEB, "enable gzip compression");
+
+ config_move(CONFIG_SECTION_GLOBAL, "web compression strategy",
+ CONFIG_SECTION_WEB, "gzip compression strategy");
+
+ config_move(CONFIG_SECTION_GLOBAL, "web compression level",
+ CONFIG_SECTION_WEB, "gzip compression level");
+
+ config_move(CONFIG_SECTION_GLOBAL, "web files owner",
+ CONFIG_SECTION_WEB, "web files owner");
+
+ config_move(CONFIG_SECTION_GLOBAL, "web files group",
+ CONFIG_SECTION_WEB, "web files group");
+
+ config_move(CONFIG_SECTION_BACKEND, "opentsdb host tags",
+ CONFIG_SECTION_BACKEND, "host tags");
+}
+
+static void get_netdata_configured_variables() {
+ backwards_compatible_config();
+
+ // ------------------------------------------------------------------------
+ // get the hostname
+
+ char buf[HOSTNAME_MAX + 1];
+ if(gethostname(buf, HOSTNAME_MAX) == -1){
+ error("Cannot get machine hostname.");
+ }
+
+ netdata_configured_hostname = config_get(CONFIG_SECTION_GLOBAL, "hostname", buf);
+ debug(D_OPTIONS, "hostname set to '%s'", netdata_configured_hostname);
+
+ // ------------------------------------------------------------------------
+ // get default database size
+
+ default_rrd_history_entries = (int) config_get_number(CONFIG_SECTION_GLOBAL, "history", align_entries_to_pagesize(default_rrd_memory_mode, RRD_DEFAULT_HISTORY_ENTRIES));
+
+ long h = align_entries_to_pagesize(default_rrd_memory_mode, default_rrd_history_entries);
+ if(h != default_rrd_history_entries) {
+ config_set_number(CONFIG_SECTION_GLOBAL, "history", h);
+ default_rrd_history_entries = (int)h;
+ }
+
+ if(default_rrd_history_entries < 5 || default_rrd_history_entries > RRD_HISTORY_ENTRIES_MAX) {
+ error("Invalid history entries %d given. Defaulting to %d.", default_rrd_history_entries, RRD_DEFAULT_HISTORY_ENTRIES);
+ default_rrd_history_entries = RRD_DEFAULT_HISTORY_ENTRIES;
+ }
+
+ // ------------------------------------------------------------------------
+ // get default database update frequency
+
+ default_rrd_update_every = (int) config_get_number(CONFIG_SECTION_GLOBAL, "update every", UPDATE_EVERY);
+ if(default_rrd_update_every < 1 || default_rrd_update_every > 600) {
+ error("Invalid data collection frequency (update every) %d given. Defaulting to %d.", default_rrd_update_every, UPDATE_EVERY_MAX);
+ default_rrd_update_every = UPDATE_EVERY;
+ }
+
+ // ------------------------------------------------------------------------
+ // get system paths
+
+ netdata_configured_user_config_dir = config_get(CONFIG_SECTION_GLOBAL, "config directory", netdata_configured_user_config_dir);
+ netdata_configured_stock_config_dir = config_get(CONFIG_SECTION_GLOBAL, "stock config directory", netdata_configured_stock_config_dir);
+ netdata_configured_log_dir = config_get(CONFIG_SECTION_GLOBAL, "log directory", netdata_configured_log_dir);
+ netdata_configured_web_dir = config_get(CONFIG_SECTION_GLOBAL, "web files directory", netdata_configured_web_dir);
+ netdata_configured_cache_dir = config_get(CONFIG_SECTION_GLOBAL, "cache directory", netdata_configured_cache_dir);
+ netdata_configured_varlib_dir = config_get(CONFIG_SECTION_GLOBAL, "lib directory", netdata_configured_varlib_dir);
+ char *env_home=getenv("HOME");
+ netdata_configured_home_dir = config_get(CONFIG_SECTION_GLOBAL, "home directory", env_home?env_home:netdata_configured_home_dir);
+
+ netdata_configured_lock_dir = initialize_lock_directory_path(netdata_configured_varlib_dir);
+
+ {
+ pluginsd_initialize_plugin_directories();
+ netdata_configured_primary_plugins_dir = plugin_directories[PLUGINSD_STOCK_PLUGINS_DIRECTORY_PATH];
+ }
+
+ // ------------------------------------------------------------------------
+ // get default memory mode for the database
+
+ default_rrd_memory_mode = rrd_memory_mode_id(config_get(CONFIG_SECTION_GLOBAL, "memory mode", rrd_memory_mode_name(default_rrd_memory_mode)));
+
+#ifdef ENABLE_DBENGINE
+ // ------------------------------------------------------------------------
+ // get default Database Engine page cache size in MiB
+
+ default_rrdeng_page_cache_mb = (int) config_get_number(CONFIG_SECTION_GLOBAL, "page cache size", default_rrdeng_page_cache_mb);
+ if(default_rrdeng_page_cache_mb < RRDENG_MIN_PAGE_CACHE_SIZE_MB) {
+ error("Invalid page cache size %d given. Defaulting to %d.", default_rrdeng_page_cache_mb, RRDENG_MIN_PAGE_CACHE_SIZE_MB);
+ default_rrdeng_page_cache_mb = RRDENG_MIN_PAGE_CACHE_SIZE_MB;
+ }
+
+ // ------------------------------------------------------------------------
+ // get default Database Engine disk space quota in MiB
+
+ default_rrdeng_disk_quota_mb = (int) config_get_number(CONFIG_SECTION_GLOBAL, "dbengine disk space", default_rrdeng_disk_quota_mb);
+ if(default_rrdeng_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) {
+ error("Invalid dbengine disk space %d given. Defaulting to %d.", default_rrdeng_disk_quota_mb, RRDENG_MIN_DISK_SPACE_MB);
+ default_rrdeng_disk_quota_mb = RRDENG_MIN_DISK_SPACE_MB;
+ }
+
+ default_multidb_disk_quota_mb = (int) config_get_number(CONFIG_SECTION_GLOBAL, "dbengine multihost disk space", compute_multidb_diskspace());
+ if(default_multidb_disk_quota_mb < RRDENG_MIN_DISK_SPACE_MB) {
+ error("Invalid multidb disk space %d given. Defaulting to %d.", default_multidb_disk_quota_mb, default_rrdeng_disk_quota_mb);
+ default_multidb_disk_quota_mb = default_rrdeng_disk_quota_mb;
+ }
+
+#endif
+ // ------------------------------------------------------------------------
+
+ netdata_configured_host_prefix = config_get(CONFIG_SECTION_GLOBAL, "host access prefix", "");
+ verify_netdata_host_prefix();
+
+ // --------------------------------------------------------------------
+ // get KSM settings
+
+#ifdef MADV_MERGEABLE
+ enable_ksm = config_get_boolean(CONFIG_SECTION_GLOBAL, "memory deduplication (ksm)", enable_ksm);
+#endif
+
+ // --------------------------------------------------------------------
+ // get various system parameters
+
+ get_system_HZ();
+ get_system_cpus();
+ get_system_pid_max();
+
+
+}
+
+static void get_system_timezone(void) {
+ // avoid flood calls to stat(/etc/localtime)
+ // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux
+ const char *tz = getenv("TZ");
+ if(!tz || !*tz)
+ setenv("TZ", config_get(CONFIG_SECTION_GLOBAL, "TZ environment variable", ":/etc/localtime"), 0);
+
+ char buffer[FILENAME_MAX + 1] = "";
+ const char *timezone = NULL;
+ ssize_t ret;
+
+ // use the TZ variable
+ if(tz && *tz && *tz != ':') {
+ timezone = tz;
+ // info("TIMEZONE: using TZ variable '%s'", timezone);
+ }
+
+ // use the contents of /etc/timezone
+ if(!timezone && !read_file("/etc/timezone", buffer, FILENAME_MAX)) {
+ timezone = buffer;
+ // info("TIMEZONE: using the contents of /etc/timezone: '%s'", timezone);
+ }
+
+ // read the link /etc/localtime
+ if(!timezone) {
+ ret = readlink("/etc/localtime", buffer, FILENAME_MAX);
+
+ if(ret > 0) {
+ buffer[ret] = '\0';
+
+ char *cmp = "/usr/share/zoneinfo/";
+ size_t cmp_len = strlen(cmp);
+
+ char *s = strstr(buffer, cmp);
+ if (s && s[cmp_len]) {
+ timezone = &s[cmp_len];
+ // info("TIMEZONE: using the link of /etc/localtime: '%s'", timezone);
+ }
+ }
+ else
+ buffer[0] = '\0';
+ }
+
+ // find the timezone from strftime()
+ if(!timezone) {
+ time_t t;
+ struct tm *tmp, tmbuf;
+
+ t = now_realtime_sec();
+ tmp = localtime_r(&t, &tmbuf);
+
+ if (tmp != NULL) {
+ if(strftime(buffer, FILENAME_MAX, "%Z", tmp) == 0)
+ buffer[0] = '\0';
+ else {
+ buffer[FILENAME_MAX] = '\0';
+ timezone = buffer;
+ // info("TIMEZONE: using strftime(): '%s'", timezone);
+ }
+ }
+ }
+
+ if(timezone && *timezone) {
+ // make sure it does not have illegal characters
+ // info("TIMEZONE: fixing '%s'", timezone);
+
+ size_t len = strlen(timezone);
+ char tmp[len + 1];
+ char *d = tmp;
+ *d = '\0';
+
+ while(*timezone) {
+ if(isalnum(*timezone) || *timezone == '_' || *timezone == '/')
+ *d++ = *timezone++;
+ else
+ timezone++;
+ }
+ *d = '\0';
+ strncpyz(buffer, tmp, len);
+ timezone = buffer;
+ // info("TIMEZONE: fixed as '%s'", timezone);
+ }
+
+ if(!timezone || !*timezone)
+ timezone = "unknown";
+
+ netdata_configured_timezone = config_get(CONFIG_SECTION_GLOBAL, "timezone", timezone);
+}
+
+void set_global_environment() {
+ {
+ char b[16];
+ snprintfz(b, 15, "%d", default_rrd_update_every);
+ setenv("NETDATA_UPDATE_EVERY", b, 1);
+ }
+
+ setenv("NETDATA_VERSION" , program_version, 1);
+ setenv("NETDATA_HOSTNAME" , netdata_configured_hostname, 1);
+ setenv("NETDATA_CONFIG_DIR" , verify_required_directory(netdata_configured_user_config_dir), 1);
+ setenv("NETDATA_USER_CONFIG_DIR" , verify_required_directory(netdata_configured_user_config_dir), 1);
+ setenv("NETDATA_STOCK_CONFIG_DIR" , verify_required_directory(netdata_configured_stock_config_dir), 1);
+ setenv("NETDATA_PLUGINS_DIR" , verify_required_directory(netdata_configured_primary_plugins_dir), 1);
+ setenv("NETDATA_WEB_DIR" , verify_required_directory(netdata_configured_web_dir), 1);
+ setenv("NETDATA_CACHE_DIR" , verify_required_directory(netdata_configured_cache_dir), 1);
+ setenv("NETDATA_LIB_DIR" , verify_required_directory(netdata_configured_varlib_dir), 1);
+ setenv("NETDATA_LOCK_DIR" , netdata_configured_lock_dir, 1);
+ setenv("NETDATA_LOG_DIR" , verify_required_directory(netdata_configured_log_dir), 1);
+ setenv("HOME" , verify_required_directory(netdata_configured_home_dir), 1);
+ setenv("NETDATA_HOST_PREFIX" , netdata_configured_host_prefix, 1);
+
+ char *default_port = appconfig_get(&netdata_config, CONFIG_SECTION_WEB, "default port", NULL);
+ int clean = 0;
+ if (!default_port) {
+ default_port = strdupz("19999");
+ clean = 1;
+ }
+
+ setenv("NETDATA_LISTEN_PORT" , default_port, 1);
+ if(clean)
+ freez(default_port);
+
+ get_system_timezone();
+
+ // set the path we need
+ char path[1024 + 1], *p = getenv("PATH");
+ if(!p) p = "/bin:/usr/bin";
+ snprintfz(path, 1024, "%s:%s", p, "/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin");
+ setenv("PATH", config_get(CONFIG_SECTION_PLUGINS, "PATH environment variable", path), 1);
+
+ // python options
+ p = getenv("PYTHONPATH");
+ if(!p) p = "";
+ setenv("PYTHONPATH", config_get(CONFIG_SECTION_PLUGINS, "PYTHONPATH environment variable", p), 1);
+
+ // disable buffering for python plugins
+ setenv("PYTHONUNBUFFERED", "1", 1);
+
+ // switch to standard locale for plugins
+ setenv("LC_ALL", "C", 1);
+}
+
+static int load_netdata_conf(char *filename, char overwrite_used) {
+ errno = 0;
+
+ int ret = 0;
+
+ if(filename && *filename) {
+ ret = config_load(filename, overwrite_used, NULL);
+ if(!ret)
+ error("CONFIG: cannot load config file '%s'.", filename);
+ }
+ else {
+ filename = strdupz_path_subpath(netdata_configured_user_config_dir, "netdata.conf");
+
+ ret = config_load(filename, overwrite_used, NULL);
+ if(!ret) {
+ info("CONFIG: cannot load user config '%s'. Will try the stock version.", filename);
+ freez(filename);
+
+ filename = strdupz_path_subpath(netdata_configured_stock_config_dir, "netdata.conf");
+ ret = config_load(filename, overwrite_used, NULL);
+ if(!ret)
+ info("CONFIG: cannot load stock config '%s'. Running with internal defaults.", filename);
+ }
+
+ freez(filename);
+ }
+
+ return ret;
+}
+
+// coverity[ +tainted_string_sanitize_content : arg-0 ]
+static inline void coverity_remove_taint(char *s)
+{
+ (void)s;
+}
+
+int get_system_info(struct rrdhost_system_info *system_info) {
+ char *script;
+ script = mallocz(sizeof(char) * (strlen(netdata_configured_primary_plugins_dir) + strlen("system-info.sh") + 2));
+ sprintf(script, "%s/%s", netdata_configured_primary_plugins_dir, "system-info.sh");
+ if (unlikely(access(script, R_OK) != 0)) {
+ info("System info script %s not found.",script);
+ freez(script);
+ return 1;
+ }
+
+ pid_t command_pid;
+
+ info("Executing %s", script);
+
+ FILE *fp = mypopen(script, &command_pid);
+ if(fp) {
+ char line[200 + 1];
+ // Removed the double strlens, if the Coverity tainted string warning reappears I'll revert.
+ // One time init code, but I'm curious about the warning...
+ while (fgets(line, 200, fp) != NULL) {
+ char *value=line;
+ while (*value && *value != '=') value++;
+ if (*value=='=') {
+ *value='\0';
+ value++;
+ char *end = value;
+ while (*end && *end != '\n') end++;
+ *end = '\0'; // Overwrite newline if present
+ coverity_remove_taint(line); // I/O is controlled result of system_info.sh - not tainted
+ coverity_remove_taint(value);
+
+ if(unlikely(rrdhost_set_system_info_variable(system_info, line, value))) {
+ info("Unexpected environment variable %s=%s", line, value);
+ }
+ else {
+ info("%s=%s", line, value);
+ setenv(line, value, 1);
+ }
+ }
+ }
+ mypclose(fp, command_pid);
+ }
+ freez(script);
+ return 0;
+}
+
+void send_statistics( const char *action, const char *action_result, const char *action_data) {
+ static char *as_script;
+
+ if (netdata_anonymous_statistics_enabled == -1) {
+ char *optout_file = mallocz(sizeof(char) * (strlen(netdata_configured_user_config_dir) +strlen(".opt-out-from-anonymous-statistics") + 2));
+ sprintf(optout_file, "%s/%s", netdata_configured_user_config_dir, ".opt-out-from-anonymous-statistics");
+ if (likely(access(optout_file, R_OK) != 0)) {
+ as_script = mallocz(sizeof(char) * (strlen(netdata_configured_primary_plugins_dir) + strlen("anonymous-statistics.sh") + 2));
+ sprintf(as_script, "%s/%s", netdata_configured_primary_plugins_dir, "anonymous-statistics.sh");
+ if (unlikely(access(as_script, R_OK) != 0)) {
+ netdata_anonymous_statistics_enabled=0;
+ info("Anonymous statistics script %s not found.",as_script);
+ freez(as_script);
+ } else {
+ netdata_anonymous_statistics_enabled=1;
+ }
+ } else {
+ netdata_anonymous_statistics_enabled = 0;
+ as_script = NULL;
+ }
+ freez(optout_file);
+ }
+ if(!netdata_anonymous_statistics_enabled) return;
+ if (!action) return;
+ if (!action_result) action_result="";
+ if (!action_data) action_data="";
+ char *command_to_run=mallocz(sizeof(char) * (strlen(action) + strlen(action_result) + strlen(action_data) + strlen(as_script) + 10));
+ pid_t command_pid;
+
+ sprintf(command_to_run,"%s '%s' '%s' '%s'", as_script, action, action_result, action_data);
+ info("%s", command_to_run);
+
+ FILE *fp = mypopen(command_to_run, &command_pid);
+ if(fp) {
+ char buffer[100 + 1];
+ while (fgets(buffer, 100, fp) != NULL);
+ mypclose(fp, command_pid);
+ }
+ freez(command_to_run);
+}
+
+void set_silencers_filename() {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/health.silencers.json", netdata_configured_varlib_dir);
+ silencers_filename = config_get(CONFIG_SECTION_HEALTH, "silencers file", filename);
+}
+
+/* Any config setting that can be accessed without a default value i.e. configget(...,...,NULL) *MUST*
+ be set in this procedure to be called in all the relevant code paths.
+*/
+void post_conf_load(char **user)
+{
+ // --------------------------------------------------------------------
+ // get the user we should run
+
+ // IMPORTANT: this is required before web_files_uid()
+ if(getuid() == 0) {
+ *user = config_get(CONFIG_SECTION_GLOBAL, "run as user", NETDATA_USER);
+ }
+ else {
+ struct passwd *passwd = getpwuid(getuid());
+ *user = config_get(CONFIG_SECTION_GLOBAL, "run as user", (passwd && passwd->pw_name)?passwd->pw_name:"");
+ }
+
+ // --------------------------------------------------------------------
+ // Check if the cloud is enabled
+#if defined( DISABLE_CLOUD ) || !defined( ENABLE_ACLK )
+ netdata_cloud_setting = 0;
+#else
+ netdata_cloud_setting = appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", 1);
+#endif
+ // This must be set before any point in the code that accesses it. Do not move it from this function.
+ appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", DEFAULT_CLOUD_BASE_URL);
+}
+
+int main(int argc, char **argv) {
+ int i;
+ int config_loaded = 0;
+ int dont_fork = 0;
+ size_t default_stacksize;
+ char *user = NULL;
+
+
+ netdata_ready=0;
+ // set the name for logging
+ program_name = "netdata";
+
+ // parse depercated options
+ // TODO: Remove this block with the next major release.
+ {
+ i = 1;
+ while(i < argc) {
+ if(strcmp(argv[i], "-pidfile") == 0 && (i+1) < argc) {
+ strncpyz(pidfile, argv[i+1], FILENAME_MAX);
+ fprintf(stderr, "%s: deprecated option -- %s -- please use -P instead.\n", argv[0], argv[i]);
+ remove_option(i, &argc, argv);
+ }
+ else if(strcmp(argv[i], "-nodaemon") == 0 || strcmp(argv[i], "-nd") == 0) {
+ dont_fork = 1;
+ fprintf(stderr, "%s: deprecated option -- %s -- please use -D instead.\n ", argv[0], argv[i]);
+ remove_option(i, &argc, argv);
+ }
+ else if(strcmp(argv[i], "-ch") == 0 && (i+1) < argc) {
+ config_set(CONFIG_SECTION_GLOBAL, "host access prefix", argv[i+1]);
+ fprintf(stderr, "%s: deprecated option -- %s -- please use -s instead.\n", argv[0], argv[i]);
+ remove_option(i, &argc, argv);
+ }
+ else if(strcmp(argv[i], "-l") == 0 && (i+1) < argc) {
+ config_set(CONFIG_SECTION_GLOBAL, "history", argv[i+1]);
+ fprintf(stderr, "%s: deprecated option -- %s -- This option will be removed with V2.*.\n", argv[0], argv[i]);
+ remove_option(i, &argc, argv);
+ }
+ else i++;
+ }
+ }
+ if (argc > 1 && strcmp(argv[1], SPAWN_SERVER_COMMAND_LINE_ARGUMENT) == 0) {
+ // don't run netdata, this is the spawn server
+ spawn_server();
+ exit(0);
+ }
+
+ // parse options
+ {
+ int num_opts = sizeof(option_definitions) / sizeof(struct option_def);
+ char optstring[(num_opts * 2) + 1];
+
+ int string_i = 0;
+ for( i = 0; i < num_opts; i++ ) {
+ optstring[string_i] = option_definitions[i].val;
+ string_i++;
+ if(option_definitions[i].arg_name) {
+ optstring[string_i] = ':';
+ string_i++;
+ }
+ }
+ // terminate optstring
+ optstring[string_i] ='\0';
+ optstring[(num_opts *2)] ='\0';
+
+ int opt;
+ while( (opt = getopt(argc, argv, optstring)) != -1 ) {
+ switch(opt) {
+ case 'c':
+ if(load_netdata_conf(optarg, 1) != 1) {
+ error("Cannot load configuration file %s.", optarg);
+ return 1;
+ }
+ else {
+ debug(D_OPTIONS, "Configuration loaded from %s.", optarg);
+ post_conf_load(&user);
+ load_cloud_conf(1);
+ config_loaded = 1;
+ }
+ break;
+ case 'D':
+ dont_fork = 1;
+ break;
+ case 'd':
+ dont_fork = 0;
+ break;
+ case 'h':
+ return help(0);
+ case 'i':
+ config_set(CONFIG_SECTION_WEB, "bind to", optarg);
+ break;
+ case 'P':
+ strncpy(pidfile, optarg, FILENAME_MAX);
+ pidfile[FILENAME_MAX] = '\0';
+ break;
+ case 'p':
+ config_set(CONFIG_SECTION_GLOBAL, "default port", optarg);
+ break;
+ case 's':
+ config_set(CONFIG_SECTION_GLOBAL, "host access prefix", optarg);
+ break;
+ case 't':
+ config_set(CONFIG_SECTION_GLOBAL, "update every", optarg);
+ break;
+ case 'u':
+ config_set(CONFIG_SECTION_GLOBAL, "run as user", optarg);
+ break;
+ case 'v':
+ case 'V':
+ printf("%s %s\n", program_name, program_version);
+ return 0;
+ case 'W':
+ {
+ char* stacksize_string = "stacksize=";
+ char* debug_flags_string = "debug_flags=";
+ char* claim_string = "claim";
+#ifdef ENABLE_DBENGINE
+ char* createdataset_string = "createdataset=";
+ char* stresstest_string = "stresstest=";
+#endif
+
+ if(strcmp(optarg, "unittest") == 0) {
+ if(unit_test_buffer()) return 1;
+ if(unit_test_str2ld()) return 1;
+ // No call to load the config file on this code-path
+ post_conf_load(&user);
+ get_netdata_configured_variables();
+ default_rrd_update_every = 1;
+ default_rrd_memory_mode = RRD_MEMORY_MODE_RAM;
+ default_health_enabled = 0;
+ registry_init();
+ if(rrd_init("unittest", NULL)) {
+ fprintf(stderr, "rrd_init failed for unittest\n");
+ return 1;
+ }
+ default_rrdpush_enabled = 0;
+ if(run_all_mockup_tests()) return 1;
+ if(unit_test_storage()) return 1;
+#ifdef ENABLE_DBENGINE
+ if(test_dbengine()) return 1;
+#endif
+ fprintf(stderr, "\n\nALL TESTS PASSED\n\n");
+ return 0;
+ }
+#ifdef ENABLE_DBENGINE
+ else if(strncmp(optarg, createdataset_string, strlen(createdataset_string)) == 0) {
+ optarg += strlen(createdataset_string);
+ unsigned history_seconds = strtoul(optarg, NULL, 0);
+ generate_dbengine_dataset(history_seconds);
+ return 0;
+ }
+ else if(strncmp(optarg, stresstest_string, strlen(stresstest_string)) == 0) {
+ char *endptr;
+ unsigned test_duration_sec = 0, dset_charts = 0, query_threads = 0, ramp_up_seconds = 0,
+ page_cache_mb = 0, disk_space_mb = 0;
+
+ optarg += strlen(stresstest_string);
+ test_duration_sec = (unsigned)strtoul(optarg, &endptr, 0);
+ if (',' == *endptr)
+ dset_charts = (unsigned)strtoul(endptr + 1, &endptr, 0);
+ if (',' == *endptr)
+ query_threads = (unsigned)strtoul(endptr + 1, &endptr, 0);
+ if (',' == *endptr)
+ ramp_up_seconds = (unsigned)strtoul(endptr + 1, &endptr, 0);
+ if (',' == *endptr)
+ page_cache_mb = (unsigned)strtoul(endptr + 1, &endptr, 0);
+ if (',' == *endptr)
+ disk_space_mb = (unsigned)strtoul(endptr + 1, &endptr, 0);
+
+ dbengine_stress_test(test_duration_sec, dset_charts, query_threads, ramp_up_seconds,
+ page_cache_mb, disk_space_mb);
+ return 0;
+ }
+#endif
+ else if(strcmp(optarg, "simple-pattern") == 0) {
+ if(optind + 2 > argc) {
+ fprintf(stderr, "%s", "\nUSAGE: -W simple-pattern 'pattern' 'string'\n\n"
+ " Checks if 'pattern' matches the given 'string'.\n"
+ " - 'pattern' can be one or more space separated words.\n"
+ " - each 'word' can contain one or more asterisks.\n"
+ " - words starting with '!' give negative matches.\n"
+ " - words are processed left to right\n"
+ "\n"
+ "Examples:\n"
+ "\n"
+ " > match all veth interfaces, except veth0:\n"
+ "\n"
+ " -W simple-pattern '!veth0 veth*' 'veth12'\n"
+ "\n"
+ "\n"
+ " > match all *.ext files directly in /path/:\n"
+ " (this will not match *.ext files in a subdir of /path/)\n"
+ "\n"
+ " -W simple-pattern '!/path/*/*.ext /path/*.ext' '/path/test.ext'\n"
+ "\n"
+ );
+ return 1;
+ }
+
+ const char *heystack = argv[optind];
+ const char *needle = argv[optind + 1];
+ size_t len = strlen(needle) + 1;
+ char wildcarded[len];
+
+ SIMPLE_PATTERN *p = simple_pattern_create(heystack, NULL, SIMPLE_PATTERN_EXACT);
+ int ret = simple_pattern_matches_extract(p, needle, wildcarded, len);
+ simple_pattern_free(p);
+
+ if(ret) {
+ fprintf(stdout, "RESULT: MATCHED - pattern '%s' matches '%s', wildcarded '%s'\n", heystack, needle, wildcarded);
+ return 0;
+ }
+ else {
+ fprintf(stdout, "RESULT: NOT MATCHED - pattern '%s' does not match '%s', wildcarded '%s'\n", heystack, needle, wildcarded);
+ return 1;
+ }
+ }
+ else if(strncmp(optarg, stacksize_string, strlen(stacksize_string)) == 0) {
+ optarg += strlen(stacksize_string);
+ config_set(CONFIG_SECTION_GLOBAL, "pthread stack size", optarg);
+ }
+ else if(strncmp(optarg, debug_flags_string, strlen(debug_flags_string)) == 0) {
+ optarg += strlen(debug_flags_string);
+ config_set(CONFIG_SECTION_GLOBAL, "debug flags", optarg);
+ debug_flags = strtoull(optarg, NULL, 0);
+ }
+ else if(strcmp(optarg, "set") == 0) {
+ if(optind + 3 > argc) {
+ fprintf(stderr, "%s", "\nUSAGE: -W set 'section' 'key' 'value'\n\n"
+ " Overwrites settings of netdata.conf.\n"
+ "\n"
+ " These options interact with: -c netdata.conf\n"
+ " If -c netdata.conf is given on the command line,\n"
+ " before -W set... the user may overwrite command\n"
+ " line parameters at netdata.conf\n"
+ " If -c netdata.conf is given after (or missing)\n"
+ " -W set... the user cannot overwrite the command line\n"
+ " parameters."
+ "\n"
+ );
+ return 1;
+ }
+ const char *section = argv[optind];
+ const char *key = argv[optind + 1];
+ const char *value = argv[optind + 2];
+ optind += 3;
+
+ // set this one as the default
+ // only if it is not already set in the config file
+ // so the caller can use -c netdata.conf before or
+ // after this parameter to prevent or allow overwriting
+ // variables at netdata.conf
+ config_set_default(section, key, value);
+
+ // fprintf(stderr, "SET section '%s', key '%s', value '%s'\n", section, key, value);
+ }
+ else if(strcmp(optarg, "set2") == 0) {
+ if(optind + 4 > argc) {
+ fprintf(stderr, "%s", "\nUSAGE: -W set 'conf_file' 'section' 'key' 'value'\n\n"
+ " Overwrites settings of netdata.conf or cloud.conf\n"
+ "\n"
+ " These options interact with: -c netdata.conf\n"
+ " If -c netdata.conf is given on the command line,\n"
+ " before -W set... the user may overwrite command\n"
+ " line parameters at netdata.conf\n"
+ " If -c netdata.conf is given after (or missing)\n"
+ " -W set... the user cannot overwrite the command line\n"
+ " parameters."
+ " conf_file can be \"cloud\" or \"netdata\".\n"
+ "\n"
+ );
+ return 1;
+ }
+ const char *conf_file = argv[optind]; /* "cloud" is cloud.conf, otherwise netdata.conf */
+ struct config *tmp_config = strcmp(conf_file, "cloud") ? &netdata_config : &cloud_config;
+ const char *section = argv[optind + 1];
+ const char *key = argv[optind + 2];
+ const char *value = argv[optind + 3];
+ optind += 4;
+
+ // set this one as the default
+ // only if it is not already set in the config file
+ // so the caller can use -c netdata.conf before or
+ // after this parameter to prevent or allow overwriting
+ // variables at netdata.conf
+ appconfig_set_default(tmp_config, section, key, value);
+
+ // fprintf(stderr, "SET section '%s', key '%s', value '%s'\n", section, key, value);
+ }
+ else if(strcmp(optarg, "get") == 0) {
+ if(optind + 3 > argc) {
+ fprintf(stderr, "%s", "\nUSAGE: -W get 'section' 'key' 'value'\n\n"
+ " Prints settings of netdata.conf.\n"
+ "\n"
+ " These options interact with: -c netdata.conf\n"
+ " -c netdata.conf has to be given before -W get.\n"
+ "\n"
+ );
+ return 1;
+ }
+
+ if(!config_loaded) {
+ fprintf(stderr, "warning: no configuration file has been loaded. Use -c CONFIG_FILE, before -W get. Using default config.\n");
+ load_netdata_conf(NULL, 0);
+ post_conf_load(&user);
+ }
+
+ get_netdata_configured_variables();
+
+ const char *section = argv[optind];
+ const char *key = argv[optind + 1];
+ const char *def = argv[optind + 2];
+ const char *value = config_get(section, key, def);
+ printf("%s\n", value);
+ return 0;
+ }
+ else if(strcmp(optarg, "get2") == 0) {
+ if(optind + 4 > argc) {
+ fprintf(stderr, "%s", "\nUSAGE: -W get2 'conf_file' 'section' 'key' 'value'\n\n"
+ " Prints settings of netdata.conf or cloud.conf\n"
+ "\n"
+ " These options interact with: -c netdata.conf\n"
+ " -c netdata.conf has to be given before -W get2.\n"
+ " conf_file can be \"cloud\" or \"netdata\".\n"
+ "\n"
+ );
+ return 1;
+ }
+
+ if(!config_loaded) {
+ fprintf(stderr, "warning: no configuration file has been loaded. Use -c CONFIG_FILE, before -W get. Using default config.\n");
+ load_netdata_conf(NULL, 0);
+ post_conf_load(&user);
+ load_cloud_conf(1);
+ }
+
+ get_netdata_configured_variables();
+
+ const char *conf_file = argv[optind]; /* "cloud" is cloud.conf, otherwise netdata.conf */
+ struct config *tmp_config = strcmp(conf_file, "cloud") ? &netdata_config : &cloud_config;
+ const char *section = argv[optind + 1];
+ const char *key = argv[optind + 2];
+ const char *def = argv[optind + 3];
+ const char *value = appconfig_get(tmp_config, section, key, def);
+ printf("%s\n", value);
+ return 0;
+ }
+ else if(strncmp(optarg, claim_string, strlen(claim_string)) == 0) {
+ /* will trigger a claiming attempt when the agent is initialized */
+ claiming_pending_arguments = optarg + strlen(claim_string);
+ }
+ else if(strcmp(optarg, "buildinfo") == 0) {
+ printf("Version: %s %s\n", program_name, program_version);
+ print_build_info();
+ return 0;
+ }
+ else {
+ fprintf(stderr, "Unknown -W parameter '%s'\n", optarg);
+ return help(1);
+ }
+ }
+ break;
+
+ default: /* ? */
+ fprintf(stderr, "Unknown parameter '%c'\n", opt);
+ return help(1);
+ }
+ }
+ }
+
+#ifdef _SC_OPEN_MAX
+ // close all open file descriptors, except the standard ones
+ // the caller may have left open files (lxc-attach has this issue)
+ {
+ int fd;
+ for(fd = (int) (sysconf(_SC_OPEN_MAX) - 1); fd > 2; fd--)
+ if(fd_is_valid(fd)) close(fd);
+ }
+#endif
+
+ if(!config_loaded)
+ {
+ load_netdata_conf(NULL, 0);
+ post_conf_load(&user);
+ load_cloud_conf(0);
+ }
+
+
+ // ------------------------------------------------------------------------
+ // initialize netdata
+ {
+ char *pmax = config_get(CONFIG_SECTION_GLOBAL, "glibc malloc arena max for plugins", "1");
+ if(pmax && *pmax)
+ setenv("MALLOC_ARENA_MAX", pmax, 1);
+
+#if defined(HAVE_C_MALLOPT)
+ i = (int)config_get_number(CONFIG_SECTION_GLOBAL, "glibc malloc arena max for netdata", 1);
+ if(i > 0)
+ mallopt(M_ARENA_MAX, 1);
+#endif
+ test_clock_boottime();
+ test_clock_monotonic_coarse();
+
+ // prepare configuration environment variables for the plugins
+
+ get_netdata_configured_variables();
+ set_global_environment();
+
+ // work while we are cd into config_dir
+ // to allow the plugins refer to their config
+ // files using relative filenames
+ if(chdir(netdata_configured_user_config_dir) == -1)
+ fatal("Cannot cd to '%s'", netdata_configured_user_config_dir);
+
+ // Get execution path before switching user to avoid permission issues
+ get_netdata_execution_path();
+ }
+
+ {
+ // --------------------------------------------------------------------
+ // get the debugging flags from the configuration file
+
+ char *flags = config_get(CONFIG_SECTION_GLOBAL, "debug flags", "0x0000000000000000");
+ setenv("NETDATA_DEBUG_FLAGS", flags, 1);
+
+ debug_flags = strtoull(flags, NULL, 0);
+ debug(D_OPTIONS, "Debug flags set to '0x%" PRIX64 "'.", debug_flags);
+
+ if(debug_flags != 0) {
+ struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
+ if(setrlimit(RLIMIT_CORE, &rl) != 0)
+ error("Cannot request unlimited core dumps for debugging... Proceeding anyway...");
+
+#ifdef HAVE_SYS_PRCTL_H
+ prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+#endif
+ }
+
+
+ // --------------------------------------------------------------------
+ // get log filenames and settings
+ log_init();
+ error_log_limit_unlimited();
+
+ // --------------------------------------------------------------------
+ // get the certificate and start security
+#ifdef ENABLE_HTTPS
+ security_init();
+#endif
+
+ // --------------------------------------------------------------------
+ // This is the safest place to start the SILENCERS structure
+ set_silencers_filename();
+ health_initialize_global_silencers();
+
+ // --------------------------------------------------------------------
+ // setup process signals
+
+ // block signals while initializing threads.
+ // this causes the threads to block signals.
+ signals_block();
+
+ // setup the signals we want to use
+ signals_init();
+
+ // setup threads configs
+ default_stacksize = netdata_threads_init();
+
+
+ // --------------------------------------------------------------------
+ // check which threads are enabled and initialize them
+
+ for (i = 0; static_threads[i].name != NULL ; i++) {
+ struct netdata_static_thread *st = &static_threads[i];
+
+ if(st->config_name)
+ st->enabled = config_get_boolean(st->config_section, st->config_name, st->enabled);
+
+ if(st->enabled && st->init_routine)
+ st->init_routine();
+ }
+
+
+ // --------------------------------------------------------------------
+ // create the listening sockets
+
+ web_client_api_v1_init();
+ web_server_threading_selection();
+
+ if(web_server_mode != WEB_SERVER_MODE_NONE)
+ api_listen_sockets_setup();
+ }
+
+ // initialize the log files
+ open_all_log_files();
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ if(debug_flags != 0) {
+ struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
+ if(setrlimit(RLIMIT_CORE, &rl) != 0)
+ error("Cannot request unlimited core dumps for debugging... Proceeding anyway...");
+#ifdef HAVE_SYS_PRCTL_H
+ prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+#endif
+ }
+#endif /* NETDATA_INTERNAL_CHECKS */
+
+ // get the max file limit
+ if(getrlimit(RLIMIT_NOFILE, &rlimit_nofile) != 0)
+ error("getrlimit(RLIMIT_NOFILE) failed");
+ else
+ info("resources control: allowed file descriptors: soft = %zu, max = %zu", (size_t)rlimit_nofile.rlim_cur, (size_t)rlimit_nofile.rlim_max);
+
+ // fork, switch user, create pid file, set process priority
+ if(become_daemon(dont_fork, user) == -1)
+ fatal("Cannot daemonize myself.");
+
+ info("netdata started on pid %d.", getpid());
+
+ // IMPORTANT: these have to run once, while single threaded
+ // but after we have switched user
+ web_files_uid();
+ web_files_gid();
+
+ netdata_threads_init_after_fork((size_t)config_get_number(CONFIG_SECTION_GLOBAL, "pthread stack size", (long)default_stacksize));
+
+ // initialyze internal registry
+ registry_init();
+ // fork the spawn server
+ spawn_init();
+ /*
+ * Libuv uv_spawn() uses SIGCHLD internally:
+ * https://github.com/libuv/libuv/blob/cc51217a317e96510fbb284721d5e6bc2af31e33/src/unix/process.c#L485
+ * and inadvertently replaces the netdata signal handler which was setup during initialization.
+ * Thusly, we must explicitly restore the signal handler for SIGCHLD.
+ * Warning: extreme care is needed when mixing and matching POSIX and libuv.
+ */
+ signals_restore_SIGCHLD();
+
+ // ------------------------------------------------------------------------
+ // initialize rrd, registry, health, rrdpush, etc.
+
+ netdata_anonymous_statistics_enabled=-1;
+ struct rrdhost_system_info *system_info = calloc(1, sizeof(struct rrdhost_system_info));
+ get_system_info(system_info);
+
+ if(rrd_init(netdata_configured_hostname, system_info))
+ fatal("Cannot initialize localhost instance with name '%s'.", netdata_configured_hostname);
+
+ // ------------------------------------------------------------------------
+ // Claim netdata agent to a cloud endpoint
+
+ if (claiming_pending_arguments)
+ claim_agent(claiming_pending_arguments);
+ load_claiming_state();
+
+ // ------------------------------------------------------------------------
+ // enable log flood protection
+
+ error_log_limit_reset();
+
+ // Load host labels
+ reload_host_labels();
+
+ // ------------------------------------------------------------------------
+ // spawn the threads
+
+ web_server_config_options();
+
+ netdata_zero_metrics_enabled = config_get_boolean_ondemand(CONFIG_SECTION_GLOBAL, "enable zero metrics", CONFIG_BOOLEAN_NO);
+
+ for (i = 0; static_threads[i].name != NULL ; i++) {
+ struct netdata_static_thread *st = &static_threads[i];
+
+ if(st->enabled) {
+ st->thread = mallocz(sizeof(netdata_thread_t));
+ debug(D_SYSTEM, "Starting thread %s.", st->name);
+ netdata_thread_create(st->thread, st->name, NETDATA_THREAD_OPTION_DEFAULT, st->start_routine, st);
+ }
+ else debug(D_SYSTEM, "Not starting thread %s.", st->name);
+ }
+
+ // ------------------------------------------------------------------------
+ // Initialize netdata agent command serving from cli and signals
+
+ commands_init();
+
+ info("netdata initialization completed. Enjoy real-time performance monitoring!");
+ netdata_ready = 1;
+
+ send_statistics("START", "-", "-");
+
+ // ------------------------------------------------------------------------
+ // Report ACLK build failure
+#ifndef ENABLE_ACLK
+ error("This agent doesn't have ACLK.");
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/.aclk_report_sent", netdata_configured_varlib_dir);
+ if (netdata_anonymous_statistics_enabled > 0 && access(filename, F_OK)) { // -1 -> not initialized
+ send_statistics("ACLK_DISABLED", "-", "-");
+#ifdef ACLK_NO_LWS
+ send_statistics("BUILD_FAIL_LWS", "-", "-");
+#endif
+#ifdef ACLK_NO_LIBMOSQ
+ send_statistics("BUILD_FAIL_MOSQ", "-", "-");
+#endif
+ int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 444);
+ if (fd == -1)
+ error("Cannot create file '%s'. Please fix this.", filename);
+ else
+ close(fd);
+ }
+#endif
+
+ // ------------------------------------------------------------------------
+ // unblock signals
+
+ signals_unblock();
+
+ // ------------------------------------------------------------------------
+ // Handle signals
+
+ signals_handle();
+
+ // should never reach this point
+ // but we need it for rpmlint #2752
+ return 1;
+}
diff --git a/daemon/main.h b/daemon/main.h
new file mode 100644
index 0000000..9d9f4ef
--- /dev/null
+++ b/daemon/main.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_MAIN_H
+#define NETDATA_MAIN_H 1
+
+#include "common.h"
+
+extern struct config netdata_config;
+
+#define NETDATA_MAIN_THREAD_RUNNING CONFIG_BOOLEAN_YES
+#define NETDATA_MAIN_THREAD_EXITING (CONFIG_BOOLEAN_YES + 1)
+#define NETDATA_MAIN_THREAD_EXITED CONFIG_BOOLEAN_NO
+
+/**
+ * This struct contains information about command line options.
+ */
+struct option_def {
+ /** The option character */
+ const char val;
+ /** The name of the long option. */
+ const char *description;
+ /** Short descripton what the option does */
+ /** Name of the argument displayed in SYNOPSIS */
+ const char *arg_name;
+ /** Default value if not set */
+ const char *default_value;
+};
+
+struct netdata_static_thread {
+ char *name; // the name of the thread as it should appear in the logs
+
+ char *config_section; // the section of netdata.conf to check if this is enabled or not
+ char *config_name; // the name of the config option to check if it is true or false
+
+ volatile sig_atomic_t enabled; // the current status of the thread
+
+ netdata_thread_t *thread; // internal use, to maintain a pointer to the created thread
+
+ void (*init_routine) (void); // an initialization function to run before spawning the thread
+ void *(*start_routine) (void *); // the threaded worker
+};
+
+extern void cancel_main_threads(void);
+extern int killpid(pid_t pid);
+extern void netdata_cleanup_and_exit(int ret) NORETURN;
+extern void send_statistics(const char *action, const char *action_result, const char *action_data);
+
+#endif /* NETDATA_MAIN_H */
diff --git a/daemon/signals.c b/daemon/signals.c
new file mode 100644
index 0000000..9e30bf1
--- /dev/null
+++ b/daemon/signals.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common.h"
+
+static int reaper_enabled = 0;
+
+typedef enum signal_action {
+ NETDATA_SIGNAL_END_OF_LIST,
+ NETDATA_SIGNAL_IGNORE,
+ NETDATA_SIGNAL_EXIT_CLEANLY,
+ NETDATA_SIGNAL_SAVE_DATABASE,
+ NETDATA_SIGNAL_REOPEN_LOGS,
+ NETDATA_SIGNAL_RELOAD_HEALTH,
+ NETDATA_SIGNAL_FATAL,
+ NETDATA_SIGNAL_CHILD,
+} SIGNAL_ACTION;
+
+static struct {
+ int signo; // the signal
+ const char *name; // the name of the signal
+ size_t count; // the number of signals received
+ SIGNAL_ACTION action; // the action to take
+} signals_waiting[] = {
+ { SIGPIPE, "SIGPIPE", 0, NETDATA_SIGNAL_IGNORE },
+ { SIGINT , "SIGINT", 0, NETDATA_SIGNAL_EXIT_CLEANLY },
+ { SIGQUIT, "SIGQUIT", 0, NETDATA_SIGNAL_EXIT_CLEANLY },
+ { SIGTERM, "SIGTERM", 0, NETDATA_SIGNAL_EXIT_CLEANLY },
+ { SIGHUP, "SIGHUP", 0, NETDATA_SIGNAL_REOPEN_LOGS },
+ { SIGUSR1, "SIGUSR1", 0, NETDATA_SIGNAL_SAVE_DATABASE },
+ { SIGUSR2, "SIGUSR2", 0, NETDATA_SIGNAL_RELOAD_HEALTH },
+ { SIGBUS, "SIGBUS", 0, NETDATA_SIGNAL_FATAL },
+ { SIGCHLD, "SIGCHLD", 0, NETDATA_SIGNAL_CHILD },
+
+ // terminator
+ { 0, "NONE", 0, NETDATA_SIGNAL_END_OF_LIST }
+};
+
+static void signal_handler(int signo) {
+ // find the entry in the list
+ int i;
+ for(i = 0; signals_waiting[i].action != NETDATA_SIGNAL_END_OF_LIST ; i++) {
+ if(unlikely(signals_waiting[i].signo == signo)) {
+ signals_waiting[i].count++;
+
+ if(signals_waiting[i].action == NETDATA_SIGNAL_FATAL) {
+ char buffer[200 + 1];
+ snprintfz(buffer, 200, "\nSIGNAL HANLDER: received: %s. Oops! This is bad!\n", signals_waiting[i].name);
+ if(write(STDERR_FILENO, buffer, strlen(buffer)) == -1) {
+ // nothing to do - we cannot write but there is no way to complain about it
+ ;
+ }
+ }
+
+ return;
+ }
+ }
+}
+
+void signals_block(void) {
+ sigset_t sigset;
+ sigfillset(&sigset);
+
+ if(pthread_sigmask(SIG_BLOCK, &sigset, NULL) == -1)
+ error("SIGNAL: Could not block signals for threads");
+}
+
+void signals_unblock(void) {
+ sigset_t sigset;
+ sigfillset(&sigset);
+
+ if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
+ error("SIGNAL: Could not unblock signals for threads");
+ }
+}
+
+void signals_init(void) {
+ // Catch signals which we want to use
+ struct sigaction sa;
+ sa.sa_flags = 0;
+
+ // Enable process tracking / reaper if running as init (pid == 1).
+ // This prevents zombie processes when running in a container.
+ if (getpid() == 1) {
+ info("SIGNAL: Enabling reaper");
+ myp_init();
+ reaper_enabled = 1;
+ } else {
+ info("SIGNAL: Not enabling reaper");
+ }
+
+ // ignore all signals while we run in a signal handler
+ sigfillset(&sa.sa_mask);
+
+ int i;
+ for (i = 0; signals_waiting[i].action != NETDATA_SIGNAL_END_OF_LIST; i++) {
+ switch (signals_waiting[i].action) {
+ case NETDATA_SIGNAL_IGNORE:
+ sa.sa_handler = SIG_IGN;
+ break;
+ case NETDATA_SIGNAL_CHILD:
+ if (reaper_enabled == 0)
+ continue;
+ // FALLTHROUGH
+ default:
+ sa.sa_handler = signal_handler;
+ break;
+ }
+
+ if(sigaction(signals_waiting[i].signo, &sa, NULL) == -1)
+ error("SIGNAL: Failed to change signal handler for: %s", signals_waiting[i].name);
+ }
+}
+
+void signals_restore_SIGCHLD(void)
+{
+ struct sigaction sa;
+
+ if (reaper_enabled == 0)
+ return;
+
+ sa.sa_flags = 0;
+ sigfillset(&sa.sa_mask);
+ sa.sa_handler = signal_handler;
+
+ if(sigaction(SIGCHLD, &sa, NULL) == -1)
+ error("SIGNAL: Failed to change signal handler for: SIGCHLD");
+}
+
+void signals_reset(void) {
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = 0;
+
+ int i;
+ for (i = 0; signals_waiting[i].action != NETDATA_SIGNAL_END_OF_LIST; i++) {
+ if(sigaction(signals_waiting[i].signo, &sa, NULL) == -1)
+ error("SIGNAL: Failed to reset signal handler for: %s", signals_waiting[i].name);
+ }
+
+ if (reaper_enabled == 1)
+ myp_free();
+}
+
+// reap_child reaps the child identified by pid.
+static void reap_child(pid_t pid) {
+ siginfo_t i;
+
+ errno = 0;
+ debug(D_CHILDS, "SIGNAL: Reaping pid: %d...", pid);
+ if (waitid(P_PID, (id_t)pid, &i, WEXITED|WNOHANG) == -1) {
+ if (errno != ECHILD)
+ error("SIGNAL: Failed to wait for: %d", pid);
+ else
+ debug(D_CHILDS, "SIGNAL: Already reaped: %d", pid);
+ return;
+ } else if (i.si_pid == 0) {
+ // Process didn't exit, this shouldn't happen.
+ return;
+ }
+
+ switch (i.si_code) {
+ case CLD_EXITED:
+ debug(D_CHILDS, "SIGNAL: Child %d exited: %d", pid, i.si_status);
+ break;
+ case CLD_KILLED:
+ debug(D_CHILDS, "SIGNAL: Child %d killed by signal: %d", pid, i.si_status);
+ break;
+ case CLD_DUMPED:
+ debug(D_CHILDS, "SIGNAL: Child %d dumped core by signal: %d", pid, i.si_status);
+ break;
+ case CLD_STOPPED:
+ debug(D_CHILDS, "SIGNAL: Child %d stopped by signal: %d", pid, i.si_status);
+ break;
+ case CLD_TRAPPED:
+ debug(D_CHILDS, "SIGNAL: Child %d trapped by signal: %d", pid, i.si_status);
+ break;
+ case CLD_CONTINUED:
+ debug(D_CHILDS, "SIGNAL: Child %d continued by signal: %d", pid, i.si_status);
+ break;
+ default:
+ debug(D_CHILDS, "SIGNAL: Child %d gave us a SIGCHLD with code %d and status %d.", pid, i.si_code, i.si_status);
+ }
+}
+
+// reap_children reaps all pending children which are not managed by myp.
+static void reap_children() {
+ siginfo_t i;
+
+ while (1 == 1) {
+ // Identify which process caused the signal so we can determine
+ // if we need to reap a re-parented process.
+ i.si_pid = 0;
+ if (waitid(P_ALL, (id_t)0, &i, WEXITED|WNOHANG|WNOWAIT) == -1) {
+ if (errno != ECHILD) // This shouldn't happen with WNOHANG but does.
+ error("SIGNAL: Failed to wait");
+ return;
+ } else if (i.si_pid == 0) {
+ // No child exited.
+ return;
+ } else if (myp_reap(i.si_pid) == 0) {
+ // myp managed, sleep for a short time to avoid busy wait while
+ // this is handled by myp.
+ usleep(10000);
+ } else {
+ // Unknown process, likely a re-parented child, reap it.
+ reap_child(i.si_pid);
+ }
+ }
+}
+
+void signals_handle(void) {
+ while(1) {
+
+ // pause() causes the calling process (or thread) to sleep until a signal
+ // is delivered that either terminates the process or causes the invocation
+ // of a signal-catching function.
+ if(pause() == -1 && errno == EINTR) {
+
+ // loop once, but keep looping while signals are coming in
+ // this is needed because a few operations may take some time
+ // so we need to check for new signals before pausing again
+ int found = 1;
+ while(found) {
+ found = 0;
+
+ // execute the actions of the signals
+ int i;
+ for (i = 0; signals_waiting[i].action != NETDATA_SIGNAL_END_OF_LIST; i++) {
+ if (signals_waiting[i].count) {
+ found = 1;
+ signals_waiting[i].count = 0;
+ const char *name = signals_waiting[i].name;
+
+ switch (signals_waiting[i].action) {
+ case NETDATA_SIGNAL_RELOAD_HEALTH:
+ error_log_limit_unlimited();
+ info("SIGNAL: Received %s. Reloading HEALTH configuration...", name);
+ error_log_limit_reset();
+ execute_command(CMD_RELOAD_HEALTH, NULL, NULL);
+ break;
+
+ case NETDATA_SIGNAL_SAVE_DATABASE:
+ error_log_limit_unlimited();
+ info("SIGNAL: Received %s. Saving databases...", name);
+ error_log_limit_reset();
+ execute_command(CMD_SAVE_DATABASE, NULL, NULL);
+ break;
+
+ case NETDATA_SIGNAL_REOPEN_LOGS:
+ error_log_limit_unlimited();
+ info("SIGNAL: Received %s. Reopening all log files...", name);
+ error_log_limit_reset();
+ execute_command(CMD_REOPEN_LOGS, NULL, NULL);
+ break;
+
+ case NETDATA_SIGNAL_EXIT_CLEANLY:
+ error_log_limit_unlimited();
+ info("SIGNAL: Received %s. Cleaning up to exit...", name);
+ commands_exit();
+ netdata_cleanup_and_exit(0);
+ exit(0);
+ break;
+
+ case NETDATA_SIGNAL_FATAL:
+ fatal("SIGNAL: Received %s. netdata now exits.", name);
+ break;
+
+ case NETDATA_SIGNAL_CHILD:
+ debug(D_CHILDS, "SIGNAL: Received %s. Reaping...", name);
+ reap_children();
+ break;
+
+ default:
+ info("SIGNAL: Received %s. No signal handler configured. Ignoring it.", name);
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ error("SIGNAL: pause() returned but it was not interrupted by a signal.");
+ }
+}
diff --git a/daemon/signals.h b/daemon/signals.h
new file mode 100644
index 0000000..3fa2b0f
--- /dev/null
+++ b/daemon/signals.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_SIGNALS_H
+#define NETDATA_SIGNALS_H 1
+
+extern void signals_init(void);
+extern void signals_block(void);
+extern void signals_unblock(void);
+extern void signals_restore_SIGCHLD(void);
+extern void signals_reset(void);
+extern void signals_handle(void) NORETURN;
+
+#endif //NETDATA_SIGNALS_H
diff --git a/daemon/system-info.sh b/daemon/system-info.sh
new file mode 100755
index 0000000..05d8667
--- /dev/null
+++ b/daemon/system-info.sh
@@ -0,0 +1,406 @@
+#!/usr/bin/env sh
+
+# -------------------------------------------------------------------------------------------------
+# detect the kernel
+
+KERNEL_NAME="$(uname -s)"
+KERNEL_VERSION="$(uname -r)"
+ARCHITECTURE="$(uname -m)"
+
+# -------------------------------------------------------------------------------------------------
+# detect the virtualization and possibly the container technology
+
+CONTAINER="unknown"
+CONT_DETECTION="none"
+
+if [ -z "${VIRTUALIZATION}" ]; then
+ VIRTUALIZATION="unknown"
+ VIRT_DETECTION="none"
+
+ if [ -n "$(command -v systemd-detect-virt 2> /dev/null)" ]; then
+ VIRTUALIZATION="$(systemd-detect-virt -v)"
+ VIRT_DETECTION="systemd-detect-virt"
+ CONTAINER="$(systemd-detect-virt -c)"
+ CONT_DETECTION="systemd-detect-virt"
+ else
+ if grep -q "^flags.*hypervisor" /proc/cpuinfo 2> /dev/null; then
+ VIRTUALIZATION="hypervisor"
+ VIRT_DETECTION="/proc/cpuinfo"
+ elif [ -n "$(command -v dmidecode)" ] && dmidecode -s system-product-name 2> /dev/null | grep -q "VMware\|Virtual\|KVM\|Bochs"; then
+ VIRTUALIZATION="$(dmidecode -s system-product-name)"
+ VIRT_DETECTION="dmidecode"
+ else
+ VIRTUALIZATION="none"
+ fi
+ fi
+ if [ -z "${VIRTUALIZATION}" ]; then
+ # Output from the command is outside of spec
+ VIRTUALIZATION="unknown"
+ fi
+else
+ # Passed from outside - probably in docker run
+ VIRT_DETECTION="provided"
+fi
+
+# -------------------------------------------------------------------------------------------------
+# detect containers with heuristics
+
+if [ "${CONTAINER}" = "unknown" ]; then
+ if [ -f /proc/1/sched ]; then
+ IFS='(, ' read -r process _ < /proc/1/sched
+ if [ "${process}" = "netdata" ]; then
+ CONTAINER="container"
+ CONT_DETECTION="process"
+ fi
+ fi
+ # ubuntu and debian supply /bin/running-in-container
+ # https://www.apt-browse.org/browse/ubuntu/trusty/main/i386/upstart/1.12.1-0ubuntu4/file/bin/running-in-container
+ if /bin/running-in-container > /dev/null 2>&1; then
+ CONTAINER="container"
+ CONT_DETECTION="/bin/running-in-container"
+ fi
+
+ # lxc sets environment variable 'container'
+ #shellcheck disable=SC2154
+ if [ -n "${container}" ]; then
+ CONTAINER="lxc"
+ CONT_DETECTION="containerenv"
+ fi
+
+ # docker creates /.dockerenv
+ # http://stackoverflow.com/a/25518345
+ if [ -f "/.dockerenv" ]; then
+ CONTAINER="docker"
+ CONT_DETECTION="dockerenv"
+ fi
+
+fi
+
+# -------------------------------------------------------------------------------------------------
+# detect the operating system
+
+# Initially assume all OS detection values are for a container, these are moved later if we are bare-metal
+
+CONTAINER_OS_DETECTION="unknown"
+CONTAINER_NAME="unknown"
+CONTAINER_VERSION="unknown"
+CONTAINER_VERSION_ID="unknown"
+CONTAINER_ID="unknown"
+CONTAINER_ID_LIKE="unknown"
+
+if [ "${KERNEL_NAME}" = "Darwin" ]; then
+ CONTAINER_ID=$(sw_vers -productName)
+ CONTAINER_ID_LIKE="mac"
+ CONTAINER_NAME="mac"
+ CONTAINER_VERSION=$(sw_vers -productVersion)
+ CONTAINER_OS_DETECTION="sw_vers"
+elif [ "${KERNEL_NAME}" = "FreeBSD" ]; then
+ CONTAINER_ID="FreeBSD"
+ CONTAINER_ID_LIKE="FreeBSD"
+ CONTAINER_NAME="FreeBSD"
+ CONTAINER_OS_DETECTION="uname"
+ CONTAINER_VERSION=$(uname -r)
+ KERNEL_VERSION=$(uname -K)
+else
+ if [ -f "/etc/os-release" ]; then
+ eval "$(grep -E "^(NAME|ID|ID_LIKE|VERSION|VERSION_ID)=" < /etc/os-release | sed 's/^/CONTAINER_/')"
+ CONTAINER_OS_DETECTION="/etc/os-release"
+ fi
+
+ # shellcheck disable=SC2153
+ if [ "${CONTAINER_NAME}" = "unknown" ] || [ "${CONTAINER_VERSION}" = "unknown" ] || [ "${CONTAINER_ID}" = "unknown" ]; then
+ if [ -f "/etc/lsb-release" ]; then
+ if [ "${CONTAINER_OS_DETECTION}" = "unknown" ]; then
+ CONTAINER_OS_DETECTION="/etc/lsb-release"
+ else
+ CONTAINER_OS_DETECTION="Mixed"
+ fi
+ DISTRIB_ID="unknown"
+ DISTRIB_RELEASE="unknown"
+ DISTRIB_CODENAME="unknown"
+ eval "$(grep -E "^(DISTRIB_ID|DISTRIB_RELEASE|DISTRIB_CODENAME)=" < /etc/lsb-release)"
+ if [ "${CONTAINER_NAME}" = "unknown" ]; then CONTAINER_NAME="${DISTRIB_ID}"; fi
+ if [ "${CONTAINER_VERSION}" = "unknown" ]; then CONTAINER_VERSION="${DISTRIB_RELEASE}"; fi
+ if [ "${CONTAINER_ID}" = "unknown" ]; then CONTAINER_ID="${DISTRIB_CODENAME}"; fi
+ fi
+ if [ -n "$(command -v lsb_release 2> /dev/null)" ]; then
+ if [ "${CONTAINER_OS_DETECTION}" = "unknown" ]; then
+ CONTAINER_OS_DETECTION="lsb_release"
+ else
+ CONTAINER_OS_DETECTION="Mixed"
+ fi
+ if [ "${CONTAINER_NAME}" = "unknown" ]; then CONTAINER_NAME="$(lsb_release -is 2> /dev/null)"; fi
+ if [ "${CONTAINER_VERSION}" = "unknown" ]; then CONTAINER_VERSION="$(lsb_release -rs 2> /dev/null)"; fi
+ if [ "${CONTAINER_ID}" = "unknown" ]; then CONTAINER_ID="$(lsb_release -cs 2> /dev/null)"; fi
+ fi
+ fi
+fi
+
+# If Netdata is not running in a container then use the local detection as the host
+HOST_OS_DETECTION="unknown"
+HOST_NAME="unknown"
+HOST_VERSION="unknown"
+HOST_VERSION_ID="unknown"
+HOST_ID="unknown"
+HOST_ID_LIKE="unknown"
+
+# 'systemd-detect-virt' returns 'none' if there is no hardware/container virtualization.
+if [ "${CONTAINER}" = "unknown" ] || [ "${CONTAINER}" = "none" ]; then
+ for v in NAME ID ID_LIKE VERSION VERSION_ID OS_DETECTION; do
+ eval "HOST_$v=\$CONTAINER_$v; CONTAINER_$v=none"
+ done
+else
+ # Otherwise try and use a user-supplied bind-mount into the container to resolve the host details
+ if [ -e "/host/etc/os-release" ]; then
+ OS_DETECTION="/etc/os-release"
+ eval "$(grep -E "^(NAME|ID|ID_LIKE|VERSION|VERSION_ID)=" < /host/etc/os-release | sed 's/^/HOST_/')"
+ HOST_OS_DETECTION="/host/etc/os-release"
+ fi
+ if [ "${HOST_NAME}" = "unknown" ] || [ "${HOST_VERSION}" = "unknown" ] || [ "${HOST_ID}" = "unknown" ]; then
+ if [ -f "/host/etc/lsb-release" ]; then
+ if [ "${HOST_OS_DETECTION}" = "unknown" ]; then
+ HOST_OS_DETECTION="/etc/lsb-release"
+ else
+ HOST_OS_DETECTION="Mixed"
+ fi
+ DISTRIB_ID="unknown"
+ DISTRIB_RELEASE="unknown"
+ DISTRIB_CODENAME="unknown"
+ eval "$(grep -E "^(DISTRIB_ID|DISTRIB_RELEASE|DISTRIB_CODENAME)=" < /etc/lsb-release)"
+ if [ "${HOST_NAME}" = "unknown" ]; then HOST_NAME="${DISTRIB_ID}"; fi
+ if [ "${HOST_VERSION}" = "unknown" ]; then HOST_VERSION="${DISTRIB_RELEASE}"; fi
+ if [ "${HOST_ID}" = "unknown" ]; then HOST_ID="${DISTRIB_CODENAME}"; fi
+ fi
+ fi
+fi
+
+# -------------------------------------------------------------------------------------------------
+# Detect information about the CPU
+
+LCPU_COUNT="unknown"
+CPU_MODEL="unknown"
+CPU_VENDOR="unknown"
+CPU_FREQ="unknown"
+CPU_INFO_SOURCE="none"
+
+possible_cpu_freq=""
+nproc="$(command -v nproc)"
+lscpu="$(command -v lscpu)"
+lscpu_output=""
+dmidecode="$(command -v dmidecode)"
+dmidecode_output=""
+
+if [ -n "${lscpu}" ] && lscpu > /dev/null 2>&1; then
+ lscpu_output="$(LC_NUMERIC=C ${lscpu} 2> /dev/null)"
+ CPU_INFO_SOURCE="lscpu"
+ LCPU_COUNT="$(echo "${lscpu_output}" | grep "^CPU(s):" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ CPU_VENDOR="$(echo "${lscpu_output}" | grep "^Vendor ID:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ CPU_MODEL="$(echo "${lscpu_output}" | grep "^Model name:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ possible_cpu_freq="$(echo "${lscpu_output}" | grep -F "CPU max MHz:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | grep -o '^[0-9]*') MHz"
+ if [ "${possible_cpu_freq}" = " MHz" ]; then
+ possible_cpu_freq="$(echo "${lscpu_output}" | grep -F "CPU MHz:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | grep -o '^[0-9]*') MHz"
+ fi
+elif [ -n "${dmidecode}" ] && dmidecode -t processor > /dev/null 2>&1; then
+ dmidecode_output="$(${dmidecode} -t processor 2> /dev/null)"
+ CPU_INFO_SOURCE="dmidecode"
+ LCPU_COUNT="$(echo "${dmidecode_output}" | grep -F "Thread Count:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ CPU_VENDOR="$(echo "${dmidecode_output}" | grep -F "Manufacturer:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ CPU_MODEL="$(echo "${dmidecode_output}" | grep -F "Version:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ possible_cpu_freq="$(echo "${dmidecode_output}" | grep -F "Current Speed:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+else
+ if [ -n "${nproc}" ]; then
+ CPU_INFO_SOURCE="nproc"
+ LCPU_COUNT="$(${nproc})"
+ elif [ "${KERNEL_NAME}" = FreeBSD ]; then
+ CPU_INFO_SOURCE="sysctl"
+ LCPU_COUNT="$(sysctl -n kern.smp.cpus)"
+ elif [ -d /sys/devices/system/cpu ]; then
+ CPU_INFO_SOURCE="sysfs"
+ # This is potentially more accurate than checking `/proc/cpuinfo`.
+ LCPU_COUNT="$(find /sys/devices/system/cpu -mindepth 1 -maxdepth 1 -type d -name 'cpu*' | grep -cEv 'idle|freq')"
+ elif [ -r /proc/cpuinfo ]; then
+ CPU_INFO_SOURCE="procfs"
+ LCPU_COUNT="$(grep -c ^processor /proc/cpuinfo)"
+ fi
+
+ # If we have GNU uname, we can use that to get CPU info (probably).
+ if uname --version 2> /dev/null | grep -qF 'GNU coreutils'; then
+ CPU_INFO_SOURCE="${CPU_INFO_SOURCE} uname"
+ CPU_MODEL="$(uname -p)"
+ CPU_VENDOR="$(uname -i)"
+ elif [ "${KERNEL_NAME}" = FreeBSD ]; then
+ if (echo "${CPU_INFO_SOURCE}" | grep -qv sysctl); then
+ CPU_INFO_SOURCE="${CPU_INFO_SOURCE} sysctl"
+ fi
+
+ CPU_MODEL="$(sysctl -n hw.model)"
+ elif [ -r /proc/cpuinfo ]; then
+ if (echo "${CPU_INFO_SOURCE}" | grep -qv procfs); then
+ CPU_INFO_SOURCE="${CPU_INFO_SOURCE} procfs"
+ fi
+
+ CPU_MODEL="$(grep -F "model name" /proc/cpuinfo | head -n 1 | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ CPU_VENDOR="$(grep -F "vendor_id" /proc/cpuinfo | head -n 1 | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ fi
+fi
+
+if [ -r /sys/devices/system/cpu/cpu0/cpufreq/base_frequency ]; then
+ if (echo "${CPU_INFO_SOURCE}" | grep -qv sysfs); then
+ CPU_INFO_SOURCE="${CPU_INFO_SOURCE} sysfs"
+ fi
+
+ CPU_FREQ="$(cat /sys/devices/system/cpu/cpu0/cpufreq/base_frequency)"
+elif [ -n "${possible_cpu_freq}" ]; then
+ CPU_FREQ="${possible_cpu_freq}"
+elif [ -r /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq ]; then
+ if (echo "${CPU_INFO_SOURCE}" | grep -qv sysfs); then
+ CPU_INFO_SOURCE="${CPU_INFO_SOURCE} sysfs"
+ fi
+
+ CPU_FREQ="$(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq)"
+fi
+
+freq_units="$(echo "${CPU_FREQ}" | cut -f 2 -d ' ')"
+
+case "${freq_units}" in
+ GHz)
+ value="$(echo "${CPU_FREQ}" | cut -f 1 -d ' ')"
+ CPU_FREQ="$((value * 1000 * 1000 * 1000))"
+ ;;
+ MHz)
+ value="$(echo "${CPU_FREQ}" | cut -f 1 -d ' ')"
+ CPU_FREQ="$((value * 1000 * 1000))"
+ ;;
+ KHz)
+ value="$(echo "${CPU_FREQ}" | cut -f 1 -d ' ')"
+ CPU_FREQ="$((value * 1000))"
+ ;;
+ *) ;;
+
+esac
+
+# -------------------------------------------------------------------------------------------------
+# Detect the total system RAM
+
+TOTAL_RAM="unknown"
+RAM_DETECTION="none"
+
+if [ "${KERNEL_NAME}" = FreeBSD ]; then
+ RAM_DETECTION="sysctl"
+ TOTAL_RAM="$(sysctl -n hw.physmem)"
+elif [ "${KERNEL_NAME}" = Darwin ]; then
+ RAM_DETECTION="sysctl"
+ TOTAL_RAM="$(sysctl -n hw.physmem)"
+elif [ -r /proc/meminfo ]; then
+ RAM_DETECTION="procfs"
+ TOTAL_RAM="$(grep -F MemTotal /proc/meminfo | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | cut -f 1 -d ' ')"
+ TOTAL_RAM="$((TOTAL_RAM * 1024))"
+fi
+
+# -------------------------------------------------------------------------------------------------
+# Detect the total system disk space
+
+DISK_SIZE="unknown"
+DISK_DETECTION="none"
+
+if [ "${KERNEL_NAME}" = "Darwin" ]; then
+ types='hfs'
+
+ if (lsvfs | grep -q apfs); then
+ types="${types},apfs"
+ fi
+
+ if (lsvfs | grep -q ufs); then
+ types="${types},ufs"
+ fi
+
+ DISK_DETECTION="df"
+ DISK_SIZE=$(($(/bin/df -k -t ${types} | tail -n +2 | sed -E 's/\/dev\/disk([[:digit:]]*)s[[:digit:]]*/\/dev\/disk\1/g' | sort -k 1 | awk -F ' ' '{s=$NF;for(i=NF-1;i>=1;i--)s=s FS $i;print s}' | uniq -f 9 | awk '{print $8}' | tr '\n' '+' | rev | cut -f 2- -d '+' | rev) * 1024))
+elif [ "${KERNEL_NAME}" = FreeBSD ]; then
+ types='ufs'
+
+ if (lsvfs | grep -q zfs); then
+ types="${types},zfs"
+ fi
+
+ DISK_DETECTION="df"
+ total="$(df -t ${types} -c -k | tail -n 1 | awk '{print $2}')"
+ DISK_SIZE="$((total * 1024))"
+else
+ if [ -d /sys/block ] && [ -r /proc/devices ]; then
+ dev_major_whitelist=''
+
+ # This is a list of device names used for block storage devices.
+ # These translate to the prefixs of files in `/dev` indicating the device type.
+ # They are sorted by lowest used device major number, with dynamically assigned ones at the end.
+ # We use this to look up device major numbers in `/proc/devices`
+ device_names='hd sd mfm ad ftl pd nftl dasd intfl mmcblk ub xvd rfd vbd nvme'
+
+ for name in ${device_names}; do
+ if grep -qE " ${name}\$" /proc/devices; then
+ dev_major_whitelist="${dev_major_whitelist}:$(grep -E "${name}\$" /proc/devices | sed -e 's/^[[:space:]]*//' | cut -f 1 -d ' ' | tr '\n' ':'):"
+ fi
+ done
+
+ DISK_DETECTION="sysfs"
+ DISK_SIZE="0"
+ for disk in /sys/block/*; do
+ if [ -r "${disk}/size" ] \
+ && (echo "${dev_major_whitelist}" | grep -q ":$(cut -f 1 -d ':' "${disk}/dev"):") \
+ && grep -qv 1 "${disk}/removable"; then
+ size="$(($(cat "${disk}/size") * 512))"
+ DISK_SIZE="$((DISK_SIZE + size))"
+ fi
+ done
+ elif df --version 2> /dev/null | grep -qF "GNU coreutils"; then
+ DISK_DETECTION="df"
+ DISK_SIZE=$(($(df -x tmpfs -x devtmpfs -x squashfs -l -B1 --output=source,size | tail -n +2 | sort -u -k 1 | awk '{print $2}' | tr '\n' '+' | head -c -1)))
+ else
+ DISK_DETECTION="df"
+ include_fs_types="ext*|btrfs|xfs|jfs|reiser*|zfs"
+ DISK_SIZE=$(($(df -T -P | tail -n +2 | sort -u -k 1 | grep "${include_fs_types}" | awk '{print $3}' | tr '\n' '+' | head -c -1) * 1024))
+ fi
+fi
+
+# -------------------------------------------------------------------------------------------------
+# Detect whether the node is kubernetes node
+
+HOST_IS_K8S_NODE="false"
+
+if [ -n "${KUBERNETES_SERVICE_HOST}" ] && [ -n "${KUBERNETES_SERVICE_PORT}" ]; then
+ # These env vars are set for every container managed by k8s.
+ HOST_IS_K8S_NODE="true"
+elif pgrep "kubelet"; then
+ # The kubelet is the primary "node agent" that runs on each node.
+ HOST_IS_K8S_NODE="true"
+fi
+
+echo "NETDATA_CONTAINER_OS_NAME=${CONTAINER_NAME}"
+echo "NETDATA_CONTAINER_OS_ID=${CONTAINER_ID}"
+echo "NETDATA_CONTAINER_OS_ID_LIKE=${CONTAINER_ID_LIKE}"
+echo "NETDATA_CONTAINER_OS_VERSION=${CONTAINER_VERSION}"
+echo "NETDATA_CONTAINER_OS_VERSION_ID=${CONTAINER_VERSION_ID}"
+echo "NETDATA_CONTAINER_OS_DETECTION=${CONTAINER_OS_DETECTION}"
+echo "NETDATA_HOST_OS_NAME=${HOST_NAME}"
+echo "NETDATA_HOST_OS_ID=${HOST_ID}"
+echo "NETDATA_HOST_OS_ID_LIKE=${HOST_ID_LIKE}"
+echo "NETDATA_HOST_OS_VERSION=${HOST_VERSION}"
+echo "NETDATA_HOST_OS_VERSION_ID=${HOST_VERSION_ID}"
+echo "NETDATA_HOST_OS_DETECTION=${HOST_OS_DETECTION}"
+echo "NETDATA_HOST_IS_K8S_NODE=${HOST_IS_K8S_NODE}"
+echo "NETDATA_SYSTEM_KERNEL_NAME=${KERNEL_NAME}"
+echo "NETDATA_SYSTEM_KERNEL_VERSION=${KERNEL_VERSION}"
+echo "NETDATA_SYSTEM_ARCHITECTURE=${ARCHITECTURE}"
+echo "NETDATA_SYSTEM_VIRTUALIZATION=${VIRTUALIZATION}"
+echo "NETDATA_SYSTEM_VIRT_DETECTION=${VIRT_DETECTION}"
+echo "NETDATA_SYSTEM_CONTAINER=${CONTAINER}"
+echo "NETDATA_SYSTEM_CONTAINER_DETECTION=${CONT_DETECTION}"
+echo "NETDATA_SYSTEM_CPU_LOGICAL_CPU_COUNT=${LCPU_COUNT}"
+echo "NETDATA_SYSTEM_CPU_VENDOR=${CPU_VENDOR}"
+echo "NETDATA_SYSTEM_CPU_MODEL=${CPU_MODEL}"
+echo "NETDATA_SYSTEM_CPU_FREQ=${CPU_FREQ}"
+echo "NETDATA_SYSTEM_CPU_DETECTION=${CPU_INFO_SOURCE}"
+echo "NETDATA_SYSTEM_TOTAL_RAM=${TOTAL_RAM}"
+echo "NETDATA_SYSTEM_RAM_DETECTION=${RAM_DETECTION}"
+echo "NETDATA_SYSTEM_TOTAL_DISK_SIZE=${DISK_SIZE}"
+echo "NETDATA_SYSTEM_DISK_DETECTION=${DISK_DETECTION}"
diff --git a/daemon/unit_test.c b/daemon/unit_test.c
new file mode 100644
index 0000000..e6a69e3
--- /dev/null
+++ b/daemon/unit_test.c
@@ -0,0 +1,2235 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common.h"
+
+static int check_number_printing(void) {
+ struct {
+ calculated_number n;
+ const char *correct;
+ } values[] = {
+ { .n = 0, .correct = "0" },
+ { .n = 0.0000001, .correct = "0.0000001" },
+ { .n = 0.00000009, .correct = "0.0000001" },
+ { .n = 0.000000001, .correct = "0" },
+ { .n = 99.99999999999999999, .correct = "100" },
+ { .n = -99.99999999999999999, .correct = "-100" },
+ { .n = 123.4567890123456789, .correct = "123.456789" },
+ { .n = 9999.9999999, .correct = "9999.9999999" },
+ { .n = -9999.9999999, .correct = "-9999.9999999" },
+ { .n = 0, .correct = NULL },
+ };
+
+ char netdata[50], system[50];
+ int i, failed = 0;
+ for(i = 0; values[i].correct ; i++) {
+ print_calculated_number(netdata, values[i].n);
+ snprintfz(system, 49, "%0.12" LONG_DOUBLE_MODIFIER, (LONG_DOUBLE)values[i].n);
+
+ int ok = 1;
+ if(strcmp(netdata, values[i].correct) != 0) {
+ ok = 0;
+ failed++;
+ }
+
+ fprintf(stderr, "'%s' (system) printed as '%s' (netdata): %s\n", system, netdata, ok?"OK":"FAILED");
+ }
+
+ if(failed) return 1;
+ return 0;
+}
+
+static int check_rrdcalc_comparisons(void) {
+ RRDCALC_STATUS a, b;
+
+ // make sure calloc() sets the status to UNINITIALIZED
+ memset(&a, 0, sizeof(RRDCALC_STATUS));
+ if(a != RRDCALC_STATUS_UNINITIALIZED) {
+ fprintf(stderr, "%s is not zero.\n", rrdcalc_status2string(RRDCALC_STATUS_UNINITIALIZED));
+ return 1;
+ }
+
+ a = RRDCALC_STATUS_REMOVED;
+ b = RRDCALC_STATUS_UNDEFINED;
+ if(!(a < b)) {
+ fprintf(stderr, "%s is not less than %s\n", rrdcalc_status2string(a), rrdcalc_status2string(b));
+ return 1;
+ }
+
+ a = RRDCALC_STATUS_UNDEFINED;
+ b = RRDCALC_STATUS_UNINITIALIZED;
+ if(!(a < b)) {
+ fprintf(stderr, "%s is not less than %s\n", rrdcalc_status2string(a), rrdcalc_status2string(b));
+ return 1;
+ }
+
+ a = RRDCALC_STATUS_UNINITIALIZED;
+ b = RRDCALC_STATUS_CLEAR;
+ if(!(a < b)) {
+ fprintf(stderr, "%s is not less than %s\n", rrdcalc_status2string(a), rrdcalc_status2string(b));
+ return 1;
+ }
+
+ a = RRDCALC_STATUS_CLEAR;
+ b = RRDCALC_STATUS_RAISED;
+ if(!(a < b)) {
+ fprintf(stderr, "%s is not less than %s\n", rrdcalc_status2string(a), rrdcalc_status2string(b));
+ return 1;
+ }
+
+ a = RRDCALC_STATUS_RAISED;
+ b = RRDCALC_STATUS_WARNING;
+ if(!(a < b)) {
+ fprintf(stderr, "%s is not less than %s\n", rrdcalc_status2string(a), rrdcalc_status2string(b));
+ return 1;
+ }
+
+ a = RRDCALC_STATUS_WARNING;
+ b = RRDCALC_STATUS_CRITICAL;
+ if(!(a < b)) {
+ fprintf(stderr, "%s is not less than %s\n", rrdcalc_status2string(a), rrdcalc_status2string(b));
+ return 1;
+ }
+
+ fprintf(stderr, "RRDCALC_STATUSes are sortable.\n");
+
+ return 0;
+}
+
+int check_storage_number(calculated_number n, int debug) {
+ char buffer[100];
+ uint32_t flags = SN_EXISTS;
+
+ storage_number s = pack_storage_number(n, flags);
+ calculated_number d = unpack_storage_number(s);
+
+ if(!does_storage_number_exist(s)) {
+ fprintf(stderr, "Exists flags missing for number " CALCULATED_NUMBER_FORMAT "!\n", n);
+ return 5;
+ }
+
+ calculated_number ddiff = d - n;
+ calculated_number dcdiff = ddiff * 100.0 / n;
+
+ if(dcdiff < 0) dcdiff = -dcdiff;
+
+ size_t len = (size_t)print_calculated_number(buffer, d);
+ calculated_number p = str2ld(buffer, NULL);
+ calculated_number pdiff = n - p;
+ calculated_number pcdiff = pdiff * 100.0 / n;
+ if(pcdiff < 0) pcdiff = -pcdiff;
+
+ if(debug) {
+ fprintf(stderr,
+ CALCULATED_NUMBER_FORMAT " original\n"
+ CALCULATED_NUMBER_FORMAT " packed and unpacked, (stored as 0x%08X, diff " CALCULATED_NUMBER_FORMAT ", " CALCULATED_NUMBER_FORMAT "%%)\n"
+ "%s printed after unpacked (%zu bytes)\n"
+ CALCULATED_NUMBER_FORMAT " re-parsed from printed (diff " CALCULATED_NUMBER_FORMAT ", " CALCULATED_NUMBER_FORMAT "%%)\n\n",
+ n,
+ d, s, ddiff, dcdiff,
+ buffer, len,
+ p, pdiff, pcdiff
+ );
+ if(len != strlen(buffer)) fprintf(stderr, "ERROR: printed number %s is reported to have length %zu but it has %zu\n", buffer, len, strlen(buffer));
+
+ if(dcdiff > ACCURACY_LOSS_ACCEPTED_PERCENT)
+ fprintf(stderr, "WARNING: packing number " CALCULATED_NUMBER_FORMAT " has accuracy loss " CALCULATED_NUMBER_FORMAT " %%\n", n, dcdiff);
+
+ if(pcdiff > ACCURACY_LOSS_ACCEPTED_PERCENT)
+ fprintf(stderr, "WARNING: re-parsing the packed, unpacked and printed number " CALCULATED_NUMBER_FORMAT " has accuracy loss " CALCULATED_NUMBER_FORMAT " %%\n", n, pcdiff);
+ }
+
+ if(len != strlen(buffer)) return 1;
+ if(dcdiff > ACCURACY_LOSS_ACCEPTED_PERCENT) return 3;
+ if(pcdiff > ACCURACY_LOSS_ACCEPTED_PERCENT) return 4;
+ return 0;
+}
+
+calculated_number storage_number_min(calculated_number n) {
+ calculated_number r = 1, last;
+
+ do {
+ last = n;
+ n /= 2.0;
+ storage_number t = pack_storage_number(n, SN_EXISTS);
+ r = unpack_storage_number(t);
+ } while(r != 0.0 && r != last);
+
+ return last;
+}
+
+void benchmark_storage_number(int loop, int multiplier) {
+ int i, j;
+ calculated_number n, d;
+ storage_number s;
+ unsigned long long user, system, total, mine, their;
+
+ calculated_number storage_number_positive_min = unpack_storage_number(STORAGE_NUMBER_POSITIVE_MIN_RAW);
+ calculated_number storage_number_positive_max = unpack_storage_number(STORAGE_NUMBER_POSITIVE_MAX_RAW);
+
+ char buffer[100];
+
+ struct rusage now, last;
+
+ fprintf(stderr, "\n\nBenchmarking %d numbers, please wait...\n\n", loop);
+
+ // ------------------------------------------------------------------------
+
+ fprintf(stderr, "SYSTEM LONG DOUBLE SIZE: %zu bytes\n", sizeof(calculated_number));
+ fprintf(stderr, "NETDATA FLOATING POINT SIZE: %zu bytes\n", sizeof(storage_number));
+
+ mine = (calculated_number)sizeof(storage_number) * (calculated_number)loop;
+ their = (calculated_number)sizeof(calculated_number) * (calculated_number)loop;
+
+ if(mine > their) {
+ fprintf(stderr, "\nNETDATA NEEDS %0.2" LONG_DOUBLE_MODIFIER " TIMES MORE MEMORY. Sorry!\n", (LONG_DOUBLE)(mine / their));
+ }
+ else {
+ fprintf(stderr, "\nNETDATA INTERNAL FLOATING POINT ARITHMETICS NEEDS %0.2" LONG_DOUBLE_MODIFIER " TIMES LESS MEMORY.\n", (LONG_DOUBLE)(their / mine));
+ }
+
+ fprintf(stderr, "\nNETDATA FLOATING POINT\n");
+ fprintf(stderr, "MIN POSITIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", unpack_storage_number(STORAGE_NUMBER_POSITIVE_MIN_RAW));
+ fprintf(stderr, "MAX POSITIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", unpack_storage_number(STORAGE_NUMBER_POSITIVE_MAX_RAW));
+ fprintf(stderr, "MIN NEGATIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", unpack_storage_number(STORAGE_NUMBER_NEGATIVE_MIN_RAW));
+ fprintf(stderr, "MAX NEGATIVE VALUE " CALCULATED_NUMBER_FORMAT "\n", unpack_storage_number(STORAGE_NUMBER_NEGATIVE_MAX_RAW));
+ fprintf(stderr, "Maximum accuracy loss accepted: " CALCULATED_NUMBER_FORMAT "%%\n\n\n", (calculated_number)ACCURACY_LOSS_ACCEPTED_PERCENT);
+
+ // ------------------------------------------------------------------------
+
+ fprintf(stderr, "INTERNAL LONG DOUBLE PRINTING: ");
+ getrusage(RUSAGE_SELF, &last);
+
+ // do the job
+ for(j = 1; j < 11 ;j++) {
+ n = storage_number_positive_min * j;
+
+ for(i = 0; i < loop ;i++) {
+ n *= multiplier;
+ if(n > storage_number_positive_max) n = storage_number_positive_min;
+
+ print_calculated_number(buffer, n);
+ }
+ }
+
+ getrusage(RUSAGE_SELF, &now);
+ user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec;
+ system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec;
+ total = user + system;
+ mine = total;
+
+ fprintf(stderr, "user %0.5" LONG_DOUBLE_MODIFIER", system %0.5" LONG_DOUBLE_MODIFIER ", total %0.5" LONG_DOUBLE_MODIFIER "\n", (LONG_DOUBLE)(user / 1000000.0), (LONG_DOUBLE)(system / 1000000.0), (LONG_DOUBLE)(total / 1000000.0));
+
+ // ------------------------------------------------------------------------
+
+ fprintf(stderr, "SYSTEM LONG DOUBLE PRINTING: ");
+ getrusage(RUSAGE_SELF, &last);
+
+ // do the job
+ for(j = 1; j < 11 ;j++) {
+ n = storage_number_positive_min * j;
+
+ for(i = 0; i < loop ;i++) {
+ n *= multiplier;
+ if(n > storage_number_positive_max) n = storage_number_positive_min;
+ snprintfz(buffer, 100, CALCULATED_NUMBER_FORMAT, n);
+ }
+ }
+
+ getrusage(RUSAGE_SELF, &now);
+ user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec;
+ system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec;
+ total = user + system;
+ their = total;
+
+ fprintf(stderr, "user %0.5" LONG_DOUBLE_MODIFIER ", system %0.5" LONG_DOUBLE_MODIFIER ", total %0.5" LONG_DOUBLE_MODIFIER "\n", (LONG_DOUBLE)(user / 1000000.0), (LONG_DOUBLE)(system / 1000000.0), (LONG_DOUBLE)(total / 1000000.0));
+
+ if(mine > total) {
+ fprintf(stderr, "NETDATA CODE IS SLOWER %0.2" LONG_DOUBLE_MODIFIER " %%\n", (LONG_DOUBLE)(mine * 100.0 / their - 100.0));
+ }
+ else {
+ fprintf(stderr, "NETDATA CODE IS F A S T E R %0.2" LONG_DOUBLE_MODIFIER " %%\n", (LONG_DOUBLE)(their * 100.0 / mine - 100.0));
+ }
+
+ // ------------------------------------------------------------------------
+
+ fprintf(stderr, "\nINTERNAL LONG DOUBLE PRINTING WITH PACK / UNPACK: ");
+ getrusage(RUSAGE_SELF, &last);
+
+ // do the job
+ for(j = 1; j < 11 ;j++) {
+ n = storage_number_positive_min * j;
+
+ for(i = 0; i < loop ;i++) {
+ n *= multiplier;
+ if(n > storage_number_positive_max) n = storage_number_positive_min;
+
+ s = pack_storage_number(n, SN_EXISTS);
+ d = unpack_storage_number(s);
+ print_calculated_number(buffer, d);
+ }
+ }
+
+ getrusage(RUSAGE_SELF, &now);
+ user = now.ru_utime.tv_sec * 1000000ULL + now.ru_utime.tv_usec - last.ru_utime.tv_sec * 1000000ULL + last.ru_utime.tv_usec;
+ system = now.ru_stime.tv_sec * 1000000ULL + now.ru_stime.tv_usec - last.ru_stime.tv_sec * 1000000ULL + last.ru_stime.tv_usec;
+ total = user + system;
+ mine = total;
+
+ fprintf(stderr, "user %0.5" LONG_DOUBLE_MODIFIER ", system %0.5" LONG_DOUBLE_MODIFIER ", total %0.5" LONG_DOUBLE_MODIFIER "\n", (LONG_DOUBLE)(user / 1000000.0), (LONG_DOUBLE)(system / 1000000.0), (LONG_DOUBLE)(total / 1000000.0));
+
+ if(mine > their) {
+ fprintf(stderr, "WITH PACKING UNPACKING NETDATA CODE IS SLOWER %0.2" LONG_DOUBLE_MODIFIER " %%\n", (LONG_DOUBLE)(mine * 100.0 / their - 100.0));
+ }
+ else {
+ fprintf(stderr, "EVEN WITH PACKING AND UNPACKING, NETDATA CODE IS F A S T E R %0.2" LONG_DOUBLE_MODIFIER " %%\n", (LONG_DOUBLE)(their * 100.0 / mine - 100.0));
+ }
+
+ // ------------------------------------------------------------------------
+
+}
+
+static int check_storage_number_exists() {
+ uint32_t flags;
+
+
+ for(flags = 0; flags < 7 ; flags++) {
+ if(get_storage_number_flags(flags << 24) != flags << 24) {
+ fprintf(stderr, "Flag 0x%08x is not checked correctly. It became 0x%08x\n", flags << 24, get_storage_number_flags(flags << 24));
+ return 1;
+ }
+ }
+
+ flags = SN_EXISTS;
+ calculated_number n = 0.0;
+
+ storage_number s = pack_storage_number(n, flags);
+ calculated_number d = unpack_storage_number(s);
+ if(get_storage_number_flags(s) != flags) {
+ fprintf(stderr, "Wrong flags. Given %08x, Got %08x!\n", flags, get_storage_number_flags(s));
+ return 1;
+ }
+ if(n != d) {
+ fprintf(stderr, "Wrong number returned. Expected " CALCULATED_NUMBER_FORMAT ", returned " CALCULATED_NUMBER_FORMAT "!\n", n, d);
+ return 1;
+ }
+
+ return 0;
+}
+
+int unit_test_storage() {
+ if(check_storage_number_exists()) return 0;
+
+ calculated_number storage_number_positive_min = unpack_storage_number(STORAGE_NUMBER_POSITIVE_MIN_RAW);
+ calculated_number storage_number_negative_max = unpack_storage_number(STORAGE_NUMBER_NEGATIVE_MAX_RAW);
+
+ calculated_number c, a = 0;
+ int i, j, g, r = 0;
+
+ for(g = -1; g <= 1 ; g++) {
+ a = 0;
+
+ if(!g) continue;
+
+ for(j = 0; j < 9 ;j++) {
+ a += 0.0000001;
+ c = a * g;
+ for(i = 0; i < 21 ;i++, c *= 10) {
+ if(c > 0 && c < storage_number_positive_min) continue;
+ if(c < 0 && c > storage_number_negative_max) continue;
+
+ if(check_storage_number(c, 1)) return 1;
+ }
+ }
+ }
+
+ // if(check_storage_number(858993459.1234567, 1)) return 1;
+ benchmark_storage_number(1000000, 2);
+ return r;
+}
+
+int unit_test_str2ld() {
+ char *values[] = {
+ "1.2345678", "-35.6", "0.00123", "23842384234234.2", ".1", "1.2e-10",
+ "hello", "1wrong", "nan", "inf", NULL
+ };
+
+ int i;
+ for(i = 0; values[i] ; i++) {
+ char *e_mine = "hello", *e_sys = "world";
+ LONG_DOUBLE mine = str2ld(values[i], &e_mine);
+ LONG_DOUBLE sys = strtold(values[i], &e_sys);
+
+ if(isnan(mine)) {
+ if(!isnan(sys)) {
+ fprintf(stderr, "Value '%s' is parsed as %" LONG_DOUBLE_MODIFIER ", but system believes it is %" LONG_DOUBLE_MODIFIER ".\n", values[i], mine, sys);
+ return -1;
+ }
+ }
+ else if(isinf(mine)) {
+ if(!isinf(sys)) {
+ fprintf(stderr, "Value '%s' is parsed as %" LONG_DOUBLE_MODIFIER ", but system believes it is %" LONG_DOUBLE_MODIFIER ".\n", values[i], mine, sys);
+ return -1;
+ }
+ }
+ else if(mine != sys && abs(mine-sys) > 0.000001) {
+ fprintf(stderr, "Value '%s' is parsed as %" LONG_DOUBLE_MODIFIER ", but system believes it is %" LONG_DOUBLE_MODIFIER ", delta %" LONG_DOUBLE_MODIFIER ".\n", values[i], mine, sys, sys-mine);
+ return -1;
+ }
+
+ if(e_mine != e_sys) {
+ fprintf(stderr, "Value '%s' is parsed correctly, but endptr is not right\n", values[i]);
+ return -1;
+ }
+
+ fprintf(stderr, "str2ld() parsed value '%s' exactly the same way with strtold(), returned %" LONG_DOUBLE_MODIFIER " vs %" LONG_DOUBLE_MODIFIER "\n", values[i], mine, sys);
+ }
+
+ return 0;
+}
+
+int unit_test_buffer() {
+ BUFFER *wb = buffer_create(1);
+ char string[2048 + 1];
+ char final[9000 + 1];
+ int i;
+
+ for(i = 0; i < 2048; i++)
+ string[i] = (char)((i % 24) + 'a');
+ string[2048] = '\0';
+
+ const char *fmt = "string1: %s\nstring2: %s\nstring3: %s\nstring4: %s";
+ buffer_sprintf(wb, fmt, string, string, string, string);
+ snprintfz(final, 9000, fmt, string, string, string, string);
+
+ const char *s = buffer_tostring(wb);
+
+ if(buffer_strlen(wb) != strlen(final) || strcmp(s, final) != 0) {
+ fprintf(stderr, "\nbuffer_sprintf() is faulty.\n");
+ fprintf(stderr, "\nstring : %s (length %zu)\n", string, strlen(string));
+ fprintf(stderr, "\nbuffer : %s (length %zu)\n", s, buffer_strlen(wb));
+ fprintf(stderr, "\nexpected: %s (length %zu)\n", final, strlen(final));
+ buffer_free(wb);
+ return -1;
+ }
+
+ fprintf(stderr, "buffer_sprintf() works as expected.\n");
+ buffer_free(wb);
+ return 0;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+struct feed_values {
+ unsigned long long microseconds;
+ collected_number value;
+};
+
+struct test {
+ char name[100];
+ char description[1024];
+
+ int update_every;
+ unsigned long long multiplier;
+ unsigned long long divisor;
+ RRD_ALGORITHM algorithm;
+
+ unsigned long feed_entries;
+ unsigned long result_entries;
+ struct feed_values *feed;
+ calculated_number *results;
+
+ collected_number *feed2;
+ calculated_number *results2;
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test1
+// test absolute values stored
+
+struct feed_values test1_feed[] = {
+ { 0, 10 },
+ { 1000000, 20 },
+ { 1000000, 30 },
+ { 1000000, 40 },
+ { 1000000, 50 },
+ { 1000000, 60 },
+ { 1000000, 70 },
+ { 1000000, 80 },
+ { 1000000, 90 },
+ { 1000000, 100 },
+};
+
+calculated_number test1_results[] = {
+ 20, 30, 40, 50, 60, 70, 80, 90, 100
+};
+
+struct test test1 = {
+ "test1", // name
+ "test absolute values stored at exactly second boundaries",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_ABSOLUTE, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test1_feed, // feed
+ test1_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test2
+// test absolute values stored in the middle of second boundaries
+
+struct feed_values test2_feed[] = {
+ { 500000, 10 },
+ { 1000000, 20 },
+ { 1000000, 30 },
+ { 1000000, 40 },
+ { 1000000, 50 },
+ { 1000000, 60 },
+ { 1000000, 70 },
+ { 1000000, 80 },
+ { 1000000, 90 },
+ { 1000000, 100 },
+};
+
+calculated_number test2_results[] = {
+ 20, 30, 40, 50, 60, 70, 80, 90, 100
+};
+
+struct test test2 = {
+ "test2", // name
+ "test absolute values stored in the middle of second boundaries",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_ABSOLUTE, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test2_feed, // feed
+ test2_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test3
+
+struct feed_values test3_feed[] = {
+ { 0, 10 },
+ { 1000000, 20 },
+ { 1000000, 30 },
+ { 1000000, 40 },
+ { 1000000, 50 },
+ { 1000000, 60 },
+ { 1000000, 70 },
+ { 1000000, 80 },
+ { 1000000, 90 },
+ { 1000000, 100 },
+};
+
+calculated_number test3_results[] = {
+ 10, 10, 10, 10, 10, 10, 10, 10, 10
+};
+
+struct test test3 = {
+ "test3", // name
+ "test incremental values stored at exactly second boundaries",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test3_feed, // feed
+ test3_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test4
+
+struct feed_values test4_feed[] = {
+ { 500000, 10 },
+ { 1000000, 20 },
+ { 1000000, 30 },
+ { 1000000, 40 },
+ { 1000000, 50 },
+ { 1000000, 60 },
+ { 1000000, 70 },
+ { 1000000, 80 },
+ { 1000000, 90 },
+ { 1000000, 100 },
+};
+
+calculated_number test4_results[] = {
+ 10, 10, 10, 10, 10, 10, 10, 10, 10
+};
+
+struct test test4 = {
+ "test4", // name
+ "test incremental values stored in the middle of second boundaries",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test4_feed, // feed
+ test4_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test5 - 32 bit overflows
+
+struct feed_values test5_feed[] = {
+ { 0, 0x00000000FFFFFFFFULL / 15 * 0 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 7 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 14 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 0 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 7 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 14 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 0 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 7 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 14 },
+ { 1000000, 0x00000000FFFFFFFFULL / 15 * 0 },
+};
+
+calculated_number test5_results[] = {
+ 0x00000000FFFFFFFFULL / 15 * 7,
+ 0x00000000FFFFFFFFULL / 15 * 7,
+ 0x00000000FFFFFFFFULL / 15,
+ 0x00000000FFFFFFFFULL / 15 * 7,
+ 0x00000000FFFFFFFFULL / 15 * 7,
+ 0x00000000FFFFFFFFULL / 15,
+ 0x00000000FFFFFFFFULL / 15 * 7,
+ 0x00000000FFFFFFFFULL / 15 * 7,
+ 0x00000000FFFFFFFFULL / 15,
+};
+
+struct test test5 = {
+ "test5", // name
+ "test 32-bit incremental values overflow",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test5_feed, // feed
+ test5_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test5b - 64 bit overflows
+
+struct feed_values test5b_feed[] = {
+ { 0, 0xFFFFFFFFFFFFFFFFULL / 15 * 0 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 7 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 14 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 0 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 7 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 14 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 0 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 7 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 14 },
+ { 1000000, 0xFFFFFFFFFFFFFFFFULL / 15 * 0 },
+};
+
+calculated_number test5b_results[] = {
+ 0xFFFFFFFFFFFFFFFFULL / 15 * 7,
+ 0xFFFFFFFFFFFFFFFFULL / 15 * 7,
+ 0xFFFFFFFFFFFFFFFFULL / 15,
+ 0xFFFFFFFFFFFFFFFFULL / 15 * 7,
+ 0xFFFFFFFFFFFFFFFFULL / 15 * 7,
+ 0xFFFFFFFFFFFFFFFFULL / 15,
+ 0xFFFFFFFFFFFFFFFFULL / 15 * 7,
+ 0xFFFFFFFFFFFFFFFFULL / 15 * 7,
+ 0xFFFFFFFFFFFFFFFFULL / 15,
+};
+
+struct test test5b = {
+ "test5b", // name
+ "test 64-bit incremental values overflow",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test5b_feed, // feed
+ test5b_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test6
+
+struct feed_values test6_feed[] = {
+ { 250000, 1000 },
+ { 250000, 2000 },
+ { 250000, 3000 },
+ { 250000, 4000 },
+ { 250000, 5000 },
+ { 250000, 6000 },
+ { 250000, 7000 },
+ { 250000, 8000 },
+ { 250000, 9000 },
+ { 250000, 10000 },
+ { 250000, 11000 },
+ { 250000, 12000 },
+ { 250000, 13000 },
+ { 250000, 14000 },
+ { 250000, 15000 },
+ { 250000, 16000 },
+};
+
+calculated_number test6_results[] = {
+ 4000, 4000, 4000, 4000
+};
+
+struct test test6 = {
+ "test6", // name
+ "test incremental values updated within the same second",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 16, // feed entries
+ 4, // result entries
+ test6_feed, // feed
+ test6_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test7
+
+struct feed_values test7_feed[] = {
+ { 500000, 1000 },
+ { 2000000, 2000 },
+ { 2000000, 3000 },
+ { 2000000, 4000 },
+ { 2000000, 5000 },
+ { 2000000, 6000 },
+ { 2000000, 7000 },
+ { 2000000, 8000 },
+ { 2000000, 9000 },
+ { 2000000, 10000 },
+};
+
+calculated_number test7_results[] = {
+ 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500
+};
+
+struct test test7 = {
+ "test7", // name
+ "test incremental values updated in long durations",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 18, // result entries
+ test7_feed, // feed
+ test7_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test8
+
+struct feed_values test8_feed[] = {
+ { 500000, 1000 },
+ { 2000000, 2000 },
+ { 2000000, 3000 },
+ { 2000000, 4000 },
+ { 2000000, 5000 },
+ { 2000000, 6000 },
+};
+
+calculated_number test8_results[] = {
+ 1250, 2000, 2250, 3000, 3250, 4000, 4250, 5000, 5250, 6000
+};
+
+struct test test8 = {
+ "test8", // name
+ "test absolute values updated in long durations",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_ABSOLUTE, // algorithm
+ 6, // feed entries
+ 10, // result entries
+ test8_feed, // feed
+ test8_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test9
+
+struct feed_values test9_feed[] = {
+ { 250000, 1000 },
+ { 250000, 2000 },
+ { 250000, 3000 },
+ { 250000, 4000 },
+ { 250000, 5000 },
+ { 250000, 6000 },
+ { 250000, 7000 },
+ { 250000, 8000 },
+ { 250000, 9000 },
+ { 250000, 10000 },
+ { 250000, 11000 },
+ { 250000, 12000 },
+ { 250000, 13000 },
+ { 250000, 14000 },
+ { 250000, 15000 },
+ { 250000, 16000 },
+};
+
+calculated_number test9_results[] = {
+ 4000, 8000, 12000, 16000
+};
+
+struct test test9 = {
+ "test9", // name
+ "test absolute values updated within the same second",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_ABSOLUTE, // algorithm
+ 16, // feed entries
+ 4, // result entries
+ test9_feed, // feed
+ test9_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test10
+
+struct feed_values test10_feed[] = {
+ { 500000, 1000 },
+ { 600000, 1000 + 600 },
+ { 200000, 1600 + 200 },
+ { 1000000, 1800 + 1000 },
+ { 200000, 2800 + 200 },
+ { 2000000, 3000 + 2000 },
+ { 600000, 5000 + 600 },
+ { 400000, 5600 + 400 },
+ { 900000, 6000 + 900 },
+ { 1000000, 6900 + 1000 },
+};
+
+calculated_number test10_results[] = {
+ 1000, 1000, 1000, 1000, 1000, 1000, 1000
+};
+
+struct test test10 = {
+ "test10", // name
+ "test incremental values updated in short and long durations",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 7, // result entries
+ test10_feed, // feed
+ test10_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test11
+
+struct feed_values test11_feed[] = {
+ { 0, 10 },
+ { 1000000, 20 },
+ { 1000000, 30 },
+ { 1000000, 40 },
+ { 1000000, 50 },
+ { 1000000, 60 },
+ { 1000000, 70 },
+ { 1000000, 80 },
+ { 1000000, 90 },
+ { 1000000, 100 },
+};
+
+collected_number test11_feed2[] = {
+ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
+};
+
+calculated_number test11_results[] = {
+ 50, 50, 50, 50, 50, 50, 50, 50, 50
+};
+
+calculated_number test11_results2[] = {
+ 50, 50, 50, 50, 50, 50, 50, 50, 50
+};
+
+struct test test11 = {
+ "test11", // name
+ "test percentage-of-incremental-row with equal values",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test11_feed, // feed
+ test11_results, // results
+ test11_feed2, // feed2
+ test11_results2 // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test12
+
+struct feed_values test12_feed[] = {
+ { 0, 10 },
+ { 1000000, 20 },
+ { 1000000, 30 },
+ { 1000000, 40 },
+ { 1000000, 50 },
+ { 1000000, 60 },
+ { 1000000, 70 },
+ { 1000000, 80 },
+ { 1000000, 90 },
+ { 1000000, 100 },
+};
+
+collected_number test12_feed2[] = {
+ 10*3, 20*3, 30*3, 40*3, 50*3, 60*3, 70*3, 80*3, 90*3, 100*3
+};
+
+calculated_number test12_results[] = {
+ 25, 25, 25, 25, 25, 25, 25, 25, 25
+};
+
+calculated_number test12_results2[] = {
+ 75, 75, 75, 75, 75, 75, 75, 75, 75
+};
+
+struct test test12 = {
+ "test12", // name
+ "test percentage-of-incremental-row with equal values",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test12_feed, // feed
+ test12_results, // results
+ test12_feed2, // feed2
+ test12_results2 // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test13
+
+struct feed_values test13_feed[] = {
+ { 500000, 1000 },
+ { 600000, 1000 + 600 },
+ { 200000, 1600 + 200 },
+ { 1000000, 1800 + 1000 },
+ { 200000, 2800 + 200 },
+ { 2000000, 3000 + 2000 },
+ { 600000, 5000 + 600 },
+ { 400000, 5600 + 400 },
+ { 900000, 6000 + 900 },
+ { 1000000, 6900 + 1000 },
+};
+
+calculated_number test13_results[] = {
+ 83.3333300, 100, 100, 100, 100, 100, 100
+};
+
+struct test test13 = {
+ "test13", // name
+ "test incremental values updated in short and long durations",
+ 1, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL, // algorithm
+ 10, // feed entries
+ 7, // result entries
+ test13_feed, // feed
+ test13_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test14
+
+struct feed_values test14_feed[] = {
+ { 0, 0x015397dc42151c41ULL },
+ { 13573000, 0x015397e612e3ff5dULL },
+ { 29969000, 0x015397f905ecdaa8ULL },
+ { 29958000, 0x0153980c2a6cb5e4ULL },
+ { 30054000, 0x0153981f4032fb83ULL },
+ { 34952000, 0x015398355efadaccULL },
+ { 25046000, 0x01539845ba4b09f8ULL },
+ { 29947000, 0x0153985948bf381dULL },
+ { 30054000, 0x0153986c5b9c27e2ULL },
+ { 29942000, 0x0153987f888982d0ULL },
+};
+
+calculated_number test14_results[] = {
+ 23.1383300, 21.8515600, 21.8804600, 21.7788000, 22.0112200, 22.4386100, 22.0906100, 21.9150800
+};
+
+struct test test14 = {
+ "test14", // name
+ "issue #981 with real data",
+ 30, // update_every
+ 8, // multiplier
+ 1000000000, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 8, // result entries
+ test14_feed, // feed
+ test14_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+struct feed_values test14b_feed[] = {
+ { 0, 0 },
+ { 13573000, 13573000 },
+ { 29969000, 13573000 + 29969000 },
+ { 29958000, 13573000 + 29969000 + 29958000 },
+ { 30054000, 13573000 + 29969000 + 29958000 + 30054000 },
+ { 34952000, 13573000 + 29969000 + 29958000 + 30054000 + 34952000 },
+ { 25046000, 13573000 + 29969000 + 29958000 + 30054000 + 34952000 + 25046000 },
+ { 29947000, 13573000 + 29969000 + 29958000 + 30054000 + 34952000 + 25046000 + 29947000 },
+ { 30054000, 13573000 + 29969000 + 29958000 + 30054000 + 34952000 + 25046000 + 29947000 + 30054000 },
+ { 29942000, 13573000 + 29969000 + 29958000 + 30054000 + 34952000 + 25046000 + 29947000 + 30054000 + 29942000 },
+};
+
+calculated_number test14b_results[] = {
+ 1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000
+};
+
+struct test test14b = {
+ "test14b", // name
+ "issue #981 with dummy data",
+ 30, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 8, // result entries
+ test14b_feed, // feed
+ test14b_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+struct feed_values test14c_feed[] = {
+ { 29000000, 29000000 },
+ { 1000000, 29000000 + 1000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 + 30000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 + 30000000 + 30000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 + 30000000 + 30000000 + 30000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 },
+ { 30000000, 29000000 + 1000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 + 30000000 },
+};
+
+calculated_number test14c_results[] = {
+ 1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000
+};
+
+struct test test14c = {
+ "test14c", // name
+ "issue #981 with dummy data, checking for late start",
+ 30, // update_every
+ 1, // multiplier
+ 1, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test14c_feed, // feed
+ test14c_results, // results
+ NULL, // feed2
+ NULL // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+// test15
+
+struct feed_values test15_feed[] = {
+ { 0, 1068066388 },
+ { 1008752, 1068822698 },
+ { 993809, 1069573072 },
+ { 995911, 1070324135 },
+ { 1014562, 1071078166 },
+ { 994684, 1071831349 },
+ { 993128, 1072235739 },
+ { 1010332, 1072958871 },
+ { 1003394, 1073707019 },
+ { 995201, 1074460255 },
+};
+
+collected_number test15_feed2[] = {
+ 178825286, 178825286, 178825286, 178825286, 178825498, 178825498, 179165652, 179202964, 179203282, 179204130
+};
+
+calculated_number test15_results[] = {
+ 5857.4080000, 5898.4540000, 5891.6590000, 5806.3160000, 5914.2640000, 3202.2630000, 5589.6560000, 5822.5260000, 5911.7520000
+};
+
+calculated_number test15_results2[] = {
+ 0.0000000, 0.0000000, 0.0024944, 1.6324779, 0.0212777, 2655.1890000, 290.5387000, 5.6733610, 6.5960220
+};
+
+struct test test15 = {
+ "test15", // name
+ "test incremental with 2 dimensions",
+ 1, // update_every
+ 8, // multiplier
+ 1024, // divisor
+ RRD_ALGORITHM_INCREMENTAL, // algorithm
+ 10, // feed entries
+ 9, // result entries
+ test15_feed, // feed
+ test15_results, // results
+ test15_feed2, // feed2
+ test15_results2 // results2
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+int run_test(struct test *test)
+{
+ fprintf(stderr, "\nRunning test '%s':\n%s\n", test->name, test->description);
+
+ default_rrd_memory_mode = RRD_MEMORY_MODE_ALLOC;
+ default_rrd_update_every = test->update_every;
+
+ char name[101];
+ snprintfz(name, 100, "unittest-%s", test->name);
+
+ // create the chart
+ RRDSET *st = rrdset_create_localhost("netdata", name, name, "netdata", NULL, "Unit Testing", "a value", "unittest", NULL, 1
+ , test->update_every, RRDSET_TYPE_LINE);
+ RRDDIM *rd = rrddim_add(st, "dim1", NULL, test->multiplier, test->divisor, test->algorithm);
+
+ RRDDIM *rd2 = NULL;
+ if(test->feed2)
+ rd2 = rrddim_add(st, "dim2", NULL, test->multiplier, test->divisor, test->algorithm);
+
+ rrdset_flag_set(st, RRDSET_FLAG_DEBUG);
+
+ // feed it with the test data
+ time_t time_now = 0, time_start = now_realtime_sec();
+ unsigned long c;
+ collected_number last = 0;
+ for(c = 0; c < test->feed_entries; c++) {
+ if(debug_flags) fprintf(stderr, "\n\n");
+
+ if(c) {
+ time_now += test->feed[c].microseconds;
+ fprintf(stderr, " > %s: feeding position %lu, after %0.3f seconds (%0.3f seconds from start), delta " CALCULATED_NUMBER_FORMAT ", rate " CALCULATED_NUMBER_FORMAT "\n",
+ test->name, c+1,
+ (float)test->feed[c].microseconds / 1000000.0,
+ (float)time_now / 1000000.0,
+ ((calculated_number)test->feed[c].value - (calculated_number)last) * (calculated_number)test->multiplier / (calculated_number)test->divisor,
+ (((calculated_number)test->feed[c].value - (calculated_number)last) * (calculated_number)test->multiplier / (calculated_number)test->divisor) / (calculated_number)test->feed[c].microseconds * (calculated_number)1000000);
+
+ // rrdset_next_usec_unfiltered(st, test->feed[c].microseconds);
+ st->usec_since_last_update = test->feed[c].microseconds;
+ }
+ else {
+ fprintf(stderr, " > %s: feeding position %lu\n", test->name, c+1);
+ }
+
+ fprintf(stderr, " >> %s with value " COLLECTED_NUMBER_FORMAT "\n", rd->name, test->feed[c].value);
+ rrddim_set(st, "dim1", test->feed[c].value);
+ last = test->feed[c].value;
+
+ if(rd2) {
+ fprintf(stderr, " >> %s with value " COLLECTED_NUMBER_FORMAT "\n", rd2->name, test->feed2[c]);
+ rrddim_set(st, "dim2", test->feed2[c]);
+ }
+
+ rrdset_done(st);
+
+ // align the first entry to second boundary
+ if(!c) {
+ fprintf(stderr, " > %s: fixing first collection time to be %llu microseconds to second boundary\n", test->name, test->feed[c].microseconds);
+ rd->last_collected_time.tv_usec = st->last_collected_time.tv_usec = st->last_updated.tv_usec = test->feed[c].microseconds;
+ // time_start = st->last_collected_time.tv_sec;
+ }
+ }
+
+ // check the result
+ int errors = 0;
+
+ if(st->counter != test->result_entries) {
+ fprintf(stderr, " %s stored %zu entries, but we were expecting %lu, ### E R R O R ###\n", test->name, st->counter, test->result_entries);
+ errors++;
+ }
+
+ unsigned long max = (st->counter < test->result_entries)?st->counter:test->result_entries;
+ for(c = 0 ; c < max ; c++) {
+ calculated_number v = unpack_storage_number(rd->values[c]);
+ calculated_number n = unpack_storage_number(pack_storage_number(test->results[c], SN_EXISTS));
+ int same = (calculated_number_round(v * 10000000.0) == calculated_number_round(n * 10000000.0))?1:0;
+ fprintf(stderr, " %s/%s: checking position %lu (at %lu secs), expecting value " CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT ", %s\n",
+ test->name, rd->name, c+1,
+ (rrdset_first_entry_t(st) + c * st->update_every) - time_start,
+ n, v, (same)?"OK":"### E R R O R ###");
+
+ if(!same) errors++;
+
+ if(rd2) {
+ v = unpack_storage_number(rd2->values[c]);
+ n = test->results2[c];
+ same = (calculated_number_round(v * 10000000.0) == calculated_number_round(n * 10000000.0))?1:0;
+ fprintf(stderr, " %s/%s: checking position %lu (at %lu secs), expecting value " CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT ", %s\n",
+ test->name, rd2->name, c+1,
+ (rrdset_first_entry_t(st) + c * st->update_every) - time_start,
+ n, v, (same)?"OK":"### E R R O R ###");
+ if(!same) errors++;
+ }
+ }
+
+ return errors;
+}
+
+static int test_variable_renames(void) {
+ fprintf(stderr, "Creating chart\n");
+ RRDSET *st = rrdset_create_localhost("chart", "ID", NULL, "family", "context", "Unit Testing", "a value", "unittest", NULL, 1, 1, RRDSET_TYPE_LINE);
+ fprintf(stderr, "Created chart with id '%s', name '%s'\n", st->id, st->name);
+
+ fprintf(stderr, "Creating dimension DIM1\n");
+ RRDDIM *rd1 = rrddim_add(st, "DIM1", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ fprintf(stderr, "Created dimension with id '%s', name '%s'\n", rd1->id, rd1->name);
+
+ fprintf(stderr, "Creating dimension DIM2\n");
+ RRDDIM *rd2 = rrddim_add(st, "DIM2", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ fprintf(stderr, "Created dimension with id '%s', name '%s'\n", rd2->id, rd2->name);
+
+ fprintf(stderr, "Renaming chart to CHARTNAME1\n");
+ rrdset_set_name(st, "CHARTNAME1");
+ fprintf(stderr, "Renamed chart with id '%s' to name '%s'\n", st->id, st->name);
+
+ fprintf(stderr, "Renaming chart to CHARTNAME2\n");
+ rrdset_set_name(st, "CHARTNAME2");
+ fprintf(stderr, "Renamed chart with id '%s' to name '%s'\n", st->id, st->name);
+
+ fprintf(stderr, "Renaming dimension DIM1 to DIM1NAME1\n");
+ rrddim_set_name(st, rd1, "DIM1NAME1");
+ fprintf(stderr, "Renamed dimension with id '%s' to name '%s'\n", rd1->id, rd1->name);
+
+ fprintf(stderr, "Renaming dimension DIM1 to DIM1NAME2\n");
+ rrddim_set_name(st, rd1, "DIM1NAME2");
+ fprintf(stderr, "Renamed dimension with id '%s' to name '%s'\n", rd1->id, rd1->name);
+
+ fprintf(stderr, "Renaming dimension DIM2 to DIM2NAME1\n");
+ rrddim_set_name(st, rd2, "DIM2NAME1");
+ fprintf(stderr, "Renamed dimension with id '%s' to name '%s'\n", rd2->id, rd2->name);
+
+ fprintf(stderr, "Renaming dimension DIM2 to DIM2NAME2\n");
+ rrddim_set_name(st, rd2, "DIM2NAME2");
+ fprintf(stderr, "Renamed dimension with id '%s' to name '%s'\n", rd2->id, rd2->name);
+
+ BUFFER *buf = buffer_create(1);
+ health_api_v1_chart_variables2json(st, buf);
+ fprintf(stderr, "%s", buffer_tostring(buf));
+ buffer_free(buf);
+ return 1;
+}
+
+int check_strdupz_path_subpath() {
+
+ struct strdupz_path_subpath_checks {
+ const char *path;
+ const char *subpath;
+ const char *result;
+ } checks[] = {
+ { "", "", "." },
+ { "/", "", "/" },
+ { "/etc/netdata", "", "/etc/netdata" },
+ { "/etc/netdata///", "", "/etc/netdata" },
+ { "/etc/netdata///", "health.d", "/etc/netdata/health.d" },
+ { "/etc/netdata///", "///health.d", "/etc/netdata/health.d" },
+ { "/etc/netdata", "///health.d", "/etc/netdata/health.d" },
+ { "", "///health.d", "./health.d" },
+ { "/", "///health.d", "/health.d" },
+
+ // terminator
+ { NULL, NULL, NULL }
+ };
+
+ size_t i;
+ for(i = 0; checks[i].result ; i++) {
+ char *s = strdupz_path_subpath(checks[i].path, checks[i].subpath);
+ fprintf(stderr, "strdupz_path_subpath(\"%s\", \"%s\") = \"%s\": ", checks[i].path, checks[i].subpath, s);
+ if(!s || strcmp(s, checks[i].result) != 0) {
+ freez(s);
+ fprintf(stderr, "FAILED\n");
+ return 1;
+ }
+ else {
+ freez(s);
+ fprintf(stderr, "OK\n");
+ }
+ }
+
+ return 0;
+}
+
+int run_all_mockup_tests(void)
+{
+ if(check_strdupz_path_subpath())
+ return 1;
+
+ if(check_number_printing())
+ return 1;
+
+ if(check_rrdcalc_comparisons())
+ return 1;
+
+ if(!test_variable_renames())
+ return 1;
+
+ if(run_test(&test1))
+ return 1;
+
+ if(run_test(&test2))
+ return 1;
+
+ if(run_test(&test3))
+ return 1;
+
+ if(run_test(&test4))
+ return 1;
+
+ if(run_test(&test5))
+ return 1;
+
+ if(run_test(&test5b))
+ return 1;
+
+ if(run_test(&test6))
+ return 1;
+
+ if(run_test(&test7))
+ return 1;
+
+ if(run_test(&test8))
+ return 1;
+
+ if(run_test(&test9))
+ return 1;
+
+ if(run_test(&test10))
+ return 1;
+
+ if(run_test(&test11))
+ return 1;
+
+ if(run_test(&test12))
+ return 1;
+
+ if(run_test(&test13))
+ return 1;
+
+ if(run_test(&test14))
+ return 1;
+
+ if(run_test(&test14b))
+ return 1;
+
+ if(run_test(&test14c))
+ return 1;
+
+ if(run_test(&test15))
+ return 1;
+
+
+
+ return 0;
+}
+
+int unit_test(long delay, long shift)
+{
+ static int repeat = 0;
+ repeat++;
+
+ char name[101];
+ snprintfz(name, 100, "unittest-%d-%ld-%ld", repeat, delay, shift);
+
+ //debug_flags = 0xffffffff;
+ default_rrd_memory_mode = RRD_MEMORY_MODE_ALLOC;
+ default_rrd_update_every = 1;
+
+ int do_abs = 1;
+ int do_inc = 1;
+ int do_abst = 0;
+ int do_absi = 0;
+
+ RRDSET *st = rrdset_create_localhost("netdata", name, name, "netdata", NULL, "Unit Testing", "a value", "unittest", NULL, 1, 1
+ , RRDSET_TYPE_LINE);
+ rrdset_flag_set(st, RRDSET_FLAG_DEBUG);
+
+ RRDDIM *rdabs = NULL;
+ RRDDIM *rdinc = NULL;
+ RRDDIM *rdabst = NULL;
+ RRDDIM *rdabsi = NULL;
+
+ if(do_abs) rdabs = rrddim_add(st, "absolute", "absolute", 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ if(do_inc) rdinc = rrddim_add(st, "incremental", "incremental", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ if(do_abst) rdabst = rrddim_add(st, "percentage-of-absolute-row", "percentage-of-absolute-row", 1, 1, RRD_ALGORITHM_PCENT_OVER_ROW_TOTAL);
+ if(do_absi) rdabsi = rrddim_add(st, "percentage-of-incremental-row", "percentage-of-incremental-row", 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+
+ long increment = 1000;
+ collected_number i = 0;
+
+ unsigned long c, dimensions = 0;
+ RRDDIM *rd;
+ for(rd = st->dimensions ; rd ; rd = rd->next) dimensions++;
+
+ for(c = 0; c < 20 ;c++) {
+ i += increment;
+
+ fprintf(stderr, "\n\nLOOP = %lu, DELAY = %ld, VALUE = " COLLECTED_NUMBER_FORMAT "\n", c, delay, i);
+ if(c) {
+ // rrdset_next_usec_unfiltered(st, delay);
+ st->usec_since_last_update = delay;
+ }
+ if(do_abs) rrddim_set(st, "absolute", i);
+ if(do_inc) rrddim_set(st, "incremental", i);
+ if(do_abst) rrddim_set(st, "percentage-of-absolute-row", i);
+ if(do_absi) rrddim_set(st, "percentage-of-incremental-row", i);
+
+ if(!c) {
+ now_realtime_timeval(&st->last_collected_time);
+ st->last_collected_time.tv_usec = shift;
+ }
+
+ // prevent it from deleting the dimensions
+ for(rd = st->dimensions ; rd ; rd = rd->next)
+ rd->last_collected_time.tv_sec = st->last_collected_time.tv_sec;
+
+ rrdset_done(st);
+ }
+
+ unsigned long oincrement = increment;
+ increment = increment * st->update_every * 1000000 / delay;
+ fprintf(stderr, "\n\nORIGINAL INCREMENT: %lu, INCREMENT %ld, DELAY %ld, SHIFT %ld\n", oincrement * 10, increment * 10, delay, shift);
+
+ int ret = 0;
+ storage_number sn;
+ calculated_number cn, v;
+ for(c = 0 ; c < st->counter ; c++) {
+ fprintf(stderr, "\nPOSITION: c = %lu, EXPECTED VALUE %lu\n", c, (oincrement + c * increment + increment * (1000000 - shift) / 1000000 )* 10);
+
+ for(rd = st->dimensions ; rd ; rd = rd->next) {
+ sn = rd->values[c];
+ cn = unpack_storage_number(sn);
+ fprintf(stderr, "\t %s " CALCULATED_NUMBER_FORMAT " (PACKED AS " STORAGE_NUMBER_FORMAT ") -> ", rd->id, cn, sn);
+
+ if(rd == rdabs) v =
+ ( oincrement
+ // + (increment * (1000000 - shift) / 1000000)
+ + (c + 1) * increment
+ );
+
+ else if(rd == rdinc) v = (c?(increment):(increment * (1000000 - shift) / 1000000));
+ else if(rd == rdabst) v = oincrement / dimensions / 10;
+ else if(rd == rdabsi) v = oincrement / dimensions / 10;
+ else v = 0;
+
+ if(v == cn) fprintf(stderr, "passed.\n");
+ else {
+ fprintf(stderr, "ERROR! (expected " CALCULATED_NUMBER_FORMAT ")\n", v);
+ ret = 1;
+ }
+ }
+ }
+
+ if(ret)
+ fprintf(stderr, "\n\nUNIT TEST(%ld, %ld) FAILED\n\n", delay, shift);
+
+ return ret;
+}
+
+#ifdef ENABLE_DBENGINE
+static inline void rrddim_set_by_pointer_fake_time(RRDDIM *rd, collected_number value, time_t now)
+{
+ rd->last_collected_time.tv_sec = now;
+ rd->last_collected_time.tv_usec = 0;
+ rd->collected_value = value;
+ rd->updated = 1;
+
+ rd->collections_counter++;
+
+ collected_number v = (value >= 0) ? value : -value;
+ if(unlikely(v > rd->collected_value_max)) rd->collected_value_max = v;
+}
+
+static RRDHOST *dbengine_rrdhost_find_or_create(char *name)
+{
+ /* We don't want to drop metrics when generating load, we prefer to block data generation itself */
+ rrdeng_drop_metrics_under_page_cache_pressure = 0;
+
+ return rrdhost_find_or_create(
+ name
+ , name
+ , name
+ , os_type
+ , netdata_configured_timezone
+ , config_get(CONFIG_SECTION_BACKEND, "host tags", "")
+ , program_name
+ , program_version
+ , default_rrd_update_every
+ , default_rrd_history_entries
+ , RRD_MEMORY_MODE_DBENGINE
+ , default_health_enabled
+ , default_rrdpush_enabled
+ , default_rrdpush_destination
+ , default_rrdpush_api_key
+ , default_rrdpush_send_charts_matching
+ , NULL
+ );
+}
+
+// costants for test_dbengine
+static const int CHARTS = 64;
+static const int DIMS = 16; // That gives us 64 * 16 = 1024 metrics
+#define REGIONS (3) // 3 regions of update_every
+// first region update_every is 2, second is 3, third is 1
+static const int REGION_UPDATE_EVERY[REGIONS] = {2, 3, 1};
+static const int REGION_POINTS[REGIONS] = {
+ 16384, // This produces 64MiB of metric data for the first region: update_every = 2
+ 16384, // This produces 64MiB of metric data for the second region: update_every = 3
+ 16384, // This produces 64MiB of metric data for the third region: update_every = 1
+};
+static const int QUERY_BATCH = 4096;
+
+static void test_dbengine_create_charts(RRDHOST *host, RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
+ int update_every)
+{
+ int i, j;
+ char name[101];
+
+ for (i = 0 ; i < CHARTS ; ++i) {
+ snprintfz(name, 100, "dbengine-chart-%d", i);
+
+ // create the chart
+ st[i] = rrdset_create(host, "netdata", name, name, "netdata", NULL, "Unit Testing", "a value", "unittest",
+ NULL, 1, update_every, RRDSET_TYPE_LINE);
+ rrdset_flag_set(st[i], RRDSET_FLAG_DEBUG);
+ rrdset_flag_set(st[i], RRDSET_FLAG_STORE_FIRST);
+ for (j = 0 ; j < DIMS ; ++j) {
+ snprintfz(name, 100, "dim-%d", j);
+
+ rd[i][j] = rrddim_add(st[i], name, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ }
+
+ // Initialize DB with the very first entries
+ for (i = 0 ; i < CHARTS ; ++i) {
+ for (j = 0 ; j < DIMS ; ++j) {
+ rd[i][j]->last_collected_time.tv_sec =
+ st[i]->last_collected_time.tv_sec = st[i]->last_updated.tv_sec = 2 * API_RELATIVE_TIME_MAX - 1;
+ rd[i][j]->last_collected_time.tv_usec =
+ st[i]->last_collected_time.tv_usec = st[i]->last_updated.tv_usec = 0;
+ }
+ }
+ for (i = 0 ; i < CHARTS ; ++i) {
+ st[i]->usec_since_last_update = USEC_PER_SEC;
+
+ for (j = 0; j < DIMS; ++j) {
+ rrddim_set_by_pointer_fake_time(rd[i][j], 69, 2 * API_RELATIVE_TIME_MAX); // set first value to 69
+ }
+ rrdset_done(st[i]);
+ }
+ // Fluh pages for subsequent real values
+ for (i = 0 ; i < CHARTS ; ++i) {
+ for (j = 0; j < DIMS; ++j) {
+ rrdeng_store_metric_flush_current_page(rd[i][j]);
+ }
+ }
+}
+
+// Feeds the database region with test data, returns last timestamp of region
+static time_t test_dbengine_create_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
+ int current_region, time_t time_start)
+{
+ time_t time_now;
+ int i, j, c, update_every;
+ collected_number next;
+
+ update_every = REGION_UPDATE_EVERY[current_region];
+ time_now = time_start + update_every;
+ // feed it with the test data
+ for (i = 0 ; i < CHARTS ; ++i) {
+ for (j = 0 ; j < DIMS ; ++j) {
+ rd[i][j]->last_collected_time.tv_sec =
+ st[i]->last_collected_time.tv_sec = st[i]->last_updated.tv_sec = time_now;
+ rd[i][j]->last_collected_time.tv_usec =
+ st[i]->last_collected_time.tv_usec = st[i]->last_updated.tv_usec = 0;
+ }
+ }
+ for (c = 0; c < REGION_POINTS[current_region] ; ++c) {
+ time_now += update_every; // time_now = start + (c + 2) * update_every
+ for (i = 0 ; i < CHARTS ; ++i) {
+ st[i]->usec_since_last_update = USEC_PER_SEC * update_every;
+
+ for (j = 0; j < DIMS; ++j) {
+ next = ((collected_number)i * DIMS) * REGION_POINTS[current_region] +
+ j * REGION_POINTS[current_region] + c;
+ rrddim_set_by_pointer_fake_time(rd[i][j], next, time_now);
+ }
+ rrdset_done(st[i]);
+ }
+ }
+ return time_now; //time_end
+}
+
+// Checks the metric data for the given region, returns number of errors
+static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
+ int current_region, time_t time_start)
+{
+ uint8_t same;
+ time_t time_now, time_retrieved;
+ int i, j, k, c, errors, update_every;
+ collected_number last;
+ calculated_number value, expected;
+ storage_number n;
+ struct rrddim_query_handle handle;
+
+ update_every = REGION_UPDATE_EVERY[current_region];
+ errors = 0;
+
+ // check the result
+ for (c = 0; c < REGION_POINTS[current_region] ; c += QUERY_BATCH) {
+ time_now = time_start + (c + 2) * update_every;
+ for (i = 0 ; i < CHARTS ; ++i) {
+ for (j = 0; j < DIMS; ++j) {
+ rd[i][j]->state->query_ops.init(rd[i][j], &handle, time_now, time_now + QUERY_BATCH * update_every);
+ for (k = 0; k < QUERY_BATCH; ++k) {
+ last = ((collected_number)i * DIMS) * REGION_POINTS[current_region] +
+ j * REGION_POINTS[current_region] + c + k;
+ expected = unpack_storage_number(pack_storage_number((calculated_number)last, SN_EXISTS));
+
+ n = rd[i][j]->state->query_ops.next_metric(&handle, &time_retrieved);
+ value = unpack_storage_number(n);
+
+ same = (calculated_number_round(value) == calculated_number_round(expected)) ? 1 : 0;
+ if(!same) {
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, expecting value "
+ CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT ", ### E R R O R ###\n",
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now + k * update_every, expected, value);
+ errors++;
+ }
+ if(time_retrieved != time_now + k * update_every) {
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, found timestamp %lu ### E R R O R ###\n",
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now + k * update_every, (unsigned long)time_retrieved);
+ errors++;
+ }
+ }
+ rd[i][j]->state->query_ops.finalize(&handle);
+ }
+ }
+ }
+ return errors;
+}
+
+// Check rrdr transformations
+static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
+ int current_region, time_t time_start, time_t time_end)
+{
+ uint8_t same;
+ time_t time_now, time_retrieved;
+ int i, j, errors, update_every;
+ long c;
+ collected_number last;
+ calculated_number value, expected;
+
+ errors = 0;
+ update_every = REGION_UPDATE_EVERY[current_region];
+ long points = (time_end - time_start) / update_every - 1;
+ for (i = 0 ; i < CHARTS ; ++i) {
+ RRDR *r = rrd2rrdr(st[i], points, time_start + update_every, time_end, RRDR_GROUPING_AVERAGE, 0, 0, NULL, NULL);
+ if (!r) {
+ fprintf(stderr, " DB-engine unittest %s: empty RRDR ### E R R O R ###\n", st[i]->name);
+ return ++errors;
+ } else {
+ assert(r->st == st[i]);
+ for (c = 0; c != rrdr_rows(r) ; ++c) {
+ RRDDIM *d;
+ time_now = time_start + (c + 2) * update_every;
+ time_retrieved = r->t[c];
+
+ // for each dimension
+ for (j = 0, d = r->st->dimensions ; d && j < r->d ; ++j, d = d->next) {
+ calculated_number *cn = &r->v[ c * r->d ];
+ value = cn[j];
+ assert(rd[i][j] == d);
+
+ last = i * DIMS * REGION_POINTS[current_region] + j * REGION_POINTS[current_region] + c;
+ expected = unpack_storage_number(pack_storage_number((calculated_number)last, SN_EXISTS));
+
+ same = (calculated_number_round(value) == calculated_number_round(expected)) ? 1 : 0;
+ if(!same) {
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, expecting value "
+ CALCULATED_NUMBER_FORMAT ", RRDR found " CALCULATED_NUMBER_FORMAT ", ### E R R O R ###\n",
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, expected, value);
+ errors++;
+ }
+ if(time_retrieved != time_now) {
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, found RRDR timestamp %lu ### E R R O R ###\n",
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, (unsigned long)time_retrieved);
+ errors++;
+ }
+ }
+ }
+ rrdr_free(r);
+ }
+ }
+ return errors;
+}
+
+int test_dbengine(void)
+{
+ int i, j, errors, update_every, current_region;
+ RRDHOST *host = NULL;
+ RRDSET *st[CHARTS];
+ RRDDIM *rd[CHARTS][DIMS];
+ time_t time_start[REGIONS], time_end[REGIONS];
+
+ error_log_limit_unlimited();
+ fprintf(stderr, "\nRunning DB-engine test\n");
+
+ default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
+
+ fprintf(stderr, "Initializing localhost with hostname 'unittest-dbengine'");
+ host = dbengine_rrdhost_find_or_create("unittest-dbengine");
+ if (NULL == host)
+ return 1;
+
+ current_region = 0; // this is the first region of data
+ update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 2 seconds
+ test_dbengine_create_charts(host, st, rd, update_every);
+
+ time_start[current_region] = 2 * API_RELATIVE_TIME_MAX;
+ time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
+
+ errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
+ if (errors)
+ goto error_out;
+
+ current_region = 1; //this is the second region of data
+ update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 3 seconds
+ // Align pages for frequency change
+ for (i = 0 ; i < CHARTS ; ++i) {
+ st[i]->update_every = update_every;
+ for (j = 0; j < DIMS; ++j) {
+ rrdeng_store_metric_flush_current_page(rd[i][j]);
+ }
+ }
+
+ time_start[current_region] = time_end[current_region - 1] + update_every;
+ if (0 != time_start[current_region] % update_every) // align to update_every
+ time_start[current_region] += update_every - time_start[current_region] % update_every;
+ time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
+
+ errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
+ if (errors)
+ goto error_out;
+
+ current_region = 2; //this is the third region of data
+ update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 1 seconds
+ // Align pages for frequency change
+ for (i = 0 ; i < CHARTS ; ++i) {
+ st[i]->update_every = update_every;
+ for (j = 0; j < DIMS; ++j) {
+ rrdeng_store_metric_flush_current_page(rd[i][j]);
+ }
+ }
+
+ time_start[current_region] = time_end[current_region - 1] + update_every;
+ if (0 != time_start[current_region] % update_every) // align to update_every
+ time_start[current_region] += update_every - time_start[current_region] % update_every;
+ time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
+
+ errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
+ if (errors)
+ goto error_out;
+
+ for (current_region = 0 ; current_region < REGIONS ; ++current_region) {
+ errors = test_dbengine_check_rrdr(st, rd, current_region, time_start[current_region], time_end[current_region]);
+ if (errors)
+ goto error_out;
+ }
+
+ current_region = 1;
+ update_every = REGION_UPDATE_EVERY[current_region]; // use the maximum update_every = 3
+ errors = 0;
+ long points = (time_end[REGIONS - 1] - time_start[0]) / update_every - 1; // cover all time regions with RRDR
+ long point_offset = (time_start[current_region] - time_start[0]) / update_every;
+ for (i = 0 ; i < CHARTS ; ++i) {
+ RRDR *r = rrd2rrdr(st[i], points, time_start[0] + update_every, time_end[REGIONS - 1], RRDR_GROUPING_AVERAGE, 0, 0, NULL, NULL);
+ if (!r) {
+ fprintf(stderr, " DB-engine unittest %s: empty RRDR ### E R R O R ###\n", st[i]->name);
+ ++errors;
+ } else {
+ long c;
+
+ assert(r->st == st[i]);
+ // test current region values only, since they must be left unchanged
+ for (c = point_offset ; c < point_offset + rrdr_rows(r) / REGIONS / 2 ; ++c) {
+ RRDDIM *d;
+ time_t time_now = time_start[current_region] + (c - point_offset + 2) * update_every;
+ time_t time_retrieved = r->t[c];
+
+ // for each dimension
+ for(j = 0, d = r->st->dimensions ; d && j < r->d ; ++j, d = d->next) {
+ calculated_number *cn = &r->v[ c * r->d ];
+ calculated_number value = cn[j];
+ assert(rd[i][j] == d);
+
+ collected_number last = i * DIMS * REGION_POINTS[current_region] + j * REGION_POINTS[current_region] + c - point_offset;
+ calculated_number expected = unpack_storage_number(pack_storage_number((calculated_number)last, SN_EXISTS));
+
+ uint8_t same = (calculated_number_round(value) == calculated_number_round(expected)) ? 1 : 0;
+ if(!same) {
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, expecting value "
+ CALCULATED_NUMBER_FORMAT ", RRDR found " CALCULATED_NUMBER_FORMAT ", ### E R R O R ###\n",
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, expected, value);
+ errors++;
+ }
+ if(time_retrieved != time_now) {
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, found RRDR timestamp %lu ### E R R O R ###\n",
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, (unsigned long)time_retrieved);
+ errors++;
+ }
+ }
+ }
+ rrdr_free(r);
+ }
+ }
+error_out:
+ rrd_wrlock();
+ rrdeng_prepare_exit(host->rrdeng_ctx);
+ rrdhost_delete_charts(host);
+ rrdeng_exit(host->rrdeng_ctx);
+ rrd_unlock();
+
+ return errors;
+}
+
+struct dbengine_chart_thread {
+ uv_thread_t thread;
+ RRDHOST *host;
+ char *chartname; /* Will be prefixed by type, e.g. "example_local1.", "example_local2." etc */
+ unsigned dset_charts; /* number of charts */
+ unsigned dset_dims; /* dimensions per chart */
+ unsigned chart_i; /* current chart offset */
+ time_t time_present; /* current virtual time of the benchmark */
+ volatile time_t time_max; /* latest timestamp of stored values */
+ unsigned history_seconds; /* how far back in the past to go */
+
+ volatile long done; /* initialize to 0, set to 1 to stop thread */
+ struct completion charts_initialized;
+ unsigned long errors, stored_metrics_nr; /* statistics */
+
+ RRDSET *st;
+ RRDDIM *rd[]; /* dset_dims elements */
+};
+
+collected_number generate_dbengine_chart_value(int chart_i, int dim_i, time_t time_current)
+{
+ collected_number value;
+
+ value = ((collected_number)time_current) * (chart_i + 1);
+ value += ((collected_number)time_current) * (dim_i + 1);
+ value %= 1024LLU;
+
+ return value;
+}
+
+static void generate_dbengine_chart(void *arg)
+{
+ struct dbengine_chart_thread *thread_info = (struct dbengine_chart_thread *)arg;
+ RRDHOST *host = thread_info->host;
+ char *chartname = thread_info->chartname;
+ const unsigned DSET_DIMS = thread_info->dset_dims;
+ unsigned history_seconds = thread_info->history_seconds;
+ time_t time_present = thread_info->time_present;
+
+ unsigned j, update_every = 1;
+ RRDSET *st;
+ RRDDIM *rd[DSET_DIMS];
+ char name[RRD_ID_LENGTH_MAX + 1];
+ time_t time_current;
+
+ // create the chart
+ snprintfz(name, RRD_ID_LENGTH_MAX, "example_local%u", thread_info->chart_i + 1);
+ thread_info->st = st = rrdset_create(host, name, chartname, chartname, "example", NULL, chartname, chartname,
+ chartname, NULL, 1, update_every, RRDSET_TYPE_LINE);
+ for (j = 0 ; j < DSET_DIMS ; ++j) {
+ snprintfz(name, RRD_ID_LENGTH_MAX, "%s%u", chartname, j + 1);
+
+ thread_info->rd[j] = rd[j] = rrddim_add(st, name, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ complete(&thread_info->charts_initialized);
+
+ // feed it with the test data
+ time_current = time_present - history_seconds;
+ for (j = 0 ; j < DSET_DIMS ; ++j) {
+ rd[j]->last_collected_time.tv_sec =
+ st->last_collected_time.tv_sec = st->last_updated.tv_sec = time_current - update_every;
+ rd[j]->last_collected_time.tv_usec =
+ st->last_collected_time.tv_usec = st->last_updated.tv_usec = 0;
+ }
+ for( ; !thread_info->done && time_current < time_present ; time_current += update_every) {
+ st->usec_since_last_update = USEC_PER_SEC * update_every;
+
+ for (j = 0; j < DSET_DIMS; ++j) {
+ collected_number value;
+
+ value = generate_dbengine_chart_value(thread_info->chart_i, j, time_current);
+ rrddim_set_by_pointer_fake_time(rd[j], value, time_current);
+ ++thread_info->stored_metrics_nr;
+ }
+ rrdset_done(st);
+ thread_info->time_max = time_current;
+ }
+ for (j = 0; j < DSET_DIMS; ++j) {
+ rrdeng_store_metric_finalize(rd[j]);
+ }
+}
+
+void generate_dbengine_dataset(unsigned history_seconds)
+{
+ const int DSET_CHARTS = 16;
+ const int DSET_DIMS = 128;
+ const uint64_t EXPECTED_COMPRESSION_RATIO = 20;
+ RRDHOST *host = NULL;
+ struct dbengine_chart_thread **thread_info;
+ int i;
+ time_t time_present;
+
+ default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
+ default_rrdeng_page_cache_mb = 128;
+ // Worst case for uncompressible data
+ default_rrdeng_disk_quota_mb = (((uint64_t)DSET_DIMS * DSET_CHARTS) * sizeof(storage_number) * history_seconds) /
+ (1024 * 1024);
+ default_rrdeng_disk_quota_mb -= default_rrdeng_disk_quota_mb * EXPECTED_COMPRESSION_RATIO / 100;
+
+ error_log_limit_unlimited();
+ fprintf(stderr, "Initializing localhost with hostname 'dbengine-dataset'");
+
+ host = dbengine_rrdhost_find_or_create("dbengine-dataset");
+ if (NULL == host)
+ return;
+
+ thread_info = mallocz(sizeof(*thread_info) * DSET_CHARTS);
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ thread_info[i] = mallocz(sizeof(*thread_info[i]) + sizeof(RRDDIM *) * DSET_DIMS);
+ }
+ fprintf(stderr, "\nRunning DB-engine workload generator\n");
+
+ time_present = now_realtime_sec();
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ thread_info[i]->host = host;
+ thread_info[i]->chartname = "random";
+ thread_info[i]->dset_charts = DSET_CHARTS;
+ thread_info[i]->chart_i = i;
+ thread_info[i]->dset_dims = DSET_DIMS;
+ thread_info[i]->history_seconds = history_seconds;
+ thread_info[i]->time_present = time_present;
+ thread_info[i]->time_max = 0;
+ thread_info[i]->done = 0;
+ init_completion(&thread_info[i]->charts_initialized);
+ assert(0 == uv_thread_create(&thread_info[i]->thread, generate_dbengine_chart, thread_info[i]));
+ wait_for_completion(&thread_info[i]->charts_initialized);
+ destroy_completion(&thread_info[i]->charts_initialized);
+ }
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ assert(0 == uv_thread_join(&thread_info[i]->thread));
+ }
+
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ freez(thread_info[i]);
+ }
+ freez(thread_info);
+ rrd_wrlock();
+ rrdhost_free(host);
+ rrd_unlock();
+}
+
+struct dbengine_query_thread {
+ uv_thread_t thread;
+ RRDHOST *host;
+ char *chartname; /* Will be prefixed by type, e.g. "example_local1.", "example_local2." etc */
+ unsigned dset_charts; /* number of charts */
+ unsigned dset_dims; /* dimensions per chart */
+ time_t time_present; /* current virtual time of the benchmark */
+ unsigned history_seconds; /* how far back in the past to go */
+ volatile long done; /* initialize to 0, set to 1 to stop thread */
+ unsigned long errors, queries_nr, queried_metrics_nr; /* statistics */
+ uint8_t delete_old_data; /* if non zero then data are deleted when disk space is exhausted */
+
+ struct dbengine_chart_thread *chart_threads[]; /* dset_charts elements */
+};
+
+static void query_dbengine_chart(void *arg)
+{
+ struct dbengine_query_thread *thread_info = (struct dbengine_query_thread *)arg;
+ const int DSET_CHARTS = thread_info->dset_charts;
+ const int DSET_DIMS = thread_info->dset_dims;
+ time_t time_after, time_before, time_min, time_approx_min, time_max, duration;
+ int i, j, update_every = 1;
+ RRDSET *st;
+ RRDDIM *rd;
+ uint8_t same;
+ time_t time_now, time_retrieved;
+ collected_number generatedv;
+ calculated_number value, expected;
+ storage_number n;
+ struct rrddim_query_handle handle;
+
+ do {
+ // pick a chart and dimension
+ i = random() % DSET_CHARTS;
+ st = thread_info->chart_threads[i]->st;
+ j = random() % DSET_DIMS;
+ rd = thread_info->chart_threads[i]->rd[j];
+
+ time_min = thread_info->time_present - thread_info->history_seconds + 1;
+ time_max = thread_info->chart_threads[i]->time_max;
+
+ if (thread_info->delete_old_data) {
+ /* A time window of twice the disk space is sufficient for compression space savings of up to 50% */
+ time_approx_min = time_max - (default_rrdeng_disk_quota_mb * 2 * 1024 * 1024) /
+ (((uint64_t) DSET_DIMS * DSET_CHARTS) * sizeof(storage_number));
+ time_min = MAX(time_min, time_approx_min);
+ }
+ if (!time_max) {
+ time_before = time_after = time_min;
+ } else {
+ time_after = time_min + random() % (MAX(time_max - time_min, 1));
+ duration = random() % 3600;
+ time_before = MIN(time_after + duration, time_max); /* up to 1 hour queries */
+ }
+
+ rd->state->query_ops.init(rd, &handle, time_after, time_before);
+ ++thread_info->queries_nr;
+ for (time_now = time_after ; time_now <= time_before ; time_now += update_every) {
+ generatedv = generate_dbengine_chart_value(i, j, time_now);
+ expected = unpack_storage_number(pack_storage_number((calculated_number) generatedv, SN_EXISTS));
+
+ if (unlikely(rd->state->query_ops.is_finished(&handle))) {
+ if (!thread_info->delete_old_data) { /* data validation only when we don't delete */
+ fprintf(stderr, " DB-engine stresstest %s/%s: at %lu secs, expecting value "
+ CALCULATED_NUMBER_FORMAT ", found data gap, ### E R R O R ###\n",
+ st->name, rd->name, (unsigned long) time_now, expected);
+ ++thread_info->errors;
+ }
+ break;
+ }
+ n = rd->state->query_ops.next_metric(&handle, &time_retrieved);
+ if (SN_EMPTY_SLOT == n) {
+ if (!thread_info->delete_old_data) { /* data validation only when we don't delete */
+ fprintf(stderr, " DB-engine stresstest %s/%s: at %lu secs, expecting value "
+ CALCULATED_NUMBER_FORMAT ", found data gap, ### E R R O R ###\n",
+ st->name, rd->name, (unsigned long) time_now, expected);
+ ++thread_info->errors;
+ }
+ break;
+ }
+ ++thread_info->queried_metrics_nr;
+ value = unpack_storage_number(n);
+
+ same = (calculated_number_round(value) == calculated_number_round(expected)) ? 1 : 0;
+ if (!same) {
+ if (!thread_info->delete_old_data) { /* data validation only when we don't delete */
+ fprintf(stderr, " DB-engine stresstest %s/%s: at %lu secs, expecting value "
+ CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT
+ ", ### E R R O R ###\n",
+ st->name, rd->name, (unsigned long) time_now, expected, value);
+ ++thread_info->errors;
+ }
+ }
+ if (time_retrieved != time_now) {
+ if (!thread_info->delete_old_data) { /* data validation only when we don't delete */
+ fprintf(stderr,
+ " DB-engine stresstest %s/%s: at %lu secs, found timestamp %lu ### E R R O R ###\n",
+ st->name, rd->name, (unsigned long) time_now, (unsigned long) time_retrieved);
+ ++thread_info->errors;
+ }
+ }
+ }
+ rd->state->query_ops.finalize(&handle);
+ } while(!thread_info->done);
+}
+
+void dbengine_stress_test(unsigned TEST_DURATION_SEC, unsigned DSET_CHARTS, unsigned QUERY_THREADS,
+ unsigned RAMP_UP_SECONDS, unsigned PAGE_CACHE_MB, unsigned DISK_SPACE_MB)
+{
+ const unsigned DSET_DIMS = 128;
+ const uint64_t EXPECTED_COMPRESSION_RATIO = 20;
+ const unsigned HISTORY_SECONDS = 3600 * 24 * 365 * 50; /* 50 year of history */
+ RRDHOST *host = NULL;
+ struct dbengine_chart_thread **chart_threads;
+ struct dbengine_query_thread **query_threads;
+ unsigned i, j;
+ time_t time_start, test_duration;
+
+ error_log_limit_unlimited();
+
+ if (!TEST_DURATION_SEC)
+ TEST_DURATION_SEC = 10;
+ if (!DSET_CHARTS)
+ DSET_CHARTS = 1;
+ if (!QUERY_THREADS)
+ QUERY_THREADS = 1;
+ if (PAGE_CACHE_MB < RRDENG_MIN_PAGE_CACHE_SIZE_MB)
+ PAGE_CACHE_MB = RRDENG_MIN_PAGE_CACHE_SIZE_MB;
+
+ default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
+ default_rrdeng_page_cache_mb = PAGE_CACHE_MB;
+ if (DISK_SPACE_MB) {
+ fprintf(stderr, "By setting disk space limit data are allowed to be deleted. "
+ "Data validation is turned off for this run.\n");
+ default_rrdeng_disk_quota_mb = DISK_SPACE_MB;
+ } else {
+ // Worst case for uncompressible data
+ default_rrdeng_disk_quota_mb =
+ (((uint64_t) DSET_DIMS * DSET_CHARTS) * sizeof(storage_number) * HISTORY_SECONDS) / (1024 * 1024);
+ default_rrdeng_disk_quota_mb -= default_rrdeng_disk_quota_mb * EXPECTED_COMPRESSION_RATIO / 100;
+ }
+
+ fprintf(stderr, "Initializing localhost with hostname 'dbengine-stress-test'\n");
+
+ host = dbengine_rrdhost_find_or_create("dbengine-stress-test");
+ if (NULL == host)
+ return;
+
+ chart_threads = mallocz(sizeof(*chart_threads) * DSET_CHARTS);
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ chart_threads[i] = mallocz(sizeof(*chart_threads[i]) + sizeof(RRDDIM *) * DSET_DIMS);
+ }
+ query_threads = mallocz(sizeof(*query_threads) * QUERY_THREADS);
+ for (i = 0 ; i < QUERY_THREADS ; ++i) {
+ query_threads[i] = mallocz(sizeof(*query_threads[i]) + sizeof(struct dbengine_chart_thread *) * DSET_CHARTS);
+ }
+ fprintf(stderr, "\nRunning DB-engine stress test, %u seconds writers ramp-up time,\n"
+ "%u seconds of concurrent readers and writers, %u writer threads, %u reader threads,\n"
+ "%u MiB of page cache.\n",
+ RAMP_UP_SECONDS, TEST_DURATION_SEC, DSET_CHARTS, QUERY_THREADS, PAGE_CACHE_MB);
+
+ time_start = now_realtime_sec() + HISTORY_SECONDS; /* move history to the future */
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ chart_threads[i]->host = host;
+ chart_threads[i]->chartname = "random";
+ chart_threads[i]->dset_charts = DSET_CHARTS;
+ chart_threads[i]->chart_i = i;
+ chart_threads[i]->dset_dims = DSET_DIMS;
+ chart_threads[i]->history_seconds = HISTORY_SECONDS;
+ chart_threads[i]->time_present = time_start;
+ chart_threads[i]->time_max = 0;
+ chart_threads[i]->done = 0;
+ chart_threads[i]->errors = chart_threads[i]->stored_metrics_nr = 0;
+ init_completion(&chart_threads[i]->charts_initialized);
+ assert(0 == uv_thread_create(&chart_threads[i]->thread, generate_dbengine_chart, chart_threads[i]));
+ }
+ /* barrier so that subsequent queries can access valid chart data */
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ wait_for_completion(&chart_threads[i]->charts_initialized);
+ destroy_completion(&chart_threads[i]->charts_initialized);
+ }
+ sleep(RAMP_UP_SECONDS);
+ /* at this point data have already began being written to the database */
+ for (i = 0 ; i < QUERY_THREADS ; ++i) {
+ query_threads[i]->host = host;
+ query_threads[i]->chartname = "random";
+ query_threads[i]->dset_charts = DSET_CHARTS;
+ query_threads[i]->dset_dims = DSET_DIMS;
+ query_threads[i]->history_seconds = HISTORY_SECONDS;
+ query_threads[i]->time_present = time_start;
+ query_threads[i]->done = 0;
+ query_threads[i]->errors = query_threads[i]->queries_nr = query_threads[i]->queried_metrics_nr = 0;
+ for (j = 0 ; j < DSET_CHARTS ; ++j) {
+ query_threads[i]->chart_threads[j] = chart_threads[j];
+ }
+ query_threads[i]->delete_old_data = DISK_SPACE_MB ? 1 : 0;
+ assert(0 == uv_thread_create(&query_threads[i]->thread, query_dbengine_chart, query_threads[i]));
+ }
+ sleep(TEST_DURATION_SEC);
+ /* stop workload */
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ chart_threads[i]->done = 1;
+ }
+ for (i = 0 ; i < QUERY_THREADS ; ++i) {
+ query_threads[i]->done = 1;
+ }
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ assert(0 == uv_thread_join(&chart_threads[i]->thread));
+ }
+ for (i = 0 ; i < QUERY_THREADS ; ++i) {
+ assert(0 == uv_thread_join(&query_threads[i]->thread));
+ }
+ test_duration = now_realtime_sec() - (time_start - HISTORY_SECONDS);
+ if (!test_duration)
+ test_duration = 1;
+ fprintf(stderr, "\nDB-engine stress test finished in %ld seconds.\n", test_duration);
+ unsigned long stored_metrics_nr = 0;
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ stored_metrics_nr += chart_threads[i]->stored_metrics_nr;
+ }
+ unsigned long queries_nr = 0, queried_metrics_nr = 0;
+ for (i = 0 ; i < QUERY_THREADS ; ++i) {
+ queries_nr += query_threads[i]->queries_nr;
+ queried_metrics_nr += query_threads[i]->queried_metrics_nr;
+ }
+ fprintf(stderr, "%u metrics were stored (dataset size of %lu MiB) in %u charts by 1 writer thread per chart.\n",
+ DSET_CHARTS * DSET_DIMS, stored_metrics_nr * sizeof(storage_number) / (1024 * 1024), DSET_CHARTS);
+ fprintf(stderr, "Metrics were being generated per 1 emulated second and time was accelerated.\n");
+ fprintf(stderr, "%lu metric data points were queried by %u reader threads.\n", queried_metrics_nr, QUERY_THREADS);
+ fprintf(stderr, "Query starting time is randomly chosen from the beginning of the time-series up to the time of\n"
+ "the latest data point, and ending time from 1 second up to 1 hour after the starting time.\n");
+ fprintf(stderr, "Performance is %lu written data points/sec and %lu read data points/sec.\n",
+ stored_metrics_nr / test_duration, queried_metrics_nr / test_duration);
+
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
+ freez(chart_threads[i]);
+ }
+ freez(chart_threads);
+ for (i = 0 ; i < QUERY_THREADS ; ++i) {
+ freez(query_threads[i]);
+ }
+ freez(query_threads);
+ rrd_wrlock();
+ rrdeng_prepare_exit(host->rrdeng_ctx);
+ rrdhost_delete_charts(host);
+ rrdeng_exit(host->rrdeng_ctx);
+ rrd_unlock();
+}
+
+#endif
diff --git a/daemon/unit_test.h b/daemon/unit_test.h
new file mode 100644
index 0000000..79d415b
--- /dev/null
+++ b/daemon/unit_test.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_UNIT_TEST_H
+#define NETDATA_UNIT_TEST_H 1
+
+extern int unit_test_storage(void);
+extern int unit_test(long delay, long shift);
+extern int run_all_mockup_tests(void);
+extern int unit_test_str2ld(void);
+extern int unit_test_buffer(void);
+#ifdef ENABLE_DBENGINE
+extern int test_dbengine(void);
+extern void generate_dbengine_dataset(unsigned history_seconds);
+extern void dbengine_stress_test(unsigned TEST_DURATION_SEC, unsigned DSET_CHARTS, unsigned QUERY_THREADS,
+ unsigned RAMP_UP_SECONDS, unsigned PAGE_CACHE_MB, unsigned DISK_SPACE_MB);
+
+#endif
+
+#endif /* NETDATA_UNIT_TEST_H */