- `/proc/meminfo` (memory information)
- `/proc/vmstat` (system performance)
- `/proc/net/rpc/nfsd` (NFS server statistics for both v3 and v4 NFS)
- - `tc` classes (QoS classes)
+ - `tc` classes (QoS classes - [with FireQOS class names](http://firehol.org/tutorial/fireqos-new-user/))
- It supports **plugins** for collecting information from other sources!
- `charts.d.plugin` provides a simple way to script data collection in BASH. It includes example plugins that collect values from:
- - `nut` (UPS load, frequency, voltage, etc)
+ - `nut` (UPS load, frequency, voltage, etc, for multiple UPSes)
- `sensors` (temperature, voltage, current, power, humidity, fans rotation sensors)
- - `cpufreq` (current CPU clock frequency)
+ - `cpufreq` (current CPU clock frequency, for all CPUs)
- `postfix` (e-mail queue size)
- `squid` (web proxy statistics)
- `mysql` (mysql global statistics)
+ - `opensips` (opensips statistics)
Of course, you can write your own using BASH scripting.
It serves its own static files and dynamic files for rendering the site.
It does not support authentication or SSL - limit its access using your firewall.
- It does not allow ` .. ` or ` / ` in the files requested (so it can only serve files stored in the `web/` directory).
+ It does not allow ` .. ` or ` / ` in the files requested (so it can only serve files stored in the web directory `/usr/share/netdata/web`).
# How it works
MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
dist_charts_SCRIPTS = \
+ README.md \
airsearches.chart.sh \
cpu_apps.chart.sh \
cpufreq.chart.sh \
# This statement does not require any privilege.
# It requires only the ability to connect to the server.
-mysql_cmd_opts=""
+mysql_cmd_opts=
mysql_update_every=5
mysql_get_stats() {
}
mysql_check() {
+ require_cmd mysql || exit 1
+ require_cmd egrep || exit 1
+ require_cmd sed || exit 1
+
# check once if the url works
local x="$(mysql_get_stats | grep "^Connections[[:space:]]")"
if [ ! $? -eq 0 -o -z "$x" ]
nut_update_every=2
nut_check() {
- if [ -z "${nut_ups}" ]
+ local x all
+
+ require_cmd upsc || return 1
+ require_cmd awk || return 1
+
+ all="$nut_ups"
+ [ -z "$all" ] && all="$(upsc -l)"
+
+ nut_ups=
+ for x in $all
+ do
+ upsc "$x" >/dev/null
+ [ $? -eq 0 ] && nut_ups="$nut_ups $x" && continue
+ echo >&2 "nut: ERROR: Cannot get information for NUT UPS '$x'."
+ done
+
+ if [ -z "$nut_ups" ]
then
echo >&2 "nut: Please set nut_ups='ups_name' in $confd/nut.conf"
return 1
fi
- upsc "${nut_ups}" >/dev/null
- if [ ! $? -eq 0 ]
- then
- echo >&2 "nut: failed to fetch info for ups '$nut_ups'. Please set nut_ups='ups_name' in $confd/nut.conf"
- return 1
- fi
-
return 0
}
nut_create() {
# create the charts
- cat <<EOF
-CHART nut.charge '' "UPS Charge" "percentage" nut '' area 21001 $nut_update_every
+ local x
+
+ for x in $nut_ups
+ do
+ cat <<EOF
+CHART nut_$x.charge '' "UPS Charge" "percentage" $x nut area 21001 $nut_update_every
DIMENSION battery_charge charge absolute 1 100
-CHART nut.battery_voltage '' "UPS Battery Voltage" "Volts" nut '' line 21002 $nut_update_every
+CHART nut_$x.battery_voltage '' "UPS Battery Voltage" "Volts" $x nut line 21002 $nut_update_every
DIMENSION battery_voltage voltage absolute 1 100
DIMENSION battery_voltage_high high absolute 1 100
DIMENSION battery_voltage_low low absolute 1 100
DIMENSION battery_voltage_nominal nominal absolute 1 100
-CHART nut.input_voltage '' "UPS Input Voltage" "Volts" nut '' line 21003 $nut_update_every
+CHART nut_$x.input_voltage '' "UPS Input Voltage" "Volts" $x nut line 21003 $nut_update_every
DIMENSION input_voltage voltage absolute 1 100
DIMENSION input_voltage_fault fault absolute 1 100
DIMENSION input_voltage_nominal nominal absolute 1 100
-CHART nut.input_current '' "UPS Input Current" "Ampere" nut '' line 21004 $nut_update_every
+CHART nut_$x.input_current '' "UPS Input Current" "Ampere" $x nut line 21004 $nut_update_every
DIMENSION input_current_nominal nominal absolute 1 100
-CHART nut.input_frequency '' "UPS Input Frequency" "Hz" nut '' line 21005 $nut_update_every
+CHART nut_$x.input_frequency '' "UPS Input Frequency" "Hz" $x nut line 21005 $nut_update_every
DIMENSION input_frequency frequency absolute 1 100
DIMENSION input_frequency_nominal nominal absolute 1 100
-CHART nut.output_voltage '' "UPS Output Voltage" "Volts" nut '' line 21006 $nut_update_every
+CHART nut_$x.output_voltage '' "UPS Output Voltage" "Volts" $x nut line 21006 $nut_update_every
DIMENSION output_voltage voltage absolute 1 100
-CHART nut.load '' "UPS Load" "percentage" nut '' area 21000 $nut_update_every
+CHART nut_$x.load '' "UPS Load" "percentage" $x nut area 21000 $nut_update_every
DIMENSION load load absolute 1 100
-CHART nut.temp '' "UPS Temperature" "temperature" nut '' line 21007 $nut_update_every
+CHART nut_$x.temp '' "UPS Temperature" "temperature" $x nut line 21007 $nut_update_every
DIMENSION temp temp absolute 1 100
EOF
-
+ done
+
return 0
}
# for each dimension
# remember: KEEP IT SIMPLE AND SHORT
- upsc "${nut_ups}" | awk "
+ local x
+
+ for x in $nut_ups
+ do
+ upsc "$x" | awk "
BEGIN {
battery_charge = 0;
battery_voltage = 0;
/^ups.load: .*/ { load = \$2 * 100 };
/^ups.temperature: .*/ { temp = \$2 * 100 };
END {
- print \"BEGIN nut.charge $1\";
+ print \"BEGIN nut_$x.charge $1\";
print \"SET battery_charge = \" battery_charge;
print \"END\"
- print \"BEGIN nut.battery_voltage $1\";
+ print \"BEGIN nut_$x.battery_voltage $1\";
print \"SET battery_voltage = \" battery_voltage;
print \"SET battery_voltage_high = \" battery_voltage_high;
print \"SET battery_voltage_low = \" battery_voltage_low;
print \"SET battery_voltage_nominal = \" battery_voltage_nominal;
print \"END\"
- print \"BEGIN nut.input_voltage $1\";
+ print \"BEGIN nut_$x.input_voltage $1\";
print \"SET input_voltage = \" input_voltage;
print \"SET input_voltage_fault = \" input_voltage_fault;
print \"SET input_voltage_nominal = \" input_voltage_nominal;
print \"END\"
- print \"BEGIN nut.input_current $1\";
+ print \"BEGIN nut_$x.input_current $1\";
print \"SET input_current_nominal = \" input_current_nominal;
print \"END\"
- print \"BEGIN nut.input_frequency $1\";
+ print \"BEGIN nut_$x.input_frequency $1\";
print \"SET input_frequency = \" input_frequency;
print \"SET input_frequency_nominal = \" input_frequency_nominal;
print \"END\"
- print \"BEGIN nut.output_voltage $1\";
+ print \"BEGIN nut_$x.output_voltage $1\";
print \"SET output_voltage = \" output_voltage;
print \"END\"
- print \"BEGIN nut.load $1\";
+ print \"BEGIN nut_$x.load $1\";
print \"SET load = \" load;
print \"END\"
- print \"BEGIN nut.temp $1\";
+ print \"BEGIN nut_$x.temp $1\";
print \"SET temp = \" temp;
print \"END\"
}
"
+ done
+
return 0
}
MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
dist_config_DATA = \
+ charts.d.conf \
apps_groups.conf \
$(NULL)
--- /dev/null
+nut=yes
+squid=yes
+postfix=yes
+sensors=yes
+cpufreq=yes
+mysql=yes
+#example=yes
+#load_average=yes
MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
dist_plugins_DATA = \
- README \
- loopsleepms.sh.inc \
+ README.md \
$(NULL)
dist_plugins_SCRIPTS = \
charts.d.dryrun-helper.sh \
charts.d.plugin \
tc-qos-helper.sh \
+ loopsleepms.sh.inc \
$(NULL)
+++ /dev/null
-
--------------------------------------------------------------------------------
-NETDATA CUSTOM PLUGINS
-
-custom chart plugins can be any program that can print a few values on its
-standard output.
-
-There are 5 lines netdata parses. lines starting with:
-
-CHART - create a new chart
-DIMENSION - add a dimension to the chart just created
-
-BEGIN - initialize data collection for a chart
-SET - set the value of a dimension for the initialized chart
-END - complete data collection for the initialized chart
-
-a single script can produce any number of charts with any number of dimensions
-each. charts can also be added any time (not just the beginning).
-
-The script should accept just one parameter: the number of seconds it is
-expected to update the values for its charts. The value passed by netdata
-to the script is controlled via the configuration file.
-
-The script can overwrite the update frequency. For example, the server may
-request per second updates, but the script may overwrite this to one update
-every 5 seconds.
-
-
--------------------------------------------------------------------------------
-CREATE NEW CHART
-
- > CHART type.id name title units family[=id] category[=type] charttype[=line]
- priority[=1000] update_every[=user default]
-
- where:
- - type.id
- uniquely identifies the chart
- this is what will be needed to add values to the chart
-
- - name
- is the name that will be presented to the used for this chart
-
- - title
- the text above the chart
-
- - units
- the label of the vertical axis of the chart
-
- - family
- is used to group charts together.
- (for example all eth0 charts should say: eth0)
-
- - category
- is not used currently.
- it will be used to set the section under which the chart will appear
- (for example mem.ram should appear in the 'system' section)
- the special word 'none' means: do not show this chart on the home page
-
- - charttype
- line, area or stacked
-
- - priority
- is the relative priority of the charts as rendered on the web page
- lower numbers make the charts appear before the ones with higher numbers
-
- - update_every
- overwrite the update frequency set by the server
-
-
--------------------------------------------------------------------------------
-ADD DIMENSIONS
-
- > DIMENSION id name[=id] algorithm[=absolute] multiplier[=1] divisor[=1]
- hidden[=not set]
-
- where:
-
- - id
- the id of this dimension (it is a text value, not numeric)
- this will be needed to add values to the dimension
-
- - name
- the name of the dimension as it will appear at the legend of the chart
-
- - algorithms
- one of:
-
- * absolute
- the value is to drawn as-is
-
- * incremental
- the value increases over time.
- the difference from the last value is presented in the chart.
- the server stretches the difference to produce 1 second increments.
- it does this at the microsecond level.
-
- * 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 differential total of
- each dimension.
-
- - multiplier
- a value to multiply the collected value.
-
- - divisor
- a value to divide the collected value.
-
- - hidden
- giving the keyword 'hidden' will make this dimension hidden.
- it will take part in the calculation but will not be presented in the chart
-
-
--------------------------------------------------------------------------------
-DATA COLLECTION
-
- data collection is defined as a series of BEGIN -> SET -> END lines.
-
- > BEGIN type.id
-
- - type.id
- is the unique identification of the chart (created with CHART)
-
- > SET id = value
-
- - id
- is the unique identification of the dimension (of the chart just began)
-
- - value
- is the collected value
-
- more SET lines may appear to update all the dimensions of the chart.
- all of them in one BEGIN -> END block.
- All SET lines within a single BEGIN -> END block have to refer to the
- same chart. If more charts need to be updated, each chart should have
- its own BEGIN -> SET -> END block.
-
- > END
-
- END does not take any parameters.
- it commits the collected values to the chart.
-
-
-
--------------------------------------------------------------------------------
-A NOTE ABOUT VALUES
-
- NetData will collect any signed value in the 64bit range:
-
- -9.223.372.036.854.775.808 to +9.223.372.036.854.775.807
-
- However, to lower its memory requirements, it stores all values in the
- signed 32bit range, divided by 10, that is:
-
- -214.748.364 to +214.748.364
-
- This division by 10, is used to give a decimal point in the charts.
- In memory, every number is 4 bytes (32bits).
-
- algorithm, multiplier and divisor help maintain all the detail required.
-
- The algorithm is applied in the wider 64bit numbers. Once the calculation
- is complete the value is multiplied by the multiplier, by 10, and then
- divided by the divider (all of these at the 64bit level).
- The 64bit result is then stored in a 32 bit signed int.
-
- So, at the chart level:
-
- - the finest number is 0.1
- - the smallest -214.748.364,8
- - the highest 214.748.364,7
-
- You should choose a multiplier and divisor to stay within these limits.
-
- Example:
-
- eth0 bytes-in is a 64bit number that is incremented every time.
- let say it is 1.000.000.000.000.000.000
- and one second later it is 1.000.000.000.999.000.000
-
- the dimension is 'incremental' with multiplier 8 (bytes to bits)
- and divider 1024 (bits to kilobits).
-
- netdata will store this value:
-
- value = (1.000.000.000.000.000.000 - 1.000.000.000.999.000.000)
- * 10
- * 8
- / 1024
- = 78.046.875
-
- or 7.804.687,5 kilobits/second (a little less than 8 gigabits/second)
-
-
--- /dev/null
+netdata plugins
+===============
+
+Any program that can print a few values to its standard output can become
+a netdata plugin.
+
+There are 5 lines netdata parses. lines starting with:
+
+- `CHART` - create a new chart
+- `DIMENSION` - add a dimension to the chart just created
+- `BEGIN` - initialize data collection for a chart
+- `SET` - set the value of a dimension for the initialized chart
+- `END` - complete data collection for the initialized chart
+
+a single program can produce any number of charts with any number of dimensions
+each.
+
+charts can also be added any time (not just the beginning).
+
+### command line parameters
+
+The plugin should accept just **one** parameter: **the number of seconds it is
+expected to update the values for its charts**. The value passed by netdata
+to the plugin is controlled via its configuration file (so there is not need
+for the plugin to handle this configuration option).
+
+The script can overwrite the update frequency. For example, the server may
+request per second updates, but the script may overwrite this to one update
+every 5 seconds.
+
+### environment variables
+
+There are a few environment variables that are set by `netdata` and are
+available for the plugin to use.
+
+variable|description
+:------:|:----------
+`NETDATA_CONFIG_DIR`|The directory where all netdata related configuration should be stored. If the plugin requires custom configuration, this is the place to save it.
+`NETDATA_PLUGINS_DIR`|The directory where all netdata plugins are stored.
+`NETDATA_WEB_DIR`|The directory where the web files of netdata are saved.
+`NETDATA_CACHE_DIR`|The directory where the cache files of netdata are stored. Use this directory if the plugin requires a place to store data. A new directory should be created for the plugin for this purpose, inside this directory.
+`NETDATA_LOG_DIR`|The directory where the log files are stored. By default the `stderr` output of the plugin will be saved in the `error.log` file of netdata.
+`NETDATA_HOST_PREFIX`|This is used in environments where system directories like `/sys` and `/proc` have to be accessed at a different path.
+`NETDATA_DEBUG_FLAGS`|This is number (probably in hex starting with `0x`), that enables certain netdata debugging features.
+`NETDATA_UPDATE_EVERY`|The minimum number of seconds between chart refreshes. This is like the **internal clock** of netdata (it is user configurable, defaulting to `1`). There is no meaning for a plugin to update its values more frequently than this number of seconds.
+
+
+# the output of the plugin
+
+The plugin should output instructions for netdata to its output (`stdout`).
+
+## CHART
+
+`CHART` defines a new chart.
+
+the template is:
+
+ > CHART type.id name title units [family [category [charttype [priority [update_every]]]]]
+
+ where:
+ - `type.id`
+
+ uniquely identifies the chart,
+ this is what will be needed to add values to the chart
+
+ - `name`
+
+ is the name that will be presented to the used for this chart
+
+ - `title`
+
+ the text above the chart
+
+ - `units`
+
+ the label of the vertical axis of the chart,
+ all dimensions added to a chart should have the same units
+ of measurement
+
+ - `family`
+
+ is used to group charts together
+ (for example all eth0 charts should say: eth0),
+ if empty or missing, the `id` part of `type.id` will be used
+
+ - `category`
+
+ the section under which the chart will appear
+ (for example mem.ram should appear in the 'system' section),
+ the special word 'none' means: do not show this chart on the home page,
+ if empty or missing, the `type` part of `type.id` will be used
+
+ - `charttype`
+
+ one of `line`, `area` or `stacked`,
+ if empty or missing, the `line` will be used
+
+ - `priority`
+
+ is the relative priority of the charts as rendered on the web page,
+ lower numbers make the charts appear before the ones with higher numbers,
+ if empty or missing, `1000` will be used
+
+ - `update_every`
+
+ overwrite the update frequency set by the server,
+ if empty or missing, the user configured value will be used
+
+
+## DIMENSION
+
+`DIMENSION` defines a new dimension for the chart
+
+the template is:
+
+ > DIMENSION id [name [algorithm [multiplier [divisor [hidden]]]]]
+
+ where:
+
+ - `id`
+
+ the `id` of this dimension (it is a text value, not numeric),
+ this will be needed later to add values to the dimension
+
+ - `name`
+
+ the name of the dimension as it will appear at the legend of the chart,
+ if empty or missing the `id` will be used
+
+ - `algorithm`
+
+ one of:
+
+ * `absolute`
+
+ the value is to drawn as-is (interpolated to second boundary),
+ if `algorithm` is empty, invalid or missing, `absolute` is used
+
+ * `incremental`
+
+ the value increases over time,
+ the difference from the last value is presented in the chart,
+ the server interpolates the value and calculates 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
+
+ - `multiplier`
+
+ an integer value to multiply the collected value,
+ if empty or missing, `1` is used
+
+ - `divisor`
+
+ an integer value to divide the collected value,
+ if empty or missing, `1` is used
+
+ - `hidden`
+
+ giving the keyword `hidden` will make this dimension hidden,
+ it will take part in the calculations but will not be presented in the chart
+
+
+## data collection
+
+data collection is defined as a series of `BEGIN` -> `SET` -> `END` lines
+
+ > BEGIN type.id [microseconds]
+
+ - `type.id`
+
+ is the unique identification of the chart (as given in `CHART`)
+
+ - `microseconds`
+
+ is the number of microseconds since the last update of the chart,
+ it is optional.
+
+ Under heavy system load, the system may have some latency transfering
+ data from the plugins to netdata via the pipe. This number improves
+ accuracy significantly, since the plugin is able to calculate the
+ duration between its iterations better than netdata.
+
+ The first time the plugin is started, no microseconds should be given
+ to netdata.
+
+ > SET id = value
+
+ - `id`
+
+ is the unique identification of the dimension (of the chart just began)
+
+ - `value`
+
+ is the collected value
+
+ > END
+
+END does not take any parameters, it commits the collected values to the chart.
+
+More `SET` lines may appear to update all the dimensions of the chart.
+All of them in one `BEGIN` -> `END` block.
+
+All `SET` lines within a single `BEGIN` -> `END` block have to refer to the
+same chart.
+
+If more charts need to be updated, each chart should have its own
+`BEGIN` -> `SET` -> `END` block.
+
+If, for any reason, a plugin has issued a `BEGIN` but wants to cancel it,
+it can issue a `FLUSH`. The `FLUSH` command will instruct netdata to ignore
+the last `BEGIN` command.
+
+If a plugin does not behave properly (outputs invalid lines, or does not
+follow these guidelines), will be disabled by netdata.
+
+
+# collected values
+
+netdata will collect any **signed** value in the 64bit range:
+`-9.223.372.036.854.775.808` to `+9.223.372.036.854.775.807`
+
+Internally, all calculations are made using 128 bit double precision and are
+stored in 30 bits as floating point.
+
+If a value is not collected, leave it empty, like this:
+
+`SET id = `
+
+or do not output the line at all.
then
shift
update_every=$x
+ [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency
continue
fi
}
}
-static void pluginsd_split_words(char *str, char **words, int max_words) {
+static int pluginsd_split_words(char *str, char **words, int max_words) {
char *s = str, quote = 0;
- int i = 0;
+ int i = 0, j;
// skip all white space
while(unlikely(pluginsd_space(*s))) s++;
}
// terminate the words
- while(likely(i < max_words)) words[i++] = NULL;
+ j = i;
+ while(likely(j < max_words)) words[j++] = NULL;
+
+ return i;
}
// debug(D_PLUGINSD, "PLUGINSD: %s: %s", cd->filename, line);
- pluginsd_split_words(line, words, MAX_WORDS);
+ int w = pluginsd_split_words(line, words, MAX_WORDS);
s = words[0];
- if(unlikely(!s || !*s)) {
+ if(unlikely(!s || !*s || !w)) {
// debug(D_PLUGINSD, "PLUGINSD: empty line");
continue;
}
char *value = words[2];
if(unlikely(!dimension || !*dimension)) {
- error("PLUGINSD: '%s' is requesting a SET on chart '%s', like this: 'SET %s = %s'. Disabling it.", cd->fullfilename, st->id, dimension?dimension:"<nothing>", value?value:"<nothing>");
+ error("PLUGINSD: '%s' is requesting a SET on chart '%s', without a dimension. Disabling it.", cd->fullfilename, st->id);
cd->enabled = 0;
killpid(cd->pid, SIGTERM);
break;
}
- if(unlikely(!value || !*value)) value = "0";
+ if(unlikely(!value || !*value)) value = NULL;
if(unlikely(!st)) {
- error("PLUGINSD: '%s' is requesting a SET on dimension %s with value %s, without a BEGIN. Disabling it.", cd->fullfilename, dimension, value);
+ error("PLUGINSD: '%s' is requesting a SET on dimension %s with value %s, without a BEGIN. Disabling it.", cd->fullfilename, dimension, value?value:"<nothing>");
cd->enabled = 0;
killpid(cd->pid, SIGTERM);
break;
}
- if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: '%s' is setting dimension %s/%s to %s", cd->fullfilename, st->id, dimension, value);
- rrddim_set(st, dimension, atoll(value));
+ if(unlikely(st->debug)) debug(D_PLUGINSD, "PLUGINSD: '%s' is setting dimension %s/%s to %s", cd->fullfilename, st->id, dimension, value?value:"<nothing>");
+
+ if(value) rrddim_set(st, dimension, atoll(value));
count++;
}
error("Cannot set pthread cancel state to ENABLE.");
char *dir_name = config_get("plugins", "plugins directory", PLUGINS_DIR);
- int automatic_run = config_get_boolean("plugins", "enable running new plugins", 0);
+ int automatic_run = config_get_boolean("plugins", "enable running new plugins", 1);
int scan_frequency = config_get_number("plugins", "check for new plugins every", 60);
DIR *dir = NULL;
struct dirent *file = NULL;
struct plugind *cd;
// enable the apps plugin by default
- config_get_boolean("plugins", "apps", 1);
+ // config_get_boolean("plugins", "apps", 1);
if(scan_frequency < 1) scan_frequency = 1;
// spawn a new thread for it
if(unlikely(pthread_create(&cd->thread, NULL, pluginsd_worker_thread, cd) != 0)) {
- error("CHARTS.D: failed to create new thread for chart.d %s.", cd->filename);
+ error("PLUGINSD: failed to create new thread for plugin '%s'.", cd->filename);
cd->obsolete = 1;
}
else if(unlikely(pthread_detach(cd->thread) != 0))
- error("CHARTS.D: Cannot request detach of newly created thread for chart.d %s.", cd->filename);
+ error("PLUGINSD: Cannot request detach of newly created thread for plugin '%s'.", cd->filename);
}
closedir(dir);
robots.txt \
theme.css \
$(NULL)
+
+install-data-hook:
+ if [ `id -u` == 0 ]; then \
+ getent group netdata > /dev/null || groupadd -r netdata; \
+ getent passwd netdata > /dev/null || useradd -r -g netdata -c netdata -s /sbin/nologin -d / netdata; \
+ chown -c -R netdata $(DESTDIR)$(cachedir) $(DESTDIR)$(webdir); \
+ fi