summaryrefslogtreecommitdiffstats
path: root/collectors/proc.plugin
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 /collectors/proc.plugin
parentInitial commit. (diff)
downloadnetdata-upstream.tar.xz
netdata-upstream.zip
Adding upstream version 1.29.3.upstream/1.29.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collectors/proc.plugin')
-rw-r--r--collectors/proc.plugin/Makefile.am8
-rw-r--r--collectors/proc.plugin/README.md571
-rw-r--r--collectors/proc.plugin/ipc.c576
-rw-r--r--collectors/proc.plugin/plugin_proc.c230
-rw-r--r--collectors/proc.plugin/plugin_proc.h83
-rw-r--r--collectors/proc.plugin/proc_diskstats.c1700
-rw-r--r--collectors/proc.plugin/proc_interrupts.c248
-rw-r--r--collectors/proc.plugin/proc_loadavg.c131
-rw-r--r--collectors/proc.plugin/proc_mdstat.c647
-rw-r--r--collectors/proc.plugin/proc_meminfo.c528
-rw-r--r--collectors/proc.plugin/proc_net_dev.c1127
-rw-r--r--collectors/proc.plugin/proc_net_ip_vs_stats.c133
-rw-r--r--collectors/proc.plugin/proc_net_netstat.c869
-rw-r--r--collectors/proc.plugin/proc_net_rpc_nfs.c454
-rw-r--r--collectors/proc.plugin/proc_net_rpc_nfsd.c1006
-rw-r--r--collectors/proc.plugin/proc_net_sctp_snmp.c373
-rw-r--r--collectors/proc.plugin/proc_net_snmp.c1130
-rw-r--r--collectors/proc.plugin/proc_net_snmp6.c1293
-rw-r--r--collectors/proc.plugin/proc_net_sockstat.c538
-rw-r--r--collectors/proc.plugin/proc_net_sockstat6.c283
-rw-r--r--collectors/proc.plugin/proc_net_softnet_stat.c151
-rw-r--r--collectors/proc.plugin/proc_net_stat_conntrack.c351
-rw-r--r--collectors/proc.plugin/proc_net_stat_synproxy.c189
-rw-r--r--collectors/proc.plugin/proc_net_wireless.c453
-rw-r--r--collectors/proc.plugin/proc_pagetypeinfo.c333
-rw-r--r--collectors/proc.plugin/proc_pressure.c178
-rw-r--r--collectors/proc.plugin/proc_pressure.h31
-rw-r--r--collectors/proc.plugin/proc_self_mountinfo.c403
-rw-r--r--collectors/proc.plugin/proc_self_mountinfo.h57
-rw-r--r--collectors/proc.plugin/proc_softirqs.c242
-rw-r--r--collectors/proc.plugin/proc_spl_kstat_zfs.c196
-rw-r--r--collectors/proc.plugin/proc_stat.c1069
-rw-r--r--collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c49
-rw-r--r--collectors/proc.plugin/proc_uptime.c46
-rw-r--r--collectors/proc.plugin/proc_vmstat.c263
-rw-r--r--collectors/proc.plugin/sys_block_zram.c292
-rw-r--r--collectors/proc.plugin/sys_class_infiniband.c704
-rw-r--r--collectors/proc.plugin/sys_class_power_supply.c410
-rw-r--r--collectors/proc.plugin/sys_devices_system_edac_mc.c208
-rw-r--r--collectors/proc.plugin/sys_devices_system_node.c164
-rw-r--r--collectors/proc.plugin/sys_fs_btrfs.c734
-rw-r--r--collectors/proc.plugin/sys_kernel_mm_ksm.c201
-rw-r--r--collectors/proc.plugin/zfs_common.c772
-rw-r--r--collectors/proc.plugin/zfs_common.h115
44 files changed, 19539 insertions, 0 deletions
diff --git a/collectors/proc.plugin/Makefile.am b/collectors/proc.plugin/Makefile.am
new file mode 100644
index 0000000..161784b
--- /dev/null
+++ b/collectors/proc.plugin/Makefile.am
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+dist_noinst_DATA = \
+ README.md \
+ $(NULL)
diff --git a/collectors/proc.plugin/README.md b/collectors/proc.plugin/README.md
new file mode 100644
index 0000000..085afb4
--- /dev/null
+++ b/collectors/proc.plugin/README.md
@@ -0,0 +1,571 @@
+<!--
+title: "proc.plugin"
+custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/proc.plugin/README.md
+-->
+
+# proc.plugin
+
+- `/proc/net/dev` (all network interfaces for all their values)
+- `/proc/diskstats` (all disks for all their values)
+- `/proc/mdstat` (status of RAID arrays)
+- `/proc/net/snmp` (total IPv4, TCP and UDP usage)
+- `/proc/net/snmp6` (total IPv6 usage)
+- `/proc/net/netstat` (more IPv4 usage)
+- `/proc/net/wireless` (wireless extension)
+- `/proc/net/stat/nf_conntrack` (connection tracking performance)
+- `/proc/net/stat/synproxy` (synproxy performance)
+- `/proc/net/ip_vs/stats` (IPVS connection statistics)
+- `/proc/stat` (CPU utilization and attributes)
+- `/proc/meminfo` (memory information)
+- `/proc/vmstat` (system performance)
+- `/proc/net/rpc/nfsd` (NFS server statistics for both v3 and v4 NFS servers)
+- `/sys/fs/cgroup` (Control Groups - Linux Containers)
+- `/proc/self/mountinfo` (mount points)
+- `/proc/interrupts` (total and per core hardware interrupts)
+- `/proc/softirqs` (total and per core software interrupts)
+- `/proc/loadavg` (system load and total processes running)
+- `/proc/pressure/{cpu,memory,io}` (pressure stall information)
+- `/proc/sys/kernel/random/entropy_avail` (random numbers pool availability - used in cryptography)
+- `/sys/class/power_supply` (power supply properties)
+- `/sys/class/infiniband` (infiniband interconnect)
+- `ipc` (IPC semaphores and message queues)
+- `ksm` Kernel Same-Page Merging performance (several files under `/sys/kernel/mm/ksm`).
+- `netdata` (internal Netdata resources utilization)
+
+- - -
+
+## Monitoring Disks
+
+> Live demo of disk monitoring at: **[http://london.netdata.rocks](https://registry.my-netdata.io/#menu_disk)**
+
+Performance monitoring for Linux disks is quite complicated. The main reason is the plethora of disk technologies available. There are many different hardware disk technologies, but there are even more **virtual disk** technologies that can provide additional storage features.
+
+Hopefully, the Linux kernel provides many metrics that can provide deep insights of what our disks our doing. The kernel measures all these metrics on all layers of storage: **virtual disks**, **physical disks** and **partitions of disks**.
+
+### Monitored disk metrics
+
+- **I/O bandwidth/s (kb/s)**
+ The amount of data transferred from and to the disk.
+- **I/O operations/s**
+ The number of I/O operations completed.
+- **Queued I/O operations**
+ The number of currently queued I/O operations. For traditional disks that execute commands one after another, one of them is being run by the disk and the rest are just waiting in a queue.
+- **Backlog size (time in ms)**
+ The expected duration of the currently queued I/O operations.
+- **Utilization (time percentage)**
+ The percentage of time the disk was busy with something. This is a very interesting metric, since for most disks, that execute commands sequentially, **this is the key indication of congestion**. A sequential disk that is 100% of the available time busy, has no time to do anything more, so even if the bandwidth or the number of operations executed by the disk is low, its capacity has been reached.
+ Of course, for newer disk technologies (like fusion cards) that are capable to execute multiple commands in parallel, this metric is just meaningless.
+- **Average I/O operation time (ms)**
+ The average time for I/O requests issued to the device to be served. This includes the time spent by the requests in queue and the time spent servicing them.
+- **Average I/O operation size (kb)**
+ The average amount of data of the completed I/O operations.
+- **Average Service Time (ms)**
+ The average service time for completed I/O operations. This metric is calculated using the total busy time of the disk and the number of completed operations. If the disk is able to execute multiple parallel operations the reporting average service time will be misleading.
+- **Merged I/O operations/s**
+ The Linux kernel is capable of merging I/O operations. So, if two requests to read data from the disk are adjacent, the Linux kernel may merge them to one before giving them to disk. This metric measures the number of operations that have been merged by the Linux kernel.
+- **Total I/O time**
+ The sum of the duration of all completed I/O operations. This number can exceed the interval if the disk is able to execute multiple I/O operations in parallel.
+- **Space usage**
+ For mounted disks, Netdata will provide a chart for their space, with 3 dimensions:
+ 1. free
+ 2. used
+ 3. reserved for root
+- **inode usage**
+ For mounted disks, Netdata will provide a chart for their inodes (number of file and directories), with 3 dimensions:
+ 1. free
+ 2. used
+ 3. reserved for root
+
+### disk names
+
+Netdata will automatically set the name of disks on the dashboard, from the mount point they are mounted, of course only when they are mounted. Changes in mount points are not currently detected (you will have to restart Netdata to change the name of the disk). To use disk IDs provided by `/dev/disk/by-id`, the `name disks by id` option should be enabled. The `preferred disk ids` simple pattern allows choosing disk IDs to be used in the first place.
+
+### performance metrics
+
+By default, Netdata will enable monitoring metrics 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). Set `yes` for a chart instead of `auto` to enable it 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.
+
+Netdata categorizes all block devices in 3 categories:
+
+1. physical disks (i.e. block devices that do not have child devices and are not partitions)
+2. virtual disks (i.e. block devices that have child devices - like RAID devices)
+3. disk partitions (i.e. block devices that are part of a physical disk)
+
+Performance metrics are enabled by default for all disk devices, except partitions and not-mounted virtual disks. Of course, you can enable/disable monitoring any block device by editing the Netdata configuration file.
+
+### Netdata configuration
+
+You can get the running Netdata configuration using this:
+
+```sh
+cd /etc/netdata
+curl "http://localhost:19999/netdata.conf" >netdata.conf.new
+mv netdata.conf.new netdata.conf
+```
+
+Then edit `netdata.conf` and find the following section. This is the basic plugin configuration.
+
+```
+[plugin:proc:/proc/diskstats]
+ # enable new disks detected at runtime = yes
+ # performance metrics for physical disks = auto
+ # performance metrics for virtual disks = auto
+ # performance metrics for partitions = no
+ # bandwidth for all disks = auto
+ # operations for all disks = auto
+ # merged operations for all disks = auto
+ # i/o time for all disks = auto
+ # queued operations for all disks = auto
+ # utilization percentage for all disks = auto
+ # backlog for all disks = auto
+ # bcache for all disks = auto
+ # bcache priority stats update every = 0
+ # remove charts of removed disks = yes
+ # path to get block device = /sys/block/%s
+ # path to get block device bcache = /sys/block/%s/bcache
+ # path to get virtual block device = /sys/devices/virtual/block/%s
+ # path to get block device infos = /sys/dev/block/%lu:%lu/%s
+ # path to device mapper = /dev/mapper
+ # path to /dev/disk/by-label = /dev/disk/by-label
+ # path to /dev/disk/by-id = /dev/disk/by-id
+ # path to /dev/vx/dsk = /dev/vx/dsk
+ # name disks by id = no
+ # preferred disk ids = *
+ # exclude disks = loop* ram*
+ # filename to monitor = /proc/diskstats
+ # performance metrics for disks with major 8 = yes
+```
+
+For each virtual disk, physical disk and partition you will have a section like this:
+
+```
+[plugin:proc:/proc/diskstats:sda]
+ # enable = yes
+ # enable performance metrics = auto
+ # bandwidth = auto
+ # operations = auto
+ # merged operations = auto
+ # i/o time = auto
+ # queued operations = auto
+ # utilization percentage = auto
+ # backlog = auto
+```
+
+For all configuration options:
+
+- `auto` = enable monitoring if the collected values are not zero
+- `yes` = enable monitoring
+- `no` = disable monitoring
+
+Of course, to set options, you will have to uncomment them. The comments show the internal defaults.
+
+After saving `/etc/netdata/netdata.conf`, restart your Netdata to apply them.
+
+#### Disabling performance metrics for individual device and to multiple devices by device type
+
+You can pretty easy disable performance metrics for individual device, for ex.:
+
+```
+[plugin:proc:/proc/diskstats:sda]
+ enable performance metrics = no
+```
+
+But sometimes you need disable performance metrics for all devices with the same type, to do it you need to figure out device type from `/proc/diskstats` for ex.:
+
+```
+ 7 0 loop0 1651 0 3452 168 0 0 0 0 0 8 168
+ 7 1 loop1 4955 0 11924 880 0 0 0 0 0 64 880
+ 7 2 loop2 36 0 216 4 0 0 0 0 0 4 4
+ 7 6 loop6 0 0 0 0 0 0 0 0 0 0 0
+ 7 7 loop7 0 0 0 0 0 0 0 0 0 0 0
+ 251 2 zram2 27487 0 219896 188 79953 0 639624 1640 0 1828 1828
+ 251 3 zram3 27348 0 218784 152 79952 0 639616 1960 0 2060 2104
+```
+
+All zram devices starts with `251` number and all loop devices starts with `7`.
+So, to disable performance metrics for all loop devices you could add `performance metrics for disks with major 7 = no` to `[plugin:proc:/proc/diskstats]` section.
+
+```
+[plugin:proc:/proc/diskstats]
+ performance metrics for disks with major 7 = no
+```
+
+## Monitoring RAID arrays
+
+### Monitored RAID array metrics
+
+1. **Health** Number of failed disks in every array (aggregate chart).
+
+2. **Disks stats**
+
+- total (number of devices array ideally would have)
+- inuse (number of devices currently are in use)
+
+3. **Mismatch count**
+
+- unsynchronized blocks
+
+4. **Current status**
+
+- resync in percent
+- recovery in percent
+- reshape in percent
+- check in percent
+
+5. **Operation status** (if resync/recovery/reshape/check is active)
+
+- finish in minutes
+- speed in megabytes/s
+
+6. **Nonredundant array availability**
+
+#### configuration
+
+```
+[plugin:proc:/proc/mdstat]
+ # faulty devices = yes
+ # nonredundant arrays availability = yes
+ # mismatch count = auto
+ # disk stats = yes
+ # operation status = yes
+ # make charts obsolete = yes
+ # filename to monitor = /proc/mdstat
+ # mismatch_cnt filename to monitor = /sys/block/%s/md/mismatch_cnt
+```
+
+## Monitoring CPUs
+
+The `/proc/stat` module monitors CPU utilization, interrupts, context switches, processes started/running, thermal
+throttling, frequency, and idle states. It gathers this information from multiple files.
+
+If your system has more than 50 processors (`physical processors * cores per processor * threads per core`), the Agent
+automatically disables CPU thermal throttling, frequency, and idle state charts. To override this default, see the next
+section on configuration.
+
+### Configuration
+
+The settings for monitoring CPUs is in the `[plugin:proc:/proc/stat]` of your `netdata.conf` file.
+
+The `keep per core files open` option lets you reduce the number of file operations on multiple files.
+
+If your system has more than 50 processors and you would like to see the CPU thermal throttling, frequency, and idle
+state charts that are automatically disabled, you can set the following boolean options in the
+`[plugin:proc:/proc/stat]` section.
+
+```conf
+ keep per core files open = yes
+ keep cpuidle files open = yes
+ core_throttle_count = yes
+ package_throttle_count = yes
+ cpu frequency = yes
+ cpu idle states = yes
+```
+
+### CPU frequency
+
+The module shows the current CPU frequency as set by the `cpufreq` kernel
+module.
+
+**Requirement:**
+You need to have `CONFIG_CPU_FREQ` and (optionally) `CONFIG_CPU_FREQ_STAT`
+enabled in your kernel.
+
+`cpufreq` interface provides two different ways of getting the information through `/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq` and `/sys/devices/system/cpu/cpu*/cpufreq/stats/time_in_state` files. The latter is more accurate so it is preferred in the module. `scaling_cur_freq` represents only the current CPU frequency, and doesn't account for any state changes which happen between updates. The module switches back and forth between these two methods if governor is changed.
+
+It produces one chart with multiple lines (one line per core).
+
+#### configuration
+
+`scaling_cur_freq filename to monitor` and `time_in_state filename to monitor` in the `[plugin:proc:/proc/stat]` configuration section
+
+### CPU idle states
+
+The module monitors the usage of CPU idle states.
+
+**Requirement:**
+Your kernel needs to have `CONFIG_CPU_IDLE` enabled.
+
+It produces one stacked chart per CPU, showing the percentage of time spent in
+each state.
+
+#### configuration
+
+`schedstat filename to monitor`, `cpuidle name filename to monitor`, and `cpuidle time filename to monitor` in the `[plugin:proc:/proc/stat]` configuration section
+
+## Monitoring Network Interfaces
+
+### Monitored network interface metrics
+
+- **Physical Network Interfaces Aggregated Bandwidth (kilobits/s)**
+ The amount of data received and sent through all physical interfaces in the system. This is the source of data for the Net Inbound and Net Outbound dials in the System Overview section.
+
+- **Bandwidth (kilobits/s)**
+ The amount of data received and sent through the interface.
+
+- **Packets (packets/s)**
+ The number of packets received, packets sent, and multicast packets transmitted through the interface.
+
+- **Interface Errors (errors/s)**
+ The number of errors for the inbound and outbound traffic on the interface.
+
+- **Interface Drops (drops/s)**
+ The number of packets dropped for the inbound and outbound traffic on the interface.
+
+- **Interface FIFO Buffer Errors (errors/s)**
+ The number of FIFO buffer errors encountered while receiving and transmitting data through the interface.
+
+- **Compressed Packets (packets/s)**
+ The number of compressed packets transmitted or received by the device driver.
+
+- **Network Interface Events (events/s)**
+ The number of packet framing errors, collisions detected on the interface, and carrier losses detected by the device driver.
+
+By default Netdata will enable monitoring metrics 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).
+
+### Monitoring wireless network interfaces
+
+The settings for monitoring wireless is in the `[plugin:proc:/proc/net/wireless]` section of your `netdata.conf` file.
+
+```conf
+ status for all interfaces = yes
+ quality for all interfaces = yes
+ discarded packets for all interfaces = yes
+ missed beacon for all interface = yes
+```
+
+You can set the following values for each configuration option:
+
+- `auto` = enable monitoring if the collected values are not zero
+- `yes` = enable monitoring
+- `no` = disable monitoring
+
+#### Monitored wireless interface metrics
+
+- **Status**
+ The current state of the interface. This is a device-dependent option.
+
+- **Link**
+ Overall quality of the link.
+
+- **Level**
+ Received signal strength (RSSI), which indicates how strong the received signal is.
+
+- **Noise**
+ Background noise level.
+
+- **Discarded packets**
+ Discarded packets for: Number of packets received with a different NWID or ESSID (`nwid`), unable to decrypt (`crypt`), hardware was not able to properly re-assemble the link layer fragments (`frag`), packets failed to deliver (`retry`), and packets lost in relation with specific wireless operations (`misc`).
+
+- **Missed beacon**
+ Number of periodic beacons from the cell or the access point the interface has missed.
+
+#### Wireless configuration
+
+#### alarms
+
+There are several alarms defined in `health.d/net.conf`.
+
+The tricky ones are `inbound packets dropped` and `inbound packets dropped ratio`. They have quite a strict policy so that they warn users about possible issues. These alarms can be annoying for some network configurations. It is especially true for some bonding configurations if an interface is a child or a bonding interface itself. If it is expected to have a certain number of drops on an interface for a certain network configuration, a separate alarm with different triggering thresholds can be created or the existing one can be disabled for this specific interface. It can be done with the help of the [families](/health/REFERENCE.md#alarm-line-families) line in the alarm configuration. For example, if you want to disable the `inbound packets dropped` alarm for `eth0`, set `families: !eth0 *` in the alarm definition for `template: inbound_packets_dropped`.
+
+#### configuration
+
+Module configuration:
+
+```
+[plugin:proc:/proc/net/dev]
+ # filename to monitor = /proc/net/dev
+ # path to get virtual interfaces = /sys/devices/virtual/net/%s
+ # path to get net device speed = /sys/class/net/%s/speed
+ # enable new interfaces detected at runtime = auto
+ # bandwidth for all interfaces = auto
+ # packets for all interfaces = auto
+ # errors for all interfaces = auto
+ # drops for all interfaces = auto
+ # fifo for all interfaces = auto
+ # compressed packets for all interfaces = auto
+ # frames, collisions, carrier counters for all interfaces = auto
+ # disable by default interfaces matching = lo fireqos* *-ifb
+ # refresh interface speed every seconds = 10
+```
+
+Per interface configuration:
+
+```
+[plugin:proc:/proc/net/dev:enp0s3]
+ # enabled = yes
+ # virtual = no
+ # bandwidth = auto
+ # packets = auto
+ # errors = auto
+ # drops = auto
+ # fifo = auto
+ # compressed = auto
+ # events = auto
+```
+
+## Linux Anti-DDoS
+
+![image6](https://cloud.githubusercontent.com/assets/2662304/14253733/53550b16-fa95-11e5-8d9d-4ed171df4735.gif)
+
+---
+
+SYNPROXY is a TCP SYN packets proxy. It can be used to protect any TCP server (like a web server) from SYN floods and similar DDos attacks.
+
+SYNPROXY is a netfilter module, in the Linux kernel (since version 3.12). It is optimized to handle millions of packets per second utilizing all CPUs available without any concurrency locking between the connections.
+
+The net effect of this, is that the real servers will not notice any change during the attack. The valid TCP connections will pass through and served, while the attack will be stopped at the firewall.
+
+Netdata does not enable SYNPROXY. It just uses the SYNPROXY metrics exposed by your kernel, so you will first need to configure it. The hard way is to run iptables SYNPROXY commands directly on the console. An easier way is to use [FireHOL](https://firehol.org/), which, is a firewall manager for iptables. FireHOL can configure SYNPROXY using the following setup guides:
+
+- **[Working with SYNPROXY](https://github.com/firehol/firehol/wiki/Working-with-SYNPROXY)**
+- **[Working with SYNPROXY and traps](https://github.com/firehol/firehol/wiki/Working-with-SYNPROXY-and-traps)**
+
+### Real-time monitoring of Linux Anti-DDoS
+
+Netdata is able to monitor in real-time (per second updates) the operation of the Linux Anti-DDoS protection.
+
+It visualizes 4 charts:
+
+1. TCP SYN Packets received on ports operated by SYNPROXY
+2. TCP Cookies (valid, invalid, retransmits)
+3. Connections Reopened
+4. Entries used
+
+Example image:
+
+![ddos](https://cloud.githubusercontent.com/assets/2662304/14398891/6016e3fc-fdf0-11e5-942b-55de6a52cb66.gif)
+
+See Linux Anti-DDoS in action at: **[Netdata demo site (with SYNPROXY enabled)](https://registry.my-netdata.io/#menu_netfilter_submenu_synproxy)**
+
+## Linux power supply
+
+This module monitors various metrics reported by power supply drivers
+on Linux. This allows tracking and alerting on things like remaining
+battery capacity.
+
+Depending on the underlying driver, it may provide the following charts
+and metrics:
+
+1. Capacity: The power supply capacity expressed as a percentage.
+
+ - capacity_now
+
+2. Charge: The charge for the power supply, expressed as amphours.
+
+ - charge_full_design
+ - charge_full
+ - charge_now
+ - charge_empty
+ - charge_empty_design
+
+3. Energy: The energy for the power supply, expressed as watthours.
+
+ - energy_full_design
+ - energy_full
+ - energy_now
+ - energy_empty
+ - energy_empty_design
+
+4. Voltage: The voltage for the power supply, expressed as volts.
+
+ - voltage_max_design
+ - voltage_max
+ - voltage_now
+ - voltage_min
+ - voltage_min_design
+
+#### configuration
+
+```
+[plugin:proc:/sys/class/power_supply]
+ # battery capacity = yes
+ # battery charge = no
+ # battery energy = no
+ # power supply voltage = no
+ # keep files open = auto
+ # directory to monitor = /sys/class/power_supply
+```
+
+#### notes
+
+- Most drivers provide at least the first chart. Battery powered ACPI
+ compliant systems (like most laptops) provide all but the third, but do
+ not provide all of the metrics for each chart.
+
+- Current, energy, and voltages are reported with a *very* high precision
+ by the power_supply framework. Usually, this is far higher than the
+ actual hardware supports reporting, so expect to see changes in these
+ charts jump instead of scaling smoothly.
+
+- If `max` or `full` attribute is defined by the driver, but not a
+ corresponding `min` or `empty` attribute, then Netdata will still provide
+ the corresponding `min` or `empty`, which will then always read as zero.
+ This way, alerts which match on these will still work.
+
+## Infiniband interconnect
+
+This module monitors every active Infiniband port. It provides generic counters statistics, and per-vendor hw-counters (if vendor is supported).
+
+### Monitored interface metrics
+
+Each port will have its counters metrics monitored, grouped in the following charts:
+
+- **Bandwidth usage**
+ Sent/Received data, in KB/s
+
+- **Packets Statistics**
+ Sent/Received packets, in 3 categories: total, unicast and multicast.
+
+- **Errors Statistics**
+ Many errors counters are provided, presenting statistics for:
+ - Packets: malformated, sent/received discarded by card/switch, missing ressource
+ - Link: downed, recovered, integrity error, minor error
+ - Other events: Tick Wait to send, buffer overrun
+
+If your vendor is supported, you'll also get HW-Counters statistics. These being vendor specific, please refer to their documentation.
+
+- Mellanox: [see statistics documentation](https://community.mellanox.com/s/article/understanding-mlx5-linux-counters-and-status-parameters)
+
+### configuration
+
+Default configuration will monitor only enabled infiniband ports, and refresh newly activated or created ports every 30 seconds
+
+```
+[plugin:proc:/sys/class/infiniband]
+ # dirname to monitor = /sys/class/infiniband
+ # bandwidth counters = yes
+ # packets counters = yes
+ # errors counters = yes
+ # hardware packets counters = auto
+ # hardware errors counters = auto
+ # monitor only ports being active = auto
+ # disable by default interfaces matching =
+ # refresh ports state every seconds = 30
+```
+
+
+## IPC
+
+### Monitored IPC metrics
+
+- **number of messages in message queues**
+- **amount of memory used by message queues**
+- **number of semaphores**
+- **number of semaphore arrays**
+- **number of shared memory segments**
+- **amount of memory used by shared memory segments**
+
+As far as the message queue charts are dynamic, sane limits are applied for the number of dimensions per chart (the limit is configurable).
+
+### configuration
+
+```
+[plugin:proc:ipc]
+ # message queues = yes
+ # semaphore totals = yes
+ # shared memory totals = yes
+ # msg filename to monitor = /proc/sysvipc/msg
+ # shm filename to monitor = /proc/sysvipc/shm
+ # max dimensions in memory allowed = 50
+```
+
+[![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%2Fcollectors%2Fproc.plugin%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/collectors/proc.plugin/ipc.c b/collectors/proc.plugin/ipc.c
new file mode 100644
index 0000000..048fe74
--- /dev/null
+++ b/collectors/proc.plugin/ipc.c
@@ -0,0 +1,576 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#include <sys/sem.h>
+#include <sys/msg.h>
+#include <sys/shm.h>
+
+
+#ifndef SEMVMX
+#define SEMVMX 32767 /* <= 32767 semaphore maximum value */
+#endif
+
+/* Some versions of libc only define IPC_INFO when __USE_GNU is defined. */
+#ifndef IPC_INFO
+#define IPC_INFO 3
+#endif
+
+struct ipc_limits {
+ uint64_t shmmni; /* max number of segments */
+ uint64_t shmmax; /* max segment size */
+ uint64_t shmall; /* max total shared memory */
+ uint64_t shmmin; /* min segment size */
+
+ int semmni; /* max number of arrays */
+ int semmsl; /* max semaphores per array */
+ int semmns; /* max semaphores system wide */
+ int semopm; /* max ops per semop call */
+ unsigned int semvmx; /* semaphore max value (constant) */
+
+ int msgmni; /* max queues system wide */
+ size_t msgmax; /* max size of message */
+ int msgmnb; /* default max size of queue */
+};
+
+struct ipc_status {
+ int semusz; /* current number of arrays */
+ int semaem; /* current semaphores system wide */
+};
+
+/*
+ * The last arg of semctl is a union semun, but where is it defined? X/OPEN
+ * tells us to define it ourselves, but until recently Linux include files
+ * would also define it.
+ */
+#ifndef HAVE_UNION_SEMUN
+/* according to X/OPEN we have to define it ourselves */
+union semun {
+ int val;
+ struct semid_ds *buf;
+ unsigned short int *array;
+ struct seminfo *__buf;
+};
+#endif
+
+struct message_queue {
+ unsigned long long id;
+ int found;
+
+ RRDDIM *rd_messages;
+ RRDDIM *rd_bytes;
+ unsigned long long messages;
+ unsigned long long bytes;
+
+ struct message_queue * next;
+};
+
+struct shm_stats {
+ unsigned long long segments;
+ unsigned long long bytes;
+};
+
+static inline int ipc_sem_get_limits(struct ipc_limits *lim) {
+ static procfile *ff = NULL;
+ static int error_shown = 0;
+ static char filename[FILENAME_MAX + 1] = "";
+
+ if(unlikely(!filename[0]))
+ snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/sem", netdata_configured_host_prefix);
+
+ if(unlikely(!ff)) {
+ ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) {
+ if(unlikely(!error_shown)) {
+ error("IPC: Cannot open file '%s'.", filename);
+ error_shown = 1;
+ }
+ goto ipc;
+ }
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) {
+ if(unlikely(!error_shown)) {
+ error("IPC: Cannot read file '%s'.", filename);
+ error_shown = 1;
+ }
+ goto ipc;
+ }
+
+ if(procfile_lines(ff) >= 1 && procfile_linewords(ff, 0) >= 4) {
+ lim->semvmx = SEMVMX;
+ lim->semmsl = str2i(procfile_lineword(ff, 0, 0));
+ lim->semmns = str2i(procfile_lineword(ff, 0, 1));
+ lim->semopm = str2i(procfile_lineword(ff, 0, 2));
+ lim->semmni = str2i(procfile_lineword(ff, 0, 3));
+ return 0;
+ }
+ else {
+ if(unlikely(!error_shown)) {
+ error("IPC: Invalid content in file '%s'.", filename);
+ error_shown = 1;
+ }
+ goto ipc;
+ }
+
+ipc:
+ // cannot do it from the file
+ // query IPC
+ {
+ struct seminfo seminfo = {.semmni = 0};
+ union semun arg = {.array = (ushort *) &seminfo};
+
+ if(unlikely(semctl(0, 0, IPC_INFO, arg) < 0)) {
+ error("IPC: Failed to read '%s' and request IPC_INFO with semctl().", filename);
+ goto error;
+ }
+
+ lim->semvmx = SEMVMX;
+ lim->semmni = seminfo.semmni;
+ lim->semmsl = seminfo.semmsl;
+ lim->semmns = seminfo.semmns;
+ lim->semopm = seminfo.semopm;
+ return 0;
+ }
+
+error:
+ lim->semvmx = 0;
+ lim->semmni = 0;
+ lim->semmsl = 0;
+ lim->semmns = 0;
+ lim->semopm = 0;
+ return -1;
+}
+
+/*
+printf ("------ Semaphore Limits --------\n");
+printf ("max number of arrays = %d\n", limits.semmni);
+printf ("max semaphores per array = %d\n", limits.semmsl);
+printf ("max semaphores system wide = %d\n", limits.semmns);
+printf ("max ops per semop call = %d\n", limits.semopm);
+printf ("semaphore max value = %u\n", limits.semvmx);
+
+printf ("------ Semaphore Status --------\n");
+printf ("used arrays = %d\n", status.semusz);
+printf ("allocated semaphores = %d\n", status.semaem);
+*/
+
+static inline int ipc_sem_get_status(struct ipc_status *st) {
+ struct seminfo seminfo;
+ union semun arg;
+
+ arg.array = (ushort *) (void *) &seminfo;
+
+ if(unlikely(semctl (0, 0, SEM_INFO, arg) < 0)) {
+ /* kernel not configured for semaphores */
+ static int error_shown = 0;
+ if(unlikely(!error_shown)) {
+ error("IPC: kernel is not configured for semaphores");
+ error_shown = 1;
+ }
+ st->semusz = 0;
+ st->semaem = 0;
+ return -1;
+ }
+
+ st->semusz = seminfo.semusz;
+ st->semaem = seminfo.semaem;
+ return 0;
+}
+
+int ipc_msq_get_info(char *msg_filename, struct message_queue **message_queue_root) {
+ static procfile *ff;
+ struct message_queue *msq;
+
+ if(unlikely(!ff)) {
+ ff = procfile_open(msg_filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 1;
+
+ size_t lines = procfile_lines(ff);
+ size_t words = 0;
+
+ if(unlikely(lines < 2)) {
+ error("Cannot read %s. Expected 2 or more lines, read %zu.", ff->filename, lines);
+ return 1;
+ }
+
+ // loop through all lines except the first and the last ones
+ size_t l;
+ for(l = 1; l < lines - 1; l++) {
+ words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) continue;
+ if(unlikely(words < 14)) {
+ error("Cannot read %s line. Expected 14 params, read %zu.", ff->filename, words);
+ continue;
+ }
+
+ // find the id in the linked list or create a new stucture
+ int found = 0;
+
+ unsigned long long id = str2ull(procfile_lineword(ff, l, 1));
+ for(msq = *message_queue_root; msq ; msq = msq->next) {
+ if(unlikely(id == msq->id)) {
+ found = 1;
+ break;
+ }
+ }
+
+ if(unlikely(!found)) {
+ msq = callocz(1, sizeof(struct message_queue));
+ msq->next = *message_queue_root;
+ *message_queue_root = msq;
+ msq->id = id;
+ }
+
+ msq->messages = str2ull(procfile_lineword(ff, l, 4));
+ msq->bytes = str2ull(procfile_lineword(ff, l, 3));
+ msq->found = 1;
+ }
+
+ return 0;
+}
+
+int ipc_shm_get_info(char *shm_filename, struct shm_stats *shm) {
+ static procfile *ff;
+
+ if(unlikely(!ff)) {
+ ff = procfile_open(shm_filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 1;
+
+ size_t lines = procfile_lines(ff);
+ size_t words = 0;
+
+ if(unlikely(lines < 2)) {
+ error("Cannot read %s. Expected 2 or more lines, read %zu.", ff->filename, lines);
+ return 1;
+ }
+
+ shm->segments = 0;
+ shm->bytes = 0;
+
+ // loop through all lines except the first and the last ones
+ size_t l;
+ for(l = 1; l < lines - 1; l++) {
+ words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) continue;
+ if(unlikely(words < 16)) {
+ error("Cannot read %s line. Expected 16 params, read %zu.", ff->filename, words);
+ continue;
+ }
+
+ shm->segments++;
+ shm->bytes += str2ull(procfile_lineword(ff, l, 3));
+ }
+
+ return 0;
+}
+
+int do_ipc(int update_every, usec_t dt) {
+ (void)dt;
+
+ static int do_sem = -1, do_msg = -1, do_shm = -1;
+ static int read_limits_next = -1;
+ static struct ipc_limits limits;
+ static struct ipc_status status;
+ static RRDVAR *arrays_max = NULL, *semaphores_max = NULL;
+ static RRDSET *st_semaphores = NULL, *st_arrays = NULL;
+ static RRDDIM *rd_semaphores = NULL, *rd_arrays = NULL;
+ static char *msg_filename = NULL;
+ static struct message_queue *message_queue_root = NULL;
+ static long long dimensions_limit;
+ static char *shm_filename = NULL;
+
+ if(unlikely(do_sem == -1)) {
+ do_msg = config_get_boolean("plugin:proc:ipc", "message queues", CONFIG_BOOLEAN_YES);
+ do_sem = config_get_boolean("plugin:proc:ipc", "semaphore totals", CONFIG_BOOLEAN_YES);
+ do_shm = config_get_boolean("plugin:proc:ipc", "shared memory totals", CONFIG_BOOLEAN_YES);
+
+ char filename[FILENAME_MAX + 1];
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sysvipc/msg");
+ msg_filename = config_get("plugin:proc:ipc", "msg filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sysvipc/shm");
+ shm_filename = config_get("plugin:proc:ipc", "shm filename to monitor", filename);
+
+ dimensions_limit = config_get_number("plugin:proc:ipc", "max dimensions in memory allowed", 50);
+
+ // make sure it works
+ if(ipc_sem_get_limits(&limits) == -1) {
+ error("unable to fetch semaphore limits");
+ do_sem = CONFIG_BOOLEAN_NO;
+ }
+ else if(ipc_sem_get_status(&status) == -1) {
+ error("unable to fetch semaphore statistics");
+ do_sem = CONFIG_BOOLEAN_NO;
+ }
+ else {
+ // create the charts
+ if(unlikely(!st_semaphores)) {
+ st_semaphores = rrdset_create_localhost(
+ "system"
+ , "ipc_semaphores"
+ , NULL
+ , "ipc semaphores"
+ , NULL
+ , "IPC Semaphores"
+ , "semaphores"
+ , PLUGIN_PROC_NAME
+ , "ipc"
+ , NETDATA_CHART_PRIO_SYSTEM_IPC_SEMAPHORES
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_AREA
+ );
+ rd_semaphores = rrddim_add(st_semaphores, "semaphores", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ if(unlikely(!st_arrays)) {
+ st_arrays = rrdset_create_localhost(
+ "system"
+ , "ipc_semaphore_arrays"
+ , NULL
+ , "ipc semaphores"
+ , NULL
+ , "IPC Semaphore Arrays"
+ , "arrays"
+ , PLUGIN_PROC_NAME
+ , "ipc"
+ , NETDATA_CHART_PRIO_SYSTEM_IPC_SEM_ARRAYS
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_AREA
+ );
+ rd_arrays = rrddim_add(st_arrays, "arrays", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ // variables
+ semaphores_max = rrdvar_custom_host_variable_create(localhost, "ipc_semaphores_max");
+ arrays_max = rrdvar_custom_host_variable_create(localhost, "ipc_semaphores_arrays_max");
+ }
+
+ struct stat stbuf;
+ if (stat(msg_filename, &stbuf)) {
+ do_msg = CONFIG_BOOLEAN_NO;
+ }
+
+ if(unlikely(do_sem == CONFIG_BOOLEAN_NO && do_msg == CONFIG_BOOLEAN_NO)) {
+ error("ipc module disabled");
+ return 1;
+ }
+ }
+
+ if(likely(do_sem != CONFIG_BOOLEAN_NO)) {
+ if(unlikely(read_limits_next < 0)) {
+ if(unlikely(ipc_sem_get_limits(&limits) == -1)) {
+ error("Unable to fetch semaphore limits.");
+ }
+ else {
+ if(semaphores_max) rrdvar_custom_host_variable_set(localhost, semaphores_max, limits.semmns);
+ if(arrays_max) rrdvar_custom_host_variable_set(localhost, arrays_max, limits.semmni);
+
+ st_arrays->red = limits.semmni;
+ st_semaphores->red = limits.semmns;
+
+ read_limits_next = 60 / update_every;
+ }
+ }
+ else
+ read_limits_next--;
+
+ if(unlikely(ipc_sem_get_status(&status) == -1)) {
+ error("Unable to get semaphore statistics");
+ return 0;
+ }
+
+ if(st_semaphores->counter_done) rrdset_next(st_semaphores);
+ rrddim_set_by_pointer(st_semaphores, rd_semaphores, status.semaem);
+ rrdset_done(st_semaphores);
+
+ if(st_arrays->counter_done) rrdset_next(st_arrays);
+ rrddim_set_by_pointer(st_arrays, rd_arrays, status.semusz);
+ rrdset_done(st_arrays);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_msg != CONFIG_BOOLEAN_NO)) {
+ static RRDSET *st_msq_messages = NULL, *st_msq_bytes = NULL;
+
+ int ret = ipc_msq_get_info(msg_filename, &message_queue_root);
+
+ if(!ret && message_queue_root) {
+ if(unlikely(!st_msq_messages))
+ st_msq_messages = rrdset_create_localhost(
+ "system"
+ , "message_queue_messages"
+ , NULL
+ , "ipc message queues"
+ , NULL
+ , "IPC Message Queue Number of Messages"
+ , "messages"
+ , PLUGIN_PROC_NAME
+ , "ipc"
+ , NETDATA_CHART_PRIO_SYSTEM_IPC_MSQ_MESSAGES
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ else
+ rrdset_next(st_msq_messages);
+
+ if(unlikely(!st_msq_bytes))
+ st_msq_bytes = rrdset_create_localhost(
+ "system"
+ , "message_queue_bytes"
+ , NULL
+ , "ipc message queues"
+ , NULL
+ , "IPC Message Queue Used Bytes"
+ , "bytes"
+ , PLUGIN_PROC_NAME
+ , "ipc"
+ , NETDATA_CHART_PRIO_SYSTEM_IPC_MSQ_SIZE
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ else
+ rrdset_next(st_msq_bytes);
+
+ struct message_queue *msq = message_queue_root, *msq_prev = NULL;
+ while(likely(msq)){
+ if(likely(msq->found)) {
+ if(unlikely(!msq->rd_messages || !msq->rd_bytes)) {
+ char id[RRD_ID_LENGTH_MAX + 1];
+ snprintfz(id, RRD_ID_LENGTH_MAX, "%llu", msq->id);
+ if(likely(!msq->rd_messages)) msq->rd_messages = rrddim_add(st_msq_messages, id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ if(likely(!msq->rd_bytes)) msq->rd_bytes = rrddim_add(st_msq_bytes, id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+
+ rrddim_set_by_pointer(st_msq_messages, msq->rd_messages, msq->messages);
+ rrddim_set_by_pointer(st_msq_bytes, msq->rd_bytes, msq->bytes);
+
+ msq->found = 0;
+ }
+ else {
+ rrddim_is_obsolete(st_msq_messages, msq->rd_messages);
+ rrddim_is_obsolete(st_msq_bytes, msq->rd_bytes);
+
+ // remove message queue from the linked list
+ if(!msq_prev)
+ message_queue_root = msq->next;
+ else
+ msq_prev->next = msq->next;
+ freez(msq);
+ msq = NULL;
+ }
+ if(likely(msq)) {
+ msq_prev = msq;
+ msq = msq->next;
+ }
+ else if(!msq_prev)
+ msq = message_queue_root;
+ else
+ msq = msq_prev->next;
+ }
+
+ rrdset_done(st_msq_messages);
+ rrdset_done(st_msq_bytes);
+
+ long long dimensions_num = 0;
+ RRDDIM *rd;
+ rrdset_rdlock(st_msq_messages);
+ rrddim_foreach_read(rd, st_msq_messages) dimensions_num++;
+ rrdset_unlock(st_msq_messages);
+
+ if(unlikely(dimensions_num > dimensions_limit)) {
+ info("Message queue statistics has been disabled");
+ info("There are %lld dimensions in memory but limit was set to %lld", dimensions_num, dimensions_limit);
+ rrdset_is_obsolete(st_msq_messages);
+ rrdset_is_obsolete(st_msq_bytes);
+ st_msq_messages = NULL;
+ st_msq_bytes = NULL;
+ do_msg = CONFIG_BOOLEAN_NO;
+ }
+ else if(unlikely(!message_queue_root)) {
+ info("Making chart %s (%s) obsolete since it does not have any dimensions", st_msq_messages->name, st_msq_messages->id);
+ rrdset_is_obsolete(st_msq_messages);
+ st_msq_messages = NULL;
+
+ info("Making chart %s (%s) obsolete since it does not have any dimensions", st_msq_bytes->name, st_msq_bytes->id);
+ rrdset_is_obsolete(st_msq_bytes);
+ st_msq_bytes = NULL;
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_shm != CONFIG_BOOLEAN_NO)) {
+ static RRDSET *st_shm_segments = NULL, *st_shm_bytes = NULL;
+ static RRDDIM *rd_shm_segments = NULL, *rd_shm_bytes = NULL;
+ struct shm_stats shm;
+
+ if(!ipc_shm_get_info(shm_filename, &shm)) {
+ if(unlikely(!st_shm_segments)) {
+ st_shm_segments = rrdset_create_localhost(
+ "system"
+ , "shared_memory_segments"
+ , NULL
+ , "ipc shared memory"
+ , NULL
+ , "IPC Shared Memory Number of Segments"
+ , "segments"
+ , PLUGIN_PROC_NAME
+ , "ipc"
+ , NETDATA_CHART_PRIO_SYSTEM_IPC_SHARED_MEM_SEGS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_shm_segments = rrddim_add(st_shm_segments, "segments", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_shm_segments);
+
+ rrddim_set_by_pointer(st_shm_segments, rd_shm_segments, shm.segments);
+
+ rrdset_done(st_shm_segments);
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(!st_shm_bytes)) {
+ st_shm_bytes = rrdset_create_localhost(
+ "system"
+ , "shared_memory_bytes"
+ , NULL
+ , "ipc shared memory"
+ , NULL
+ , "IPC Shared Memory Used Bytes"
+ , "bytes"
+ , PLUGIN_PROC_NAME
+ , "ipc"
+ , NETDATA_CHART_PRIO_SYSTEM_IPC_SHARED_MEM_SIZE
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_shm_bytes = rrddim_add(st_shm_bytes, "bytes", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_shm_bytes);
+
+ rrddim_set_by_pointer(st_shm_bytes, rd_shm_bytes, shm.bytes);
+
+ rrdset_done(st_shm_bytes);
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/plugin_proc.c b/collectors/proc.plugin/plugin_proc.c
new file mode 100644
index 0000000..19230c0
--- /dev/null
+++ b/collectors/proc.plugin/plugin_proc.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+static struct proc_module {
+ const char *name;
+ const char *dim;
+
+ int enabled;
+
+ int (*func)(int update_every, usec_t dt);
+ usec_t duration;
+
+ RRDDIM *rd;
+
+} proc_modules[] = {
+
+ // system metrics
+ { .name = "/proc/stat", .dim = "stat", .func = do_proc_stat },
+ { .name = "/proc/uptime", .dim = "uptime", .func = do_proc_uptime },
+ { .name = "/proc/loadavg", .dim = "loadavg", .func = do_proc_loadavg },
+ { .name = "/proc/sys/kernel/random/entropy_avail", .dim = "entropy", .func = do_proc_sys_kernel_random_entropy_avail },
+
+ // pressure metrics
+ { .name = "/proc/pressure", .dim = "pressure", .func = do_proc_pressure },
+
+ // CPU metrics
+ { .name = "/proc/interrupts", .dim = "interrupts", .func = do_proc_interrupts },
+ { .name = "/proc/softirqs", .dim = "softirqs", .func = do_proc_softirqs },
+
+ // memory metrics
+ { .name = "/proc/vmstat", .dim = "vmstat", .func = do_proc_vmstat },
+ { .name = "/proc/meminfo", .dim = "meminfo", .func = do_proc_meminfo },
+ { .name = "/sys/kernel/mm/ksm", .dim = "ksm", .func = do_sys_kernel_mm_ksm },
+ { .name = "/sys/block/zram", .dim = "zram", .func = do_sys_block_zram },
+ { .name = "/sys/devices/system/edac/mc", .dim = "ecc", .func = do_proc_sys_devices_system_edac_mc },
+ { .name = "/sys/devices/system/node", .dim = "numa", .func = do_proc_sys_devices_system_node },
+ { .name = "/proc/pagetypeinfo", .dim = "pagetypeinfo", .func = do_proc_pagetypeinfo },
+
+ // network metrics
+ { .name = "/proc/net/dev", .dim = "netdev", .func = do_proc_net_dev },
+ { .name = "/proc/net/wireless", .dim = "netwireless", .func = do_proc_net_wireless },
+ { .name = "/proc/net/sockstat", .dim = "sockstat", .func = do_proc_net_sockstat },
+ { .name = "/proc/net/sockstat6", .dim = "sockstat6", .func = do_proc_net_sockstat6 },
+ { .name = "/proc/net/netstat", .dim = "netstat", .func = do_proc_net_netstat }, // this has to be before /proc/net/snmp, because there is a shared metric
+ { .name = "/proc/net/snmp", .dim = "snmp", .func = do_proc_net_snmp },
+ { .name = "/proc/net/snmp6", .dim = "snmp6", .func = do_proc_net_snmp6 },
+ { .name = "/proc/net/sctp/snmp", .dim = "sctp", .func = do_proc_net_sctp_snmp },
+ { .name = "/proc/net/softnet_stat", .dim = "softnet", .func = do_proc_net_softnet_stat },
+ { .name = "/proc/net/ip_vs/stats", .dim = "ipvs", .func = do_proc_net_ip_vs_stats },
+ { .name = "/sys/class/infiniband", .dim = "infiniband", .func = do_sys_class_infiniband },
+
+ // firewall metrics
+ { .name = "/proc/net/stat/conntrack", .dim = "conntrack", .func = do_proc_net_stat_conntrack },
+ { .name = "/proc/net/stat/synproxy", .dim = "synproxy", .func = do_proc_net_stat_synproxy },
+
+ // disk metrics
+ { .name = "/proc/diskstats", .dim = "diskstats", .func = do_proc_diskstats },
+ { .name = "/proc/mdstat", .dim = "mdstat", .func = do_proc_mdstat },
+
+ // NFS metrics
+ { .name = "/proc/net/rpc/nfsd", .dim = "nfsd", .func = do_proc_net_rpc_nfsd },
+ { .name = "/proc/net/rpc/nfs", .dim = "nfs", .func = do_proc_net_rpc_nfs },
+
+ // ZFS metrics
+ { .name = "/proc/spl/kstat/zfs/arcstats", .dim = "zfs_arcstats", .func = do_proc_spl_kstat_zfs_arcstats },
+
+ // BTRFS metrics
+ { .name = "/sys/fs/btrfs", .dim = "btrfs", .func = do_sys_fs_btrfs },
+
+ // IPC metrics
+ { .name = "ipc", .dim = "ipc", .func = do_ipc },
+
+ // linux power supply metrics
+ { .name = "/sys/class/power_supply", .dim = "power_supply", .func = do_sys_class_power_supply },
+
+ // the terminator of this array
+ { .name = NULL, .dim = NULL, .func = NULL }
+};
+
+static void proc_main_cleanup(void *ptr) {
+ struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
+
+ info("cleaning up...");
+
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
+}
+
+void *proc_main(void *ptr) {
+ netdata_thread_cleanup_push(proc_main_cleanup, ptr);
+
+ int vdo_cpu_netdata = config_get_boolean("plugin:proc", "netdata server resources", CONFIG_BOOLEAN_YES);
+
+ config_get_boolean("plugin:proc", "/proc/pagetypeinfo", CONFIG_BOOLEAN_NO);
+
+ // check the enabled status for each module
+ int i;
+ for(i = 0 ; proc_modules[i].name ;i++) {
+ struct proc_module *pm = &proc_modules[i];
+
+ pm->enabled = config_get_boolean("plugin:proc", pm->name, CONFIG_BOOLEAN_YES);
+ pm->duration = 0ULL;
+ pm->rd = NULL;
+ }
+
+ usec_t step = localhost->rrd_update_every * USEC_PER_SEC;
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+ size_t iterations = 0;
+
+ while(!netdata_exit) {
+ iterations++;
+ (void)iterations;
+
+ usec_t hb_dt = heartbeat_next(&hb, step);
+ usec_t duration = 0ULL;
+
+ if(unlikely(netdata_exit)) break;
+
+ // BEGIN -- the job to be done
+
+ for(i = 0 ; proc_modules[i].name ;i++) {
+ struct proc_module *pm = &proc_modules[i];
+ if(unlikely(!pm->enabled)) continue;
+
+ debug(D_PROCNETDEV_LOOP, "PROC calling %s.", pm->name);
+
+//#ifdef NETDATA_LOG_ALLOCATIONS
+// if(pm->func == do_proc_interrupts)
+// log_thread_memory_allocations = iterations;
+//#endif
+ pm->enabled = !pm->func(localhost->rrd_update_every, hb_dt);
+ pm->duration = heartbeat_monotonic_dt_to_now_usec(&hb) - duration;
+ duration += pm->duration;
+
+//#ifdef NETDATA_LOG_ALLOCATIONS
+// if(pm->func == do_proc_interrupts)
+// log_thread_memory_allocations = 0;
+//#endif
+
+ if(unlikely(netdata_exit)) break;
+ }
+
+ // END -- the job is done
+
+ // --------------------------------------------------------------------
+
+ if(vdo_cpu_netdata) {
+ static RRDSET *st = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_find_active_bytype_localhost("netdata", "plugin_proc_modules");
+
+ if(!st) {
+ st = rrdset_create_localhost(
+ "netdata"
+ , "plugin_proc_modules"
+ , NULL
+ , "proc"
+ , NULL
+ , "NetData Proc Plugin Modules Durations"
+ , "milliseconds/run"
+ , "netdata"
+ , "stats"
+ , 132001
+ , localhost->rrd_update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ for(i = 0 ; proc_modules[i].name ;i++) {
+ struct proc_module *pm = &proc_modules[i];
+ if(unlikely(!pm->enabled)) continue;
+
+ pm->rd = rrddim_add(st, pm->dim, NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ }
+ }
+ else rrdset_next(st);
+
+ for(i = 0 ; proc_modules[i].name ;i++) {
+ struct proc_module *pm = &proc_modules[i];
+ if(unlikely(!pm->enabled)) continue;
+
+ rrddim_set_by_pointer(st, pm->rd, pm->duration);
+ }
+ rrdset_done(st);
+
+ global_statistics_charts();
+ registry_statistics();
+ }
+ }
+
+ netdata_thread_cleanup_pop(1);
+ return NULL;
+}
+
+int get_numa_node_count(void)
+{
+ static int numa_node_count = -1;
+
+ if (numa_node_count != -1)
+ return numa_node_count;
+
+ numa_node_count = 0;
+
+ char name[FILENAME_MAX + 1];
+ snprintfz(name, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/node");
+ char *dirname = config_get("plugin:proc:/sys/devices/system/node", "directory to monitor", name);
+
+ DIR *dir = opendir(dirname);
+ if(dir) {
+ struct dirent *de = NULL;
+ while((de = readdir(dir))) {
+ if(de->d_type != DT_DIR)
+ continue;
+
+ if(strncmp(de->d_name, "node", 4) != 0)
+ continue;
+
+ if(!isdigit(de->d_name[4]))
+ continue;
+
+ numa_node_count++;
+ }
+ closedir(dir);
+ }
+
+ return numa_node_count;
+}
diff --git a/collectors/proc.plugin/plugin_proc.h b/collectors/proc.plugin/plugin_proc.h
new file mode 100644
index 0000000..108c026
--- /dev/null
+++ b/collectors/proc.plugin/plugin_proc.h
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_PLUGIN_PROC_H
+#define NETDATA_PLUGIN_PROC_H 1
+
+#include "../../daemon/common.h"
+
+#if (TARGET_OS == OS_LINUX)
+
+#define NETDATA_PLUGIN_HOOK_LINUX_PROC \
+ { \
+ .name = "PLUGIN[proc]", \
+ .config_section = CONFIG_SECTION_PLUGINS, \
+ .config_name = "proc", \
+ .enabled = 1, \
+ .thread = NULL, \
+ .init_routine = NULL, \
+ .start_routine = proc_main \
+ },
+
+
+#define PLUGIN_PROC_CONFIG_NAME "proc"
+#define PLUGIN_PROC_NAME PLUGIN_PROC_CONFIG_NAME ".plugin"
+
+extern void *proc_main(void *ptr);
+
+extern int do_proc_net_dev(int update_every, usec_t dt);
+extern int do_proc_net_wireless(int update_every, usec_t dt);
+extern int do_proc_diskstats(int update_every, usec_t dt);
+extern int do_proc_mdstat(int update_every, usec_t dt);
+extern int do_proc_net_snmp(int update_every, usec_t dt);
+extern int do_proc_net_snmp6(int update_every, usec_t dt);
+extern int do_proc_net_netstat(int update_every, usec_t dt);
+extern int do_proc_net_stat_conntrack(int update_every, usec_t dt);
+extern int do_proc_net_ip_vs_stats(int update_every, usec_t dt);
+extern int do_proc_stat(int update_every, usec_t dt);
+extern int do_proc_meminfo(int update_every, usec_t dt);
+extern int do_proc_vmstat(int update_every, usec_t dt);
+extern int do_proc_net_rpc_nfs(int update_every, usec_t dt);
+extern int do_proc_net_rpc_nfsd(int update_every, usec_t dt);
+extern int do_proc_sys_kernel_random_entropy_avail(int update_every, usec_t dt);
+extern int do_proc_interrupts(int update_every, usec_t dt);
+extern int do_proc_softirqs(int update_every, usec_t dt);
+extern int do_proc_pressure(int update_every, usec_t dt);
+extern int do_sys_kernel_mm_ksm(int update_every, usec_t dt);
+extern int do_sys_block_zram(int update_every, usec_t dt);
+extern int do_proc_loadavg(int update_every, usec_t dt);
+extern int do_proc_net_stat_synproxy(int update_every, usec_t dt);
+extern int do_proc_net_softnet_stat(int update_every, usec_t dt);
+extern int do_proc_uptime(int update_every, usec_t dt);
+extern int do_proc_sys_devices_system_edac_mc(int update_every, usec_t dt);
+extern int do_proc_sys_devices_system_node(int update_every, usec_t dt);
+extern int do_proc_spl_kstat_zfs_arcstats(int update_every, usec_t dt);
+extern int do_sys_fs_btrfs(int update_every, usec_t dt);
+extern int do_proc_net_sockstat(int update_every, usec_t dt);
+extern int do_proc_net_sockstat6(int update_every, usec_t dt);
+extern int do_proc_net_sctp_snmp(int update_every, usec_t dt);
+extern int do_ipc(int update_every, usec_t dt);
+extern int do_sys_class_power_supply(int update_every, usec_t dt);
+extern int do_proc_pagetypeinfo(int update_every, usec_t dt);
+extern int do_sys_class_infiniband(int update_every, usec_t dt);
+extern int get_numa_node_count(void);
+
+// metrics that need to be shared among data collectors
+extern unsigned long long tcpext_TCPSynRetrans;
+
+// netdev renames
+extern void netdev_rename_device_add(
+ const char *host_device, const char *container_device, const char *container_name, struct label *labels);
+extern void netdev_rename_device_del(const char *host_device);
+
+#include "proc_self_mountinfo.h"
+#include "proc_pressure.h"
+#include "zfs_common.h"
+
+#else // (TARGET_OS == OS_LINUX)
+
+#define NETDATA_PLUGIN_HOOK_LINUX_PROC
+
+#endif // (TARGET_OS == OS_LINUX)
+
+
+#endif /* NETDATA_PLUGIN_PROC_H */
diff --git a/collectors/proc.plugin/proc_diskstats.c b/collectors/proc.plugin/proc_diskstats.c
new file mode 100644
index 0000000..eee0cbe
--- /dev/null
+++ b/collectors/proc.plugin/proc_diskstats.c
@@ -0,0 +1,1700 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define RRD_TYPE_DISK "disk"
+#define PLUGIN_PROC_MODULE_DISKSTATS_NAME "/proc/diskstats"
+#define CONFIG_SECTION_PLUGIN_PROC_DISKSTATS "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_DISKSTATS_NAME
+
+#define DISK_TYPE_UNKNOWN 0
+#define DISK_TYPE_PHYSICAL 1
+#define DISK_TYPE_PARTITION 2
+#define DISK_TYPE_VIRTUAL 3
+
+#define DEFAULT_PREFERRED_IDS "*"
+#define DEFAULT_EXCLUDED_DISKS "loop* ram*"
+
+static struct disk {
+ char *disk; // the name of the disk (sda, sdb, etc, after being looked up)
+ char *device; // the device of the disk (before being looked up)
+ unsigned long major;
+ unsigned long minor;
+ int sector_size;
+ int type;
+
+ char *mount_point;
+
+ // disk options caching
+ int do_io;
+ int do_ops;
+ int do_mops;
+ int do_iotime;
+ int do_qops;
+ int do_util;
+ int do_backlog;
+ int do_bcache;
+
+ int updated;
+
+ int device_is_bcache;
+
+ char *bcache_filename_dirty_data;
+ char *bcache_filename_writeback_rate;
+ char *bcache_filename_cache_congested;
+ char *bcache_filename_cache_available_percent;
+ char *bcache_filename_stats_five_minute_cache_hit_ratio;
+ char *bcache_filename_stats_hour_cache_hit_ratio;
+ char *bcache_filename_stats_day_cache_hit_ratio;
+ char *bcache_filename_stats_total_cache_hit_ratio;
+ char *bcache_filename_stats_total_cache_hits;
+ char *bcache_filename_stats_total_cache_misses;
+ char *bcache_filename_stats_total_cache_miss_collisions;
+ char *bcache_filename_stats_total_cache_bypass_hits;
+ char *bcache_filename_stats_total_cache_bypass_misses;
+ char *bcache_filename_stats_total_cache_readaheads;
+ char *bcache_filename_cache_read_races;
+ char *bcache_filename_cache_io_errors;
+ char *bcache_filename_priority_stats;
+
+ usec_t bcache_priority_stats_update_every_usec;
+ usec_t bcache_priority_stats_elapsed_usec;
+
+ RRDSET *st_io;
+ RRDDIM *rd_io_reads;
+ RRDDIM *rd_io_writes;
+
+ RRDSET *st_ops;
+ RRDDIM *rd_ops_reads;
+ RRDDIM *rd_ops_writes;
+
+ RRDSET *st_qops;
+ RRDDIM *rd_qops_operations;
+
+ RRDSET *st_backlog;
+ RRDDIM *rd_backlog_backlog;
+
+ RRDSET *st_util;
+ RRDDIM *rd_util_utilization;
+
+ RRDSET *st_mops;
+ RRDDIM *rd_mops_reads;
+ RRDDIM *rd_mops_writes;
+
+ RRDSET *st_iotime;
+ RRDDIM *rd_iotime_reads;
+ RRDDIM *rd_iotime_writes;
+
+ RRDSET *st_await;
+ RRDDIM *rd_await_reads;
+ RRDDIM *rd_await_writes;
+
+ RRDSET *st_avgsz;
+ RRDDIM *rd_avgsz_reads;
+ RRDDIM *rd_avgsz_writes;
+
+ RRDSET *st_svctm;
+ RRDDIM *rd_svctm_svctm;
+
+ RRDSET *st_bcache_size;
+ RRDDIM *rd_bcache_dirty_size;
+
+ RRDSET *st_bcache_usage;
+ RRDDIM *rd_bcache_available_percent;
+
+ RRDSET *st_bcache_hit_ratio;
+ RRDDIM *rd_bcache_hit_ratio_5min;
+ RRDDIM *rd_bcache_hit_ratio_1hour;
+ RRDDIM *rd_bcache_hit_ratio_1day;
+ RRDDIM *rd_bcache_hit_ratio_total;
+
+ RRDSET *st_bcache;
+ RRDDIM *rd_bcache_hits;
+ RRDDIM *rd_bcache_misses;
+ RRDDIM *rd_bcache_miss_collisions;
+
+ RRDSET *st_bcache_bypass;
+ RRDDIM *rd_bcache_bypass_hits;
+ RRDDIM *rd_bcache_bypass_misses;
+
+ RRDSET *st_bcache_rates;
+ RRDDIM *rd_bcache_rate_congested;
+ RRDDIM *rd_bcache_readaheads;
+ RRDDIM *rd_bcache_rate_writeback;
+
+ RRDSET *st_bcache_cache_allocations;
+ RRDDIM *rd_bcache_cache_allocations_unused;
+ RRDDIM *rd_bcache_cache_allocations_clean;
+ RRDDIM *rd_bcache_cache_allocations_dirty;
+ RRDDIM *rd_bcache_cache_allocations_metadata;
+ RRDDIM *rd_bcache_cache_allocations_unknown;
+
+ RRDSET *st_bcache_cache_read_races;
+ RRDDIM *rd_bcache_cache_read_races;
+ RRDDIM *rd_bcache_cache_io_errors;
+
+ struct disk *next;
+} *disk_root = NULL;
+
+#define rrdset_obsolete_and_pointer_null(st) do { if(st) { rrdset_is_obsolete(st); (st) = NULL; } } while(st)
+
+// static char *path_to_get_hw_sector_size = NULL;
+// static char *path_to_get_hw_sector_size_partitions = NULL;
+static char *path_to_sys_dev_block_major_minor_string = NULL;
+static char *path_to_sys_block_device = NULL;
+static char *path_to_sys_block_device_bcache = NULL;
+static char *path_to_sys_devices_virtual_block_device = NULL;
+static char *path_to_device_mapper = NULL;
+static char *path_to_device_label = NULL;
+static char *path_to_device_id = NULL;
+static char *path_to_veritas_volume_groups = NULL;
+static int name_disks_by_id = CONFIG_BOOLEAN_NO;
+static int global_bcache_priority_stats_update_every = 0; // disabled by default
+
+static int global_enable_new_disks_detected_at_runtime = CONFIG_BOOLEAN_YES,
+ global_enable_performance_for_physical_disks = CONFIG_BOOLEAN_AUTO,
+ global_enable_performance_for_virtual_disks = CONFIG_BOOLEAN_AUTO,
+ global_enable_performance_for_partitions = CONFIG_BOOLEAN_NO,
+ global_do_io = CONFIG_BOOLEAN_AUTO,
+ global_do_ops = CONFIG_BOOLEAN_AUTO,
+ global_do_mops = CONFIG_BOOLEAN_AUTO,
+ global_do_iotime = CONFIG_BOOLEAN_AUTO,
+ global_do_qops = CONFIG_BOOLEAN_AUTO,
+ global_do_util = CONFIG_BOOLEAN_AUTO,
+ global_do_backlog = CONFIG_BOOLEAN_AUTO,
+ global_do_bcache = CONFIG_BOOLEAN_AUTO,
+ globals_initialized = 0,
+ global_cleanup_removed_disks = 1;
+
+static SIMPLE_PATTERN *preferred_ids = NULL;
+static SIMPLE_PATTERN *excluded_disks = NULL;
+
+static unsigned long long int bcache_read_number_with_units(const char *filename) {
+ char buffer[50 + 1];
+ if(read_file(filename, buffer, 50) == 0) {
+ static int unknown_units_error = 10;
+
+ char *end = NULL;
+ long double value = str2ld(buffer, &end);
+ if(end && *end) {
+ if(*end == 'k')
+ return (unsigned long long int)(value * 1024.0);
+ else if(*end == 'M')
+ return (unsigned long long int)(value * 1024.0 * 1024.0);
+ else if(*end == 'G')
+ return (unsigned long long int)(value * 1024.0 * 1024.0 * 1024.0);
+ else if(*end == 'T')
+ return (unsigned long long int)(value * 1024.0 * 1024.0 * 1024.0 * 1024.0);
+ else if(unknown_units_error > 0) {
+ error("bcache file '%s' provides value '%s' with unknown units '%s'", filename, buffer, end);
+ unknown_units_error--;
+ }
+ }
+
+ return (unsigned long long int)value;
+ }
+
+ return 0;
+}
+
+void bcache_read_priority_stats(struct disk *d, const char *family, int update_every, usec_t dt) {
+ static procfile *ff = NULL;
+ static char *separators = " \t:%[]";
+
+ static ARL_BASE *arl_base = NULL;
+
+ static unsigned long long unused;
+ static unsigned long long clean;
+ static unsigned long long dirty;
+ static unsigned long long metadata;
+ static unsigned long long unknown;
+
+ // check if it is time to update this metric
+ d->bcache_priority_stats_elapsed_usec += dt;
+ if(likely(d->bcache_priority_stats_elapsed_usec < d->bcache_priority_stats_update_every_usec)) return;
+ d->bcache_priority_stats_elapsed_usec = 0;
+
+ // initialize ARL
+ if(unlikely(!arl_base)) {
+ arl_base = arl_create("bcache/priority_stats", NULL, 60);
+ arl_expect(arl_base, "Unused", &unused);
+ arl_expect(arl_base, "Clean", &clean);
+ arl_expect(arl_base, "Dirty", &dirty);
+ arl_expect(arl_base, "Metadata", &metadata);
+ }
+
+ ff = procfile_reopen(ff, d->bcache_filename_priority_stats, separators, PROCFILE_FLAG_DEFAULT);
+ if(likely(ff)) ff = procfile_readall(ff);
+ if(unlikely(!ff)) {
+ separators = " \t:%[]";
+ return;
+ }
+
+ // do not reset the separators on every iteration
+ separators = NULL;
+
+ arl_begin(arl_base);
+ unused = clean = dirty = metadata = unknown = 0;
+
+ size_t lines = procfile_lines(ff), l;
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) {
+ if(unlikely(words)) error("Cannot read '%s' line %zu. Expected 2 params, read %zu.", d->bcache_filename_priority_stats, l, words);
+ continue;
+ }
+
+ if(unlikely(arl_check(arl_base,
+ procfile_lineword(ff, l, 0),
+ procfile_lineword(ff, l, 1)))) break;
+ }
+
+ unknown = 100 - unused - clean - dirty - metadata;
+
+ // create / update the cache allocations chart
+ {
+ if(unlikely(!d->st_bcache_cache_allocations)) {
+ d->st_bcache_cache_allocations = rrdset_create_localhost(
+ "disk_bcache_cache_alloc"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache_cache_alloc"
+ , "BCache Cache Allocations"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_CACHE_ALLOC
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ d->rd_bcache_cache_allocations_unused = rrddim_add(d->st_bcache_cache_allocations, "unused", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_cache_allocations_dirty = rrddim_add(d->st_bcache_cache_allocations, "dirty", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_cache_allocations_clean = rrddim_add(d->st_bcache_cache_allocations, "clean", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_cache_allocations_metadata = rrddim_add(d->st_bcache_cache_allocations, "metadata", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_cache_allocations_unknown = rrddim_add(d->st_bcache_cache_allocations, "undefined", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ d->bcache_priority_stats_update_every_usec = update_every * USEC_PER_SEC;
+ }
+ else rrdset_next(d->st_bcache_cache_allocations);
+
+ rrddim_set_by_pointer(d->st_bcache_cache_allocations, d->rd_bcache_cache_allocations_unused, unused);
+ rrddim_set_by_pointer(d->st_bcache_cache_allocations, d->rd_bcache_cache_allocations_dirty, dirty);
+ rrddim_set_by_pointer(d->st_bcache_cache_allocations, d->rd_bcache_cache_allocations_clean, clean);
+ rrddim_set_by_pointer(d->st_bcache_cache_allocations, d->rd_bcache_cache_allocations_metadata, metadata);
+ rrddim_set_by_pointer(d->st_bcache_cache_allocations, d->rd_bcache_cache_allocations_unknown, unknown);
+ rrdset_done(d->st_bcache_cache_allocations);
+ }
+}
+
+static inline int is_major_enabled(int major) {
+ static int8_t *major_configs = NULL;
+ static size_t major_size = 0;
+
+ if(major < 0) return 1;
+
+ size_t wanted_size = (size_t)major + 1;
+
+ if(major_size < wanted_size) {
+ major_configs = reallocz(major_configs, wanted_size * sizeof(int8_t));
+
+ size_t i;
+ for(i = major_size; i < wanted_size ; i++)
+ major_configs[i] = -1;
+
+ major_size = wanted_size;
+ }
+
+ if(major_configs[major] == -1) {
+ char buffer[CONFIG_MAX_NAME + 1];
+ snprintfz(buffer, CONFIG_MAX_NAME, "performance metrics for disks with major %d", major);
+ major_configs[major] = (char)config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, buffer, 1);
+ }
+
+ return (int)major_configs[major];
+}
+
+static inline int get_disk_name_from_path(const char *path, char *result, size_t result_size, unsigned long major, unsigned long minor, char *disk, char *prefix, int depth) {
+ //info("DEVICE-MAPPER ('%s', %lu:%lu): examining directory '%s' (allowed depth %d).", disk, major, minor, path, depth);
+
+ int found = 0, preferred = 0;
+
+ char *first_result = mallocz(result_size);
+
+ DIR *dir = opendir(path);
+ if (!dir) {
+ error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot open directory '%s'.", disk, major, minor, path);
+ goto failed;
+ }
+
+ struct dirent *de = NULL;
+ while ((de = readdir(dir))) {
+ if(de->d_type == DT_DIR) {
+ if((de->d_name[0] == '.' && de->d_name[1] == '\0') || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0'))
+ continue;
+
+ if(depth <= 0) {
+ error("DEVICE-MAPPER ('%s', %lu:%lu): Depth limit reached for path '%s/%s'. Ignoring path.", disk, major, minor, path, de->d_name);
+ break;
+ }
+ else {
+ char *path_nested = NULL;
+ char *prefix_nested = NULL;
+
+ {
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%s/%s", path, de->d_name);
+ path_nested = strdupz(buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s%s%s", (prefix)?prefix:"", (prefix)?"_":"", de->d_name);
+ prefix_nested = strdupz(buffer);
+ }
+
+ found = get_disk_name_from_path(path_nested, result, result_size, major, minor, disk, prefix_nested, depth - 1);
+ freez(path_nested);
+ freez(prefix_nested);
+
+ if(found) break;
+ }
+ }
+ else if(de->d_type == DT_LNK || de->d_type == DT_BLK) {
+ char filename[FILENAME_MAX + 1];
+
+ if(de->d_type == DT_LNK) {
+ snprintfz(filename, FILENAME_MAX, "%s/%s", path, de->d_name);
+ ssize_t len = readlink(filename, result, result_size - 1);
+ if(len <= 0) {
+ error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot read link '%s'.", disk, major, minor, filename);
+ continue;
+ }
+
+ result[len] = '\0';
+ if(result[0] != '/')
+ snprintfz(filename, FILENAME_MAX, "%s/%s", path, result);
+ else
+ strncpyz(filename, result, FILENAME_MAX);
+ }
+ else {
+ snprintfz(filename, FILENAME_MAX, "%s/%s", path, de->d_name);
+ }
+
+ struct stat sb;
+ if(stat(filename, &sb) == -1) {
+ error("DEVICE-MAPPER ('%s', %lu:%lu): Cannot stat() file '%s'.", disk, major, minor, filename);
+ continue;
+ }
+
+ if((sb.st_mode & S_IFMT) != S_IFBLK) {
+ //info("DEVICE-MAPPER ('%s', %lu:%lu): file '%s' is not a block device.", disk, major, minor, filename);
+ continue;
+ }
+
+ if(major(sb.st_rdev) != major || minor(sb.st_rdev) != minor) {
+ //info("DEVICE-MAPPER ('%s', %lu:%lu): filename '%s' does not match %lu:%lu.", disk, major, minor, filename, (unsigned long)major(sb.st_rdev), (unsigned long)minor(sb.st_rdev));
+ continue;
+ }
+
+ //info("DEVICE-MAPPER ('%s', %lu:%lu): filename '%s' matches.", disk, major, minor, filename);
+
+ snprintfz(result, result_size - 1, "%s%s%s", (prefix)?prefix:"", (prefix)?"_":"", de->d_name);
+
+ if(!found) {
+ strncpyz(first_result, result, result_size);
+ found = 1;
+ }
+
+ if(simple_pattern_matches(preferred_ids, result)) {
+ preferred = 1;
+ break;
+ }
+ }
+ }
+ closedir(dir);
+
+
+failed:
+
+ if(!found)
+ result[0] = '\0';
+ else if(!preferred)
+ strncpyz(result, first_result, result_size);
+
+ freez(first_result);
+
+ return found;
+}
+
+static inline char *get_disk_name(unsigned long major, unsigned long minor, char *disk) {
+ char result[FILENAME_MAX + 1] = "";
+
+ if(!path_to_device_mapper || !*path_to_device_mapper || !get_disk_name_from_path(path_to_device_mapper, result, FILENAME_MAX + 1, major, minor, disk, NULL, 0))
+ if(!path_to_device_label || !*path_to_device_label || !get_disk_name_from_path(path_to_device_label, result, FILENAME_MAX + 1, major, minor, disk, NULL, 0))
+ if(!path_to_veritas_volume_groups || !*path_to_veritas_volume_groups || !get_disk_name_from_path(path_to_veritas_volume_groups, result, FILENAME_MAX + 1, major, minor, disk, "vx", 2))
+ if(name_disks_by_id != CONFIG_BOOLEAN_YES || !path_to_device_id || !*path_to_device_id || !get_disk_name_from_path(path_to_device_id, result, FILENAME_MAX + 1, major, minor, disk, NULL, 0))
+ strncpy(result, disk, FILENAME_MAX);
+
+ if(!result[0])
+ strncpy(result, disk, FILENAME_MAX);
+
+ netdata_fix_chart_name(result);
+ return strdup(result);
+}
+
+static void get_disk_config(struct disk *d) {
+ int def_enable = global_enable_new_disks_detected_at_runtime;
+
+ if(def_enable != CONFIG_BOOLEAN_NO && (simple_pattern_matches(excluded_disks, d->device) || simple_pattern_matches(excluded_disks, d->disk)))
+ def_enable = CONFIG_BOOLEAN_NO;
+
+ char var_name[4096 + 1];
+ snprintfz(var_name, 4096, CONFIG_SECTION_PLUGIN_PROC_DISKSTATS ":%s", d->disk);
+
+ def_enable = config_get_boolean_ondemand(var_name, "enable", def_enable);
+ if(unlikely(def_enable == CONFIG_BOOLEAN_NO)) {
+ // the user does not want any metrics for this disk
+ d->do_io = CONFIG_BOOLEAN_NO;
+ d->do_ops = CONFIG_BOOLEAN_NO;
+ d->do_mops = CONFIG_BOOLEAN_NO;
+ d->do_iotime = CONFIG_BOOLEAN_NO;
+ d->do_qops = CONFIG_BOOLEAN_NO;
+ d->do_util = CONFIG_BOOLEAN_NO;
+ d->do_backlog = CONFIG_BOOLEAN_NO;
+ d->do_bcache = CONFIG_BOOLEAN_NO;
+ }
+ else {
+ // this disk is enabled
+ // check its direct settings
+
+ int def_performance = CONFIG_BOOLEAN_AUTO;
+
+ // since this is 'on demand' we can figure the performance settings
+ // based on the type of disk
+
+ if(!d->device_is_bcache) {
+ switch(d->type) {
+ default:
+ case DISK_TYPE_UNKNOWN:
+ break;
+
+ case DISK_TYPE_PHYSICAL:
+ def_performance = global_enable_performance_for_physical_disks;
+ break;
+
+ case DISK_TYPE_PARTITION:
+ def_performance = global_enable_performance_for_partitions;
+ break;
+
+ case DISK_TYPE_VIRTUAL:
+ def_performance = global_enable_performance_for_virtual_disks;
+ break;
+ }
+ }
+
+ // check if we have to disable performance for this disk
+ if(def_performance)
+ def_performance = is_major_enabled((int)d->major);
+
+ // ------------------------------------------------------------
+ // now we have def_performance and def_space
+ // to work further
+
+ // def_performance
+ // check the user configuration (this will also show our 'on demand' decision)
+ def_performance = config_get_boolean_ondemand(var_name, "enable performance metrics", def_performance);
+
+ int ddo_io = CONFIG_BOOLEAN_NO,
+ ddo_ops = CONFIG_BOOLEAN_NO,
+ ddo_mops = CONFIG_BOOLEAN_NO,
+ ddo_iotime = CONFIG_BOOLEAN_NO,
+ ddo_qops = CONFIG_BOOLEAN_NO,
+ ddo_util = CONFIG_BOOLEAN_NO,
+ ddo_backlog = CONFIG_BOOLEAN_NO,
+ ddo_bcache = CONFIG_BOOLEAN_NO;
+
+ // we enable individual performance charts only when def_performance is not disabled
+ if(unlikely(def_performance != CONFIG_BOOLEAN_NO)) {
+ ddo_io = global_do_io,
+ ddo_ops = global_do_ops,
+ ddo_mops = global_do_mops,
+ ddo_iotime = global_do_iotime,
+ ddo_qops = global_do_qops,
+ ddo_util = global_do_util,
+ ddo_backlog = global_do_backlog,
+ ddo_bcache = global_do_bcache;
+ }
+
+ d->do_io = config_get_boolean_ondemand(var_name, "bandwidth", ddo_io);
+ d->do_ops = config_get_boolean_ondemand(var_name, "operations", ddo_ops);
+ d->do_mops = config_get_boolean_ondemand(var_name, "merged operations", ddo_mops);
+ d->do_iotime = config_get_boolean_ondemand(var_name, "i/o time", ddo_iotime);
+ d->do_qops = config_get_boolean_ondemand(var_name, "queued operations", ddo_qops);
+ d->do_util = config_get_boolean_ondemand(var_name, "utilization percentage", ddo_util);
+ d->do_backlog = config_get_boolean_ondemand(var_name, "backlog", ddo_backlog);
+
+ if(d->device_is_bcache)
+ d->do_bcache = config_get_boolean_ondemand(var_name, "bcache", ddo_bcache);
+ else
+ d->do_bcache = 0;
+ }
+}
+
+static struct disk *get_disk(unsigned long major, unsigned long minor, char *disk) {
+ static struct mountinfo *disk_mountinfo_root = NULL;
+
+ struct disk *d;
+
+ // search for it in our RAM list.
+ // this is sequential, but since we just walk through
+ // and the number of disks / partitions in a system
+ // should not be that many, it should be acceptable
+ for(d = disk_root; d ; d = d->next)
+ if(unlikely(d->major == major && d->minor == minor))
+ return d;
+
+ // not found
+ // create a new disk structure
+ d = (struct disk *)callocz(1, sizeof(struct disk));
+
+ d->disk = get_disk_name(major, minor, disk);
+ d->device = strdupz(disk);
+ d->major = major;
+ d->minor = minor;
+ d->type = DISK_TYPE_UNKNOWN; // Default type. Changed later if not correct.
+ d->sector_size = 512; // the default, will be changed below
+ d->next = NULL;
+
+ // append it to the list
+ if(unlikely(!disk_root))
+ disk_root = d;
+ else {
+ struct disk *last;
+ for(last = disk_root; last->next ;last = last->next);
+ last->next = d;
+ }
+
+ char buffer[FILENAME_MAX + 1];
+
+ // find if it is a physical disk
+ // by checking if /sys/block/DISK is readable.
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_block_device, disk);
+ if(likely(access(buffer, R_OK) == 0)) {
+ // assign it here, but it will be overwritten if it is not a physical disk
+ d->type = DISK_TYPE_PHYSICAL;
+ }
+
+ // find if it is a partition
+ // by checking if /sys/dev/block/MAJOR:MINOR/partition is readable.
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_dev_block_major_minor_string, major, minor, "partition");
+ if(likely(access(buffer, R_OK) == 0)) {
+ d->type = DISK_TYPE_PARTITION;
+ }
+ else {
+ // find if it is a virtual disk
+ // by checking if /sys/devices/virtual/block/DISK is readable.
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_devices_virtual_block_device, disk);
+ if(likely(access(buffer, R_OK) == 0)) {
+ d->type = DISK_TYPE_VIRTUAL;
+ }
+ else {
+ // find if it is a virtual device
+ // by checking if /sys/dev/block/MAJOR:MINOR/slaves has entries
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_dev_block_major_minor_string, major, minor, "slaves/");
+ DIR *dirp = opendir(buffer);
+ if (likely(dirp != NULL)) {
+ struct dirent *dp;
+ while ((dp = readdir(dirp))) {
+ // . and .. are also files in empty folders.
+ if (unlikely(strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)) {
+ continue;
+ }
+
+ d->type = DISK_TYPE_VIRTUAL;
+
+ // Stop the loop after we found one file.
+ break;
+ }
+ if (unlikely(closedir(dirp) == -1))
+ error("Unable to close dir %s", buffer);
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // check if we can find its mount point
+
+ // mountinfo_find() can be called with NULL disk_mountinfo_root
+ struct mountinfo *mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor);
+ if(unlikely(!mi)) {
+ // mountinfo_free_all can be called with NULL
+ mountinfo_free_all(disk_mountinfo_root);
+ disk_mountinfo_root = mountinfo_read(0);
+ mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor);
+ }
+
+ if(unlikely(mi))
+ d->mount_point = strdupz(mi->mount_point);
+ else
+ d->mount_point = NULL;
+
+ // ------------------------------------------------------------------------
+ // find the disk sector size
+
+ /*
+ * sector size is always 512 bytes inside the kernel #3481
+ *
+ {
+ char tf[FILENAME_MAX + 1], *t;
+ strncpyz(tf, d->device, FILENAME_MAX);
+
+ // replace all / with !
+ for(t = tf; *t ;t++)
+ if(unlikely(*t == '/')) *t = '!';
+
+ if(likely(d->type == DISK_TYPE_PARTITION))
+ snprintfz(buffer, FILENAME_MAX, path_to_get_hw_sector_size_partitions, d->major, d->minor, tf);
+ else
+ snprintfz(buffer, FILENAME_MAX, path_to_get_hw_sector_size, tf);
+
+ FILE *fpss = fopen(buffer, "r");
+ if(likely(fpss)) {
+ char buffer2[1024 + 1];
+ char *tmp = fgets(buffer2, 1024, fpss);
+
+ if(likely(tmp)) {
+ d->sector_size = str2i(tmp);
+ if(unlikely(d->sector_size <= 0)) {
+ error("Invalid sector size %d for device %s in %s. Assuming 512.", d->sector_size, d->device, buffer);
+ d->sector_size = 512;
+ }
+ }
+ else error("Cannot read data for sector size for device %s from %s. Assuming 512.", d->device, buffer);
+
+ fclose(fpss);
+ }
+ else error("Cannot read sector size for device %s from %s. Assuming 512.", d->device, buffer);
+ }
+ */
+
+ // ------------------------------------------------------------------------
+ // check if the device is a bcache
+
+ struct stat bcache;
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_block_device_bcache, disk);
+ if(unlikely(stat(buffer, &bcache) == 0 && (bcache.st_mode & S_IFMT) == S_IFDIR)) {
+ // we have the 'bcache' directory
+ d->device_is_bcache = 1;
+
+ char buffer2[FILENAME_MAX + 1];
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/cache/congested", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_cache_congested = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/readahead", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_total_cache_readaheads = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/cache/cache0/priority_stats", buffer); // only one cache is supported by bcache
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_priority_stats = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/cache/internal/cache_read_races", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_cache_read_races = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/cache/cache0/io_errors", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_cache_io_errors = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/dirty_data", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_dirty_data = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/writeback_rate", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_writeback_rate = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/cache/cache_available_percent", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_cache_available_percent = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_hits", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_total_cache_hits = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_five_minute/cache_hit_ratio", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_five_minute_cache_hit_ratio = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_hour/cache_hit_ratio", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_hour_cache_hit_ratio = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_day/cache_hit_ratio", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_day_cache_hit_ratio = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_hit_ratio", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_total_cache_hit_ratio = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_misses", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_total_cache_misses = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_bypass_hits", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_total_cache_bypass_hits = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_bypass_misses", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_total_cache_bypass_misses = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+
+ snprintfz(buffer2, FILENAME_MAX, "%s/stats_total/cache_miss_collisions", buffer);
+ if(access(buffer2, R_OK) == 0)
+ d->bcache_filename_stats_total_cache_miss_collisions = strdupz(buffer2);
+ else
+ error("bcache file '%s' cannot be read.", buffer2);
+ }
+
+ get_disk_config(d);
+ return d;
+}
+
+int do_proc_diskstats(int update_every, usec_t dt) {
+ static procfile *ff = NULL;
+
+ if(unlikely(!globals_initialized)) {
+ globals_initialized = 1;
+
+ global_enable_new_disks_detected_at_runtime = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "enable new disks detected at runtime", global_enable_new_disks_detected_at_runtime);
+ global_enable_performance_for_physical_disks = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "performance metrics for physical disks", global_enable_performance_for_physical_disks);
+ global_enable_performance_for_virtual_disks = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "performance metrics for virtual disks", global_enable_performance_for_virtual_disks);
+ global_enable_performance_for_partitions = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "performance metrics for partitions", global_enable_performance_for_partitions);
+
+ global_do_io = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "bandwidth for all disks", global_do_io);
+ global_do_ops = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "operations for all disks", global_do_ops);
+ global_do_mops = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "merged operations for all disks", global_do_mops);
+ global_do_iotime = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "i/o time for all disks", global_do_iotime);
+ global_do_qops = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "queued operations for all disks", global_do_qops);
+ global_do_util = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "utilization percentage for all disks", global_do_util);
+ global_do_backlog = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "backlog for all disks", global_do_backlog);
+ global_do_bcache = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "bcache for all disks", global_do_bcache);
+ global_bcache_priority_stats_update_every = (int)config_get_number(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "bcache priority stats update every", global_bcache_priority_stats_update_every);
+
+ global_cleanup_removed_disks = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "remove charts of removed disks" , global_cleanup_removed_disks);
+
+ char buffer[FILENAME_MAX + 1];
+
+ snprintfz(buffer, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/block/%s");
+ path_to_sys_block_device = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to get block device", buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/block/%s/bcache");
+ path_to_sys_block_device_bcache = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to get block device bcache", buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/virtual/block/%s");
+ path_to_sys_devices_virtual_block_device = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to get virtual block device", buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/dev/block/%lu:%lu/%s");
+ path_to_sys_dev_block_major_minor_string = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to get block device infos", buffer);
+
+ //snprintfz(buffer, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/block/%s/queue/hw_sector_size");
+ //path_to_get_hw_sector_size = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to get h/w sector size", buffer);
+
+ //snprintfz(buffer, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/dev/block/%lu:%lu/subsystem/%s/../queue/hw_sector_size");
+ //path_to_get_hw_sector_size_partitions = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to get h/w sector size for partitions", buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s/dev/mapper", netdata_configured_host_prefix);
+ path_to_device_mapper = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to device mapper", buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s/dev/disk/by-label", netdata_configured_host_prefix);
+ path_to_device_label = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to /dev/disk/by-label", buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s/dev/disk/by-id", netdata_configured_host_prefix);
+ path_to_device_id = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to /dev/disk/by-id", buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "%s/dev/vx/dsk", netdata_configured_host_prefix);
+ path_to_veritas_volume_groups = config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "path to /dev/vx/dsk", buffer);
+
+ name_disks_by_id = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "name disks by id", name_disks_by_id);
+
+ preferred_ids = simple_pattern_create(
+ config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "preferred disk ids", DEFAULT_PREFERRED_IDS)
+ , NULL
+ , SIMPLE_PATTERN_EXACT
+ );
+
+ excluded_disks = simple_pattern_create(
+ config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "exclude disks", DEFAULT_EXCLUDED_DISKS)
+ , NULL
+ , SIMPLE_PATTERN_EXACT
+ );
+ }
+
+ // --------------------------------------------------------------------------
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/diskstats");
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
+ }
+ if(unlikely(!ff)) return 0;
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ collected_number system_read_kb = 0, system_write_kb = 0;
+
+ for(l = 0; l < lines ;l++) {
+ // --------------------------------------------------------------------------
+ // Read parameters
+
+ char *disk;
+ unsigned long major = 0, minor = 0;
+
+ collected_number reads = 0, mreads = 0, readsectors = 0, readms = 0,
+ writes = 0, mwrites = 0, writesectors = 0, writems = 0,
+ queued_ios = 0, busy_ms = 0, backlog_ms = 0;
+
+ collected_number last_reads = 0, last_readsectors = 0, last_readms = 0,
+ last_writes = 0, last_writesectors = 0, last_writems = 0,
+ last_busy_ms = 0;
+
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 14)) continue;
+
+ major = str2ul(procfile_lineword(ff, l, 0));
+ minor = str2ul(procfile_lineword(ff, l, 1));
+ disk = procfile_lineword(ff, l, 2);
+
+ // # of reads completed # of writes completed
+ // This is the total number of reads or writes completed successfully.
+ reads = str2ull(procfile_lineword(ff, l, 3)); // rd_ios
+ writes = str2ull(procfile_lineword(ff, l, 7)); // wr_ios
+
+ // # of reads merged # of writes merged
+ // Reads and writes which are adjacent to each other may be merged for
+ // efficiency. Thus two 4K reads may become one 8K read before it is
+ // ultimately handed to the disk, and so it will be counted (and queued)
+ mreads = str2ull(procfile_lineword(ff, l, 4)); // rd_merges_or_rd_sec
+ mwrites = str2ull(procfile_lineword(ff, l, 8)); // wr_merges
+
+ // # of sectors read # of sectors written
+ // This is the total number of sectors read or written successfully.
+ readsectors = str2ull(procfile_lineword(ff, l, 5)); // rd_sec_or_wr_ios
+ writesectors = str2ull(procfile_lineword(ff, l, 9)); // wr_sec
+
+ // # of milliseconds spent reading # of milliseconds spent writing
+ // This is the total number of milliseconds spent by all reads or writes (as
+ // measured from __make_request() to end_that_request_last()).
+ readms = str2ull(procfile_lineword(ff, l, 6)); // rd_ticks_or_wr_sec
+ writems = str2ull(procfile_lineword(ff, l, 10)); // wr_ticks
+
+ // # of I/Os currently in progress
+ // The only field that should go to zero. Incremented as requests are
+ // given to appropriate struct request_queue and decremented as they finish.
+ queued_ios = str2ull(procfile_lineword(ff, l, 11)); // ios_pgr
+
+ // # of milliseconds spent doing I/Os
+ // This field increases so long as field queued_ios is nonzero.
+ busy_ms = str2ull(procfile_lineword(ff, l, 12)); // tot_ticks
+
+ // weighted # of milliseconds spent doing I/Os
+ // This field is incremented at each I/O start, I/O completion, I/O
+ // merge, or read of these stats by the number of I/Os in progress
+ // (field queued_ios) times the number of milliseconds spent doing I/O since the
+ // last update of this field. This can provide an easy measure of both
+ // I/O completion time and the backlog that may be accumulating.
+ backlog_ms = str2ull(procfile_lineword(ff, l, 13)); // rq_ticks
+
+
+ // --------------------------------------------------------------------------
+ // remove slashes from disk names
+ char *s;
+ for(s = disk; *s ;s++)
+ if(*s == '/') *s = '_';
+
+ // --------------------------------------------------------------------------
+ // get a disk structure for the disk
+
+ struct disk *d = get_disk(major, minor, disk);
+ d->updated = 1;
+
+ // --------------------------------------------------------------------------
+ // count the global system disk I/O of physical disks
+
+ if(unlikely(d->type == DISK_TYPE_PHYSICAL)) {
+ system_read_kb += readsectors * d->sector_size / 1024;
+ system_write_kb += writesectors * d->sector_size / 1024;
+ }
+
+ // --------------------------------------------------------------------------
+ // Set its family based on mount point
+
+ char *family = d->mount_point;
+ if(!family) family = d->disk;
+
+
+ // --------------------------------------------------------------------------
+ // Do performance metrics
+
+ if(d->do_io == CONFIG_BOOLEAN_YES || (d->do_io == CONFIG_BOOLEAN_AUTO &&
+ (readsectors || writesectors ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ d->do_io = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!d->st_io)) {
+ d->st_io = rrdset_create_localhost(
+ RRD_TYPE_DISK
+ , d->device
+ , d->disk
+ , family
+ , "disk.io"
+ , "Disk I/O Bandwidth"
+ , "KiB/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_IO
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ d->rd_io_reads = rrddim_add(d->st_io, "reads", NULL, d->sector_size, 1024, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_io_writes = rrddim_add(d->st_io, "writes", NULL, d->sector_size * -1, 1024, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_io);
+
+ last_readsectors = rrddim_set_by_pointer(d->st_io, d->rd_io_reads, readsectors);
+ last_writesectors = rrddim_set_by_pointer(d->st_io, d->rd_io_writes, writesectors);
+ rrdset_done(d->st_io);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(d->do_ops == CONFIG_BOOLEAN_YES || (d->do_ops == CONFIG_BOOLEAN_AUTO &&
+ (reads || writes || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ d->do_ops = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!d->st_ops)) {
+ d->st_ops = rrdset_create_localhost(
+ "disk_ops"
+ , d->device
+ , d->disk
+ , family
+ , "disk.ops"
+ , "Disk Completed I/O Operations"
+ , "operations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_OPS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_ops, RRDSET_FLAG_DETAIL);
+
+ d->rd_ops_reads = rrddim_add(d->st_ops, "reads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_ops_writes = rrddim_add(d->st_ops, "writes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_ops);
+
+ last_reads = rrddim_set_by_pointer(d->st_ops, d->rd_ops_reads, reads);
+ last_writes = rrddim_set_by_pointer(d->st_ops, d->rd_ops_writes, writes);
+ rrdset_done(d->st_ops);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(d->do_qops == CONFIG_BOOLEAN_YES || (d->do_qops == CONFIG_BOOLEAN_AUTO &&
+ (queued_ios || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ d->do_qops = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!d->st_qops)) {
+ d->st_qops = rrdset_create_localhost(
+ "disk_qops"
+ , d->device
+ , d->disk
+ , family
+ , "disk.qops"
+ , "Disk Current I/O Operations"
+ , "operations"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_QOPS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_qops, RRDSET_FLAG_DETAIL);
+
+ d->rd_qops_operations = rrddim_add(d->st_qops, "operations", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_qops);
+
+ rrddim_set_by_pointer(d->st_qops, d->rd_qops_operations, queued_ios);
+ rrdset_done(d->st_qops);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(d->do_backlog == CONFIG_BOOLEAN_YES || (d->do_backlog == CONFIG_BOOLEAN_AUTO &&
+ (backlog_ms || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ d->do_backlog = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!d->st_backlog)) {
+ d->st_backlog = rrdset_create_localhost(
+ "disk_backlog"
+ , d->device
+ , d->disk
+ , family
+ , "disk.backlog"
+ , "Disk Backlog"
+ , "milliseconds"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_BACKLOG
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrdset_flag_set(d->st_backlog, RRDSET_FLAG_DETAIL);
+
+ d->rd_backlog_backlog = rrddim_add(d->st_backlog, "backlog", NULL, 1, 10, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_backlog);
+
+ rrddim_set_by_pointer(d->st_backlog, d->rd_backlog_backlog, backlog_ms);
+ rrdset_done(d->st_backlog);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(d->do_util == CONFIG_BOOLEAN_YES || (d->do_util == CONFIG_BOOLEAN_AUTO &&
+ (busy_ms || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ d->do_util = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!d->st_util)) {
+ d->st_util = rrdset_create_localhost(
+ "disk_util"
+ , d->device
+ , d->disk
+ , family
+ , "disk.util"
+ , "Disk Utilization Time"
+ , "% of time working"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_UTIL
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrdset_flag_set(d->st_util, RRDSET_FLAG_DETAIL);
+
+ d->rd_util_utilization = rrddim_add(d->st_util, "utilization", NULL, 1, 10, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_util);
+
+ last_busy_ms = rrddim_set_by_pointer(d->st_util, d->rd_util_utilization, busy_ms);
+ rrdset_done(d->st_util);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(d->do_mops == CONFIG_BOOLEAN_YES || (d->do_mops == CONFIG_BOOLEAN_AUTO &&
+ (mreads || mwrites || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ d->do_mops = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!d->st_mops)) {
+ d->st_mops = rrdset_create_localhost(
+ "disk_mops"
+ , d->device
+ , d->disk
+ , family
+ , "disk.mops"
+ , "Disk Merged Operations"
+ , "merged operations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_MOPS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_mops, RRDSET_FLAG_DETAIL);
+
+ d->rd_mops_reads = rrddim_add(d->st_mops, "reads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_mops_writes = rrddim_add(d->st_mops, "writes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_mops);
+
+ rrddim_set_by_pointer(d->st_mops, d->rd_mops_reads, mreads);
+ rrddim_set_by_pointer(d->st_mops, d->rd_mops_writes, mwrites);
+ rrdset_done(d->st_mops);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(d->do_iotime == CONFIG_BOOLEAN_YES || (d->do_iotime == CONFIG_BOOLEAN_AUTO &&
+ (readms || writems || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ d->do_iotime = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!d->st_iotime)) {
+ d->st_iotime = rrdset_create_localhost(
+ "disk_iotime"
+ , d->device
+ , d->disk
+ , family
+ , "disk.iotime"
+ , "Disk Total I/O Time"
+ , "milliseconds/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_IOTIME
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_iotime, RRDSET_FLAG_DETAIL);
+
+ d->rd_iotime_reads = rrddim_add(d->st_iotime, "reads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_iotime_writes = rrddim_add(d->st_iotime, "writes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_iotime);
+
+ last_readms = rrddim_set_by_pointer(d->st_iotime, d->rd_iotime_reads, readms);
+ last_writems = rrddim_set_by_pointer(d->st_iotime, d->rd_iotime_writes, writems);
+ rrdset_done(d->st_iotime);
+ }
+
+ // --------------------------------------------------------------------
+ // calculate differential charts
+ // only if this is not the first time we run
+
+ if(likely(dt)) {
+ if( (d->do_iotime == CONFIG_BOOLEAN_YES || (d->do_iotime == CONFIG_BOOLEAN_AUTO &&
+ (readms || writems ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) &&
+ (d->do_ops == CONFIG_BOOLEAN_YES || (d->do_ops == CONFIG_BOOLEAN_AUTO &&
+ (reads || writes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))) {
+
+ if(unlikely(!d->st_await)) {
+ d->st_await = rrdset_create_localhost(
+ "disk_await"
+ , d->device
+ , d->disk
+ , family
+ , "disk.await"
+ , "Average Completed I/O Operation Time"
+ , "milliseconds/operation"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_AWAIT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_await, RRDSET_FLAG_DETAIL);
+
+ d->rd_await_reads = rrddim_add(d->st_await, "reads", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_await_writes = rrddim_add(d->st_await, "writes", NULL, -1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_await);
+
+ rrddim_set_by_pointer(d->st_await, d->rd_await_reads, (reads - last_reads) ? (readms - last_readms) / (reads - last_reads) : 0);
+ rrddim_set_by_pointer(d->st_await, d->rd_await_writes, (writes - last_writes) ? (writems - last_writems) / (writes - last_writes) : 0);
+ rrdset_done(d->st_await);
+ }
+
+ if( (d->do_io == CONFIG_BOOLEAN_YES || (d->do_io == CONFIG_BOOLEAN_AUTO &&
+ (readsectors || writesectors || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) &&
+ (d->do_ops == CONFIG_BOOLEAN_YES || (d->do_ops == CONFIG_BOOLEAN_AUTO &&
+ (reads || writes || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))) {
+
+ if(unlikely(!d->st_avgsz)) {
+ d->st_avgsz = rrdset_create_localhost(
+ "disk_avgsz"
+ , d->device
+ , d->disk
+ , family
+ , "disk.avgsz"
+ , "Average Completed I/O Operation Bandwidth"
+ , "KiB/operation"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_AVGSZ
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrdset_flag_set(d->st_avgsz, RRDSET_FLAG_DETAIL);
+
+ d->rd_avgsz_reads = rrddim_add(d->st_avgsz, "reads", NULL, d->sector_size, 1024, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_avgsz_writes = rrddim_add(d->st_avgsz, "writes", NULL, d->sector_size * -1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_avgsz);
+
+ rrddim_set_by_pointer(d->st_avgsz, d->rd_avgsz_reads, (reads - last_reads) ? (readsectors - last_readsectors) / (reads - last_reads) : 0);
+ rrddim_set_by_pointer(d->st_avgsz, d->rd_avgsz_writes, (writes - last_writes) ? (writesectors - last_writesectors) / (writes - last_writes) : 0);
+ rrdset_done(d->st_avgsz);
+ }
+
+ if( (d->do_util == CONFIG_BOOLEAN_YES || (d->do_util == CONFIG_BOOLEAN_AUTO &&
+ (busy_ms ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) &&
+ (d->do_ops == CONFIG_BOOLEAN_YES || (d->do_ops == CONFIG_BOOLEAN_AUTO &&
+ (reads || writes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))) {
+
+ if(unlikely(!d->st_svctm)) {
+ d->st_svctm = rrdset_create_localhost(
+ "disk_svctm"
+ , d->device
+ , d->disk
+ , family
+ , "disk.svctm"
+ , "Average Service Time"
+ , "milliseconds/operation"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_DISK_SVCTM
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_svctm, RRDSET_FLAG_DETAIL);
+
+ d->rd_svctm_svctm = rrddim_add(d->st_svctm, "svctm", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_svctm);
+
+ rrddim_set_by_pointer(d->st_svctm, d->rd_svctm_svctm, ((reads - last_reads) + (writes - last_writes)) ? (busy_ms - last_busy_ms) / ((reads - last_reads) + (writes - last_writes)) : 0);
+ rrdset_done(d->st_svctm);
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // read bcache metrics and generate the bcache charts
+
+ if(d->device_is_bcache && d->do_bcache != CONFIG_BOOLEAN_NO) {
+ unsigned long long int
+ stats_total_cache_bypass_hits = 0,
+ stats_total_cache_bypass_misses = 0,
+ stats_total_cache_hits = 0,
+ stats_total_cache_miss_collisions = 0,
+ stats_total_cache_misses = 0,
+ stats_five_minute_cache_hit_ratio = 0,
+ stats_hour_cache_hit_ratio = 0,
+ stats_day_cache_hit_ratio = 0,
+ stats_total_cache_hit_ratio = 0,
+ cache_available_percent = 0,
+ cache_readaheads = 0,
+ cache_read_races = 0,
+ cache_io_errors = 0,
+ cache_congested = 0,
+ dirty_data = 0,
+ writeback_rate = 0;
+
+ // read the bcache values
+
+ if(d->bcache_filename_dirty_data)
+ dirty_data = bcache_read_number_with_units(d->bcache_filename_dirty_data);
+
+ if(d->bcache_filename_writeback_rate)
+ writeback_rate = bcache_read_number_with_units(d->bcache_filename_writeback_rate);
+
+ if(d->bcache_filename_cache_congested)
+ cache_congested = bcache_read_number_with_units(d->bcache_filename_cache_congested);
+
+ if(d->bcache_filename_cache_available_percent)
+ read_single_number_file(d->bcache_filename_cache_available_percent, &cache_available_percent);
+
+ if(d->bcache_filename_stats_five_minute_cache_hit_ratio)
+ read_single_number_file(d->bcache_filename_stats_five_minute_cache_hit_ratio, &stats_five_minute_cache_hit_ratio);
+
+ if(d->bcache_filename_stats_hour_cache_hit_ratio)
+ read_single_number_file(d->bcache_filename_stats_hour_cache_hit_ratio, &stats_hour_cache_hit_ratio);
+
+ if(d->bcache_filename_stats_day_cache_hit_ratio)
+ read_single_number_file(d->bcache_filename_stats_day_cache_hit_ratio, &stats_day_cache_hit_ratio);
+
+ if(d->bcache_filename_stats_total_cache_hit_ratio)
+ read_single_number_file(d->bcache_filename_stats_total_cache_hit_ratio, &stats_total_cache_hit_ratio);
+
+ if(d->bcache_filename_stats_total_cache_hits)
+ read_single_number_file(d->bcache_filename_stats_total_cache_hits, &stats_total_cache_hits);
+
+ if(d->bcache_filename_stats_total_cache_misses)
+ read_single_number_file(d->bcache_filename_stats_total_cache_misses, &stats_total_cache_misses);
+
+ if(d->bcache_filename_stats_total_cache_miss_collisions)
+ read_single_number_file(d->bcache_filename_stats_total_cache_miss_collisions, &stats_total_cache_miss_collisions);
+
+ if(d->bcache_filename_stats_total_cache_bypass_hits)
+ read_single_number_file(d->bcache_filename_stats_total_cache_bypass_hits, &stats_total_cache_bypass_hits);
+
+ if(d->bcache_filename_stats_total_cache_bypass_misses)
+ read_single_number_file(d->bcache_filename_stats_total_cache_bypass_misses, &stats_total_cache_bypass_misses);
+
+ if(d->bcache_filename_stats_total_cache_readaheads)
+ cache_readaheads = bcache_read_number_with_units(d->bcache_filename_stats_total_cache_readaheads);
+
+ if(d->bcache_filename_cache_read_races)
+ read_single_number_file(d->bcache_filename_cache_read_races, &cache_read_races);
+
+ if(d->bcache_filename_cache_io_errors)
+ read_single_number_file(d->bcache_filename_cache_io_errors, &cache_io_errors);
+
+ if(d->bcache_filename_priority_stats && global_bcache_priority_stats_update_every >= 1)
+ bcache_read_priority_stats(d, family, global_bcache_priority_stats_update_every, dt);
+
+ // update the charts
+
+ {
+ if(unlikely(!d->st_bcache_hit_ratio)) {
+ d->st_bcache_hit_ratio = rrdset_create_localhost(
+ "disk_bcache_hit_ratio"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache_hit_ratio"
+ , "BCache Cache Hit Ratio"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_HIT_RATIO
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ d->rd_bcache_hit_ratio_5min = rrddim_add(d->st_bcache_hit_ratio, "5min", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_hit_ratio_1hour = rrddim_add(d->st_bcache_hit_ratio, "1hour", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_hit_ratio_1day = rrddim_add(d->st_bcache_hit_ratio, "1day", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_hit_ratio_total = rrddim_add(d->st_bcache_hit_ratio, "ever", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_bcache_hit_ratio);
+
+ rrddim_set_by_pointer(d->st_bcache_hit_ratio, d->rd_bcache_hit_ratio_5min, stats_five_minute_cache_hit_ratio);
+ rrddim_set_by_pointer(d->st_bcache_hit_ratio, d->rd_bcache_hit_ratio_1hour, stats_hour_cache_hit_ratio);
+ rrddim_set_by_pointer(d->st_bcache_hit_ratio, d->rd_bcache_hit_ratio_1day, stats_day_cache_hit_ratio);
+ rrddim_set_by_pointer(d->st_bcache_hit_ratio, d->rd_bcache_hit_ratio_total, stats_total_cache_hit_ratio);
+ rrdset_done(d->st_bcache_hit_ratio);
+ }
+
+ {
+
+ if(unlikely(!d->st_bcache_rates)) {
+ d->st_bcache_rates = rrdset_create_localhost(
+ "disk_bcache_rates"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache_rates"
+ , "BCache Rates"
+ , "KiB/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_RATES
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ d->rd_bcache_rate_congested = rrddim_add(d->st_bcache_rates, "congested", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_bcache_rate_writeback = rrddim_add(d->st_bcache_rates, "writeback", NULL, -1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_bcache_rates);
+
+ rrddim_set_by_pointer(d->st_bcache_rates, d->rd_bcache_rate_writeback, writeback_rate);
+ rrddim_set_by_pointer(d->st_bcache_rates, d->rd_bcache_rate_congested, cache_congested);
+ rrdset_done(d->st_bcache_rates);
+ }
+
+ {
+ if(unlikely(!d->st_bcache_size)) {
+ d->st_bcache_size = rrdset_create_localhost(
+ "disk_bcache_size"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache_size"
+ , "BCache Cache Sizes"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_SIZE
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ d->rd_bcache_dirty_size = rrddim_add(d->st_bcache_size, "dirty", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_bcache_size);
+
+ rrddim_set_by_pointer(d->st_bcache_size, d->rd_bcache_dirty_size, dirty_data);
+ rrdset_done(d->st_bcache_size);
+ }
+
+ {
+ if(unlikely(!d->st_bcache_usage)) {
+ d->st_bcache_usage = rrdset_create_localhost(
+ "disk_bcache_usage"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache_usage"
+ , "BCache Cache Usage"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_USAGE
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ d->rd_bcache_available_percent = rrddim_add(d->st_bcache_usage, "avail", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(d->st_bcache_usage);
+
+ rrddim_set_by_pointer(d->st_bcache_usage, d->rd_bcache_available_percent, cache_available_percent);
+ rrdset_done(d->st_bcache_usage);
+ }
+
+ {
+
+ if(unlikely(!d->st_bcache_cache_read_races)) {
+ d->st_bcache_cache_read_races = rrdset_create_localhost(
+ "disk_bcache_cache_read_races"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache_cache_read_races"
+ , "BCache Cache Read Races"
+ , "operations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_CACHE_READ_RACES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ d->rd_bcache_cache_read_races = rrddim_add(d->st_bcache_cache_read_races, "races", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_bcache_cache_io_errors = rrddim_add(d->st_bcache_cache_read_races, "errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_bcache_cache_read_races);
+
+ rrddim_set_by_pointer(d->st_bcache_cache_read_races, d->rd_bcache_cache_read_races, cache_read_races);
+ rrddim_set_by_pointer(d->st_bcache_cache_read_races, d->rd_bcache_cache_io_errors, cache_io_errors);
+ rrdset_done(d->st_bcache_cache_read_races);
+ }
+
+ if(d->do_bcache == CONFIG_BOOLEAN_YES || (d->do_bcache == CONFIG_BOOLEAN_AUTO &&
+ (stats_total_cache_hits ||
+ stats_total_cache_misses ||
+ stats_total_cache_miss_collisions ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+
+ if(unlikely(!d->st_bcache)) {
+ d->st_bcache = rrdset_create_localhost(
+ "disk_bcache"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache"
+ , "BCache Cache I/O Operations"
+ , "operations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_OPS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_bcache, RRDSET_FLAG_DETAIL);
+
+ d->rd_bcache_hits = rrddim_add(d->st_bcache, "hits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_bcache_misses = rrddim_add(d->st_bcache, "misses", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_bcache_miss_collisions = rrddim_add(d->st_bcache, "collisions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_bcache_readaheads = rrddim_add(d->st_bcache, "readaheads", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_bcache);
+
+ rrddim_set_by_pointer(d->st_bcache, d->rd_bcache_hits, stats_total_cache_hits);
+ rrddim_set_by_pointer(d->st_bcache, d->rd_bcache_misses, stats_total_cache_misses);
+ rrddim_set_by_pointer(d->st_bcache, d->rd_bcache_miss_collisions, stats_total_cache_miss_collisions);
+ rrddim_set_by_pointer(d->st_bcache, d->rd_bcache_readaheads, cache_readaheads);
+ rrdset_done(d->st_bcache);
+ }
+
+ if(d->do_bcache == CONFIG_BOOLEAN_YES || (d->do_bcache == CONFIG_BOOLEAN_AUTO &&
+ (stats_total_cache_bypass_hits ||
+ stats_total_cache_bypass_misses ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+
+ if(unlikely(!d->st_bcache_bypass)) {
+ d->st_bcache_bypass = rrdset_create_localhost(
+ "disk_bcache_bypass"
+ , d->device
+ , d->disk
+ , family
+ , "disk.bcache_bypass"
+ , "BCache Cache Bypass I/O Operations"
+ , "operations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_BCACHE_BYPASS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_bcache_bypass, RRDSET_FLAG_DETAIL);
+
+ d->rd_bcache_bypass_hits = rrddim_add(d->st_bcache_bypass, "hits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_bcache_bypass_misses = rrddim_add(d->st_bcache_bypass, "misses", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_bcache_bypass);
+
+ rrddim_set_by_pointer(d->st_bcache_bypass, d->rd_bcache_bypass_hits, stats_total_cache_bypass_hits);
+ rrddim_set_by_pointer(d->st_bcache_bypass, d->rd_bcache_bypass_misses, stats_total_cache_bypass_misses);
+ rrdset_done(d->st_bcache_bypass);
+ }
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // update the system total I/O
+
+ if(global_do_io == CONFIG_BOOLEAN_YES || (global_do_io == CONFIG_BOOLEAN_AUTO &&
+ (system_read_kb || system_write_kb ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ static RRDSET *st_io = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_io)) {
+ st_io = rrdset_create_localhost(
+ "system"
+ , "io"
+ , NULL
+ , "disk"
+ , NULL
+ , "Disk I/O"
+ , "KiB/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_DISKSTATS_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_IO
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_in = rrddim_add(st_io, "in", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_io, "out", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_io);
+
+ rrddim_set_by_pointer(st_io, rd_in, system_read_kb);
+ rrddim_set_by_pointer(st_io, rd_out, system_write_kb);
+ rrdset_done(st_io);
+ }
+
+
+ // ------------------------------------------------------------------------
+ // cleanup removed disks
+
+ struct disk *d = disk_root, *last = NULL;
+ while(d) {
+ if(unlikely(global_cleanup_removed_disks && !d->updated)) {
+ struct disk *t = d;
+
+ rrdset_obsolete_and_pointer_null(d->st_avgsz);
+ rrdset_obsolete_and_pointer_null(d->st_await);
+ rrdset_obsolete_and_pointer_null(d->st_backlog);
+ rrdset_obsolete_and_pointer_null(d->st_io);
+ rrdset_obsolete_and_pointer_null(d->st_iotime);
+ rrdset_obsolete_and_pointer_null(d->st_mops);
+ rrdset_obsolete_and_pointer_null(d->st_ops);
+ rrdset_obsolete_and_pointer_null(d->st_qops);
+ rrdset_obsolete_and_pointer_null(d->st_svctm);
+ rrdset_obsolete_and_pointer_null(d->st_util);
+ rrdset_obsolete_and_pointer_null(d->st_bcache);
+ rrdset_obsolete_and_pointer_null(d->st_bcache_bypass);
+ rrdset_obsolete_and_pointer_null(d->st_bcache_rates);
+ rrdset_obsolete_and_pointer_null(d->st_bcache_size);
+ rrdset_obsolete_and_pointer_null(d->st_bcache_usage);
+ rrdset_obsolete_and_pointer_null(d->st_bcache_hit_ratio);
+
+ if(d == disk_root) {
+ disk_root = d = d->next;
+ last = NULL;
+ }
+ else if(last) {
+ last->next = d = d->next;
+ }
+
+ freez(t->bcache_filename_dirty_data);
+ freez(t->bcache_filename_writeback_rate);
+ freez(t->bcache_filename_cache_congested);
+ freez(t->bcache_filename_cache_available_percent);
+ freez(t->bcache_filename_stats_five_minute_cache_hit_ratio);
+ freez(t->bcache_filename_stats_hour_cache_hit_ratio);
+ freez(t->bcache_filename_stats_day_cache_hit_ratio);
+ freez(t->bcache_filename_stats_total_cache_hit_ratio);
+ freez(t->bcache_filename_stats_total_cache_hits);
+ freez(t->bcache_filename_stats_total_cache_misses);
+ freez(t->bcache_filename_stats_total_cache_miss_collisions);
+ freez(t->bcache_filename_stats_total_cache_bypass_hits);
+ freez(t->bcache_filename_stats_total_cache_bypass_misses);
+ freez(t->bcache_filename_stats_total_cache_readaheads);
+ freez(t->bcache_filename_cache_read_races);
+ freez(t->bcache_filename_cache_io_errors);
+ freez(t->bcache_filename_priority_stats);
+
+ freez(t->disk);
+ freez(t->device);
+ freez(t->mount_point);
+ freez(t);
+ }
+ else {
+ d->updated = 0;
+ last = d;
+ d = d->next;
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_interrupts.c b/collectors/proc.plugin/proc_interrupts.c
new file mode 100644
index 0000000..73b1171
--- /dev/null
+++ b/collectors/proc.plugin/proc_interrupts.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_INTERRUPTS_NAME "/proc/interrupts"
+#define CONFIG_SECTION_PLUGIN_PROC_INTERRUPTS "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_INTERRUPTS_NAME
+
+#define MAX_INTERRUPT_NAME 50
+
+struct cpu_interrupt {
+ unsigned long long value;
+ RRDDIM *rd;
+};
+
+struct interrupt {
+ int used;
+ char *id;
+ char name[MAX_INTERRUPT_NAME + 1];
+ RRDDIM *rd;
+ unsigned long long total;
+ struct cpu_interrupt cpu[];
+};
+
+// since each interrupt is variable in size
+// we use this to calculate its record size
+#define recordsize(cpus) (sizeof(struct interrupt) + ((cpus) * sizeof(struct cpu_interrupt)))
+
+// given a base, get a pointer to each record
+#define irrindex(base, line, cpus) ((struct interrupt *)&((char *)(base))[(line) * recordsize(cpus)])
+
+static inline struct interrupt *get_interrupts_array(size_t lines, int cpus) {
+ static struct interrupt *irrs = NULL;
+ static size_t allocated = 0;
+
+ if(unlikely(lines != allocated)) {
+ size_t l;
+ int c;
+
+ irrs = (struct interrupt *)reallocz(irrs, lines * recordsize(cpus));
+
+ // reset all interrupt RRDDIM pointers as any line could have shifted
+ for(l = 0; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ irr->rd = NULL;
+ irr->name[0] = '\0';
+ for(c = 0; c < cpus ;c++)
+ irr->cpu[c].rd = NULL;
+ }
+
+ allocated = lines;
+ }
+
+ return irrs;
+}
+
+int do_proc_interrupts(int update_every, usec_t dt) {
+ (void)dt;
+ static procfile *ff = NULL;
+ static int cpus = -1, do_per_core = CONFIG_BOOLEAN_INVALID;
+ struct interrupt *irrs = NULL;
+
+ if(unlikely(do_per_core == CONFIG_BOOLEAN_INVALID))
+ do_per_core = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_INTERRUPTS, "interrupts per core", CONFIG_BOOLEAN_AUTO);
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/interrupts");
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_INTERRUPTS, "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
+ }
+ if(unlikely(!ff))
+ return 1;
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+ size_t words = procfile_linewords(ff, 0);
+
+ if(unlikely(!lines)) {
+ error("Cannot read /proc/interrupts, zero lines reported.");
+ return 1;
+ }
+
+ // find how many CPUs are there
+ if(unlikely(cpus == -1)) {
+ uint32_t w;
+ cpus = 0;
+ for(w = 0; w < words ; w++) {
+ if(likely(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0))
+ cpus++;
+ }
+ }
+
+ if(unlikely(!cpus)) {
+ error("PLUGIN: PROC_INTERRUPTS: Cannot find the number of CPUs in /proc/interrupts");
+ return 1;
+ }
+
+ // allocate the size we need;
+ irrs = get_interrupts_array(lines, cpus);
+ irrs[0].used = 0;
+
+ // loop through all lines
+ for(l = 1; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ irr->used = 0;
+ irr->total = 0;
+
+ words = procfile_linewords(ff, l);
+ if(unlikely(!words)) continue;
+
+ irr->id = procfile_lineword(ff, l, 0);
+ if(unlikely(!irr->id || !irr->id[0])) continue;
+
+ size_t idlen = strlen(irr->id);
+ if(irr->id[idlen - 1] == ':')
+ irr->id[--idlen] = '\0';
+
+ int c;
+ for(c = 0; c < cpus ;c++) {
+ if(likely((c + 1) < (int)words))
+ irr->cpu[c].value = str2ull(procfile_lineword(ff, l, (uint32_t)(c + 1)));
+ else
+ irr->cpu[c].value = 0;
+
+ irr->total += irr->cpu[c].value;
+ }
+
+ if(unlikely(isdigit(irr->id[0]) && (uint32_t)(cpus + 2) < words)) {
+ strncpyz(irr->name, procfile_lineword(ff, l, words - 1), MAX_INTERRUPT_NAME);
+ size_t nlen = strlen(irr->name);
+ if(likely(nlen + 1 + idlen <= MAX_INTERRUPT_NAME)) {
+ irr->name[nlen] = '_';
+ strncpyz(&irr->name[nlen + 1], irr->id, MAX_INTERRUPT_NAME - nlen - 1);
+ }
+ else {
+ irr->name[MAX_INTERRUPT_NAME - idlen - 1] = '_';
+ strncpyz(&irr->name[MAX_INTERRUPT_NAME - idlen], irr->id, idlen);
+ }
+ }
+ else {
+ strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME);
+ }
+
+ irr->used = 1;
+ }
+
+ // --------------------------------------------------------------------
+
+ static RRDSET *st_system_interrupts = NULL;
+ if(unlikely(!st_system_interrupts))
+ st_system_interrupts = rrdset_create_localhost(
+ "system"
+ , "interrupts"
+ , NULL
+ , "interrupts"
+ , NULL
+ , "System interrupts"
+ , "interrupts/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_INTERRUPTS_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_INTERRUPTS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ else
+ rrdset_next(st_system_interrupts);
+
+ for(l = 0; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(irr->used && irr->total) {
+ // some interrupt may have changed without changing the total number of lines
+ // if the same number of interrupts have been added and removed between two
+ // calls of this function.
+ if(unlikely(!irr->rd || strncmp(irr->rd->name, irr->name, MAX_INTERRUPT_NAME) != 0)) {
+ irr->rd = rrddim_add(st_system_interrupts, irr->id, irr->name, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_set_name(st_system_interrupts, irr->rd, irr->name);
+
+ // also reset per cpu RRDDIMs to avoid repeating strncmp() in the per core loop
+ if(likely(do_per_core != CONFIG_BOOLEAN_NO)) {
+ int c;
+ for(c = 0; c < cpus; c++) irr->cpu[c].rd = NULL;
+ }
+ }
+
+ rrddim_set_by_pointer(st_system_interrupts, irr->rd, irr->total);
+ }
+ }
+
+ rrdset_done(st_system_interrupts);
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_per_core != CONFIG_BOOLEAN_NO)) {
+ static RRDSET **core_st = NULL;
+ static int old_cpus = 0;
+
+ if(old_cpus < cpus) {
+ core_st = reallocz(core_st, sizeof(RRDSET *) * cpus);
+ memset(&core_st[old_cpus], 0, sizeof(RRDSET *) * (cpus - old_cpus));
+ old_cpus = cpus;
+ }
+
+ int c;
+
+ for(c = 0; c < cpus ;c++) {
+ if(unlikely(!core_st[c])) {
+ char id[50+1];
+ snprintfz(id, 50, "cpu%d_interrupts", c);
+
+ char title[100+1];
+ snprintfz(title, 100, "CPU%d Interrupts", c);
+ core_st[c] = rrdset_create_localhost(
+ "cpu"
+ , id
+ , NULL
+ , "interrupts"
+ , "cpu.interrupts"
+ , title
+ , "interrupts/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_INTERRUPTS_NAME
+ , NETDATA_CHART_PRIO_INTERRUPTS_PER_CORE + c
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(core_st[c]);
+
+ for(l = 0; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if(irr->used && (do_per_core == CONFIG_BOOLEAN_YES || irr->cpu[c].value)) {
+ if(unlikely(!irr->cpu[c].rd)) {
+ irr->cpu[c].rd = rrddim_add(core_st[c], irr->id, irr->name, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_set_name(core_st[c], irr->cpu[c].rd, irr->name);
+ }
+
+ rrddim_set_by_pointer(core_st[c], irr->cpu[c].rd, irr->cpu[c].value);
+ }
+ }
+
+ rrdset_done(core_st[c]);
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_loadavg.c b/collectors/proc.plugin/proc_loadavg.c
new file mode 100644
index 0000000..8b78ecc
--- /dev/null
+++ b/collectors/proc.plugin/proc_loadavg.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_LOADAVG_NAME "/proc/loadavg"
+#define CONFIG_SECTION_PLUGIN_PROC_LOADAVG "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_LOADAVG_NAME
+
+// linux calculates this once every 5 seconds
+#define MIN_LOADAVG_UPDATE_EVERY 5
+
+int do_proc_loadavg(int update_every, usec_t dt) {
+ static procfile *ff = NULL;
+ static int do_loadavg = -1, do_all_processes = -1;
+ static usec_t next_loadavg_dt = 0;
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/loadavg");
+
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_LOADAVG, "filename to monitor", filename), " \t,:|/", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff))
+ return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ if(unlikely(do_loadavg == -1)) {
+ do_loadavg = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_LOADAVG, "enable load average", 1);
+ do_all_processes = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_LOADAVG, "enable total processes", 1);
+ }
+
+ if(unlikely(procfile_lines(ff) < 1)) {
+ error("/proc/loadavg has no lines.");
+ return 1;
+ }
+ if(unlikely(procfile_linewords(ff, 0) < 6)) {
+ error("/proc/loadavg has less than 6 words in it.");
+ return 1;
+ }
+
+ double load1 = strtod(procfile_lineword(ff, 0, 0), NULL);
+ double load5 = strtod(procfile_lineword(ff, 0, 1), NULL);
+ double load15 = strtod(procfile_lineword(ff, 0, 2), NULL);
+
+ //unsigned long long running_processes = str2ull(procfile_lineword(ff, 0, 3));
+ unsigned long long active_processes = str2ull(procfile_lineword(ff, 0, 4));
+
+ //get system pid_max
+ unsigned long long max_processes = get_system_pid_max();
+ //
+ //unsigned long long next_pid = str2ull(procfile_lineword(ff, 0, 5));
+
+
+ // --------------------------------------------------------------------
+
+ if(next_loadavg_dt <= dt) {
+ if(likely(do_loadavg)) {
+ static RRDSET *load_chart = NULL;
+ static RRDDIM *rd_load1 = NULL, *rd_load5 = NULL, *rd_load15 = NULL;
+
+ if(unlikely(!load_chart)) {
+ load_chart = rrdset_create_localhost(
+ "system"
+ , "load"
+ , NULL
+ , "load"
+ , NULL
+ , "System Load Average"
+ , "load"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_LOADAVG_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_LOAD
+ , (update_every < MIN_LOADAVG_UPDATE_EVERY) ? MIN_LOADAVG_UPDATE_EVERY : update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_load1 = rrddim_add(load_chart, "load1", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_load5 = rrddim_add(load_chart, "load5", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_load15 = rrddim_add(load_chart, "load15", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(load_chart);
+
+ rrddim_set_by_pointer(load_chart, rd_load1, (collected_number) (load1 * 1000));
+ rrddim_set_by_pointer(load_chart, rd_load5, (collected_number) (load5 * 1000));
+ rrddim_set_by_pointer(load_chart, rd_load15, (collected_number) (load15 * 1000));
+ rrdset_done(load_chart);
+
+ next_loadavg_dt = load_chart->update_every * USEC_PER_SEC;
+ }
+ else next_loadavg_dt = MIN_LOADAVG_UPDATE_EVERY * USEC_PER_SEC;
+ }
+ else next_loadavg_dt -= dt;
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_all_processes)) {
+ static RRDSET *processes_chart = NULL;
+ static RRDDIM *rd_active = NULL;
+ static RRDSETVAR *rd_pidmax;
+
+ if(unlikely(!processes_chart)) {
+ processes_chart = rrdset_create_localhost(
+ "system"
+ , "active_processes"
+ , NULL
+ , "processes"
+ , NULL
+ , "System Active Processes"
+ , "processes"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_LOADAVG_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_ACTIVE_PROCESSES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_active = rrddim_add(processes_chart, "active", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_pidmax = rrdsetvar_custom_chart_variable_create(processes_chart, "pidmax");
+ }
+ else rrdset_next(processes_chart);
+
+ rrddim_set_by_pointer(processes_chart, rd_active, active_processes);
+ rrdsetvar_custom_chart_variable_set(rd_pidmax, max_processes);
+ rrdset_done(processes_chart);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_mdstat.c b/collectors/proc.plugin/proc_mdstat.c
new file mode 100644
index 0000000..e932453
--- /dev/null
+++ b/collectors/proc.plugin/proc_mdstat.c
@@ -0,0 +1,647 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_MDSTAT_NAME "/proc/mdstat"
+
+struct raid {
+ int redundant;
+ char *name;
+ uint32_t hash;
+
+ RRDDIM *rd_health;
+ unsigned long long failed_disks;
+
+ RRDSET *st_disks;
+ RRDDIM *rd_down;
+ RRDDIM *rd_inuse;
+ unsigned long long total_disks;
+ unsigned long long inuse_disks;
+
+ RRDSET *st_operation;
+ RRDDIM *rd_check;
+ RRDDIM *rd_resync;
+ RRDDIM *rd_recovery;
+ RRDDIM *rd_reshape;
+ unsigned long long check;
+ unsigned long long resync;
+ unsigned long long recovery;
+ unsigned long long reshape;
+
+ RRDSET *st_finish;
+ RRDDIM *rd_finish_in;
+ unsigned long long finish_in;
+
+ RRDSET *st_speed;
+ RRDDIM *rd_speed;
+ unsigned long long speed;
+
+ char *mismatch_cnt_filename;
+ RRDSET *st_mismatch_cnt;
+ RRDDIM *rd_mismatch_cnt;
+ unsigned long long mismatch_cnt;
+
+ RRDSET *st_nonredundant;
+ RRDDIM *rd_nonredundant;
+};
+
+struct old_raid {
+ int redundant;
+ char *name;
+ uint32_t hash;
+ int found;
+};
+
+static inline char *remove_trailing_chars(char *s, char c)
+{
+ while (*s) {
+ if (unlikely(*s == c)) {
+ *s = '\0';
+ }
+ s++;
+ }
+ return s;
+}
+
+static inline void make_chart_obsolete(char *name, const char *id_modifier)
+{
+ char id[50 + 1];
+ RRDSET *st = NULL;
+
+ if (likely(name && id_modifier)) {
+ snprintfz(id, 50, "mdstat.%s_%s", name, id_modifier);
+ st = rrdset_find_active_byname_localhost(id);
+ if (likely(st))
+ rrdset_is_obsolete(st);
+ }
+}
+
+int do_proc_mdstat(int update_every, usec_t dt)
+{
+ (void)dt;
+ static procfile *ff = NULL;
+ static int do_health = -1, do_nonredundant = -1, do_disks = -1, do_operations = -1, do_mismatch = -1,
+ do_mismatch_config = -1;
+ static int make_charts_obsolete = -1;
+ static char *mdstat_filename = NULL, *mismatch_cnt_filename = NULL;
+ static struct raid *raids = NULL;
+ static size_t raids_allocated = 0;
+ size_t raids_num = 0, raid_idx = 0, redundant_num = 0;
+ static struct old_raid *old_raids = NULL;
+ static size_t old_raids_allocated = 0;
+ size_t old_raid_idx = 0;
+
+ if (unlikely(do_health == -1)) {
+ do_health =
+ config_get_boolean("plugin:proc:/proc/mdstat", "faulty devices", CONFIG_BOOLEAN_YES);
+ do_nonredundant =
+ config_get_boolean("plugin:proc:/proc/mdstat", "nonredundant arrays availability", CONFIG_BOOLEAN_YES);
+ do_mismatch_config =
+ config_get_boolean_ondemand("plugin:proc:/proc/mdstat", "mismatch count", CONFIG_BOOLEAN_AUTO);
+ do_disks =
+ config_get_boolean("plugin:proc:/proc/mdstat", "disk stats", CONFIG_BOOLEAN_YES);
+ do_operations =
+ config_get_boolean("plugin:proc:/proc/mdstat", "operation status", CONFIG_BOOLEAN_YES);
+
+ make_charts_obsolete =
+ config_get_boolean("plugin:proc:/proc/mdstat", "make charts obsolete", CONFIG_BOOLEAN_YES);
+
+ char filename[FILENAME_MAX + 1];
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/mdstat");
+ mdstat_filename = config_get("plugin:proc:/proc/mdstat", "filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/block/%s/md/mismatch_cnt");
+ mismatch_cnt_filename = config_get("plugin:proc:/proc/mdstat", "mismatch_cnt filename to monitor", filename);
+ }
+
+ if (unlikely(!ff)) {
+ ff = procfile_open(mdstat_filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if (unlikely(!ff))
+ return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if (unlikely(!ff))
+ return 0; // we return 0, so that we will retry opening it next time
+
+ size_t lines = procfile_lines(ff);
+ size_t words = 0;
+
+ if (unlikely(lines < 2)) {
+ error("Cannot read /proc/mdstat. Expected 2 or more lines, read %zu.", lines);
+ return 1;
+ }
+
+ // find how many raids are there
+ size_t l;
+ raids_num = 0;
+ for (l = 1; l < lines - 2; l++) {
+ if (unlikely(procfile_lineword(ff, l, 1)[0] == 'a')) // check if the raid is active
+ raids_num++;
+ }
+
+ if (unlikely(!raids_num && !old_raids_allocated))
+ return 0; // we return 0, so that we will retry searching for raids next time
+
+ // allocate the memory we need;
+ if (unlikely(raids_num != raids_allocated)) {
+ for (raid_idx = 0; raid_idx < raids_allocated; raid_idx++) {
+ struct raid *raid = &raids[raid_idx];
+ freez(raid->name);
+ freez(raid->mismatch_cnt_filename);
+ }
+ if (raids_num) {
+ raids = (struct raid *)reallocz(raids, raids_num * sizeof(struct raid));
+ memset(raids, 0, raids_num * sizeof(struct raid));
+ } else {
+ freez(raids);
+ raids = NULL;
+ }
+ raids_allocated = raids_num;
+ }
+
+ // loop through all lines except the first and the last ones
+ for (l = 1, raid_idx = 0; l < (lines - 2) && raid_idx < raids_num; l++) {
+ struct raid *raid = &raids[raid_idx];
+ raid->redundant = 0;
+
+ words = procfile_linewords(ff, l);
+
+ if (unlikely(words < 2))
+ continue;
+
+ if (unlikely(procfile_lineword(ff, l, 1)[0] != 'a'))
+ continue;
+
+ if (unlikely(!raid->name)) {
+ raid->name = strdupz(procfile_lineword(ff, l, 0));
+ raid->hash = simple_hash(raid->name);
+ } else if (unlikely(strcmp(raid->name, procfile_lineword(ff, l, 0)))) {
+ freez(raid->name);
+ freez(raid->mismatch_cnt_filename);
+ memset(raid, 0, sizeof(struct raid));
+ raid->name = strdupz(procfile_lineword(ff, l, 0));
+ raid->hash = simple_hash(raid->name);
+ }
+
+ if (unlikely(!raid->name || !raid->name[0]))
+ continue;
+
+ raid_idx++;
+
+ // check if raid has disk status
+ l++;
+ words = procfile_linewords(ff, l);
+ if (words < 2 || procfile_lineword(ff, l, words - 1)[0] != '[')
+ continue;
+
+ // split inuse and total number of disks
+ if (likely(do_health || do_disks)) {
+ char *s = NULL, *str_total = NULL, *str_inuse = NULL;
+
+ s = procfile_lineword(ff, l, words - 2);
+ if (unlikely(s[0] != '[')) {
+ error("Cannot read /proc/mdstat raid health status. Unexpected format: missing opening bracket.");
+ continue;
+ }
+ str_total = ++s;
+ while (*s) {
+ if (unlikely(*s == '/')) {
+ *s = '\0';
+ str_inuse = s + 1;
+ } else if (unlikely(*s == ']')) {
+ *s = '\0';
+ break;
+ }
+ s++;
+ }
+ if (unlikely(str_total[0] == '\0' || !str_inuse || str_inuse[0] == '\0')) {
+ error("Cannot read /proc/mdstat raid health status. Unexpected format.");
+ continue;
+ }
+
+ raid->inuse_disks = str2ull(str_inuse);
+ raid->total_disks = str2ull(str_total);
+ raid->failed_disks = raid->total_disks - raid->inuse_disks;
+ }
+
+ raid->redundant = 1;
+ redundant_num++;
+ l++;
+
+ // check if any operation is performed on the raid
+ if (likely(do_operations)) {
+ char *s = NULL;
+
+ raid->check = 0;
+ raid->resync = 0;
+ raid->recovery = 0;
+ raid->reshape = 0;
+ raid->finish_in = 0;
+ raid->speed = 0;
+
+ words = procfile_linewords(ff, l);
+
+ if (likely(words < 2))
+ continue;
+
+ if (unlikely(procfile_lineword(ff, l, 0)[0] != '['))
+ continue;
+
+ if (unlikely(words < 7)) {
+ error("Cannot read /proc/mdstat line. Expected 7 params, read %zu.", words);
+ continue;
+ }
+
+ char *word;
+ word = procfile_lineword(ff, l, 3);
+ remove_trailing_chars(word, '%');
+
+ unsigned long long percentage = (unsigned long long)(str2ld(word, NULL) * 100);
+ // possible operations: check, resync, recovery, reshape
+ // 4-th character is unique for each operation so it is checked
+ switch (procfile_lineword(ff, l, 1)[3]) {
+ case 'c': // check
+ raid->check = percentage;
+ break;
+ case 'y': // resync
+ raid->resync = percentage;
+ break;
+ case 'o': // recovery
+ raid->recovery = percentage;
+ break;
+ case 'h': // reshape
+ raid->reshape = percentage;
+ break;
+ }
+
+ word = procfile_lineword(ff, l, 5);
+ s = remove_trailing_chars(word, 'm'); // remove trailing "min"
+
+ word += 7; // skip leading "finish="
+
+ if (likely(s > word))
+ raid->finish_in = (unsigned long long)(str2ld(word, NULL) * 60);
+
+ word = procfile_lineword(ff, l, 6);
+ s = remove_trailing_chars(word, 'K'); // remove trailing "K/sec"
+
+ word += 6; // skip leading "speed="
+
+ if (likely(s > word))
+ raid->speed = str2ull(word);
+ }
+ }
+
+ // read mismatch_cnt files
+ if (do_mismatch == -1) {
+ if (do_mismatch_config == CONFIG_BOOLEAN_AUTO) {
+ if (raids_num > 50)
+ do_mismatch = CONFIG_BOOLEAN_NO;
+ else
+ do_mismatch = CONFIG_BOOLEAN_YES;
+ } else
+ do_mismatch = do_mismatch_config;
+ }
+
+ if (likely(do_mismatch)) {
+ for (raid_idx = 0; raid_idx < raids_num; raid_idx++) {
+ char filename[FILENAME_MAX + 1];
+ struct raid *raid = &raids[raid_idx];
+
+ if (likely(raid->redundant)) {
+ if (unlikely(!raid->mismatch_cnt_filename)) {
+ snprintfz(filename, FILENAME_MAX, mismatch_cnt_filename, raid->name);
+ raid->mismatch_cnt_filename = strdupz(filename);
+ }
+ if (unlikely(read_single_number_file(raid->mismatch_cnt_filename, &raid->mismatch_cnt))) {
+ error("Cannot read file '%s'", raid->mismatch_cnt_filename);
+ do_mismatch = CONFIG_BOOLEAN_NO;
+ error("Monitoring for mismatch count has been disabled");
+ break;
+ }
+ }
+ }
+ }
+
+ // check for disappeared raids
+ for (old_raid_idx = 0; old_raid_idx < old_raids_allocated; old_raid_idx++) {
+ struct old_raid *old_raid = &old_raids[old_raid_idx];
+ int found = 0;
+
+ for (raid_idx = 0; raid_idx < raids_num; raid_idx++) {
+ struct raid *raid = &raids[raid_idx];
+
+ if (unlikely(
+ raid->hash == old_raid->hash && !strcmp(raid->name, old_raid->name) &&
+ raid->redundant == old_raid->redundant))
+ found = 1;
+ }
+
+ old_raid->found = found;
+ }
+
+ int raid_disappeared = 0;
+ for (old_raid_idx = 0; old_raid_idx < old_raids_allocated; old_raid_idx++) {
+ struct old_raid *old_raid = &old_raids[old_raid_idx];
+
+ if (unlikely(!old_raid->found)) {
+ if (likely(make_charts_obsolete)) {
+ make_chart_obsolete(old_raid->name, "disks");
+ make_chart_obsolete(old_raid->name, "mismatch");
+ make_chart_obsolete(old_raid->name, "operation");
+ make_chart_obsolete(old_raid->name, "finish");
+ make_chart_obsolete(old_raid->name, "speed");
+ make_chart_obsolete(old_raid->name, "availability");
+ }
+ raid_disappeared = 1;
+ }
+ }
+
+ // allocate memory for nonredundant arrays
+ if (unlikely(raid_disappeared || old_raids_allocated != raids_num)) {
+ for (old_raid_idx = 0; old_raid_idx < old_raids_allocated; old_raid_idx++) {
+ freez(old_raids[old_raid_idx].name);
+ }
+ if (likely(raids_num)) {
+ old_raids = reallocz(old_raids, sizeof(struct old_raid) * raids_num);
+ memset(old_raids, 0, sizeof(struct old_raid) * raids_num);
+ } else {
+ freez(old_raids);
+ old_raids = NULL;
+ }
+ old_raids_allocated = raids_num;
+ for (old_raid_idx = 0; old_raid_idx < old_raids_allocated; old_raid_idx++) {
+ struct old_raid *old_raid = &old_raids[old_raid_idx];
+ struct raid *raid = &raids[old_raid_idx];
+
+ old_raid->name = strdupz(raid->name);
+ old_raid->hash = raid->hash;
+ old_raid->redundant = raid->redundant;
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if (likely(do_health && redundant_num)) {
+ static RRDSET *st_mdstat_health = NULL;
+ if (unlikely(!st_mdstat_health)) {
+ st_mdstat_health = rrdset_create_localhost(
+ "mdstat",
+ "mdstat_health",
+ NULL,
+ "health",
+ "md.health",
+ "Faulty Devices In MD",
+ "failed disks",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_MDSTAT_NAME,
+ NETDATA_CHART_PRIO_MDSTAT_HEALTH,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_isnot_obsolete(st_mdstat_health);
+ } else
+ rrdset_next(st_mdstat_health);
+
+ if (!redundant_num) {
+ if (likely(make_charts_obsolete))
+ make_chart_obsolete("mdstat", "health");
+ } else {
+ for (raid_idx = 0; raid_idx < raids_num; raid_idx++) {
+ struct raid *raid = &raids[raid_idx];
+
+ if (likely(raid->redundant)) {
+ if (unlikely(!raid->rd_health && !(raid->rd_health = rrddim_find_active(st_mdstat_health, raid->name))))
+ raid->rd_health = rrddim_add(st_mdstat_health, raid->name, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ rrddim_set_by_pointer(st_mdstat_health, raid->rd_health, raid->failed_disks);
+ }
+ }
+
+ rrdset_done(st_mdstat_health);
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ for (raid_idx = 0; raid_idx < raids_num; raid_idx++) {
+ struct raid *raid = &raids[raid_idx];
+ char id[50 + 1];
+ char family[50 + 1];
+
+ if (likely(raid->redundant)) {
+ if (likely(do_disks)) {
+ snprintfz(id, 50, "%s_disks", raid->name);
+
+ if (unlikely(!raid->st_disks && !(raid->st_disks = rrdset_find_active_byname_localhost(id)))) {
+ snprintfz(family, 50, "%s", raid->name);
+
+ raid->st_disks = rrdset_create_localhost(
+ "mdstat",
+ id,
+ NULL,
+ family,
+ "md.disks",
+ "Disks Stats",
+ "disks",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_MDSTAT_NAME,
+ NETDATA_CHART_PRIO_MDSTAT_DISKS + raid_idx * 10,
+ update_every,
+ RRDSET_TYPE_STACKED);
+
+ rrdset_isnot_obsolete(raid->st_disks);
+ } else
+ rrdset_next(raid->st_disks);
+
+ if (unlikely(!raid->rd_inuse && !(raid->rd_inuse = rrddim_find_active(raid->st_disks, "inuse"))))
+ raid->rd_inuse = rrddim_add(raid->st_disks, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ if (unlikely(!raid->rd_down && !(raid->rd_down = rrddim_find_active(raid->st_disks, "down"))))
+ raid->rd_down = rrddim_add(raid->st_disks, "down", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ rrddim_set_by_pointer(raid->st_disks, raid->rd_inuse, raid->inuse_disks);
+ rrddim_set_by_pointer(raid->st_disks, raid->rd_down, raid->failed_disks);
+
+ rrdset_done(raid->st_disks);
+ }
+
+ // --------------------------------------------------------------------
+
+ if (likely(do_mismatch)) {
+ snprintfz(id, 50, "%s_mismatch", raid->name);
+
+ if (unlikely(!raid->st_mismatch_cnt && !(raid->st_mismatch_cnt = rrdset_find_active_byname_localhost(id)))) {
+ snprintfz(family, 50, "%s", raid->name);
+
+ raid->st_mismatch_cnt = rrdset_create_localhost(
+ "mdstat",
+ id,
+ NULL,
+ family,
+ "md.mismatch_cnt",
+ "Mismatch Count",
+ "unsynchronized blocks",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_MDSTAT_NAME,
+ NETDATA_CHART_PRIO_MDSTAT_MISMATCH + raid_idx * 10,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_isnot_obsolete(raid->st_mismatch_cnt);
+ } else
+ rrdset_next(raid->st_mismatch_cnt);
+
+ if (unlikely(!raid->rd_mismatch_cnt && !(raid->rd_mismatch_cnt = rrddim_find_active(raid->st_mismatch_cnt, "count"))))
+ raid->rd_mismatch_cnt = rrddim_add(raid->st_mismatch_cnt, "count", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ rrddim_set_by_pointer(raid->st_mismatch_cnt, raid->rd_mismatch_cnt, raid->mismatch_cnt);
+
+ rrdset_done(raid->st_mismatch_cnt);
+ }
+
+ // --------------------------------------------------------------------
+
+ if (likely(do_operations)) {
+ snprintfz(id, 50, "%s_operation", raid->name);
+
+ if (unlikely(!raid->st_operation && !(raid->st_operation = rrdset_find_active_byname_localhost(id)))) {
+ snprintfz(family, 50, "%s", raid->name);
+
+ raid->st_operation = rrdset_create_localhost(
+ "mdstat",
+ id,
+ NULL,
+ family,
+ "md.status",
+ "Current Status",
+ "percent",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_MDSTAT_NAME,
+ NETDATA_CHART_PRIO_MDSTAT_OPERATION + raid_idx * 10,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_isnot_obsolete(raid->st_operation);
+ } else
+ rrdset_next(raid->st_operation);
+
+ if(unlikely(!raid->rd_check && !(raid->rd_check = rrddim_find_active(raid->st_operation, "check"))))
+ raid->rd_check = rrddim_add(raid->st_operation, "check", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ if(unlikely(!raid->rd_resync && !(raid->rd_resync = rrddim_find_active(raid->st_operation, "resync"))))
+ raid->rd_resync = rrddim_add(raid->st_operation, "resync", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ if(unlikely(!raid->rd_recovery && !(raid->rd_recovery = rrddim_find_active(raid->st_operation, "recovery"))))
+ raid->rd_recovery = rrddim_add(raid->st_operation, "recovery", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ if(unlikely(!raid->rd_reshape && !(raid->rd_reshape = rrddim_find_active(raid->st_operation, "reshape"))))
+ raid->rd_reshape = rrddim_add(raid->st_operation, "reshape", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+
+ rrddim_set_by_pointer(raid->st_operation, raid->rd_check, raid->check);
+ rrddim_set_by_pointer(raid->st_operation, raid->rd_resync, raid->resync);
+ rrddim_set_by_pointer(raid->st_operation, raid->rd_recovery, raid->recovery);
+ rrddim_set_by_pointer(raid->st_operation, raid->rd_reshape, raid->reshape);
+
+ rrdset_done(raid->st_operation);
+
+ // --------------------------------------------------------------------
+
+ snprintfz(id, 50, "%s_finish", raid->name);
+
+ if (unlikely(!raid->st_finish && !(raid->st_finish = rrdset_find_active_byname_localhost(id)))) {
+ snprintfz(family, 50, "%s", raid->name);
+
+ raid->st_finish = rrdset_create_localhost(
+ "mdstat",
+ id,
+ NULL,
+ family,
+ "md.rate",
+ "Approximate Time Unit Finish",
+ "seconds",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_MDSTAT_NAME,
+ NETDATA_CHART_PRIO_MDSTAT_FINISH + raid_idx * 10,
+ update_every, RRDSET_TYPE_LINE);
+
+ rrdset_isnot_obsolete(raid->st_finish);
+ } else
+ rrdset_next(raid->st_finish);
+
+ if(unlikely(!raid->rd_finish_in && !(raid->rd_finish_in = rrddim_find_active(raid->st_finish, "finish_in"))))
+ raid->rd_finish_in = rrddim_add(raid->st_finish, "finish_in", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ rrddim_set_by_pointer(raid->st_finish, raid->rd_finish_in, raid->finish_in);
+
+ rrdset_done(raid->st_finish);
+
+ // --------------------------------------------------------------------
+
+ snprintfz(id, 50, "%s_speed", raid->name);
+
+ if (unlikely(!raid->st_speed && !(raid->st_speed = rrdset_find_active_byname_localhost(id)))) {
+ snprintfz(family, 50, "%s", raid->name);
+
+ raid->st_speed = rrdset_create_localhost(
+ "mdstat",
+ id,
+ NULL,
+ family,
+ "md.rate",
+ "Operation Speed",
+ "KiB/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_MDSTAT_NAME,
+ NETDATA_CHART_PRIO_MDSTAT_SPEED + raid_idx * 10,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_isnot_obsolete(raid->st_speed);
+ } else
+ rrdset_next(raid->st_speed);
+
+ if (unlikely(!raid->rd_speed && !(raid->rd_speed = rrddim_find_active(raid->st_speed, "speed"))))
+ raid->rd_speed = rrddim_add(raid->st_speed, "speed", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ rrddim_set_by_pointer(raid->st_speed, raid->rd_speed, raid->speed);
+
+ rrdset_done(raid->st_speed);
+ }
+ } else {
+ // --------------------------------------------------------------------
+
+ if (likely(do_nonredundant)) {
+ snprintfz(id, 50, "%s_availability", raid->name);
+
+ if (unlikely(!raid->st_nonredundant && !(raid->st_nonredundant = rrdset_find_active_localhost(id)))) {
+ snprintfz(family, 50, "%s", raid->name);
+
+ raid->st_nonredundant = rrdset_create_localhost(
+ "mdstat",
+ id,
+ NULL,
+ family,
+ "md.nonredundant",
+ "Nonredundant Array Availability",
+ "boolean",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_MDSTAT_NAME,
+ NETDATA_CHART_PRIO_MDSTAT_NONREDUNDANT + raid_idx * 10,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_isnot_obsolete(raid->st_nonredundant);
+ } else
+ rrdset_next(raid->st_nonredundant);
+
+ if (unlikely(!raid->rd_nonredundant && !(raid->rd_nonredundant = rrddim_find_active(raid->st_nonredundant, "available"))))
+ raid->rd_nonredundant = rrddim_add(raid->st_nonredundant, "available", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+
+ rrddim_set_by_pointer(raid->st_nonredundant, raid->rd_nonredundant, 1);
+
+ rrdset_done(raid->st_nonredundant);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_meminfo.c b/collectors/proc.plugin/proc_meminfo.c
new file mode 100644
index 0000000..51d77fe
--- /dev/null
+++ b/collectors/proc.plugin/proc_meminfo.c
@@ -0,0 +1,528 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_MEMINFO_NAME "/proc/meminfo"
+#define CONFIG_SECTION_PLUGIN_PROC_MEMINFO "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_MEMINFO_NAME
+
+int do_proc_meminfo(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+ static int do_ram = -1, do_swap = -1, do_hwcorrupt = -1, do_committed = -1, do_writeback = -1, do_kernel = -1, do_slab = -1, do_hugepages = -1, do_transparent_hugepages = -1;
+
+ static ARL_BASE *arl_base = NULL;
+ static ARL_ENTRY *arl_hwcorrupted = NULL, *arl_memavailable = NULL;
+
+ static unsigned long long
+ MemTotal = 0,
+ MemFree = 0,
+ MemAvailable = 0,
+ Buffers = 0,
+ Cached = 0,
+ //SwapCached = 0,
+ //Active = 0,
+ //Inactive = 0,
+ //ActiveAnon = 0,
+ //InactiveAnon = 0,
+ //ActiveFile = 0,
+ //InactiveFile = 0,
+ //Unevictable = 0,
+ //Mlocked = 0,
+ SwapTotal = 0,
+ SwapFree = 0,
+ Dirty = 0,
+ Writeback = 0,
+ //AnonPages = 0,
+ //Mapped = 0,
+ Shmem = 0,
+ Slab = 0,
+ SReclaimable = 0,
+ SUnreclaim = 0,
+ KernelStack = 0,
+ PageTables = 0,
+ NFS_Unstable = 0,
+ Bounce = 0,
+ WritebackTmp = 0,
+ //CommitLimit = 0,
+ Committed_AS = 0,
+ //VmallocTotal = 0,
+ VmallocUsed = 0,
+ //VmallocChunk = 0,
+ AnonHugePages = 0,
+ ShmemHugePages = 0,
+ HugePages_Total = 0,
+ HugePages_Free = 0,
+ HugePages_Rsvd = 0,
+ HugePages_Surp = 0,
+ Hugepagesize = 0,
+ //DirectMap4k = 0,
+ //DirectMap2M = 0,
+ HardwareCorrupted = 0;
+
+ if(unlikely(!arl_base)) {
+ do_ram = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "system ram", 1);
+ do_swap = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "system swap", CONFIG_BOOLEAN_AUTO);
+ do_hwcorrupt = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "hardware corrupted ECC", CONFIG_BOOLEAN_AUTO);
+ do_committed = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "committed memory", 1);
+ do_writeback = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "writeback memory", 1);
+ do_kernel = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "kernel memory", 1);
+ do_slab = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "slab memory", 1);
+ do_hugepages = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "hugepages", CONFIG_BOOLEAN_AUTO);
+ do_transparent_hugepages = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "transparent hugepages", CONFIG_BOOLEAN_AUTO);
+
+ arl_base = arl_create("meminfo", NULL, 60);
+ arl_expect(arl_base, "MemTotal", &MemTotal);
+ arl_expect(arl_base, "MemFree", &MemFree);
+ arl_memavailable = arl_expect(arl_base, "MemAvailable", &MemAvailable);
+ arl_expect(arl_base, "Buffers", &Buffers);
+ arl_expect(arl_base, "Cached", &Cached);
+ //arl_expect(arl_base, "SwapCached", &SwapCached);
+ //arl_expect(arl_base, "Active", &Active);
+ //arl_expect(arl_base, "Inactive", &Inactive);
+ //arl_expect(arl_base, "ActiveAnon", &ActiveAnon);
+ //arl_expect(arl_base, "InactiveAnon", &InactiveAnon);
+ //arl_expect(arl_base, "ActiveFile", &ActiveFile);
+ //arl_expect(arl_base, "InactiveFile", &InactiveFile);
+ //arl_expect(arl_base, "Unevictable", &Unevictable);
+ //arl_expect(arl_base, "Mlocked", &Mlocked);
+ arl_expect(arl_base, "SwapTotal", &SwapTotal);
+ arl_expect(arl_base, "SwapFree", &SwapFree);
+ arl_expect(arl_base, "Dirty", &Dirty);
+ arl_expect(arl_base, "Writeback", &Writeback);
+ //arl_expect(arl_base, "AnonPages", &AnonPages);
+ //arl_expect(arl_base, "Mapped", &Mapped);
+ arl_expect(arl_base, "Shmem", &Shmem);
+ arl_expect(arl_base, "Slab", &Slab);
+ arl_expect(arl_base, "SReclaimable", &SReclaimable);
+ arl_expect(arl_base, "SUnreclaim", &SUnreclaim);
+ arl_expect(arl_base, "KernelStack", &KernelStack);
+ arl_expect(arl_base, "PageTables", &PageTables);
+ arl_expect(arl_base, "NFS_Unstable", &NFS_Unstable);
+ arl_expect(arl_base, "Bounce", &Bounce);
+ arl_expect(arl_base, "WritebackTmp", &WritebackTmp);
+ //arl_expect(arl_base, "CommitLimit", &CommitLimit);
+ arl_expect(arl_base, "Committed_AS", &Committed_AS);
+ //arl_expect(arl_base, "VmallocTotal", &VmallocTotal);
+ arl_expect(arl_base, "VmallocUsed", &VmallocUsed);
+ //arl_expect(arl_base, "VmallocChunk", &VmallocChunk);
+ arl_hwcorrupted = arl_expect(arl_base, "HardwareCorrupted", &HardwareCorrupted);
+ arl_expect(arl_base, "AnonHugePages", &AnonHugePages);
+ arl_expect(arl_base, "ShmemHugePages", &ShmemHugePages);
+ arl_expect(arl_base, "HugePages_Total", &HugePages_Total);
+ arl_expect(arl_base, "HugePages_Free", &HugePages_Free);
+ arl_expect(arl_base, "HugePages_Rsvd", &HugePages_Rsvd);
+ arl_expect(arl_base, "HugePages_Surp", &HugePages_Surp);
+ arl_expect(arl_base, "Hugepagesize", &Hugepagesize);
+ //arl_expect(arl_base, "DirectMap4k", &DirectMap4k);
+ //arl_expect(arl_base, "DirectMap2M", &DirectMap2M);
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/meminfo");
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_MEMINFO, "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff))
+ return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ arl_begin(arl_base);
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) continue;
+
+ if(unlikely(arl_check(arl_base,
+ procfile_lineword(ff, l, 0),
+ procfile_lineword(ff, l, 1)))) break;
+ }
+
+ // --------------------------------------------------------------------
+
+ // http://calimeroteknik.free.fr/blag/?article20/really-used-memory-on-gnu-linux
+ unsigned long long MemCached = Cached + SReclaimable - Shmem;
+ unsigned long long MemUsed = MemTotal - MemFree - MemCached - Buffers;
+
+ if(do_ram) {
+ {
+ static RRDSET *st_system_ram = NULL;
+ static RRDDIM *rd_free = NULL, *rd_used = NULL, *rd_cached = NULL, *rd_buffers = NULL;
+
+ if(unlikely(!st_system_ram)) {
+ st_system_ram = rrdset_create_localhost(
+ "system"
+ , "ram"
+ , NULL
+ , "ram"
+ , NULL
+ , "System RAM"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_RAM
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_free = rrddim_add(st_system_ram, "free", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_used = rrddim_add(st_system_ram, "used", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_cached = rrddim_add(st_system_ram, "cached", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_buffers = rrddim_add(st_system_ram, "buffers", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_system_ram);
+
+ rrddim_set_by_pointer(st_system_ram, rd_free, MemFree);
+ rrddim_set_by_pointer(st_system_ram, rd_used, MemUsed);
+ rrddim_set_by_pointer(st_system_ram, rd_cached, MemCached);
+ rrddim_set_by_pointer(st_system_ram, rd_buffers, Buffers);
+
+ rrdset_done(st_system_ram);
+ }
+
+ if(arl_memavailable->flags & ARL_ENTRY_FLAG_FOUND) {
+ static RRDSET *st_mem_available = NULL;
+ static RRDDIM *rd_avail = NULL;
+
+ if(unlikely(!st_mem_available)) {
+ st_mem_available = rrdset_create_localhost(
+ "mem"
+ , "available"
+ , NULL
+ , "system"
+ , NULL
+ , "Available RAM for applications"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_SYSTEM_AVAILABLE
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_avail = rrddim_add(st_mem_available, "MemAvailable", "avail", 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_available);
+
+ rrddim_set_by_pointer(st_mem_available, rd_avail, MemAvailable);
+
+ rrdset_done(st_mem_available);
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ unsigned long long SwapUsed = SwapTotal - SwapFree;
+
+ if(do_swap == CONFIG_BOOLEAN_YES || (do_swap == CONFIG_BOOLEAN_AUTO &&
+ (SwapTotal || SwapUsed || SwapFree ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_swap = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_system_swap = NULL;
+ static RRDDIM *rd_free = NULL, *rd_used = NULL;
+
+ if(unlikely(!st_system_swap)) {
+ st_system_swap = rrdset_create_localhost(
+ "system"
+ , "swap"
+ , NULL
+ , "swap"
+ , NULL
+ , "System Swap"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_SWAP
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rrdset_flag_set(st_system_swap, RRDSET_FLAG_DETAIL);
+
+ rd_free = rrddim_add(st_system_swap, "free", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_used = rrddim_add(st_system_swap, "used", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_system_swap);
+
+ rrddim_set_by_pointer(st_system_swap, rd_used, SwapUsed);
+ rrddim_set_by_pointer(st_system_swap, rd_free, SwapFree);
+
+ rrdset_done(st_system_swap);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(arl_hwcorrupted->flags & ARL_ENTRY_FLAG_FOUND &&
+ (do_hwcorrupt == CONFIG_BOOLEAN_YES || (do_hwcorrupt == CONFIG_BOOLEAN_AUTO &&
+ (HardwareCorrupted > 0 ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))) {
+ do_hwcorrupt = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_mem_hwcorrupt = NULL;
+ static RRDDIM *rd_corrupted = NULL;
+
+ if(unlikely(!st_mem_hwcorrupt)) {
+ st_mem_hwcorrupt = rrdset_create_localhost(
+ "mem"
+ , "hwcorrupt"
+ , NULL
+ , "ecc"
+ , NULL
+ , "Corrupted Memory, detected by ECC"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_HW
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_mem_hwcorrupt, RRDSET_FLAG_DETAIL);
+
+ rd_corrupted = rrddim_add(st_mem_hwcorrupt, "HardwareCorrupted", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_hwcorrupt);
+
+ rrddim_set_by_pointer(st_mem_hwcorrupt, rd_corrupted, HardwareCorrupted);
+
+ rrdset_done(st_mem_hwcorrupt);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_committed) {
+ static RRDSET *st_mem_committed = NULL;
+ static RRDDIM *rd_committed = NULL;
+
+ if(unlikely(!st_mem_committed)) {
+ st_mem_committed = rrdset_create_localhost(
+ "mem"
+ , "committed"
+ , NULL
+ , "system"
+ , NULL
+ , "Committed (Allocated) Memory"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_SYSTEM_COMMITTED
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrdset_flag_set(st_mem_committed, RRDSET_FLAG_DETAIL);
+
+ rd_committed = rrddim_add(st_mem_committed, "Committed_AS", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_committed);
+
+ rrddim_set_by_pointer(st_mem_committed, rd_committed, Committed_AS);
+
+ rrdset_done(st_mem_committed);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_writeback) {
+ static RRDSET *st_mem_writeback = NULL;
+ static RRDDIM *rd_dirty = NULL, *rd_writeback = NULL, *rd_fusewriteback = NULL, *rd_nfs_writeback = NULL, *rd_bounce = NULL;
+
+ if(unlikely(!st_mem_writeback)) {
+ st_mem_writeback = rrdset_create_localhost(
+ "mem"
+ , "writeback"
+ , NULL
+ , "kernel"
+ , NULL
+ , "Writeback Memory"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_KERNEL
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st_mem_writeback, RRDSET_FLAG_DETAIL);
+
+ rd_dirty = rrddim_add(st_mem_writeback, "Dirty", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_writeback = rrddim_add(st_mem_writeback, "Writeback", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_fusewriteback = rrddim_add(st_mem_writeback, "FuseWriteback", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_nfs_writeback = rrddim_add(st_mem_writeback, "NfsWriteback", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_bounce = rrddim_add(st_mem_writeback, "Bounce", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_writeback);
+
+ rrddim_set_by_pointer(st_mem_writeback, rd_dirty, Dirty);
+ rrddim_set_by_pointer(st_mem_writeback, rd_writeback, Writeback);
+ rrddim_set_by_pointer(st_mem_writeback, rd_fusewriteback, WritebackTmp);
+ rrddim_set_by_pointer(st_mem_writeback, rd_nfs_writeback, NFS_Unstable);
+ rrddim_set_by_pointer(st_mem_writeback, rd_bounce, Bounce);
+
+ rrdset_done(st_mem_writeback);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_kernel) {
+ static RRDSET *st_mem_kernel = NULL;
+ static RRDDIM *rd_slab = NULL, *rd_kernelstack = NULL, *rd_pagetables = NULL, *rd_vmallocused = NULL;
+
+ if(unlikely(!st_mem_kernel)) {
+ st_mem_kernel = rrdset_create_localhost(
+ "mem"
+ , "kernel"
+ , NULL
+ , "kernel"
+ , NULL
+ , "Memory Used by Kernel"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_KERNEL + 1
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rrdset_flag_set(st_mem_kernel, RRDSET_FLAG_DETAIL);
+
+ rd_slab = rrddim_add(st_mem_kernel, "Slab", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_kernelstack = rrddim_add(st_mem_kernel, "KernelStack", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_pagetables = rrddim_add(st_mem_kernel, "PageTables", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_vmallocused = rrddim_add(st_mem_kernel, "VmallocUsed", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_kernel);
+
+ rrddim_set_by_pointer(st_mem_kernel, rd_slab, Slab);
+ rrddim_set_by_pointer(st_mem_kernel, rd_kernelstack, KernelStack);
+ rrddim_set_by_pointer(st_mem_kernel, rd_pagetables, PageTables);
+ rrddim_set_by_pointer(st_mem_kernel, rd_vmallocused, VmallocUsed);
+
+ rrdset_done(st_mem_kernel);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_slab) {
+ static RRDSET *st_mem_slab = NULL;
+ static RRDDIM *rd_reclaimable = NULL, *rd_unreclaimable = NULL;
+
+ if(unlikely(!st_mem_slab)) {
+ st_mem_slab = rrdset_create_localhost(
+ "mem"
+ , "slab"
+ , NULL
+ , "slab"
+ , NULL
+ , "Reclaimable Kernel Memory"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_SLAB
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rrdset_flag_set(st_mem_slab, RRDSET_FLAG_DETAIL);
+
+ rd_reclaimable = rrddim_add(st_mem_slab, "reclaimable", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_unreclaimable = rrddim_add(st_mem_slab, "unreclaimable", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_slab);
+
+ rrddim_set_by_pointer(st_mem_slab, rd_reclaimable, SReclaimable);
+ rrddim_set_by_pointer(st_mem_slab, rd_unreclaimable, SUnreclaim);
+
+ rrdset_done(st_mem_slab);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_hugepages == CONFIG_BOOLEAN_YES || (do_hugepages == CONFIG_BOOLEAN_AUTO &&
+ ((Hugepagesize && HugePages_Total) ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_hugepages = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_mem_hugepages = NULL;
+ static RRDDIM *rd_used = NULL, *rd_free = NULL, *rd_rsvd = NULL, *rd_surp = NULL;
+
+ if(unlikely(!st_mem_hugepages)) {
+ st_mem_hugepages = rrdset_create_localhost(
+ "mem"
+ , "hugepages"
+ , NULL
+ , "hugepages"
+ , NULL
+ , "Dedicated HugePages Memory"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_HUGEPAGES + 1
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rrdset_flag_set(st_mem_hugepages, RRDSET_FLAG_DETAIL);
+
+ rd_free = rrddim_add(st_mem_hugepages, "free", NULL, Hugepagesize, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_used = rrddim_add(st_mem_hugepages, "used", NULL, Hugepagesize, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_surp = rrddim_add(st_mem_hugepages, "surplus", NULL, Hugepagesize, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_rsvd = rrddim_add(st_mem_hugepages, "reserved", NULL, Hugepagesize, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_hugepages);
+
+ rrddim_set_by_pointer(st_mem_hugepages, rd_used, HugePages_Total - HugePages_Free - HugePages_Rsvd);
+ rrddim_set_by_pointer(st_mem_hugepages, rd_free, HugePages_Free);
+ rrddim_set_by_pointer(st_mem_hugepages, rd_rsvd, HugePages_Rsvd);
+ rrddim_set_by_pointer(st_mem_hugepages, rd_surp, HugePages_Surp);
+
+ rrdset_done(st_mem_hugepages);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_transparent_hugepages == CONFIG_BOOLEAN_YES || (do_transparent_hugepages == CONFIG_BOOLEAN_AUTO &&
+ (AnonHugePages ||
+ ShmemHugePages ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_transparent_hugepages = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_mem_transparent_hugepages = NULL;
+ static RRDDIM *rd_anonymous = NULL, *rd_shared = NULL;
+
+ if(unlikely(!st_mem_transparent_hugepages)) {
+ st_mem_transparent_hugepages = rrdset_create_localhost(
+ "mem"
+ , "transparent_hugepages"
+ , NULL
+ , "hugepages"
+ , NULL
+ , "Transparent HugePages Memory"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_MEMINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_HUGEPAGES
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rrdset_flag_set(st_mem_transparent_hugepages, RRDSET_FLAG_DETAIL);
+
+ rd_anonymous = rrddim_add(st_mem_transparent_hugepages, "anonymous", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_shared = rrddim_add(st_mem_transparent_hugepages, "shmem", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_mem_transparent_hugepages);
+
+ rrddim_set_by_pointer(st_mem_transparent_hugepages, rd_anonymous, AnonHugePages);
+ rrddim_set_by_pointer(st_mem_transparent_hugepages, rd_shared, ShmemHugePages);
+
+ rrdset_done(st_mem_transparent_hugepages);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_dev.c b/collectors/proc.plugin/proc_net_dev.c
new file mode 100644
index 0000000..a7db37e
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_dev.c
@@ -0,0 +1,1127 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_NETDEV_NAME "/proc/net/dev"
+#define CONFIG_SECTION_PLUGIN_PROC_NETDEV "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_NETDEV_NAME
+
+// As defined in https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net
+const char *operstate_names[] = { "unknown", "notpresent", "down", "lowerlayerdown", "testing", "dormant", "up" };
+
+static inline int get_operstate(char *operstate)
+{
+ int i;
+
+ for (i = 0; i < (int) (sizeof(operstate_names) / sizeof(char *)); i++) {
+ if (!strcmp(operstate, operstate_names[i])) {
+ return i;
+ }
+ }
+ return 0;
+}
+
+// ----------------------------------------------------------------------------
+// netdev list
+
+static struct netdev {
+ char *name;
+ uint32_t hash;
+ size_t len;
+
+ // flags
+ int virtual;
+ int configured;
+ int enabled;
+ int updated;
+
+ int do_bandwidth;
+ int do_packets;
+ int do_errors;
+ int do_drops;
+ int do_fifo;
+ int do_compressed;
+ int do_events;
+
+ const char *chart_type_net_bytes;
+ const char *chart_type_net_packets;
+ const char *chart_type_net_errors;
+ const char *chart_type_net_fifo;
+ const char *chart_type_net_events;
+ const char *chart_type_net_drops;
+ const char *chart_type_net_compressed;
+
+ const char *chart_id_net_bytes;
+ const char *chart_id_net_packets;
+ const char *chart_id_net_errors;
+ const char *chart_id_net_fifo;
+ const char *chart_id_net_events;
+ const char *chart_id_net_drops;
+ const char *chart_id_net_compressed;
+
+ const char *chart_family;
+
+ struct label *chart_labels;
+
+ int flipped;
+ unsigned long priority;
+
+ // data collected
+ kernel_uint_t rbytes;
+ kernel_uint_t rpackets;
+ kernel_uint_t rerrors;
+ kernel_uint_t rdrops;
+ kernel_uint_t rfifo;
+ kernel_uint_t rframe;
+ kernel_uint_t rcompressed;
+ kernel_uint_t rmulticast;
+
+ kernel_uint_t tbytes;
+ kernel_uint_t tpackets;
+ kernel_uint_t terrors;
+ kernel_uint_t tdrops;
+ kernel_uint_t tfifo;
+ kernel_uint_t tcollisions;
+ kernel_uint_t tcarrier;
+ kernel_uint_t tcompressed;
+ kernel_uint_t speed;
+ kernel_uint_t duplex;
+ kernel_uint_t operstate;
+
+ // charts
+ RRDSET *st_bandwidth;
+ RRDSET *st_packets;
+ RRDSET *st_errors;
+ RRDSET *st_drops;
+ RRDSET *st_fifo;
+ RRDSET *st_compressed;
+ RRDSET *st_events;
+
+ // dimensions
+ RRDDIM *rd_rbytes;
+ RRDDIM *rd_rpackets;
+ RRDDIM *rd_rerrors;
+ RRDDIM *rd_rdrops;
+ RRDDIM *rd_rfifo;
+ RRDDIM *rd_rframe;
+ RRDDIM *rd_rcompressed;
+ RRDDIM *rd_rmulticast;
+
+ RRDDIM *rd_tbytes;
+ RRDDIM *rd_tpackets;
+ RRDDIM *rd_terrors;
+ RRDDIM *rd_tdrops;
+ RRDDIM *rd_tfifo;
+ RRDDIM *rd_tcollisions;
+ RRDDIM *rd_tcarrier;
+ RRDDIM *rd_tcompressed;
+
+ usec_t speed_last_collected_usec;
+ usec_t duplex_last_collected_usec;
+ usec_t operstate_last_collected_usec;
+
+ char *filename_speed;
+ RRDSETVAR *chart_var_speed;
+
+ char *filename_duplex;
+ RRDSETVAR *chart_var_duplex;
+
+ char *filename_operstate;
+ RRDSETVAR *chart_var_operstate;
+
+ struct netdev *next;
+} *netdev_root = NULL, *netdev_last_used = NULL;
+
+static size_t netdev_added = 0, netdev_found = 0;
+
+// ----------------------------------------------------------------------------
+
+static void netdev_charts_release(struct netdev *d) {
+ if(d->st_bandwidth) rrdset_is_obsolete(d->st_bandwidth);
+ if(d->st_packets) rrdset_is_obsolete(d->st_packets);
+ if(d->st_errors) rrdset_is_obsolete(d->st_errors);
+ if(d->st_drops) rrdset_is_obsolete(d->st_drops);
+ if(d->st_fifo) rrdset_is_obsolete(d->st_fifo);
+ if(d->st_compressed) rrdset_is_obsolete(d->st_compressed);
+ if(d->st_events) rrdset_is_obsolete(d->st_events);
+
+ d->st_bandwidth = NULL;
+ d->st_compressed = NULL;
+ d->st_drops = NULL;
+ d->st_errors = NULL;
+ d->st_events = NULL;
+ d->st_fifo = NULL;
+ d->st_packets = NULL;
+
+ d->rd_rbytes = NULL;
+ d->rd_rpackets = NULL;
+ d->rd_rerrors = NULL;
+ d->rd_rdrops = NULL;
+ d->rd_rfifo = NULL;
+ d->rd_rframe = NULL;
+ d->rd_rcompressed = NULL;
+ d->rd_rmulticast = NULL;
+
+ d->rd_tbytes = NULL;
+ d->rd_tpackets = NULL;
+ d->rd_terrors = NULL;
+ d->rd_tdrops = NULL;
+ d->rd_tfifo = NULL;
+ d->rd_tcollisions = NULL;
+ d->rd_tcarrier = NULL;
+ d->rd_tcompressed = NULL;
+
+ d->chart_var_speed = NULL;
+ d->chart_var_duplex = NULL;
+ d->chart_var_operstate = NULL;
+}
+
+static void netdev_free_chart_strings(struct netdev *d) {
+ freez((void *)d->chart_type_net_bytes);
+ freez((void *)d->chart_type_net_compressed);
+ freez((void *)d->chart_type_net_drops);
+ freez((void *)d->chart_type_net_errors);
+ freez((void *)d->chart_type_net_events);
+ freez((void *)d->chart_type_net_fifo);
+ freez((void *)d->chart_type_net_packets);
+
+ freez((void *)d->chart_id_net_bytes);
+ freez((void *)d->chart_id_net_compressed);
+ freez((void *)d->chart_id_net_drops);
+ freez((void *)d->chart_id_net_errors);
+ freez((void *)d->chart_id_net_events);
+ freez((void *)d->chart_id_net_fifo);
+ freez((void *)d->chart_id_net_packets);
+
+ freez((void *)d->chart_family);
+}
+
+static void netdev_free(struct netdev *d) {
+ netdev_charts_release(d);
+ netdev_free_chart_strings(d);
+ free_label_list(d->chart_labels);
+
+ freez((void *)d->name);
+ freez((void *)d->filename_speed);
+ freez((void *)d->filename_duplex);
+ freez((void *)d->filename_operstate);
+ freez((void *)d);
+ netdev_added--;
+}
+
+// ----------------------------------------------------------------------------
+// netdev renames
+
+static struct netdev_rename {
+ const char *host_device;
+ uint32_t hash;
+
+ const char *container_device;
+ const char *container_name;
+
+ struct label *chart_labels;
+
+ int processed;
+
+ struct netdev_rename *next;
+} *netdev_rename_root = NULL;
+
+static int netdev_pending_renames = 0;
+static netdata_mutex_t netdev_rename_mutex = NETDATA_MUTEX_INITIALIZER;
+
+static struct netdev_rename *netdev_rename_find(const char *host_device, uint32_t hash) {
+ struct netdev_rename *r;
+
+ for(r = netdev_rename_root; r ; r = r->next)
+ if(r->hash == hash && !strcmp(host_device, r->host_device))
+ return r;
+
+ return NULL;
+}
+
+// other threads can call this function to register a rename to a netdev
+void netdev_rename_device_add(
+ const char *host_device, const char *container_device, const char *container_name, struct label *labels)
+{
+ netdata_mutex_lock(&netdev_rename_mutex);
+
+ uint32_t hash = simple_hash(host_device);
+ struct netdev_rename *r = netdev_rename_find(host_device, hash);
+ if(!r) {
+ r = callocz(1, sizeof(struct netdev_rename));
+ r->host_device = strdupz(host_device);
+ r->container_device = strdupz(container_device);
+ r->container_name = strdupz(container_name);
+ update_label_list(&r->chart_labels, labels);
+ r->hash = hash;
+ r->next = netdev_rename_root;
+ r->processed = 0;
+ netdev_rename_root = r;
+ netdev_pending_renames++;
+ info("CGROUP: registered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+ }
+ else {
+ if(strcmp(r->container_device, container_device) != 0 || strcmp(r->container_name, container_name) != 0) {
+ freez((void *) r->container_device);
+ freez((void *) r->container_name);
+
+ r->container_device = strdupz(container_device);
+ r->container_name = strdupz(container_name);
+
+ update_label_list(&r->chart_labels, labels);
+
+ r->processed = 0;
+ netdev_pending_renames++;
+ info("CGROUP: altered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+ }
+ }
+
+ netdata_mutex_unlock(&netdev_rename_mutex);
+}
+
+// other threads can call this function to delete a rename to a netdev
+void netdev_rename_device_del(const char *host_device) {
+ netdata_mutex_lock(&netdev_rename_mutex);
+
+ struct netdev_rename *r, *last = NULL;
+
+ uint32_t hash = simple_hash(host_device);
+ for(r = netdev_rename_root; r ; last = r, r = r->next) {
+ if (r->hash == hash && !strcmp(host_device, r->host_device)) {
+ if (netdev_rename_root == r)
+ netdev_rename_root = r->next;
+ else if (last)
+ last->next = r->next;
+
+ if(!r->processed)
+ netdev_pending_renames--;
+
+ info("CGROUP: unregistered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+
+ freez((void *) r->host_device);
+ freez((void *) r->container_name);
+ freez((void *) r->container_device);
+ free_label_list(r->chart_labels);
+ freez((void *) r);
+ break;
+ }
+ }
+
+ netdata_mutex_unlock(&netdev_rename_mutex);
+}
+
+static inline void netdev_rename_cgroup(struct netdev *d, struct netdev_rename *r) {
+ info("CGROUP: renaming network interface '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name);
+
+ netdev_charts_release(d);
+ netdev_free_chart_strings(d);
+
+ char buffer[RRD_ID_LENGTH_MAX + 1];
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "cgroup_%s", r->container_name);
+ d->chart_type_net_bytes = strdupz(buffer);
+ d->chart_type_net_compressed = strdupz(buffer);
+ d->chart_type_net_drops = strdupz(buffer);
+ d->chart_type_net_errors = strdupz(buffer);
+ d->chart_type_net_events = strdupz(buffer);
+ d->chart_type_net_fifo = strdupz(buffer);
+ d->chart_type_net_packets = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net_%s", r->container_device);
+ d->chart_id_net_bytes = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net_compressed_%s", r->container_device);
+ d->chart_id_net_compressed = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net_drops_%s", r->container_device);
+ d->chart_id_net_drops = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net_errors_%s", r->container_device);
+ d->chart_id_net_errors = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net_events_%s", r->container_device);
+ d->chart_id_net_events = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net_fifo_%s", r->container_device);
+ d->chart_id_net_fifo = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net_packets_%s", r->container_device);
+ d->chart_id_net_packets = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "net %s", r->container_device);
+ d->chart_family = strdupz(buffer);
+
+ update_label_list(&d->chart_labels, r->chart_labels);
+
+ d->priority = NETDATA_CHART_PRIO_CGROUP_NET_IFACE;
+ d->flipped = 1;
+}
+
+static inline void netdev_rename(struct netdev *d) {
+ struct netdev_rename *r = netdev_rename_find(d->name, d->hash);
+ if(unlikely(r && !r->processed)) {
+ netdev_rename_cgroup(d, r);
+ r->processed = 1;
+ netdev_pending_renames--;
+ }
+}
+
+static inline void netdev_rename_lock(struct netdev *d) {
+ netdata_mutex_lock(&netdev_rename_mutex);
+ netdev_rename(d);
+ netdata_mutex_unlock(&netdev_rename_mutex);
+}
+
+static inline void netdev_rename_all_lock(void) {
+ netdata_mutex_lock(&netdev_rename_mutex);
+
+ struct netdev *d;
+ for(d = netdev_root; d ; d = d->next)
+ netdev_rename(d);
+
+ netdev_pending_renames = 0;
+ netdata_mutex_unlock(&netdev_rename_mutex);
+}
+
+// ----------------------------------------------------------------------------
+// netdev data collection
+
+static void netdev_cleanup() {
+ if(likely(netdev_found == netdev_added)) return;
+
+ netdev_added = 0;
+ struct netdev *d = netdev_root, *last = NULL;
+ while(d) {
+ if(unlikely(!d->updated)) {
+ // info("Removing network device '%s', linked after '%s'", d->name, last?last->name:"ROOT");
+
+ if(netdev_last_used == d)
+ netdev_last_used = last;
+
+ struct netdev *t = d;
+
+ if(d == netdev_root || !last)
+ netdev_root = d = d->next;
+
+ else
+ last->next = d = d->next;
+
+ t->next = NULL;
+ netdev_free(t);
+ }
+ else {
+ netdev_added++;
+ last = d;
+ d->updated = 0;
+ d = d->next;
+ }
+ }
+}
+
+static struct netdev *get_netdev(const char *name) {
+ struct netdev *d;
+
+ uint32_t hash = simple_hash(name);
+
+ // search it, from the last position to the end
+ for(d = netdev_last_used ; d ; d = d->next) {
+ if(unlikely(hash == d->hash && !strcmp(name, d->name))) {
+ netdev_last_used = d->next;
+ return d;
+ }
+ }
+
+ // search it from the beginning to the last position we used
+ for(d = netdev_root ; d != netdev_last_used ; d = d->next) {
+ if(unlikely(hash == d->hash && !strcmp(name, d->name))) {
+ netdev_last_used = d->next;
+ return d;
+ }
+ }
+
+ // create a new one
+ d = callocz(1, sizeof(struct netdev));
+ d->name = strdupz(name);
+ d->hash = simple_hash(d->name);
+ d->len = strlen(d->name);
+
+ d->chart_type_net_bytes = strdupz("net");
+ d->chart_type_net_compressed = strdupz("net_compressed");
+ d->chart_type_net_drops = strdupz("net_drops");
+ d->chart_type_net_errors = strdupz("net_errors");
+ d->chart_type_net_events = strdupz("net_events");
+ d->chart_type_net_fifo = strdupz("net_fifo");
+ d->chart_type_net_packets = strdupz("net_packets");
+
+ d->chart_id_net_bytes = strdupz(d->name);
+ d->chart_id_net_compressed = strdupz(d->name);
+ d->chart_id_net_drops = strdupz(d->name);
+ d->chart_id_net_errors = strdupz(d->name);
+ d->chart_id_net_events = strdupz(d->name);
+ d->chart_id_net_fifo = strdupz(d->name);
+ d->chart_id_net_packets = strdupz(d->name);
+
+ d->chart_family = strdupz(d->name);
+ d->priority = NETDATA_CHART_PRIO_FIRST_NET_IFACE;
+
+ netdev_rename_lock(d);
+
+ netdev_added++;
+
+ // link it to the end
+ if(netdev_root) {
+ struct netdev *e;
+ for(e = netdev_root; e->next ; e = e->next) ;
+ e->next = d;
+ }
+ else
+ netdev_root = d;
+
+ return d;
+}
+
+int do_proc_net_dev(int update_every, usec_t dt) {
+ (void)dt;
+ static SIMPLE_PATTERN *disabled_list = NULL;
+ static procfile *ff = NULL;
+ static int enable_new_interfaces = -1;
+ static int do_bandwidth = -1, do_packets = -1, do_errors = -1, do_drops = -1, do_fifo = -1, do_compressed = -1,
+ do_events = -1;
+ static char *path_to_sys_devices_virtual_net = NULL, *path_to_sys_class_net_speed = NULL,
+ *proc_net_dev_filename = NULL;
+ static char *path_to_sys_class_net_duplex = NULL;
+ static char *path_to_sys_class_net_operstate = NULL;
+ static long long int dt_to_refresh_speed = 0;
+ static long long int dt_to_refresh_duplex = 0;
+ static long long int dt_to_refresh_operstate = 0;
+
+ if(unlikely(enable_new_interfaces == -1)) {
+ char filename[FILENAME_MAX + 1];
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, (*netdata_configured_host_prefix)?"/proc/1/net/dev":"/proc/net/dev");
+ proc_net_dev_filename = config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/virtual/net/%s");
+ path_to_sys_devices_virtual_net = config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "path to get virtual interfaces", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/class/net/%s/speed");
+ path_to_sys_class_net_speed = config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "path to get net device speed", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/class/net/%s/duplex");
+ path_to_sys_class_net_duplex = config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "path to get net device duplex", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/class/net/%s/operstate");
+ path_to_sys_class_net_operstate = config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "path to get net device operstate", filename);
+
+
+ enable_new_interfaces = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "enable new interfaces detected at runtime", CONFIG_BOOLEAN_AUTO);
+
+ do_bandwidth = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "bandwidth for all interfaces", CONFIG_BOOLEAN_AUTO);
+ do_packets = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "packets for all interfaces", CONFIG_BOOLEAN_AUTO);
+ do_errors = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "errors for all interfaces", CONFIG_BOOLEAN_AUTO);
+ do_drops = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "drops for all interfaces", CONFIG_BOOLEAN_AUTO);
+ do_fifo = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "fifo for all interfaces", CONFIG_BOOLEAN_AUTO);
+ do_compressed = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "compressed packets for all interfaces", CONFIG_BOOLEAN_AUTO);
+ do_events = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "frames, collisions, carrier counters for all interfaces", CONFIG_BOOLEAN_AUTO);
+
+ disabled_list = simple_pattern_create(config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "disable by default interfaces matching", "lo fireqos* *-ifb"), NULL, SIMPLE_PATTERN_EXACT);
+
+ dt_to_refresh_speed = config_get_number(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "refresh interface speed every seconds", 10) * USEC_PER_SEC;
+ dt_to_refresh_duplex = config_get_number(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "refresh interface duplex every seconds", 10) * USEC_PER_SEC;
+ dt_to_refresh_operstate = config_get_number(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "refresh interface operstate every seconds", 10) * USEC_PER_SEC;
+
+ if (dt_to_refresh_operstate < 0)
+ dt_to_refresh_operstate = 0;
+ if (dt_to_refresh_duplex < 0)
+ dt_to_refresh_duplex = 0;
+ if (dt_to_refresh_speed < 0)
+ dt_to_refresh_speed = 0;
+ }
+
+ if(unlikely(!ff)) {
+ ff = procfile_open(proc_net_dev_filename, " \t,|", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ // rename all the devices, if we have pending renames
+ if(unlikely(netdev_pending_renames))
+ netdev_rename_all_lock();
+
+ netdev_found = 0;
+
+ kernel_uint_t system_rbytes = 0;
+ kernel_uint_t system_tbytes = 0;
+
+ size_t lines = procfile_lines(ff), l;
+ for(l = 2; l < lines ;l++) {
+ // require 17 words on each line
+ if(unlikely(procfile_linewords(ff, l) < 17)) continue;
+
+ char *name = procfile_lineword(ff, l, 0);
+ size_t len = strlen(name);
+ if(name[len - 1] == ':') name[len - 1] = '\0';
+
+ struct netdev *d = get_netdev(name);
+ d->updated = 1;
+ netdev_found++;
+
+ if(unlikely(!d->configured)) {
+ // this is the first time we see this interface
+
+ // remember we configured it
+ d->configured = 1;
+
+ d->enabled = enable_new_interfaces;
+
+ if(d->enabled)
+ d->enabled = !simple_pattern_matches(disabled_list, d->name);
+
+ char buffer[FILENAME_MAX + 1];
+
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_devices_virtual_net, d->name);
+ if(likely(access(buffer, R_OK) == 0)) {
+ d->virtual = 1;
+ }
+ else
+ d->virtual = 0;
+
+ if(likely(!d->virtual)) {
+ // set the filename to get the interface speed
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_class_net_speed, d->name);
+ d->filename_speed = strdupz(buffer);
+
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_class_net_duplex, d->name);
+ d->filename_duplex = strdupz(buffer);
+ }
+ snprintfz(buffer, FILENAME_MAX, path_to_sys_class_net_operstate, d->name);
+ d->filename_operstate = strdupz(buffer);
+
+ snprintfz(buffer, FILENAME_MAX, "plugin:proc:/proc/net/dev:%s", d->name);
+ d->enabled = config_get_boolean_ondemand(buffer, "enabled", d->enabled);
+ d->virtual = config_get_boolean(buffer, "virtual", d->virtual);
+
+ if(d->enabled == CONFIG_BOOLEAN_NO)
+ continue;
+
+ d->do_bandwidth = config_get_boolean_ondemand(buffer, "bandwidth", do_bandwidth);
+ d->do_packets = config_get_boolean_ondemand(buffer, "packets", do_packets);
+ d->do_errors = config_get_boolean_ondemand(buffer, "errors", do_errors);
+ d->do_drops = config_get_boolean_ondemand(buffer, "drops", do_drops);
+ d->do_fifo = config_get_boolean_ondemand(buffer, "fifo", do_fifo);
+ d->do_compressed = config_get_boolean_ondemand(buffer, "compressed", do_compressed);
+ d->do_events = config_get_boolean_ondemand(buffer, "events", do_events);
+ }
+
+ if(unlikely(!d->enabled))
+ continue;
+
+ if(likely(d->do_bandwidth != CONFIG_BOOLEAN_NO || !d->virtual)) {
+ d->rbytes = str2kernel_uint_t(procfile_lineword(ff, l, 1));
+ d->tbytes = str2kernel_uint_t(procfile_lineword(ff, l, 9));
+
+ if(likely(!d->virtual)) {
+ system_rbytes += d->rbytes;
+ system_tbytes += d->tbytes;
+ }
+ }
+
+ if(likely(d->do_packets != CONFIG_BOOLEAN_NO)) {
+ d->rpackets = str2kernel_uint_t(procfile_lineword(ff, l, 2));
+ d->rmulticast = str2kernel_uint_t(procfile_lineword(ff, l, 8));
+ d->tpackets = str2kernel_uint_t(procfile_lineword(ff, l, 10));
+ }
+
+ if(likely(d->do_errors != CONFIG_BOOLEAN_NO)) {
+ d->rerrors = str2kernel_uint_t(procfile_lineword(ff, l, 3));
+ d->terrors = str2kernel_uint_t(procfile_lineword(ff, l, 11));
+ }
+
+ if(likely(d->do_drops != CONFIG_BOOLEAN_NO)) {
+ d->rdrops = str2kernel_uint_t(procfile_lineword(ff, l, 4));
+ d->tdrops = str2kernel_uint_t(procfile_lineword(ff, l, 12));
+ }
+
+ if(likely(d->do_fifo != CONFIG_BOOLEAN_NO)) {
+ d->rfifo = str2kernel_uint_t(procfile_lineword(ff, l, 5));
+ d->tfifo = str2kernel_uint_t(procfile_lineword(ff, l, 13));
+ }
+
+ if(likely(d->do_compressed != CONFIG_BOOLEAN_NO)) {
+ d->rcompressed = str2kernel_uint_t(procfile_lineword(ff, l, 7));
+ d->tcompressed = str2kernel_uint_t(procfile_lineword(ff, l, 16));
+ }
+
+ if(likely(d->do_events != CONFIG_BOOLEAN_NO)) {
+ d->rframe = str2kernel_uint_t(procfile_lineword(ff, l, 6));
+ d->tcollisions = str2kernel_uint_t(procfile_lineword(ff, l, 14));
+ d->tcarrier = str2kernel_uint_t(procfile_lineword(ff, l, 15));
+ }
+
+ //info("PROC_NET_DEV: %s speed %zu, bytes %zu/%zu, packets %zu/%zu/%zu, errors %zu/%zu, drops %zu/%zu, fifo %zu/%zu, compressed %zu/%zu, rframe %zu, tcollisions %zu, tcarrier %zu"
+ // , d->name, d->speed
+ // , d->rbytes, d->tbytes
+ // , d->rpackets, d->tpackets, d->rmulticast
+ // , d->rerrors, d->terrors
+ // , d->rdrops, d->tdrops
+ // , d->rfifo, d->tfifo
+ // , d->rcompressed, d->tcompressed
+ // , d->rframe, d->tcollisions, d->tcarrier
+ // );
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(d->do_bandwidth == CONFIG_BOOLEAN_AUTO &&
+ (d->rbytes || d->tbytes || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))
+ d->do_bandwidth = CONFIG_BOOLEAN_YES;
+
+ if(d->do_bandwidth == CONFIG_BOOLEAN_YES) {
+ if(unlikely(!d->st_bandwidth)) {
+
+ d->st_bandwidth = rrdset_create_localhost(
+ d->chart_type_net_bytes
+ , d->chart_id_net_bytes
+ , NULL
+ , d->chart_family
+ , "net.net"
+ , "Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , d->priority
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrdset_update_labels(d->st_bandwidth, d->chart_labels);
+
+ d->rd_rbytes = rrddim_add(d->st_bandwidth, "received", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_tbytes = rrddim_add(d->st_bandwidth, "sent", NULL, -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+
+ if(d->flipped) {
+ // flip receive/trasmit
+
+ RRDDIM *td = d->rd_rbytes;
+ d->rd_rbytes = d->rd_tbytes;
+ d->rd_tbytes = td;
+ }
+ }
+ else rrdset_next(d->st_bandwidth);
+
+ rrddim_set_by_pointer(d->st_bandwidth, d->rd_rbytes, (collected_number)d->rbytes);
+ rrddim_set_by_pointer(d->st_bandwidth, d->rd_tbytes, (collected_number)d->tbytes);
+ rrdset_done(d->st_bandwidth);
+
+ // update the interface speed
+ if(d->filename_speed) {
+ d->speed_last_collected_usec += dt;
+
+ if(unlikely(d->speed_last_collected_usec >= (usec_t)dt_to_refresh_speed)) {
+
+ if(unlikely(!d->chart_var_speed)) {
+ d->chart_var_speed = rrdsetvar_custom_chart_variable_create(d->st_bandwidth, "nic_speed_max");
+ if(!d->chart_var_speed) {
+ error("Cannot create interface %s chart variable 'nic_speed_max'. Will not update its speed anymore.", d->name);
+ freez(d->filename_speed);
+ d->filename_speed = NULL;
+ }
+ }
+
+ if(d->filename_speed && d->chart_var_speed) {
+ if(read_single_number_file(d->filename_speed, (unsigned long long *) &d->speed)) {
+ error("Cannot refresh interface %s speed by reading '%s'. Will not update its speed anymore.", d->name, d->filename_speed);
+ freez(d->filename_speed);
+ d->filename_speed = NULL;
+ }
+ else {
+ rrdsetvar_custom_chart_variable_set(d->chart_var_speed, (calculated_number) d->speed);
+ d->speed_last_collected_usec = 0;
+ }
+ }
+ }
+ }
+
+ if (d->filename_duplex) {
+ d->duplex_last_collected_usec += dt;
+
+ if (unlikely(d->duplex_last_collected_usec >= (usec_t)dt_to_refresh_duplex)) {
+ if (unlikely(!d->chart_var_duplex)) {
+ d->chart_var_duplex = rrdsetvar_custom_chart_variable_create(d->st_bandwidth, "duplex");
+ if (!d->chart_var_duplex) {
+ error("Cannot create interface %s chart variable 'duplex'. Will not update the duplex status anymore.", d->name);
+ freez(d->filename_duplex);
+ d->filename_duplex = NULL;
+ }
+ }
+
+ if (d->filename_duplex && d->chart_var_duplex) {
+ char buffer[32 + 1];
+
+ if (read_file(d->filename_duplex, buffer, 32)) {
+ error("Cannot refresh interface %s duplex state by reading '%s'. I will stop updating it.", d->name, d->filename_duplex);
+ freez(d->filename_duplex);
+ d->filename_duplex = NULL;
+ } else {
+ // values can be unknown, half or full -- just check the first letter for speed
+ if (buffer[0] == 'f')
+ d->duplex = 2;
+ else if (buffer[0] == 'h')
+ d->duplex = 1;
+ else
+ d->duplex = 0;
+
+ rrdsetvar_custom_chart_variable_set(d->chart_var_duplex, (calculated_number)d->duplex);
+ d->duplex_last_collected_usec = 0;
+ }
+ }
+ }
+ }
+
+ if (d->filename_operstate) {
+ d->operstate_last_collected_usec += dt;
+
+ if (unlikely(d->operstate_last_collected_usec >= (usec_t)dt_to_refresh_operstate)) {
+ if (unlikely(!d->chart_var_operstate)) {
+ d->chart_var_operstate = rrdsetvar_custom_chart_variable_create(d->st_bandwidth, "operstate");
+ if (!d->chart_var_operstate) {
+ error(
+ "Cannot create interface %s chart variable 'operstate'. I will stop updating it.",
+ d->name);
+ freez(d->filename_operstate);
+ d->filename_operstate = NULL;
+ }
+ }
+
+ if (d->filename_operstate && d->chart_var_operstate) {
+ char buffer[32 + 1], *trimmed_buffer;
+
+ if (read_file(d->filename_operstate, buffer, 32)) {
+ error(
+ "Cannot refresh %s operstate by reading '%s'. Will not update its status anymore.",
+ d->name, d->filename_operstate);
+ freez(d->filename_operstate);
+ d->filename_operstate = NULL;
+ } else {
+ trimmed_buffer = trim(buffer);
+ d->operstate = get_operstate(trimmed_buffer);
+ rrdsetvar_custom_chart_variable_set(d->chart_var_operstate, (calculated_number)d->operstate);
+ d->operstate_last_collected_usec = 0;
+ }
+ }
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(d->do_packets == CONFIG_BOOLEAN_AUTO &&
+ (d->rpackets || d->tpackets || d->rmulticast || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))
+ d->do_packets = CONFIG_BOOLEAN_YES;
+
+ if(d->do_packets == CONFIG_BOOLEAN_YES) {
+ if(unlikely(!d->st_packets)) {
+
+ d->st_packets = rrdset_create_localhost(
+ d->chart_type_net_packets
+ , d->chart_id_net_packets
+ , NULL
+ , d->chart_family
+ , "net.packets"
+ , "Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , d->priority + 1
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_packets, RRDSET_FLAG_DETAIL);
+
+ rrdset_update_labels(d->st_packets, d->chart_labels);
+
+ d->rd_rpackets = rrddim_add(d->st_packets, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_tpackets = rrddim_add(d->st_packets, "sent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_rmulticast = rrddim_add(d->st_packets, "multicast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ if(d->flipped) {
+ // flip receive/trasmit
+
+ RRDDIM *td = d->rd_rpackets;
+ d->rd_rpackets = d->rd_tpackets;
+ d->rd_tpackets = td;
+ }
+ }
+ else rrdset_next(d->st_packets);
+
+ rrddim_set_by_pointer(d->st_packets, d->rd_rpackets, (collected_number)d->rpackets);
+ rrddim_set_by_pointer(d->st_packets, d->rd_tpackets, (collected_number)d->tpackets);
+ rrddim_set_by_pointer(d->st_packets, d->rd_rmulticast, (collected_number)d->rmulticast);
+ rrdset_done(d->st_packets);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(d->do_errors == CONFIG_BOOLEAN_AUTO &&
+ (d->rerrors || d->terrors || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))
+ d->do_errors = CONFIG_BOOLEAN_YES;
+
+ if(d->do_errors == CONFIG_BOOLEAN_YES) {
+ if(unlikely(!d->st_errors)) {
+
+ d->st_errors = rrdset_create_localhost(
+ d->chart_type_net_errors
+ , d->chart_id_net_errors
+ , NULL
+ , d->chart_family
+ , "net.errors"
+ , "Interface Errors"
+ , "errors/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , d->priority + 2
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_errors, RRDSET_FLAG_DETAIL);
+
+ rrdset_update_labels(d->st_errors, d->chart_labels);
+
+ d->rd_rerrors = rrddim_add(d->st_errors, "inbound", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_terrors = rrddim_add(d->st_errors, "outbound", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ if(d->flipped) {
+ // flip receive/trasmit
+
+ RRDDIM *td = d->rd_rerrors;
+ d->rd_rerrors = d->rd_terrors;
+ d->rd_terrors = td;
+ }
+ }
+ else rrdset_next(d->st_errors);
+
+ rrddim_set_by_pointer(d->st_errors, d->rd_rerrors, (collected_number)d->rerrors);
+ rrddim_set_by_pointer(d->st_errors, d->rd_terrors, (collected_number)d->terrors);
+ rrdset_done(d->st_errors);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(d->do_drops == CONFIG_BOOLEAN_AUTO &&
+ (d->rdrops || d->tdrops || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))
+ d->do_drops = CONFIG_BOOLEAN_YES;
+
+ if(d->do_drops == CONFIG_BOOLEAN_YES) {
+ if(unlikely(!d->st_drops)) {
+
+ d->st_drops = rrdset_create_localhost(
+ d->chart_type_net_drops
+ , d->chart_id_net_drops
+ , NULL
+ , d->chart_family
+ , "net.drops"
+ , "Interface Drops"
+ , "drops/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , d->priority + 3
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_drops, RRDSET_FLAG_DETAIL);
+
+ rrdset_update_labels(d->st_drops, d->chart_labels);
+
+ d->rd_rdrops = rrddim_add(d->st_drops, "inbound", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_tdrops = rrddim_add(d->st_drops, "outbound", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ if(d->flipped) {
+ // flip receive/trasmit
+
+ RRDDIM *td = d->rd_rdrops;
+ d->rd_rdrops = d->rd_tdrops;
+ d->rd_tdrops = td;
+ }
+ }
+ else rrdset_next(d->st_drops);
+
+ rrddim_set_by_pointer(d->st_drops, d->rd_rdrops, (collected_number)d->rdrops);
+ rrddim_set_by_pointer(d->st_drops, d->rd_tdrops, (collected_number)d->tdrops);
+ rrdset_done(d->st_drops);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(d->do_fifo == CONFIG_BOOLEAN_AUTO &&
+ (d->rfifo || d->tfifo || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))
+ d->do_fifo = CONFIG_BOOLEAN_YES;
+
+ if(d->do_fifo == CONFIG_BOOLEAN_YES) {
+ if(unlikely(!d->st_fifo)) {
+
+ d->st_fifo = rrdset_create_localhost(
+ d->chart_type_net_fifo
+ , d->chart_id_net_fifo
+ , NULL
+ , d->chart_family
+ , "net.fifo"
+ , "Interface FIFO Buffer Errors"
+ , "errors"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , d->priority + 4
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_fifo, RRDSET_FLAG_DETAIL);
+
+ rrdset_update_labels(d->st_fifo, d->chart_labels);
+
+ d->rd_rfifo = rrddim_add(d->st_fifo, "receive", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_tfifo = rrddim_add(d->st_fifo, "transmit", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ if(d->flipped) {
+ // flip receive/trasmit
+
+ RRDDIM *td = d->rd_rfifo;
+ d->rd_rfifo = d->rd_tfifo;
+ d->rd_tfifo = td;
+ }
+ }
+ else rrdset_next(d->st_fifo);
+
+ rrddim_set_by_pointer(d->st_fifo, d->rd_rfifo, (collected_number)d->rfifo);
+ rrddim_set_by_pointer(d->st_fifo, d->rd_tfifo, (collected_number)d->tfifo);
+ rrdset_done(d->st_fifo);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(d->do_compressed == CONFIG_BOOLEAN_AUTO &&
+ (d->rcompressed || d->tcompressed || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))
+ d->do_compressed = CONFIG_BOOLEAN_YES;
+
+ if(d->do_compressed == CONFIG_BOOLEAN_YES) {
+ if(unlikely(!d->st_compressed)) {
+
+ d->st_compressed = rrdset_create_localhost(
+ d->chart_type_net_compressed
+ , d->chart_id_net_compressed
+ , NULL
+ , d->chart_family
+ , "net.compressed"
+ , "Compressed Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , d->priority + 5
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_compressed, RRDSET_FLAG_DETAIL);
+
+ rrdset_update_labels(d->st_compressed, d->chart_labels);
+
+ d->rd_rcompressed = rrddim_add(d->st_compressed, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_tcompressed = rrddim_add(d->st_compressed, "sent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ if(d->flipped) {
+ // flip receive/trasmit
+
+ RRDDIM *td = d->rd_rcompressed;
+ d->rd_rcompressed = d->rd_tcompressed;
+ d->rd_tcompressed = td;
+ }
+ }
+ else rrdset_next(d->st_compressed);
+
+ rrddim_set_by_pointer(d->st_compressed, d->rd_rcompressed, (collected_number)d->rcompressed);
+ rrddim_set_by_pointer(d->st_compressed, d->rd_tcompressed, (collected_number)d->tcompressed);
+ rrdset_done(d->st_compressed);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(unlikely(d->do_events == CONFIG_BOOLEAN_AUTO &&
+ (d->rframe || d->tcollisions || d->tcarrier || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)))
+ d->do_events = CONFIG_BOOLEAN_YES;
+
+ if(d->do_events == CONFIG_BOOLEAN_YES) {
+ if(unlikely(!d->st_events)) {
+
+ d->st_events = rrdset_create_localhost(
+ d->chart_type_net_events
+ , d->chart_id_net_events
+ , NULL
+ , d->chart_family
+ , "net.events"
+ , "Network Interface Events"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , d->priority + 6
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(d->st_events, RRDSET_FLAG_DETAIL);
+
+ rrdset_update_labels(d->st_events, d->chart_labels);
+
+ d->rd_rframe = rrddim_add(d->st_events, "frames", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_tcollisions = rrddim_add(d->st_events, "collisions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ d->rd_tcarrier = rrddim_add(d->st_events, "carrier", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(d->st_events);
+
+ rrddim_set_by_pointer(d->st_events, d->rd_rframe, (collected_number)d->rframe);
+ rrddim_set_by_pointer(d->st_events, d->rd_tcollisions, (collected_number)d->tcollisions);
+ rrddim_set_by_pointer(d->st_events, d->rd_tcarrier, (collected_number)d->tcarrier);
+ rrdset_done(d->st_events);
+ }
+ }
+
+ if(do_bandwidth == CONFIG_BOOLEAN_YES || (do_bandwidth == CONFIG_BOOLEAN_AUTO &&
+ (system_rbytes || system_tbytes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_bandwidth = CONFIG_BOOLEAN_YES;
+ static RRDSET *st_system_net = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_system_net)) {
+ st_system_net = rrdset_create_localhost(
+ "system"
+ , "net"
+ , NULL
+ , "network"
+ , NULL
+ , "Physical Network Interfaces Aggregated Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETDEV_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_NET
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_in = rrddim_add(st_system_net, "InOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_system_net, "OutOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_system_net);
+
+ rrddim_set_by_pointer(st_system_net, rd_in, (collected_number)system_rbytes);
+ rrddim_set_by_pointer(st_system_net, rd_out, (collected_number)system_tbytes);
+
+ rrdset_done(st_system_net);
+ }
+
+ netdev_cleanup();
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_ip_vs_stats.c b/collectors/proc.plugin/proc_net_ip_vs_stats.c
new file mode 100644
index 0000000..43dcf2a
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_ip_vs_stats.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define RRD_TYPE_NET_IPVS "ipvs"
+#define PLUGIN_PROC_MODULE_NET_IPVS_NAME "/proc/net/ip_vs_stats"
+#define CONFIG_SECTION_PLUGIN_PROC_NET_IPVS "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_NET_IPVS_NAME
+
+int do_proc_net_ip_vs_stats(int update_every, usec_t dt) {
+ (void)dt;
+ static int do_bandwidth = -1, do_sockets = -1, do_packets = -1;
+ static procfile *ff = NULL;
+
+ if(do_bandwidth == -1) do_bandwidth = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NET_IPVS, "IPVS bandwidth", 1);
+ if(do_sockets == -1) do_sockets = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NET_IPVS, "IPVS connections", 1);
+ if(do_packets == -1) do_packets = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NET_IPVS, "IPVS packets", 1);
+
+ if(!ff) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/ip_vs_stats");
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_NET_IPVS, "filename to monitor", filename), " \t,:|", PROCFILE_FLAG_DEFAULT);
+ }
+ if(!ff) return 1;
+
+ ff = procfile_readall(ff);
+ if(!ff) return 0; // we return 0, so that we will retry to open it next time
+
+ // make sure we have 3 lines
+ if(procfile_lines(ff) < 3) return 1;
+
+ // make sure we have 5 words on the 3rd line
+ if(procfile_linewords(ff, 2) < 5) return 1;
+
+ unsigned long long entries, InPackets, OutPackets, InBytes, OutBytes;
+
+ entries = strtoull(procfile_lineword(ff, 2, 0), NULL, 16);
+ InPackets = strtoull(procfile_lineword(ff, 2, 1), NULL, 16);
+ OutPackets = strtoull(procfile_lineword(ff, 2, 2), NULL, 16);
+ InBytes = strtoull(procfile_lineword(ff, 2, 3), NULL, 16);
+ OutBytes = strtoull(procfile_lineword(ff, 2, 4), NULL, 16);
+
+
+ // --------------------------------------------------------------------
+
+ if(do_sockets) {
+ static RRDSET *st = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_IPVS
+ , "sockets"
+ , NULL
+ , RRD_TYPE_NET_IPVS
+ , NULL
+ , "IPVS New Connections"
+ , "connections/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_IPVS_NAME
+ , NETDATA_CHART_PRIO_IPVS_SOCKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrddim_add(st, "connections", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "connections", entries);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_packets) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_IPVS
+ , "packets"
+ , NULL
+ , RRD_TYPE_NET_IPVS
+ , NULL
+ , "IPVS Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_IPVS_NAME
+ , NETDATA_CHART_PRIO_IPVS_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrddim_add(st, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(st, "sent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "received", InPackets);
+ rrddim_set(st, "sent", OutPackets);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_bandwidth) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_IPVS
+ , "net"
+ , NULL
+ , RRD_TYPE_NET_IPVS
+ , NULL
+ , "IPVS Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_IPVS_NAME
+ , NETDATA_CHART_PRIO_IPVS_NET
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrddim_add(st, "received", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(st, "sent", NULL, -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "received", InBytes);
+ rrddim_set(st, "sent", OutBytes);
+ rrdset_done(st);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_netstat.c b/collectors/proc.plugin/proc_net_netstat.c
new file mode 100644
index 0000000..ab8206b
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_netstat.c
@@ -0,0 +1,869 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define RRD_TYPE_NET_NETSTAT "ip"
+#define PLUGIN_PROC_MODULE_NETSTAT_NAME "/proc/net/netstat"
+#define CONFIG_SECTION_PLUGIN_PROC_NETSTAT "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_NETSTAT_NAME
+
+unsigned long long tcpext_TCPSynRetrans = 0;
+
+static void parse_line_pair(procfile *ff, ARL_BASE *base, size_t header_line, size_t values_line) {
+ size_t hwords = procfile_linewords(ff, header_line);
+ size_t vwords = procfile_linewords(ff, values_line);
+ size_t w;
+
+ if(unlikely(vwords > hwords)) {
+ error("File /proc/net/netstat on header line %zu has %zu words, but on value line %zu has %zu words.", header_line, hwords, values_line, vwords);
+ vwords = hwords;
+ }
+
+ for(w = 1; w < vwords ;w++) {
+ if(unlikely(arl_check(base, procfile_lineword(ff, header_line, w), procfile_lineword(ff, values_line, w))))
+ break;
+ }
+}
+
+int do_proc_net_netstat(int update_every, usec_t dt) {
+ (void)dt;
+
+ static int do_bandwidth = -1, do_inerrors = -1, do_mcast = -1, do_bcast = -1, do_mcast_p = -1, do_bcast_p = -1, do_ecn = -1, \
+ do_tcpext_reorder = -1, do_tcpext_syscookies = -1, do_tcpext_ofo = -1, do_tcpext_connaborts = -1, do_tcpext_memory = -1,
+ do_tcpext_syn_queue = -1, do_tcpext_accept_queue = -1;
+
+ static uint32_t hash_ipext = 0, hash_tcpext = 0;
+ static procfile *ff = NULL;
+
+ static ARL_BASE *arl_tcpext = NULL;
+ static ARL_BASE *arl_ipext = NULL;
+
+ // --------------------------------------------------------------------
+ // IP
+
+ // IP bandwidth
+ static unsigned long long ipext_InOctets = 0;
+ static unsigned long long ipext_OutOctets = 0;
+
+ // IP input errors
+ static unsigned long long ipext_InNoRoutes = 0;
+ static unsigned long long ipext_InTruncatedPkts = 0;
+ static unsigned long long ipext_InCsumErrors = 0;
+
+ // IP multicast bandwidth
+ static unsigned long long ipext_InMcastOctets = 0;
+ static unsigned long long ipext_OutMcastOctets = 0;
+
+ // IP multicast packets
+ static unsigned long long ipext_InMcastPkts = 0;
+ static unsigned long long ipext_OutMcastPkts = 0;
+
+ // IP broadcast bandwidth
+ static unsigned long long ipext_InBcastOctets = 0;
+ static unsigned long long ipext_OutBcastOctets = 0;
+
+ // IP broadcast packets
+ static unsigned long long ipext_InBcastPkts = 0;
+ static unsigned long long ipext_OutBcastPkts = 0;
+
+ // IP ECN
+ static unsigned long long ipext_InNoECTPkts = 0;
+ static unsigned long long ipext_InECT1Pkts = 0;
+ static unsigned long long ipext_InECT0Pkts = 0;
+ static unsigned long long ipext_InCEPkts = 0;
+
+ // --------------------------------------------------------------------
+ // IP TCP
+
+ // IP TCP Reordering
+ static unsigned long long tcpext_TCPRenoReorder = 0;
+ static unsigned long long tcpext_TCPFACKReorder = 0;
+ static unsigned long long tcpext_TCPSACKReorder = 0;
+ static unsigned long long tcpext_TCPTSReorder = 0;
+
+ // IP TCP SYN Cookies
+ static unsigned long long tcpext_SyncookiesSent = 0;
+ static unsigned long long tcpext_SyncookiesRecv = 0;
+ static unsigned long long tcpext_SyncookiesFailed = 0;
+
+ // IP TCP Out Of Order Queue
+ // http://www.spinics.net/lists/netdev/msg204696.html
+ static unsigned long long tcpext_TCPOFOQueue = 0; // Number of packets queued in OFO queue
+ static unsigned long long tcpext_TCPOFODrop = 0; // Number of packets meant to be queued in OFO but dropped because socket rcvbuf limit hit.
+ static unsigned long long tcpext_TCPOFOMerge = 0; // Number of packets in OFO that were merged with other packets.
+ static unsigned long long tcpext_OfoPruned = 0; // packets dropped from out-of-order queue because of socket buffer overrun
+
+ // IP TCP connection resets
+ // https://github.com/ecki/net-tools/blob/bd8bceaed2311651710331a7f8990c3e31be9840/statistics.c
+ static unsigned long long tcpext_TCPAbortOnData = 0; // connections reset due to unexpected data
+ static unsigned long long tcpext_TCPAbortOnClose = 0; // connections reset due to early user close
+ static unsigned long long tcpext_TCPAbortOnMemory = 0; // connections aborted due to memory pressure
+ static unsigned long long tcpext_TCPAbortOnTimeout = 0; // connections aborted due to timeout
+ static unsigned long long tcpext_TCPAbortOnLinger = 0; // connections aborted after user close in linger timeout
+ static unsigned long long tcpext_TCPAbortFailed = 0; // times unable to send RST due to no memory
+
+ // https://perfchron.com/2015/12/26/investigating-linux-network-issues-with-netstat-and-nstat/
+ static unsigned long long tcpext_ListenOverflows = 0; // times the listen queue of a socket overflowed
+ static unsigned long long tcpext_ListenDrops = 0; // SYNs to LISTEN sockets ignored
+
+ // IP TCP memory pressures
+ static unsigned long long tcpext_TCPMemoryPressures = 0;
+
+ static unsigned long long tcpext_TCPReqQFullDrop = 0;
+ static unsigned long long tcpext_TCPReqQFullDoCookies = 0;
+
+ // shared: tcpext_TCPSynRetrans
+
+
+ if(unlikely(!arl_ipext)) {
+ hash_ipext = simple_hash("IpExt");
+ hash_tcpext = simple_hash("TcpExt");
+
+ do_bandwidth = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_inerrors = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "input errors", CONFIG_BOOLEAN_AUTO);
+ do_mcast = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "multicast bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_bcast = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "broadcast bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_mcast_p = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "multicast packets", CONFIG_BOOLEAN_AUTO);
+ do_bcast_p = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "broadcast packets", CONFIG_BOOLEAN_AUTO);
+ do_ecn = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "ECN packets", CONFIG_BOOLEAN_AUTO);
+
+ do_tcpext_reorder = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "TCP reorders", CONFIG_BOOLEAN_AUTO);
+ do_tcpext_syscookies = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "TCP SYN cookies", CONFIG_BOOLEAN_AUTO);
+ do_tcpext_ofo = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "TCP out-of-order queue", CONFIG_BOOLEAN_AUTO);
+ do_tcpext_connaborts = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "TCP connection aborts", CONFIG_BOOLEAN_AUTO);
+ do_tcpext_memory = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "TCP memory pressures", CONFIG_BOOLEAN_AUTO);
+
+ do_tcpext_syn_queue = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "TCP SYN queue", CONFIG_BOOLEAN_AUTO);
+ do_tcpext_accept_queue = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "TCP accept queue", CONFIG_BOOLEAN_AUTO);
+
+ arl_ipext = arl_create("netstat/ipext", NULL, 60);
+ arl_tcpext = arl_create("netstat/tcpext", NULL, 60);
+
+ // --------------------------------------------------------------------
+ // IP
+
+ if(do_bandwidth != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_ipext, "InOctets", &ipext_InOctets);
+ arl_expect(arl_ipext, "OutOctets", &ipext_OutOctets);
+ }
+
+ if(do_inerrors != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_ipext, "InNoRoutes", &ipext_InNoRoutes);
+ arl_expect(arl_ipext, "InTruncatedPkts", &ipext_InTruncatedPkts);
+ arl_expect(arl_ipext, "InCsumErrors", &ipext_InCsumErrors);
+ }
+
+ if(do_mcast != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_ipext, "InMcastOctets", &ipext_InMcastOctets);
+ arl_expect(arl_ipext, "OutMcastOctets", &ipext_OutMcastOctets);
+ }
+
+ if(do_mcast_p != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_ipext, "InMcastPkts", &ipext_InMcastPkts);
+ arl_expect(arl_ipext, "OutMcastPkts", &ipext_OutMcastPkts);
+ }
+
+ if(do_bcast != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_ipext, "InBcastPkts", &ipext_InBcastPkts);
+ arl_expect(arl_ipext, "OutBcastPkts", &ipext_OutBcastPkts);
+ }
+
+ if(do_bcast_p != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_ipext, "InBcastOctets", &ipext_InBcastOctets);
+ arl_expect(arl_ipext, "OutBcastOctets", &ipext_OutBcastOctets);
+ }
+
+ if(do_ecn != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_ipext, "InNoECTPkts", &ipext_InNoECTPkts);
+ arl_expect(arl_ipext, "InECT1Pkts", &ipext_InECT1Pkts);
+ arl_expect(arl_ipext, "InECT0Pkts", &ipext_InECT0Pkts);
+ arl_expect(arl_ipext, "InCEPkts", &ipext_InCEPkts);
+ }
+
+ // --------------------------------------------------------------------
+ // IP TCP
+
+ if(do_tcpext_reorder != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_tcpext, "TCPFACKReorder", &tcpext_TCPFACKReorder);
+ arl_expect(arl_tcpext, "TCPSACKReorder", &tcpext_TCPSACKReorder);
+ arl_expect(arl_tcpext, "TCPRenoReorder", &tcpext_TCPRenoReorder);
+ arl_expect(arl_tcpext, "TCPTSReorder", &tcpext_TCPTSReorder);
+ }
+
+ if(do_tcpext_syscookies != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_tcpext, "SyncookiesSent", &tcpext_SyncookiesSent);
+ arl_expect(arl_tcpext, "SyncookiesRecv", &tcpext_SyncookiesRecv);
+ arl_expect(arl_tcpext, "SyncookiesFailed", &tcpext_SyncookiesFailed);
+ }
+
+ if(do_tcpext_ofo != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_tcpext, "TCPOFOQueue", &tcpext_TCPOFOQueue);
+ arl_expect(arl_tcpext, "TCPOFODrop", &tcpext_TCPOFODrop);
+ arl_expect(arl_tcpext, "TCPOFOMerge", &tcpext_TCPOFOMerge);
+ arl_expect(arl_tcpext, "OfoPruned", &tcpext_OfoPruned);
+ }
+
+ if(do_tcpext_connaborts != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_tcpext, "TCPAbortOnData", &tcpext_TCPAbortOnData);
+ arl_expect(arl_tcpext, "TCPAbortOnClose", &tcpext_TCPAbortOnClose);
+ arl_expect(arl_tcpext, "TCPAbortOnMemory", &tcpext_TCPAbortOnMemory);
+ arl_expect(arl_tcpext, "TCPAbortOnTimeout", &tcpext_TCPAbortOnTimeout);
+ arl_expect(arl_tcpext, "TCPAbortOnLinger", &tcpext_TCPAbortOnLinger);
+ arl_expect(arl_tcpext, "TCPAbortFailed", &tcpext_TCPAbortFailed);
+ }
+
+ if(do_tcpext_memory != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_tcpext, "TCPMemoryPressures", &tcpext_TCPMemoryPressures);
+ }
+
+ if(do_tcpext_accept_queue != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_tcpext, "ListenOverflows", &tcpext_ListenOverflows);
+ arl_expect(arl_tcpext, "ListenDrops", &tcpext_ListenDrops);
+ }
+
+ if(do_tcpext_syn_queue != CONFIG_BOOLEAN_NO) {
+ arl_expect(arl_tcpext, "TCPReqQFullDrop", &tcpext_TCPReqQFullDrop);
+ arl_expect(arl_tcpext, "TCPReqQFullDoCookies", &tcpext_TCPReqQFullDoCookies);
+ }
+
+ // shared metrics
+ arl_expect(arl_tcpext, "TCPSynRetrans", &tcpext_TCPSynRetrans);
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/netstat");
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_NETSTAT, "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+ size_t words;
+
+ arl_begin(arl_ipext);
+ arl_begin(arl_tcpext);
+
+ for(l = 0; l < lines ;l++) {
+ char *key = procfile_lineword(ff, l, 0);
+ uint32_t hash = simple_hash(key);
+
+ if(unlikely(hash == hash_ipext && strcmp(key, "IpExt") == 0)) {
+ size_t h = l++;
+
+ words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) {
+ error("Cannot read /proc/net/netstat IpExt line. Expected 2+ params, read %zu.", words);
+ continue;
+ }
+
+ parse_line_pair(ff, arl_ipext, h, l);
+
+ // --------------------------------------------------------------------
+
+ if(do_bandwidth == CONFIG_BOOLEAN_YES || (do_bandwidth == CONFIG_BOOLEAN_AUTO &&
+ (ipext_InOctets ||
+ ipext_OutOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_bandwidth = CONFIG_BOOLEAN_YES;
+ static RRDSET *st_system_ip = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_system_ip)) {
+ st_system_ip = rrdset_create_localhost(
+ "system"
+ , RRD_TYPE_NET_NETSTAT
+ , NULL
+ , "network"
+ , NULL
+ , "IP Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_IP
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_in = rrddim_add(st_system_ip, "InOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_system_ip, "OutOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_system_ip);
+
+ rrddim_set_by_pointer(st_system_ip, rd_in, ipext_InOctets);
+ rrddim_set_by_pointer(st_system_ip, rd_out, ipext_OutOctets);
+
+ rrdset_done(st_system_ip);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_inerrors == CONFIG_BOOLEAN_YES || (do_inerrors == CONFIG_BOOLEAN_AUTO &&
+ (ipext_InNoRoutes ||
+ ipext_InTruncatedPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_inerrors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st_ip_inerrors = NULL;
+ static RRDDIM *rd_noroutes = NULL, *rd_truncated = NULL, *rd_checksum = NULL;
+
+ if(unlikely(!st_ip_inerrors)) {
+ st_ip_inerrors = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "inerrors"
+ , NULL
+ , "errors"
+ , NULL
+ , "IP Input Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_ip_inerrors, RRDSET_FLAG_DETAIL);
+
+ rd_noroutes = rrddim_add(st_ip_inerrors, "InNoRoutes", "noroutes", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_truncated = rrddim_add(st_ip_inerrors, "InTruncatedPkts", "truncated", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_checksum = rrddim_add(st_ip_inerrors, "InCsumErrors", "checksum", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_ip_inerrors);
+
+ rrddim_set_by_pointer(st_ip_inerrors, rd_noroutes, ipext_InNoRoutes);
+ rrddim_set_by_pointer(st_ip_inerrors, rd_truncated, ipext_InTruncatedPkts);
+ rrddim_set_by_pointer(st_ip_inerrors, rd_checksum, ipext_InCsumErrors);
+
+ rrdset_done(st_ip_inerrors);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_mcast == CONFIG_BOOLEAN_YES || (do_mcast == CONFIG_BOOLEAN_AUTO &&
+ (ipext_InMcastOctets ||
+ ipext_OutMcastOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_mcast = CONFIG_BOOLEAN_YES;
+ static RRDSET *st_ip_mcast = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_ip_mcast)) {
+ st_ip_mcast = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "mcast"
+ , NULL
+ , "multicast"
+ , NULL
+ , "IP Multicast Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_MCAST
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrdset_flag_set(st_ip_mcast, RRDSET_FLAG_DETAIL);
+
+ rd_in = rrddim_add(st_ip_mcast, "InMcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_ip_mcast, "OutMcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_ip_mcast);
+
+ rrddim_set_by_pointer(st_ip_mcast, rd_in, ipext_InMcastOctets);
+ rrddim_set_by_pointer(st_ip_mcast, rd_out, ipext_OutMcastOctets);
+
+ rrdset_done(st_ip_mcast);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_bcast == CONFIG_BOOLEAN_YES || (do_bcast == CONFIG_BOOLEAN_AUTO &&
+ (ipext_InBcastOctets ||
+ ipext_OutBcastOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_bcast = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_ip_bcast = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_ip_bcast)) {
+ st_ip_bcast = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "bcast"
+ , NULL
+ , "broadcast"
+ , NULL
+ , "IP Broadcast Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_BCAST
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rrdset_flag_set(st_ip_bcast, RRDSET_FLAG_DETAIL);
+
+ rd_in = rrddim_add(st_ip_bcast, "InBcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_ip_bcast, "OutBcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_ip_bcast);
+
+ rrddim_set_by_pointer(st_ip_bcast, rd_in, ipext_InBcastOctets);
+ rrddim_set_by_pointer(st_ip_bcast, rd_out, ipext_OutBcastOctets);
+
+ rrdset_done(st_ip_bcast);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_mcast_p == CONFIG_BOOLEAN_YES || (do_mcast_p == CONFIG_BOOLEAN_AUTO &&
+ (ipext_InMcastPkts ||
+ ipext_OutMcastPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_mcast_p = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_ip_mcastpkts = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_ip_mcastpkts)) {
+ st_ip_mcastpkts = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "mcastpkts"
+ , NULL
+ , "multicast"
+ , NULL
+ , "IP Multicast Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_MCAST_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_ip_mcastpkts, RRDSET_FLAG_DETAIL);
+
+ rd_in = rrddim_add(st_ip_mcastpkts, "InMcastPkts", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_ip_mcastpkts, "OutMcastPkts", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_ip_mcastpkts);
+
+ rrddim_set_by_pointer(st_ip_mcastpkts, rd_in, ipext_InMcastPkts);
+ rrddim_set_by_pointer(st_ip_mcastpkts, rd_out, ipext_OutMcastPkts);
+
+ rrdset_done(st_ip_mcastpkts);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_bcast_p == CONFIG_BOOLEAN_YES || (do_bcast_p == CONFIG_BOOLEAN_AUTO &&
+ (ipext_InBcastPkts ||
+ ipext_OutBcastPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_bcast_p = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_ip_bcastpkts = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_ip_bcastpkts)) {
+ st_ip_bcastpkts = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "bcastpkts"
+ , NULL
+ , "broadcast"
+ , NULL
+ , "IP Broadcast Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_BCAST_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_ip_bcastpkts, RRDSET_FLAG_DETAIL);
+
+ rd_in = rrddim_add(st_ip_bcastpkts, "InBcastPkts", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_ip_bcastpkts, "OutBcastPkts", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_ip_bcastpkts);
+
+ rrddim_set_by_pointer(st_ip_bcastpkts, rd_in, ipext_InBcastPkts);
+ rrddim_set_by_pointer(st_ip_bcastpkts, rd_out, ipext_OutBcastPkts);
+
+ rrdset_done(st_ip_bcastpkts);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ecn == CONFIG_BOOLEAN_YES || (do_ecn == CONFIG_BOOLEAN_AUTO &&
+ (ipext_InCEPkts ||
+ ipext_InECT0Pkts ||
+ ipext_InECT1Pkts ||
+ ipext_InNoECTPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ecn = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_ecnpkts = NULL;
+ static RRDDIM *rd_cep = NULL, *rd_noectp = NULL, *rd_ectp0 = NULL, *rd_ectp1 = NULL;
+
+ if(unlikely(!st_ecnpkts)) {
+ st_ecnpkts = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "ecnpkts"
+ , NULL
+ , "ecn"
+ , NULL
+ , "IP ECN Statistics"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_ECN
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_ecnpkts, RRDSET_FLAG_DETAIL);
+
+ rd_cep = rrddim_add(st_ecnpkts, "InCEPkts", "CEP", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_noectp = rrddim_add(st_ecnpkts, "InNoECTPkts", "NoECTP", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_ectp0 = rrddim_add(st_ecnpkts, "InECT0Pkts", "ECTP0", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_ectp1 = rrddim_add(st_ecnpkts, "InECT1Pkts", "ECTP1", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_ecnpkts);
+
+ rrddim_set_by_pointer(st_ecnpkts, rd_cep, ipext_InCEPkts);
+ rrddim_set_by_pointer(st_ecnpkts, rd_noectp, ipext_InNoECTPkts);
+ rrddim_set_by_pointer(st_ecnpkts, rd_ectp0, ipext_InECT0Pkts);
+ rrddim_set_by_pointer(st_ecnpkts, rd_ectp1, ipext_InECT1Pkts);
+
+ rrdset_done(st_ecnpkts);
+ }
+ }
+ else if(unlikely(hash == hash_tcpext && strcmp(key, "TcpExt") == 0)) {
+ size_t h = l++;
+
+ words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) {
+ error("Cannot read /proc/net/netstat TcpExt line. Expected 2+ params, read %zu.", words);
+ continue;
+ }
+
+ parse_line_pair(ff, arl_tcpext, h, l);
+
+ // --------------------------------------------------------------------
+
+ if(do_tcpext_memory == CONFIG_BOOLEAN_YES || (do_tcpext_memory == CONFIG_BOOLEAN_AUTO &&
+ (tcpext_TCPMemoryPressures ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcpext_memory = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_tcpmemorypressures = NULL;
+ static RRDDIM *rd_pressures = NULL;
+
+ if(unlikely(!st_tcpmemorypressures)) {
+ st_tcpmemorypressures = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "tcpmemorypressures"
+ , NULL
+ , "tcp"
+ , NULL
+ , "TCP Memory Pressures"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_TCP_MEM
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_pressures = rrddim_add(st_tcpmemorypressures, "TCPMemoryPressures", "pressures", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_tcpmemorypressures);
+
+ rrddim_set_by_pointer(st_tcpmemorypressures, rd_pressures, tcpext_TCPMemoryPressures);
+
+ rrdset_done(st_tcpmemorypressures);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcpext_connaborts == CONFIG_BOOLEAN_YES || (do_tcpext_connaborts == CONFIG_BOOLEAN_AUTO &&
+ (tcpext_TCPAbortOnData ||
+ tcpext_TCPAbortOnClose ||
+ tcpext_TCPAbortOnMemory ||
+ tcpext_TCPAbortOnTimeout ||
+ tcpext_TCPAbortOnLinger ||
+ tcpext_TCPAbortFailed ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcpext_connaborts = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_tcpconnaborts = NULL;
+ static RRDDIM *rd_baddata = NULL, *rd_userclosed = NULL, *rd_nomemory = NULL, *rd_timeout = NULL, *rd_linger = NULL, *rd_failed = NULL;
+
+ if(unlikely(!st_tcpconnaborts)) {
+ st_tcpconnaborts = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "tcpconnaborts"
+ , NULL
+ , "tcp"
+ , NULL
+ , "TCP Connection Aborts"
+ , "connections/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_TCP_CONNABORTS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_baddata = rrddim_add(st_tcpconnaborts, "TCPAbortOnData", "baddata", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_userclosed = rrddim_add(st_tcpconnaborts, "TCPAbortOnClose", "userclosed", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_nomemory = rrddim_add(st_tcpconnaborts, "TCPAbortOnMemory", "nomemory", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_timeout = rrddim_add(st_tcpconnaborts, "TCPAbortOnTimeout", "timeout", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_linger = rrddim_add(st_tcpconnaborts, "TCPAbortOnLinger", "linger", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_failed = rrddim_add(st_tcpconnaborts, "TCPAbortFailed", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_tcpconnaborts);
+
+ rrddim_set_by_pointer(st_tcpconnaborts, rd_baddata, tcpext_TCPAbortOnData);
+ rrddim_set_by_pointer(st_tcpconnaborts, rd_userclosed, tcpext_TCPAbortOnClose);
+ rrddim_set_by_pointer(st_tcpconnaborts, rd_nomemory, tcpext_TCPAbortOnMemory);
+ rrddim_set_by_pointer(st_tcpconnaborts, rd_timeout, tcpext_TCPAbortOnTimeout);
+ rrddim_set_by_pointer(st_tcpconnaborts, rd_linger, tcpext_TCPAbortOnLinger);
+ rrddim_set_by_pointer(st_tcpconnaborts, rd_failed, tcpext_TCPAbortFailed);
+
+ rrdset_done(st_tcpconnaborts);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcpext_reorder == CONFIG_BOOLEAN_YES || (do_tcpext_reorder == CONFIG_BOOLEAN_AUTO &&
+ (tcpext_TCPRenoReorder ||
+ tcpext_TCPFACKReorder ||
+ tcpext_TCPSACKReorder ||
+ tcpext_TCPTSReorder ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcpext_reorder = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_tcpreorders = NULL;
+ static RRDDIM *rd_timestamp = NULL, *rd_sack = NULL, *rd_fack = NULL, *rd_reno = NULL;
+
+ if(unlikely(!st_tcpreorders)) {
+ st_tcpreorders = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "tcpreorders"
+ , NULL
+ , "tcp"
+ , NULL
+ , "TCP Reordered Packets by Detection Method"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_TCP_REORDERS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_timestamp = rrddim_add(st_tcpreorders, "TCPTSReorder", "timestamp", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sack = rrddim_add(st_tcpreorders, "TCPSACKReorder", "sack", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_fack = rrddim_add(st_tcpreorders, "TCPFACKReorder", "fack", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_reno = rrddim_add(st_tcpreorders, "TCPRenoReorder", "reno", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_tcpreorders);
+
+ rrddim_set_by_pointer(st_tcpreorders, rd_timestamp, tcpext_TCPTSReorder);
+ rrddim_set_by_pointer(st_tcpreorders, rd_sack, tcpext_TCPSACKReorder);
+ rrddim_set_by_pointer(st_tcpreorders, rd_fack, tcpext_TCPFACKReorder);
+ rrddim_set_by_pointer(st_tcpreorders, rd_reno, tcpext_TCPRenoReorder);
+
+ rrdset_done(st_tcpreorders);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcpext_ofo == CONFIG_BOOLEAN_YES || (do_tcpext_ofo == CONFIG_BOOLEAN_AUTO &&
+ (tcpext_TCPOFOQueue ||
+ tcpext_TCPOFODrop ||
+ tcpext_TCPOFOMerge ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcpext_ofo = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_ip_tcpofo = NULL;
+ static RRDDIM *rd_inqueue = NULL, *rd_dropped = NULL, *rd_merged = NULL, *rd_pruned = NULL;
+
+ if(unlikely(!st_ip_tcpofo)) {
+
+ st_ip_tcpofo = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "tcpofo"
+ , NULL
+ , "tcp"
+ , NULL
+ , "TCP Out-Of-Order Queue"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_TCP_OFO
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inqueue = rrddim_add(st_ip_tcpofo, "TCPOFOQueue", "inqueue", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_dropped = rrddim_add(st_ip_tcpofo, "TCPOFODrop", "dropped", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_merged = rrddim_add(st_ip_tcpofo, "TCPOFOMerge", "merged", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pruned = rrddim_add(st_ip_tcpofo, "OfoPruned", "pruned", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_ip_tcpofo);
+
+ rrddim_set_by_pointer(st_ip_tcpofo, rd_inqueue, tcpext_TCPOFOQueue);
+ rrddim_set_by_pointer(st_ip_tcpofo, rd_dropped, tcpext_TCPOFODrop);
+ rrddim_set_by_pointer(st_ip_tcpofo, rd_merged, tcpext_TCPOFOMerge);
+ rrddim_set_by_pointer(st_ip_tcpofo, rd_pruned, tcpext_OfoPruned);
+
+ rrdset_done(st_ip_tcpofo);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcpext_syscookies == CONFIG_BOOLEAN_YES || (do_tcpext_syscookies == CONFIG_BOOLEAN_AUTO &&
+ (tcpext_SyncookiesSent ||
+ tcpext_SyncookiesRecv ||
+ tcpext_SyncookiesFailed ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcpext_syscookies = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_syncookies = NULL;
+ static RRDDIM *rd_received = NULL, *rd_sent = NULL, *rd_failed = NULL;
+
+ if(unlikely(!st_syncookies)) {
+
+ st_syncookies = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "tcpsyncookies"
+ , NULL
+ , "tcp"
+ , NULL
+ , "TCP SYN Cookies"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_TCP_SYNCOOKIES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_received = rrddim_add(st_syncookies, "SyncookiesRecv", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st_syncookies, "SyncookiesSent", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_failed = rrddim_add(st_syncookies, "SyncookiesFailed", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_syncookies);
+
+ rrddim_set_by_pointer(st_syncookies, rd_received, tcpext_SyncookiesRecv);
+ rrddim_set_by_pointer(st_syncookies, rd_sent, tcpext_SyncookiesSent);
+ rrddim_set_by_pointer(st_syncookies, rd_failed, tcpext_SyncookiesFailed);
+
+ rrdset_done(st_syncookies);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcpext_syn_queue == CONFIG_BOOLEAN_YES || (do_tcpext_syn_queue == CONFIG_BOOLEAN_AUTO &&
+ (tcpext_TCPReqQFullDrop ||
+ tcpext_TCPReqQFullDoCookies ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcpext_syn_queue = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_syn_queue = NULL;
+ static RRDDIM
+ *rd_TCPReqQFullDrop = NULL,
+ *rd_TCPReqQFullDoCookies = NULL;
+
+ if(unlikely(!st_syn_queue)) {
+
+ st_syn_queue = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "tcp_syn_queue"
+ , NULL
+ , "tcp"
+ , NULL
+ , "TCP SYN Queue Issues"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_TCP_SYN_QUEUE
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_TCPReqQFullDrop = rrddim_add(st_syn_queue, "TCPReqQFullDrop", "drops", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_TCPReqQFullDoCookies = rrddim_add(st_syn_queue, "TCPReqQFullDoCookies", "cookies", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_syn_queue);
+
+ rrddim_set_by_pointer(st_syn_queue, rd_TCPReqQFullDrop, tcpext_TCPReqQFullDrop);
+ rrddim_set_by_pointer(st_syn_queue, rd_TCPReqQFullDoCookies, tcpext_TCPReqQFullDoCookies);
+
+ rrdset_done(st_syn_queue);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcpext_accept_queue == CONFIG_BOOLEAN_YES || (do_tcpext_accept_queue == CONFIG_BOOLEAN_AUTO &&
+ (tcpext_ListenOverflows ||
+ tcpext_ListenDrops ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcpext_accept_queue = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_accept_queue = NULL;
+ static RRDDIM *rd_overflows = NULL,
+ *rd_drops = NULL;
+
+ if(unlikely(!st_accept_queue)) {
+
+ st_accept_queue = rrdset_create_localhost(
+ RRD_TYPE_NET_NETSTAT
+ , "tcp_accept_queue"
+ , NULL
+ , "tcp"
+ , NULL
+ , "TCP Accept Queue Issues"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NETSTAT_NAME
+ , NETDATA_CHART_PRIO_IP_TCP_ACCEPT_QUEUE
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_overflows = rrddim_add(st_accept_queue, "ListenOverflows", "overflows", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_drops = rrddim_add(st_accept_queue, "ListenDrops", "drops", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_accept_queue);
+
+ rrddim_set_by_pointer(st_accept_queue, rd_overflows, tcpext_ListenOverflows);
+ rrddim_set_by_pointer(st_accept_queue, rd_drops, tcpext_ListenDrops);
+
+ rrdset_done(st_accept_queue);
+ }
+
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_rpc_nfs.c b/collectors/proc.plugin/proc_net_rpc_nfs.c
new file mode 100644
index 0000000..f570285
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_rpc_nfs.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_NFS_NAME "/proc/net/rpc/nfs"
+#define CONFIG_SECTION_PLUGIN_PROC_NFS "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_NFS_NAME
+
+struct nfs_procs {
+ char name[30];
+ unsigned long long value;
+ int present;
+ RRDDIM *rd;
+};
+
+struct nfs_procs nfs_proc2_values[] = {
+ { "null" , 0ULL, 0, NULL}
+ , {"getattr" , 0ULL, 0, NULL}
+ , {"setattr" , 0ULL, 0, NULL}
+ , {"root" , 0ULL, 0, NULL}
+ , {"lookup" , 0ULL, 0, NULL}
+ , {"readlink", 0ULL, 0, NULL}
+ , {"read" , 0ULL, 0, NULL}
+ , {"wrcache" , 0ULL, 0, NULL}
+ , {"write" , 0ULL, 0, NULL}
+ , {"create" , 0ULL, 0, NULL}
+ , {"remove" , 0ULL, 0, NULL}
+ , {"rename" , 0ULL, 0, NULL}
+ , {"link" , 0ULL, 0, NULL}
+ , {"symlink" , 0ULL, 0, NULL}
+ , {"mkdir" , 0ULL, 0, NULL}
+ , {"rmdir" , 0ULL, 0, NULL}
+ , {"readdir" , 0ULL, 0, NULL}
+ , {"fsstat" , 0ULL, 0, NULL}
+ ,
+
+ /* termination */
+ { "" , 0ULL, 0, NULL}
+};
+
+struct nfs_procs nfs_proc3_values[] = {
+ { "null" , 0ULL, 0, NULL}
+ , {"getattr" , 0ULL, 0, NULL}
+ , {"setattr" , 0ULL, 0, NULL}
+ , {"lookup" , 0ULL, 0, NULL}
+ , {"access" , 0ULL, 0, NULL}
+ , {"readlink" , 0ULL, 0, NULL}
+ , {"read" , 0ULL, 0, NULL}
+ , {"write" , 0ULL, 0, NULL}
+ , {"create" , 0ULL, 0, NULL}
+ , {"mkdir" , 0ULL, 0, NULL}
+ , {"symlink" , 0ULL, 0, NULL}
+ , {"mknod" , 0ULL, 0, NULL}
+ , {"remove" , 0ULL, 0, NULL}
+ , {"rmdir" , 0ULL, 0, NULL}
+ , {"rename" , 0ULL, 0, NULL}
+ , {"link" , 0ULL, 0, NULL}
+ , {"readdir" , 0ULL, 0, NULL}
+ , {"readdirplus", 0ULL, 0, NULL}
+ , {"fsstat" , 0ULL, 0, NULL}
+ , {"fsinfo" , 0ULL, 0, NULL}
+ , {"pathconf" , 0ULL, 0, NULL}
+ , {"commit" , 0ULL, 0, NULL}
+ ,
+
+ /* termination */
+ { "" , 0ULL, 0, NULL}
+};
+
+struct nfs_procs nfs_proc4_values[] = {
+ { "null" , 0ULL, 0, NULL}
+ , {"read" , 0ULL, 0, NULL}
+ , {"write" , 0ULL, 0, NULL}
+ , {"commit" , 0ULL, 0, NULL}
+ , {"open" , 0ULL, 0, NULL}
+ , {"open_conf" , 0ULL, 0, NULL}
+ , {"open_noat" , 0ULL, 0, NULL}
+ , {"open_dgrd" , 0ULL, 0, NULL}
+ , {"close" , 0ULL, 0, NULL}
+ , {"setattr" , 0ULL, 0, NULL}
+ , {"fsinfo" , 0ULL, 0, NULL}
+ , {"renew" , 0ULL, 0, NULL}
+ , {"setclntid" , 0ULL, 0, NULL}
+ , {"confirm" , 0ULL, 0, NULL}
+ , {"lock" , 0ULL, 0, NULL}
+ , {"lockt" , 0ULL, 0, NULL}
+ , {"locku" , 0ULL, 0, NULL}
+ , {"access" , 0ULL, 0, NULL}
+ , {"getattr" , 0ULL, 0, NULL}
+ , {"lookup" , 0ULL, 0, NULL}
+ , {"lookup_root" , 0ULL, 0, NULL}
+ , {"remove" , 0ULL, 0, NULL}
+ , {"rename" , 0ULL, 0, NULL}
+ , {"link" , 0ULL, 0, NULL}
+ , {"symlink" , 0ULL, 0, NULL}
+ , {"create" , 0ULL, 0, NULL}
+ , {"pathconf" , 0ULL, 0, NULL}
+ , {"statfs" , 0ULL, 0, NULL}
+ , {"readlink" , 0ULL, 0, NULL}
+ , {"readdir" , 0ULL, 0, NULL}
+ , {"server_caps" , 0ULL, 0, NULL}
+ , {"delegreturn" , 0ULL, 0, NULL}
+ , {"getacl" , 0ULL, 0, NULL}
+ , {"setacl" , 0ULL, 0, NULL}
+ , {"fs_locations" , 0ULL, 0, NULL}
+ , {"rel_lkowner" , 0ULL, 0, NULL}
+ , {"secinfo" , 0ULL, 0, NULL}
+ , {"fsid_present" , 0ULL, 0, NULL}
+ ,
+
+ /* nfsv4.1 client ops */
+ { "exchange_id" , 0ULL, 0, NULL}
+ , {"create_session" , 0ULL, 0, NULL}
+ , {"destroy_session" , 0ULL, 0, NULL}
+ , {"sequence" , 0ULL, 0, NULL}
+ , {"get_lease_time" , 0ULL, 0, NULL}
+ , {"reclaim_comp" , 0ULL, 0, NULL}
+ , {"layoutget" , 0ULL, 0, NULL}
+ , {"getdevinfo" , 0ULL, 0, NULL}
+ , {"layoutcommit" , 0ULL, 0, NULL}
+ , {"layoutreturn" , 0ULL, 0, NULL}
+ , {"secinfo_no" , 0ULL, 0, NULL}
+ , {"test_stateid" , 0ULL, 0, NULL}
+ , {"free_stateid" , 0ULL, 0, NULL}
+ , {"getdevicelist" , 0ULL, 0, NULL}
+ , {"bind_conn_to_ses", 0ULL, 0, NULL}
+ , {"destroy_clientid", 0ULL, 0, NULL}
+ ,
+
+ /* nfsv4.2 client ops */
+ { "seek" , 0ULL, 0, NULL}
+ , {"allocate" , 0ULL, 0, NULL}
+ , {"deallocate" , 0ULL, 0, NULL}
+ , {"layoutstats" , 0ULL, 0, NULL}
+ , {"clone" , 0ULL, 0, NULL}
+ ,
+
+ /* termination */
+ { "" , 0ULL, 0, NULL}
+};
+
+int do_proc_net_rpc_nfs(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+ static int do_net = -1, do_rpc = -1, do_proc2 = -1, do_proc3 = -1, do_proc4 = -1;
+ static int proc2_warning = 0, proc3_warning = 0, proc4_warning = 0;
+
+ if(!ff) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/rpc/nfs");
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_NFS, "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
+ }
+ if(!ff) return 1;
+
+ ff = procfile_readall(ff);
+ if(!ff) return 0; // we return 0, so that we will retry to open it next time
+
+ if(do_net == -1) do_net = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NFS, "network", 1);
+ if(do_rpc == -1) do_rpc = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NFS, "rpc", 1);
+ if(do_proc2 == -1) do_proc2 = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NFS, "NFS v2 procedures", 1);
+ if(do_proc3 == -1) do_proc3 = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NFS, "NFS v3 procedures", 1);
+ if(do_proc4 == -1) do_proc4 = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_NFS, "NFS v4 procedures", 1);
+
+ // if they are enabled, reset them to 1
+ // later we do them =2 to avoid doing strcmp() for all lines
+ if(do_net) do_net = 1;
+ if(do_rpc) do_rpc = 1;
+ if(do_proc2) do_proc2 = 1;
+ if(do_proc3) do_proc3 = 1;
+ if(do_proc4) do_proc4 = 1;
+
+ size_t lines = procfile_lines(ff), l;
+
+ char *type;
+ unsigned long long net_count = 0, net_udp_count = 0, net_tcp_count = 0, net_tcp_connections = 0;
+ unsigned long long rpc_calls = 0, rpc_retransmits = 0, rpc_auth_refresh = 0;
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(!words) continue;
+
+ type = procfile_lineword(ff, l, 0);
+
+ if(do_net == 1 && strcmp(type, "net") == 0) {
+ if(words < 5) {
+ error("%s line of /proc/net/rpc/nfs has %zu words, expected %d", type, words, 5);
+ continue;
+ }
+
+ net_count = str2ull(procfile_lineword(ff, l, 1));
+ net_udp_count = str2ull(procfile_lineword(ff, l, 2));
+ net_tcp_count = str2ull(procfile_lineword(ff, l, 3));
+ net_tcp_connections = str2ull(procfile_lineword(ff, l, 4));
+
+ unsigned long long sum = net_count + net_udp_count + net_tcp_count + net_tcp_connections;
+ if(sum == 0ULL) do_net = -1;
+ else do_net = 2;
+ }
+ else if(do_rpc == 1 && strcmp(type, "rpc") == 0) {
+ if(words < 4) {
+ error("%s line of /proc/net/rpc/nfs has %zu words, expected %d", type, words, 6);
+ continue;
+ }
+
+ rpc_calls = str2ull(procfile_lineword(ff, l, 1));
+ rpc_retransmits = str2ull(procfile_lineword(ff, l, 2));
+ rpc_auth_refresh = str2ull(procfile_lineword(ff, l, 3));
+
+ unsigned long long sum = rpc_calls + rpc_retransmits + rpc_auth_refresh;
+ if(sum == 0ULL) do_rpc = -1;
+ else do_rpc = 2;
+ }
+ else if(do_proc2 == 1 && strcmp(type, "proc2") == 0) {
+ // the first number is the count of numbers present
+ // so we start for word 2
+
+ unsigned long long sum = 0;
+ unsigned int i, j;
+ for(i = 0, j = 2; j < words && nfs_proc2_values[i].name[0] ; i++, j++) {
+ nfs_proc2_values[i].value = str2ull(procfile_lineword(ff, l, j));
+ nfs_proc2_values[i].present = 1;
+ sum += nfs_proc2_values[i].value;
+ }
+
+ if(sum == 0ULL) {
+ if(!proc2_warning) {
+ error("Disabling /proc/net/rpc/nfs v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ proc2_warning = 1;
+ }
+ do_proc2 = 0;
+ }
+ else do_proc2 = 2;
+ }
+ else if(do_proc3 == 1 && strcmp(type, "proc3") == 0) {
+ // the first number is the count of numbers present
+ // so we start for word 2
+
+ unsigned long long sum = 0;
+ unsigned int i, j;
+ for(i = 0, j = 2; j < words && nfs_proc3_values[i].name[0] ; i++, j++) {
+ nfs_proc3_values[i].value = str2ull(procfile_lineword(ff, l, j));
+ nfs_proc3_values[i].present = 1;
+ sum += nfs_proc3_values[i].value;
+ }
+
+ if(sum == 0ULL) {
+ if(!proc3_warning) {
+ info("Disabling /proc/net/rpc/nfs v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ proc3_warning = 1;
+ }
+ do_proc3 = 0;
+ }
+ else do_proc3 = 2;
+ }
+ else if(do_proc4 == 1 && strcmp(type, "proc4") == 0) {
+ // the first number is the count of numbers present
+ // so we start for word 2
+
+ unsigned long long sum = 0;
+ unsigned int i, j;
+ for(i = 0, j = 2; j < words && nfs_proc4_values[i].name[0] ; i++, j++) {
+ nfs_proc4_values[i].value = str2ull(procfile_lineword(ff, l, j));
+ nfs_proc4_values[i].present = 1;
+ sum += nfs_proc4_values[i].value;
+ }
+
+ if(sum == 0ULL) {
+ if(!proc4_warning) {
+ info("Disabling /proc/net/rpc/nfs v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ proc4_warning = 1;
+ }
+ do_proc4 = 0;
+ }
+ else do_proc4 = 2;
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_net == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_udp = NULL,
+ *rd_tcp = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfs"
+ , "net"
+ , NULL
+ , "network"
+ , NULL
+ , "NFS Client Network"
+ , "operations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFS_NAME
+ , NETDATA_CHART_PRIO_NFS_NET
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_udp = rrddim_add(st, "udp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_tcp = rrddim_add(st, "tcp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ // ignore net_count, net_tcp_connections
+ (void)net_count;
+ (void)net_tcp_connections;
+
+ rrddim_set_by_pointer(st, rd_udp, net_udp_count);
+ rrddim_set_by_pointer(st, rd_tcp, net_tcp_count);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_rpc == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_calls = NULL,
+ *rd_retransmits = NULL,
+ *rd_auth_refresh = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfs"
+ , "rpc"
+ , NULL
+ , "rpc"
+ , NULL
+ , "NFS Client Remote Procedure Calls Statistics"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFS_NAME
+ , NETDATA_CHART_PRIO_NFS_RPC
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_calls = rrddim_add(st, "calls", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_retransmits = rrddim_add(st, "retransmits", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_auth_refresh = rrddim_add(st, "auth_refresh", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_calls, rpc_calls);
+ rrddim_set_by_pointer(st, rd_retransmits, rpc_retransmits);
+ rrddim_set_by_pointer(st, rd_auth_refresh, rpc_auth_refresh);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_proc2 == 2) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfs"
+ , "proc2"
+ , NULL
+ , "nfsv2rpc"
+ , NULL
+ , "NFS v2 Client Remote Procedure Calls"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFS_NAME
+ , NETDATA_CHART_PRIO_NFS_PROC2
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(st);
+
+ size_t i;
+ for(i = 0; nfs_proc2_values[i].present ; i++) {
+ if(unlikely(!nfs_proc2_values[i].rd))
+ nfs_proc2_values[i].rd = rrddim_add(st, nfs_proc2_values[i].name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(st, nfs_proc2_values[i].rd, nfs_proc2_values[i].value);
+ }
+
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_proc3 == 2) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfs"
+ , "proc3"
+ , NULL
+ , "nfsv3rpc"
+ , NULL
+ , "NFS v3 Client Remote Procedure Calls"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFS_NAME
+ , NETDATA_CHART_PRIO_NFS_PROC3
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(st);
+
+ size_t i;
+ for(i = 0; nfs_proc3_values[i].present ; i++) {
+ if(unlikely(!nfs_proc3_values[i].rd))
+ nfs_proc3_values[i].rd = rrddim_add(st, nfs_proc3_values[i].name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(st, nfs_proc3_values[i].rd, nfs_proc3_values[i].value);
+ }
+
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_proc4 == 2) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfs"
+ , "proc4"
+ , NULL
+ , "nfsv4rpc"
+ , NULL
+ , "NFS v4 Client Remote Procedure Calls"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFS_NAME
+ , NETDATA_CHART_PRIO_NFS_PROC4
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(st);
+
+ size_t i;
+ for(i = 0; nfs_proc4_values[i].present ; i++) {
+ if(unlikely(!nfs_proc4_values[i].rd))
+ nfs_proc4_values[i].rd = rrddim_add(st, nfs_proc4_values[i].name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(st, nfs_proc4_values[i].rd, nfs_proc4_values[i].value);
+ }
+
+ rrdset_done(st);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_rpc_nfsd.c b/collectors/proc.plugin/proc_net_rpc_nfsd.c
new file mode 100644
index 0000000..29ef7a3
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_rpc_nfsd.c
@@ -0,0 +1,1006 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_NFSD_NAME "/proc/net/rpc/nfsd"
+
+struct nfsd_procs {
+ char name[30];
+ unsigned long long value;
+ int present;
+ RRDDIM *rd;
+};
+
+struct nfsd_procs nfsd_proc2_values[] = {
+ { "null" , 0ULL, 0, NULL}
+ , {"getattr" , 0ULL, 0, NULL}
+ , {"setattr" , 0ULL, 0, NULL}
+ , {"root" , 0ULL, 0, NULL}
+ , {"lookup" , 0ULL, 0, NULL}
+ , {"readlink", 0ULL, 0, NULL}
+ , {"read" , 0ULL, 0, NULL}
+ , {"wrcache" , 0ULL, 0, NULL}
+ , {"write" , 0ULL, 0, NULL}
+ , {"create" , 0ULL, 0, NULL}
+ , {"remove" , 0ULL, 0, NULL}
+ , {"rename" , 0ULL, 0, NULL}
+ , {"link" , 0ULL, 0, NULL}
+ , {"symlink" , 0ULL, 0, NULL}
+ , {"mkdir" , 0ULL, 0, NULL}
+ , {"rmdir" , 0ULL, 0, NULL}
+ , {"readdir" , 0ULL, 0, NULL}
+ , {"fsstat" , 0ULL, 0, NULL}
+ ,
+
+ /* termination */
+ { "" , 0ULL, 0, NULL}
+};
+
+struct nfsd_procs nfsd_proc3_values[] = {
+ { "null" , 0ULL, 0, NULL}
+ , {"getattr" , 0ULL, 0, NULL}
+ , {"setattr" , 0ULL, 0, NULL}
+ , {"lookup" , 0ULL, 0, NULL}
+ , {"access" , 0ULL, 0, NULL}
+ , {"readlink" , 0ULL, 0, NULL}
+ , {"read" , 0ULL, 0, NULL}
+ , {"write" , 0ULL, 0, NULL}
+ , {"create" , 0ULL, 0, NULL}
+ , {"mkdir" , 0ULL, 0, NULL}
+ , {"symlink" , 0ULL, 0, NULL}
+ , {"mknod" , 0ULL, 0, NULL}
+ , {"remove" , 0ULL, 0, NULL}
+ , {"rmdir" , 0ULL, 0, NULL}
+ , {"rename" , 0ULL, 0, NULL}
+ , {"link" , 0ULL, 0, NULL}
+ , {"readdir" , 0ULL, 0, NULL}
+ , {"readdirplus", 0ULL, 0, NULL}
+ , {"fsstat" , 0ULL, 0, NULL}
+ , {"fsinfo" , 0ULL, 0, NULL}
+ , {"pathconf" , 0ULL, 0, NULL}
+ , {"commit" , 0ULL, 0, NULL}
+ ,
+
+ /* termination */
+ { "" , 0ULL, 0, NULL}
+};
+
+struct nfsd_procs nfsd_proc4_values[] = {
+ { "null" , 0ULL, 0, NULL}
+ , {"read" , 0ULL, 0, NULL}
+ , {"write" , 0ULL, 0, NULL}
+ , {"commit" , 0ULL, 0, NULL}
+ , {"open" , 0ULL, 0, NULL}
+ , {"open_conf" , 0ULL, 0, NULL}
+ , {"open_noat" , 0ULL, 0, NULL}
+ , {"open_dgrd" , 0ULL, 0, NULL}
+ , {"close" , 0ULL, 0, NULL}
+ , {"setattr" , 0ULL, 0, NULL}
+ , {"fsinfo" , 0ULL, 0, NULL}
+ , {"renew" , 0ULL, 0, NULL}
+ , {"setclntid" , 0ULL, 0, NULL}
+ , {"confirm" , 0ULL, 0, NULL}
+ , {"lock" , 0ULL, 0, NULL}
+ , {"lockt" , 0ULL, 0, NULL}
+ , {"locku" , 0ULL, 0, NULL}
+ , {"access" , 0ULL, 0, NULL}
+ , {"getattr" , 0ULL, 0, NULL}
+ , {"lookup" , 0ULL, 0, NULL}
+ , {"lookup_root" , 0ULL, 0, NULL}
+ , {"remove" , 0ULL, 0, NULL}
+ , {"rename" , 0ULL, 0, NULL}
+ , {"link" , 0ULL, 0, NULL}
+ , {"symlink" , 0ULL, 0, NULL}
+ , {"create" , 0ULL, 0, NULL}
+ , {"pathconf" , 0ULL, 0, NULL}
+ , {"statfs" , 0ULL, 0, NULL}
+ , {"readlink" , 0ULL, 0, NULL}
+ , {"readdir" , 0ULL, 0, NULL}
+ , {"server_caps" , 0ULL, 0, NULL}
+ , {"delegreturn" , 0ULL, 0, NULL}
+ , {"getacl" , 0ULL, 0, NULL}
+ , {"setacl" , 0ULL, 0, NULL}
+ , {"fs_locations" , 0ULL, 0, NULL}
+ , {"rel_lkowner" , 0ULL, 0, NULL}
+ , {"secinfo" , 0ULL, 0, NULL}
+ , {"fsid_present" , 0ULL, 0, NULL}
+ ,
+
+ /* nfsv4.1 client ops */
+ { "exchange_id" , 0ULL, 0, NULL}
+ , {"create_session" , 0ULL, 0, NULL}
+ , {"destroy_session" , 0ULL, 0, NULL}
+ , {"sequence" , 0ULL, 0, NULL}
+ , {"get_lease_time" , 0ULL, 0, NULL}
+ , {"reclaim_comp" , 0ULL, 0, NULL}
+ , {"layoutget" , 0ULL, 0, NULL}
+ , {"getdevinfo" , 0ULL, 0, NULL}
+ , {"layoutcommit" , 0ULL, 0, NULL}
+ , {"layoutreturn" , 0ULL, 0, NULL}
+ , {"secinfo_no" , 0ULL, 0, NULL}
+ , {"test_stateid" , 0ULL, 0, NULL}
+ , {"free_stateid" , 0ULL, 0, NULL}
+ , {"getdevicelist" , 0ULL, 0, NULL}
+ , {"bind_conn_to_ses", 0ULL, 0, NULL}
+ , {"destroy_clientid", 0ULL, 0, NULL}
+ ,
+
+ /* nfsv4.2 client ops */
+ { "seek" , 0ULL, 0, NULL}
+ , {"allocate" , 0ULL, 0, NULL}
+ , {"deallocate" , 0ULL, 0, NULL}
+ , {"layoutstats" , 0ULL, 0, NULL}
+ , {"clone" , 0ULL, 0, NULL}
+ ,
+
+ /* termination */
+ { "" , 0ULL, 0, NULL}
+};
+
+struct nfsd_procs nfsd4_ops_values[] = {
+ { "unused_op0" , 0ULL, 0, NULL}
+ , {"unused_op1" , 0ULL, 0, NULL}
+ , {"future_op2" , 0ULL, 0, NULL}
+ , {"access" , 0ULL, 0, NULL}
+ , {"close" , 0ULL, 0, NULL}
+ , {"commit" , 0ULL, 0, NULL}
+ , {"create" , 0ULL, 0, NULL}
+ , {"delegpurge" , 0ULL, 0, NULL}
+ , {"delegreturn" , 0ULL, 0, NULL}
+ , {"getattr" , 0ULL, 0, NULL}
+ , {"getfh" , 0ULL, 0, NULL}
+ , {"link" , 0ULL, 0, NULL}
+ , {"lock" , 0ULL, 0, NULL}
+ , {"lockt" , 0ULL, 0, NULL}
+ , {"locku" , 0ULL, 0, NULL}
+ , {"lookup" , 0ULL, 0, NULL}
+ , {"lookup_root" , 0ULL, 0, NULL}
+ , {"nverify" , 0ULL, 0, NULL}
+ , {"open" , 0ULL, 0, NULL}
+ , {"openattr" , 0ULL, 0, NULL}
+ , {"open_confirm" , 0ULL, 0, NULL}
+ , {"open_downgrade" , 0ULL, 0, NULL}
+ , {"putfh" , 0ULL, 0, NULL}
+ , {"putpubfh" , 0ULL, 0, NULL}
+ , {"putrootfh" , 0ULL, 0, NULL}
+ , {"read" , 0ULL, 0, NULL}
+ , {"readdir" , 0ULL, 0, NULL}
+ , {"readlink" , 0ULL, 0, NULL}
+ , {"remove" , 0ULL, 0, NULL}
+ , {"rename" , 0ULL, 0, NULL}
+ , {"renew" , 0ULL, 0, NULL}
+ , {"restorefh" , 0ULL, 0, NULL}
+ , {"savefh" , 0ULL, 0, NULL}
+ , {"secinfo" , 0ULL, 0, NULL}
+ , {"setattr" , 0ULL, 0, NULL}
+ , {"setclientid" , 0ULL, 0, NULL}
+ , {"setclientid_confirm" , 0ULL, 0, NULL}
+ , {"verify" , 0ULL, 0, NULL}
+ , {"write" , 0ULL, 0, NULL}
+ , {"release_lockowner" , 0ULL, 0, NULL}
+ ,
+
+ /* nfs41 */
+ { "backchannel_ctl" , 0ULL, 0, NULL}
+ , {"bind_conn_to_session", 0ULL, 0, NULL}
+ , {"exchange_id" , 0ULL, 0, NULL}
+ , {"create_session" , 0ULL, 0, NULL}
+ , {"destroy_session" , 0ULL, 0, NULL}
+ , {"free_stateid" , 0ULL, 0, NULL}
+ , {"get_dir_delegation" , 0ULL, 0, NULL}
+ , {"getdeviceinfo" , 0ULL, 0, NULL}
+ , {"getdevicelist" , 0ULL, 0, NULL}
+ , {"layoutcommit" , 0ULL, 0, NULL}
+ , {"layoutget" , 0ULL, 0, NULL}
+ , {"layoutreturn" , 0ULL, 0, NULL}
+ , {"secinfo_no_name" , 0ULL, 0, NULL}
+ , {"sequence" , 0ULL, 0, NULL}
+ , {"set_ssv" , 0ULL, 0, NULL}
+ , {"test_stateid" , 0ULL, 0, NULL}
+ , {"want_delegation" , 0ULL, 0, NULL}
+ , {"destroy_clientid" , 0ULL, 0, NULL}
+ , {"reclaim_complete" , 0ULL, 0, NULL}
+ ,
+
+ /* nfs42 */
+ { "allocate" , 0ULL, 0, NULL}
+ , {"copy" , 0ULL, 0, NULL}
+ , {"copy_notify" , 0ULL, 0, NULL}
+ , {"deallocate" , 0ULL, 0, NULL}
+ , {"ioadvise" , 0ULL, 0, NULL}
+ , {"layouterror" , 0ULL, 0, NULL}
+ , {"layoutstats" , 0ULL, 0, NULL}
+ , {"offload_cancel" , 0ULL, 0, NULL}
+ , {"offload_status" , 0ULL, 0, NULL}
+ , {"read_plus" , 0ULL, 0, NULL}
+ , {"seek" , 0ULL, 0, NULL}
+ , {"write_same" , 0ULL, 0, NULL}
+ ,
+
+ /* termination */
+ { "" , 0ULL, 0, NULL}
+};
+
+
+int do_proc_net_rpc_nfsd(int update_every, usec_t dt) {
+ (void)dt;
+ static procfile *ff = NULL;
+ static int do_rc = -1, do_fh = -1, do_io = -1, do_th = -1, do_ra = -1, do_net = -1, do_rpc = -1, do_proc2 = -1, do_proc3 = -1, do_proc4 = -1, do_proc4ops = -1;
+ static int ra_warning = 0, th_warning = 0, proc2_warning = 0, proc3_warning = 0, proc4_warning = 0, proc4ops_warning = 0;
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/rpc/nfsd");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/rpc/nfsd", "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ if(unlikely(do_rc == -1)) {
+ do_rc = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "read cache", 1);
+ do_fh = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "file handles", 1);
+ do_io = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "I/O", 1);
+ do_th = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "threads", 1);
+ do_ra = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "read ahead", 1);
+ do_net = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "network", 1);
+ do_rpc = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "rpc", 1);
+ do_proc2 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v2 procedures", 1);
+ do_proc3 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v3 procedures", 1);
+ do_proc4 = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v4 procedures", 1);
+ do_proc4ops = config_get_boolean("plugin:proc:/proc/net/rpc/nfsd", "NFS v4 operations", 1);
+ }
+
+ // if they are enabled, reset them to 1
+ // later we do them = 2 to avoid doing strcmp() for all lines
+ if(do_rc) do_rc = 1;
+ if(do_fh) do_fh = 1;
+ if(do_io) do_io = 1;
+ if(do_th) do_th = 1;
+ if(do_ra) do_ra = 1;
+ if(do_net) do_net = 1;
+ if(do_rpc) do_rpc = 1;
+ if(do_proc2) do_proc2 = 1;
+ if(do_proc3) do_proc3 = 1;
+ if(do_proc4) do_proc4 = 1;
+ if(do_proc4ops) do_proc4ops = 1;
+
+ size_t lines = procfile_lines(ff), l;
+
+ char *type;
+ unsigned long long rc_hits = 0, rc_misses = 0, rc_nocache = 0;
+ unsigned long long fh_stale = 0, fh_total_lookups = 0, fh_anonymous_lookups = 0, fh_dir_not_in_dcache = 0, fh_non_dir_not_in_dcache = 0;
+ unsigned long long io_read = 0, io_write = 0;
+ unsigned long long th_threads = 0, th_fullcnt = 0, th_hist10 = 0, th_hist20 = 0, th_hist30 = 0, th_hist40 = 0, th_hist50 = 0, th_hist60 = 0, th_hist70 = 0, th_hist80 = 0, th_hist90 = 0, th_hist100 = 0;
+ unsigned long long ra_size = 0, ra_hist10 = 0, ra_hist20 = 0, ra_hist30 = 0, ra_hist40 = 0, ra_hist50 = 0, ra_hist60 = 0, ra_hist70 = 0, ra_hist80 = 0, ra_hist90 = 0, ra_hist100 = 0, ra_none = 0;
+ unsigned long long net_count = 0, net_udp_count = 0, net_tcp_count = 0, net_tcp_connections = 0;
+ unsigned long long rpc_calls = 0, rpc_bad_format = 0, rpc_bad_auth = 0, rpc_bad_client = 0;
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(!words)) continue;
+
+ type = procfile_lineword(ff, l, 0);
+
+ if(do_rc == 1 && strcmp(type, "rc") == 0) {
+ if(unlikely(words < 4)) {
+ error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 4);
+ continue;
+ }
+
+ rc_hits = str2ull(procfile_lineword(ff, l, 1));
+ rc_misses = str2ull(procfile_lineword(ff, l, 2));
+ rc_nocache = str2ull(procfile_lineword(ff, l, 3));
+
+ unsigned long long sum = rc_hits + rc_misses + rc_nocache;
+ if(sum == 0ULL) do_rc = -1;
+ else do_rc = 2;
+ }
+ else if(do_fh == 1 && strcmp(type, "fh") == 0) {
+ if(unlikely(words < 6)) {
+ error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 6);
+ continue;
+ }
+
+ fh_stale = str2ull(procfile_lineword(ff, l, 1));
+ fh_total_lookups = str2ull(procfile_lineword(ff, l, 2));
+ fh_anonymous_lookups = str2ull(procfile_lineword(ff, l, 3));
+ fh_dir_not_in_dcache = str2ull(procfile_lineword(ff, l, 4));
+ fh_non_dir_not_in_dcache = str2ull(procfile_lineword(ff, l, 5));
+
+ unsigned long long sum = fh_stale + fh_total_lookups + fh_anonymous_lookups + fh_dir_not_in_dcache + fh_non_dir_not_in_dcache;
+ if(sum == 0ULL) do_fh = -1;
+ else do_fh = 2;
+ }
+ else if(do_io == 1 && strcmp(type, "io") == 0) {
+ if(unlikely(words < 3)) {
+ error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 3);
+ continue;
+ }
+
+ io_read = str2ull(procfile_lineword(ff, l, 1));
+ io_write = str2ull(procfile_lineword(ff, l, 2));
+
+ unsigned long long sum = io_read + io_write;
+ if(sum == 0ULL) do_io = -1;
+ else do_io = 2;
+ }
+ else if(do_th == 1 && strcmp(type, "th") == 0) {
+ if(unlikely(words < 13)) {
+ error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 13);
+ continue;
+ }
+
+ th_threads = str2ull(procfile_lineword(ff, l, 1));
+ th_fullcnt = str2ull(procfile_lineword(ff, l, 2));
+ th_hist10 = (unsigned long long)(atof(procfile_lineword(ff, l, 3)) * 1000.0);
+ th_hist20 = (unsigned long long)(atof(procfile_lineword(ff, l, 4)) * 1000.0);
+ th_hist30 = (unsigned long long)(atof(procfile_lineword(ff, l, 5)) * 1000.0);
+ th_hist40 = (unsigned long long)(atof(procfile_lineword(ff, l, 6)) * 1000.0);
+ th_hist50 = (unsigned long long)(atof(procfile_lineword(ff, l, 7)) * 1000.0);
+ th_hist60 = (unsigned long long)(atof(procfile_lineword(ff, l, 8)) * 1000.0);
+ th_hist70 = (unsigned long long)(atof(procfile_lineword(ff, l, 9)) * 1000.0);
+ th_hist80 = (unsigned long long)(atof(procfile_lineword(ff, l, 10)) * 1000.0);
+ th_hist90 = (unsigned long long)(atof(procfile_lineword(ff, l, 11)) * 1000.0);
+ th_hist100 = (unsigned long long)(atof(procfile_lineword(ff, l, 12)) * 1000.0);
+
+ // threads histogram has been disabled on recent kernels
+ // http://permalink.gmane.org/gmane.linux.nfs/24528
+ unsigned long long sum = th_hist10 + th_hist20 + th_hist30 + th_hist40 + th_hist50 + th_hist60 + th_hist70 + th_hist80 + th_hist90 + th_hist100;
+ if(sum == 0ULL) {
+ if(!th_warning) {
+ info("Disabling /proc/net/rpc/nfsd threads histogram. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ th_warning = 1;
+ }
+ do_th = -1;
+ }
+ else do_th = 2;
+ }
+ else if(do_ra == 1 && strcmp(type, "ra") == 0) {
+ if(unlikely(words < 13)) {
+ error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 13);
+ continue;
+ }
+
+ ra_size = str2ull(procfile_lineword(ff, l, 1));
+ ra_hist10 = str2ull(procfile_lineword(ff, l, 2));
+ ra_hist20 = str2ull(procfile_lineword(ff, l, 3));
+ ra_hist30 = str2ull(procfile_lineword(ff, l, 4));
+ ra_hist40 = str2ull(procfile_lineword(ff, l, 5));
+ ra_hist50 = str2ull(procfile_lineword(ff, l, 6));
+ ra_hist60 = str2ull(procfile_lineword(ff, l, 7));
+ ra_hist70 = str2ull(procfile_lineword(ff, l, 8));
+ ra_hist80 = str2ull(procfile_lineword(ff, l, 9));
+ ra_hist90 = str2ull(procfile_lineword(ff, l, 10));
+ ra_hist100 = str2ull(procfile_lineword(ff, l, 11));
+ ra_none = str2ull(procfile_lineword(ff, l, 12));
+
+ unsigned long long sum = ra_hist10 + ra_hist20 + ra_hist30 + ra_hist40 + ra_hist50 + ra_hist60 + ra_hist70 + ra_hist80 + ra_hist90 + ra_hist100 + ra_none;
+ if(sum == 0ULL) {
+ if(!ra_warning) {
+ info("Disabling /proc/net/rpc/nfsd read ahead histogram. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ ra_warning = 1;
+ }
+ do_ra = -1;
+ }
+ else do_ra = 2;
+ }
+ else if(do_net == 1 && strcmp(type, "net") == 0) {
+ if(unlikely(words < 5)) {
+ error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 5);
+ continue;
+ }
+
+ net_count = str2ull(procfile_lineword(ff, l, 1));
+ net_udp_count = str2ull(procfile_lineword(ff, l, 2));
+ net_tcp_count = str2ull(procfile_lineword(ff, l, 3));
+ net_tcp_connections = str2ull(procfile_lineword(ff, l, 4));
+
+ unsigned long long sum = net_count + net_udp_count + net_tcp_count + net_tcp_connections;
+ if(sum == 0ULL) do_net = -1;
+ else do_net = 2;
+ }
+ else if(do_rpc == 1 && strcmp(type, "rpc") == 0) {
+ if(unlikely(words < 6)) {
+ error("%s line of /proc/net/rpc/nfsd has %zu words, expected %d", type, words, 6);
+ continue;
+ }
+
+ rpc_calls = str2ull(procfile_lineword(ff, l, 1));
+ rpc_bad_format = str2ull(procfile_lineword(ff, l, 2));
+ rpc_bad_auth = str2ull(procfile_lineword(ff, l, 3));
+ rpc_bad_client = str2ull(procfile_lineword(ff, l, 4));
+
+ unsigned long long sum = rpc_calls + rpc_bad_format + rpc_bad_auth + rpc_bad_client;
+ if(sum == 0ULL) do_rpc = -1;
+ else do_rpc = 2;
+ }
+ else if(do_proc2 == 1 && strcmp(type, "proc2") == 0) {
+ // the first number is the count of numbers present
+ // so we start for word 2
+
+ unsigned long long sum = 0;
+ unsigned int i, j;
+ for(i = 0, j = 2; j < words && nfsd_proc2_values[i].name[0] ; i++, j++) {
+ nfsd_proc2_values[i].value = str2ull(procfile_lineword(ff, l, j));
+ nfsd_proc2_values[i].present = 1;
+ sum += nfsd_proc2_values[i].value;
+ }
+
+ if(sum == 0ULL) {
+ if(!proc2_warning) {
+ error("Disabling /proc/net/rpc/nfsd v2 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ proc2_warning = 1;
+ }
+ do_proc2 = 0;
+ }
+ else do_proc2 = 2;
+ }
+ else if(do_proc3 == 1 && strcmp(type, "proc3") == 0) {
+ // the first number is the count of numbers present
+ // so we start for word 2
+
+ unsigned long long sum = 0;
+ unsigned int i, j;
+ for(i = 0, j = 2; j < words && nfsd_proc3_values[i].name[0] ; i++, j++) {
+ nfsd_proc3_values[i].value = str2ull(procfile_lineword(ff, l, j));
+ nfsd_proc3_values[i].present = 1;
+ sum += nfsd_proc3_values[i].value;
+ }
+
+ if(sum == 0ULL) {
+ if(!proc3_warning) {
+ info("Disabling /proc/net/rpc/nfsd v3 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ proc3_warning = 1;
+ }
+ do_proc3 = 0;
+ }
+ else do_proc3 = 2;
+ }
+ else if(do_proc4 == 1 && strcmp(type, "proc4") == 0) {
+ // the first number is the count of numbers present
+ // so we start for word 2
+
+ unsigned long long sum = 0;
+ unsigned int i, j;
+ for(i = 0, j = 2; j < words && nfsd_proc4_values[i].name[0] ; i++, j++) {
+ nfsd_proc4_values[i].value = str2ull(procfile_lineword(ff, l, j));
+ nfsd_proc4_values[i].present = 1;
+ sum += nfsd_proc4_values[i].value;
+ }
+
+ if(sum == 0ULL) {
+ if(!proc4_warning) {
+ info("Disabling /proc/net/rpc/nfsd v4 procedure calls chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ proc4_warning = 1;
+ }
+ do_proc4 = 0;
+ }
+ else do_proc4 = 2;
+ }
+ else if(do_proc4ops == 1 && strcmp(type, "proc4ops") == 0) {
+ // the first number is the count of numbers present
+ // so we start for word 2
+
+ unsigned long long sum = 0;
+ unsigned int i, j;
+ for(i = 0, j = 2; j < words && nfsd4_ops_values[i].name[0] ; i++, j++) {
+ nfsd4_ops_values[i].value = str2ull(procfile_lineword(ff, l, j));
+ nfsd4_ops_values[i].present = 1;
+ sum += nfsd4_ops_values[i].value;
+ }
+
+ if(sum == 0ULL) {
+ if(!proc4ops_warning) {
+ info("Disabling /proc/net/rpc/nfsd v4 operations chart. It seems unused on this machine. It will be enabled automatically when found with data in it.");
+ proc4ops_warning = 1;
+ }
+ do_proc4ops = 0;
+ }
+ else do_proc4ops = 2;
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_rc == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_hits = NULL,
+ *rd_misses = NULL,
+ *rd_nocache = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "readcache"
+ , NULL
+ , "cache"
+ , NULL
+ , "NFS Server Read Cache"
+ , "reads/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_READCACHE
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_hits = rrddim_add(st, "hits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_misses = rrddim_add(st, "misses", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_nocache = rrddim_add(st, "nocache", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_hits, rc_hits);
+ rrddim_set_by_pointer(st, rd_misses, rc_misses);
+ rrddim_set_by_pointer(st, rd_nocache, rc_nocache);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_fh == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_stale = NULL,
+ *rd_total_lookups = NULL,
+ *rd_anonymous_lookups = NULL,
+ *rd_dir_not_in_dcache = NULL,
+ *rd_non_dir_not_in_dcache = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "filehandles"
+ , NULL
+ , "filehandles"
+ , NULL
+ , "NFS Server File Handles"
+ , "handles/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_FILEHANDLES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_stale = rrddim_add(st, "stale", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_total_lookups = rrddim_add(st, "total_lookups", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_anonymous_lookups = rrddim_add(st, "anonymous_lookups", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_dir_not_in_dcache = rrddim_add(st, "dir_not_in_dcache", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_non_dir_not_in_dcache = rrddim_add(st, "non_dir_not_in_dcache", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_stale, fh_stale);
+ rrddim_set_by_pointer(st, rd_total_lookups, fh_total_lookups);
+ rrddim_set_by_pointer(st, rd_anonymous_lookups, fh_anonymous_lookups);
+ rrddim_set_by_pointer(st, rd_dir_not_in_dcache, fh_dir_not_in_dcache);
+ rrddim_set_by_pointer(st, rd_non_dir_not_in_dcache, fh_non_dir_not_in_dcache);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_io == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_read = NULL,
+ *rd_write = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "io"
+ , NULL
+ , "io"
+ , NULL
+ , "NFS Server I/O"
+ , "kilobytes/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_IO
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_read = rrddim_add(st, "read", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ rd_write = rrddim_add(st, "write", NULL, -1, 1000, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_read, io_read);
+ rrddim_set_by_pointer(st, rd_write, io_write);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_th == 2) {
+ {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_threads = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "threads"
+ , NULL
+ , "threads"
+ , NULL
+ , "NFS Server Threads"
+ , "threads"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_THREADS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_threads = rrddim_add(st, "threads", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_threads, th_threads);
+ rrdset_done(st);
+ }
+
+ {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_full_count = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "threads_fullcnt"
+ , NULL
+ , "threads"
+ , NULL
+ , "NFS Server Threads Full Count"
+ , "events"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_THREADS_FULLCNT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_full_count = rrddim_add(st, "full_count", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_full_count, th_fullcnt);
+ rrdset_done(st);
+ }
+
+ {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_th_hist10 = NULL,
+ *rd_th_hist20 = NULL,
+ *rd_th_hist30 = NULL,
+ *rd_th_hist40 = NULL,
+ *rd_th_hist50 = NULL,
+ *rd_th_hist60 = NULL,
+ *rd_th_hist70 = NULL,
+ *rd_th_hist80 = NULL,
+ *rd_th_hist90 = NULL,
+ *rd_th_hist100 = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "threads_histogram"
+ , NULL
+ , "threads"
+ , NULL
+ , "NFS Server Threads Usage Histogram"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_THREADS_HISTOGRAM
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_th_hist10 = rrddim_add(st, "0%-10%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist20 = rrddim_add(st, "10%-20%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist30 = rrddim_add(st, "20%-30%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist40 = rrddim_add(st, "30%-40%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist50 = rrddim_add(st, "40%-50%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist60 = rrddim_add(st, "50%-60%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist70 = rrddim_add(st, "60%-70%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist80 = rrddim_add(st, "70%-80%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist90 = rrddim_add(st, "80%-90%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rd_th_hist100 = rrddim_add(st, "90%-100%", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_th_hist10, th_hist10);
+ rrddim_set_by_pointer(st, rd_th_hist20, th_hist20);
+ rrddim_set_by_pointer(st, rd_th_hist30, th_hist30);
+ rrddim_set_by_pointer(st, rd_th_hist40, th_hist40);
+ rrddim_set_by_pointer(st, rd_th_hist50, th_hist50);
+ rrddim_set_by_pointer(st, rd_th_hist60, th_hist60);
+ rrddim_set_by_pointer(st, rd_th_hist70, th_hist70);
+ rrddim_set_by_pointer(st, rd_th_hist80, th_hist80);
+ rrddim_set_by_pointer(st, rd_th_hist90, th_hist90);
+ rrddim_set_by_pointer(st, rd_th_hist100, th_hist100);
+ rrdset_done(st);
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ra == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_ra_hist10 = NULL,
+ *rd_ra_hist20 = NULL,
+ *rd_ra_hist30 = NULL,
+ *rd_ra_hist40 = NULL,
+ *rd_ra_hist50 = NULL,
+ *rd_ra_hist60 = NULL,
+ *rd_ra_hist70 = NULL,
+ *rd_ra_hist80 = NULL,
+ *rd_ra_hist90 = NULL,
+ *rd_ra_hist100 = NULL,
+ *rd_ra_none = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "readahead"
+ , NULL
+ , "readahead"
+ , NULL
+ , "NFS Server Read Ahead Depth"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_READAHEAD
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_ra_hist10 = rrddim_add(st, "10%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist20 = rrddim_add(st, "20%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist30 = rrddim_add(st, "30%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist40 = rrddim_add(st, "40%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist50 = rrddim_add(st, "50%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist60 = rrddim_add(st, "60%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist70 = rrddim_add(st, "70%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist80 = rrddim_add(st, "80%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist90 = rrddim_add(st, "90%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_hist100 = rrddim_add(st, "100%", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_ra_none = rrddim_add(st, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else rrdset_next(st);
+
+ // ignore ra_size
+ (void)ra_size;
+
+ rrddim_set_by_pointer(st, rd_ra_hist10, ra_hist10);
+ rrddim_set_by_pointer(st, rd_ra_hist20, ra_hist20);
+ rrddim_set_by_pointer(st, rd_ra_hist30, ra_hist30);
+ rrddim_set_by_pointer(st, rd_ra_hist40, ra_hist40);
+ rrddim_set_by_pointer(st, rd_ra_hist50, ra_hist50);
+ rrddim_set_by_pointer(st, rd_ra_hist60, ra_hist60);
+ rrddim_set_by_pointer(st, rd_ra_hist70, ra_hist70);
+ rrddim_set_by_pointer(st, rd_ra_hist80, ra_hist80);
+ rrddim_set_by_pointer(st, rd_ra_hist90, ra_hist90);
+ rrddim_set_by_pointer(st, rd_ra_hist100,ra_hist100);
+ rrddim_set_by_pointer(st, rd_ra_none, ra_none);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_net == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_udp = NULL,
+ *rd_tcp = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "net"
+ , NULL
+ , "network"
+ , NULL
+ , "NFS Server Network Statistics"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_NET
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_udp = rrddim_add(st, "udp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_tcp = rrddim_add(st, "tcp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ // ignore net_count, net_tcp_connections
+ (void)net_count;
+ (void)net_tcp_connections;
+
+ rrddim_set_by_pointer(st, rd_udp, net_udp_count);
+ rrddim_set_by_pointer(st, rd_tcp, net_tcp_count);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_rpc == 2) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_calls = NULL,
+ *rd_bad_format = NULL,
+ *rd_bad_auth = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "rpc"
+ , NULL
+ , "rpc"
+ , NULL
+ , "NFS Server Remote Procedure Calls Statistics"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_RPC
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_calls = rrddim_add(st, "calls", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_bad_format = rrddim_add(st, "bad_format", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_bad_auth = rrddim_add(st, "bad_auth", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ // ignore rpc_bad_client
+ (void)rpc_bad_client;
+
+ rrddim_set_by_pointer(st, rd_calls, rpc_calls);
+ rrddim_set_by_pointer(st, rd_bad_format, rpc_bad_format);
+ rrddim_set_by_pointer(st, rd_bad_auth, rpc_bad_auth);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_proc2 == 2) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "proc2"
+ , NULL
+ , "nfsv2rpc"
+ , NULL
+ , "NFS v2 Server Remote Procedure Calls"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_PROC2
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(st);
+
+ size_t i;
+ for(i = 0; nfsd_proc2_values[i].present ; i++) {
+ if(unlikely(!nfsd_proc2_values[i].rd))
+ nfsd_proc2_values[i].rd = rrddim_add(st, nfsd_proc2_values[i].name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(st, nfsd_proc2_values[i].rd, nfsd_proc2_values[i].value);
+ }
+
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_proc3 == 2) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "proc3"
+ , NULL
+ , "nfsv3rpc"
+ , NULL
+ , "NFS v3 Server Remote Procedure Calls"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_PROC3
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(st);
+
+ size_t i;
+ for(i = 0; nfsd_proc3_values[i].present ; i++) {
+ if(unlikely(!nfsd_proc3_values[i].rd))
+ nfsd_proc3_values[i].rd = rrddim_add(st, nfsd_proc3_values[i].name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(st, nfsd_proc3_values[i].rd, nfsd_proc3_values[i].value);
+ }
+
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_proc4 == 2) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "proc4"
+ , NULL
+ , "nfsv4rpc"
+ , NULL
+ , "NFS v4 Server Remote Procedure Calls"
+ , "calls/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_PROC4
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(st);
+
+ size_t i;
+ for(i = 0; nfsd_proc4_values[i].present ; i++) {
+ if(unlikely(!nfsd_proc4_values[i].rd))
+ nfsd_proc4_values[i].rd = rrddim_add(st, nfsd_proc4_values[i].name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(st, nfsd_proc4_values[i].rd, nfsd_proc4_values[i].value);
+ }
+
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_proc4ops == 2) {
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "nfsd"
+ , "proc4ops"
+ , NULL
+ , "nfsv2ops"
+ , NULL
+ , "NFS v4 Server Operations"
+ , "operations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NFSD_NAME
+ , NETDATA_CHART_PRIO_NFSD_PROC4OPS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else rrdset_next(st);
+
+ size_t i;
+ for(i = 0; nfsd4_ops_values[i].present ; i++) {
+ if(unlikely(!nfsd4_ops_values[i].rd))
+ nfsd4_ops_values[i].rd = rrddim_add(st, nfsd4_ops_values[i].name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(st, nfsd4_ops_values[i].rd, nfsd4_ops_values[i].value);
+ }
+
+ rrdset_done(st);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_sctp_snmp.c b/collectors/proc.plugin/proc_net_sctp_snmp.c
new file mode 100644
index 0000000..343cc5a
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_sctp_snmp.c
@@ -0,0 +1,373 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+#define PLUGIN_PROC_MODULE_NET_SCTP_SNMP_NAME "/proc/net/sctp/snmp"
+
+int do_proc_net_sctp_snmp(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+
+ static int
+ do_associations = -1,
+ do_transitions = -1,
+ do_packet_errors = -1,
+ do_packets = -1,
+ do_fragmentation = -1,
+ do_chunk_types = -1;
+
+ static ARL_BASE *arl_base = NULL;
+
+ static unsigned long long SctpCurrEstab = 0ULL;
+ static unsigned long long SctpActiveEstabs = 0ULL;
+ static unsigned long long SctpPassiveEstabs = 0ULL;
+ static unsigned long long SctpAborteds = 0ULL;
+ static unsigned long long SctpShutdowns = 0ULL;
+ static unsigned long long SctpOutOfBlues = 0ULL;
+ static unsigned long long SctpChecksumErrors = 0ULL;
+ static unsigned long long SctpOutCtrlChunks = 0ULL;
+ static unsigned long long SctpOutOrderChunks = 0ULL;
+ static unsigned long long SctpOutUnorderChunks = 0ULL;
+ static unsigned long long SctpInCtrlChunks = 0ULL;
+ static unsigned long long SctpInOrderChunks = 0ULL;
+ static unsigned long long SctpInUnorderChunks = 0ULL;
+ static unsigned long long SctpFragUsrMsgs = 0ULL;
+ static unsigned long long SctpReasmUsrMsgs = 0ULL;
+ static unsigned long long SctpOutSCTPPacks = 0ULL;
+ static unsigned long long SctpInSCTPPacks = 0ULL;
+ static unsigned long long SctpT1InitExpireds = 0ULL;
+ static unsigned long long SctpT1CookieExpireds = 0ULL;
+ static unsigned long long SctpT2ShutdownExpireds = 0ULL;
+ static unsigned long long SctpT3RtxExpireds = 0ULL;
+ static unsigned long long SctpT4RtoExpireds = 0ULL;
+ static unsigned long long SctpT5ShutdownGuardExpireds = 0ULL;
+ static unsigned long long SctpDelaySackExpireds = 0ULL;
+ static unsigned long long SctpAutocloseExpireds = 0ULL;
+ static unsigned long long SctpT3Retransmits = 0ULL;
+ static unsigned long long SctpPmtudRetransmits = 0ULL;
+ static unsigned long long SctpFastRetransmits = 0ULL;
+ static unsigned long long SctpInPktSoftirq = 0ULL;
+ static unsigned long long SctpInPktBacklog = 0ULL;
+ static unsigned long long SctpInPktDiscards = 0ULL;
+ static unsigned long long SctpInDataChunkDiscards = 0ULL;
+
+ if(unlikely(!arl_base)) {
+ do_associations = config_get_boolean_ondemand("plugin:proc:/proc/net/sctp/snmp", "established associations", CONFIG_BOOLEAN_AUTO);
+ do_transitions = config_get_boolean_ondemand("plugin:proc:/proc/net/sctp/snmp", "association transitions", CONFIG_BOOLEAN_AUTO);
+ do_fragmentation = config_get_boolean_ondemand("plugin:proc:/proc/net/sctp/snmp", "fragmentation", CONFIG_BOOLEAN_AUTO);
+ do_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/sctp/snmp", "packets", CONFIG_BOOLEAN_AUTO);
+ do_packet_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/sctp/snmp", "packet errors", CONFIG_BOOLEAN_AUTO);
+ do_chunk_types = config_get_boolean_ondemand("plugin:proc:/proc/net/sctp/snmp", "chunk types", CONFIG_BOOLEAN_AUTO);
+
+ arl_base = arl_create("sctp", NULL, 60);
+ arl_expect(arl_base, "SctpCurrEstab", &SctpCurrEstab);
+ arl_expect(arl_base, "SctpActiveEstabs", &SctpActiveEstabs);
+ arl_expect(arl_base, "SctpPassiveEstabs", &SctpPassiveEstabs);
+ arl_expect(arl_base, "SctpAborteds", &SctpAborteds);
+ arl_expect(arl_base, "SctpShutdowns", &SctpShutdowns);
+ arl_expect(arl_base, "SctpOutOfBlues", &SctpOutOfBlues);
+ arl_expect(arl_base, "SctpChecksumErrors", &SctpChecksumErrors);
+ arl_expect(arl_base, "SctpOutCtrlChunks", &SctpOutCtrlChunks);
+ arl_expect(arl_base, "SctpOutOrderChunks", &SctpOutOrderChunks);
+ arl_expect(arl_base, "SctpOutUnorderChunks", &SctpOutUnorderChunks);
+ arl_expect(arl_base, "SctpInCtrlChunks", &SctpInCtrlChunks);
+ arl_expect(arl_base, "SctpInOrderChunks", &SctpInOrderChunks);
+ arl_expect(arl_base, "SctpInUnorderChunks", &SctpInUnorderChunks);
+ arl_expect(arl_base, "SctpFragUsrMsgs", &SctpFragUsrMsgs);
+ arl_expect(arl_base, "SctpReasmUsrMsgs", &SctpReasmUsrMsgs);
+ arl_expect(arl_base, "SctpOutSCTPPacks", &SctpOutSCTPPacks);
+ arl_expect(arl_base, "SctpInSCTPPacks", &SctpInSCTPPacks);
+ arl_expect(arl_base, "SctpT1InitExpireds", &SctpT1InitExpireds);
+ arl_expect(arl_base, "SctpT1CookieExpireds", &SctpT1CookieExpireds);
+ arl_expect(arl_base, "SctpT2ShutdownExpireds", &SctpT2ShutdownExpireds);
+ arl_expect(arl_base, "SctpT3RtxExpireds", &SctpT3RtxExpireds);
+ arl_expect(arl_base, "SctpT4RtoExpireds", &SctpT4RtoExpireds);
+ arl_expect(arl_base, "SctpT5ShutdownGuardExpireds", &SctpT5ShutdownGuardExpireds);
+ arl_expect(arl_base, "SctpDelaySackExpireds", &SctpDelaySackExpireds);
+ arl_expect(arl_base, "SctpAutocloseExpireds", &SctpAutocloseExpireds);
+ arl_expect(arl_base, "SctpT3Retransmits", &SctpT3Retransmits);
+ arl_expect(arl_base, "SctpPmtudRetransmits", &SctpPmtudRetransmits);
+ arl_expect(arl_base, "SctpFastRetransmits", &SctpFastRetransmits);
+ arl_expect(arl_base, "SctpInPktSoftirq", &SctpInPktSoftirq);
+ arl_expect(arl_base, "SctpInPktBacklog", &SctpInPktBacklog);
+ arl_expect(arl_base, "SctpInPktDiscards", &SctpInPktDiscards);
+ arl_expect(arl_base, "SctpInDataChunkDiscards", &SctpInDataChunkDiscards);
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/sctp/snmp");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/sctp/snmp", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff))
+ return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ arl_begin(arl_base);
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) {
+ if(unlikely(words)) error("Cannot read /proc/net/sctp/snmp line %zu. Expected 2 params, read %zu.", l, words);
+ continue;
+ }
+
+ if(unlikely(arl_check(arl_base,
+ procfile_lineword(ff, l, 0),
+ procfile_lineword(ff, l, 1)))) break;
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_associations == CONFIG_BOOLEAN_YES || (do_associations == CONFIG_BOOLEAN_AUTO &&
+ (SctpCurrEstab || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_associations = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_established = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "sctp"
+ , "established"
+ , NULL
+ , "associations"
+ , NULL
+ , "SCTP current total number of established associations"
+ , "associations"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SCTP_SNMP_NAME
+ , NETDATA_CHART_PRIO_SCTP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_established = rrddim_add(st, "SctpCurrEstab", "established", 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_established, SctpCurrEstab);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_transitions == CONFIG_BOOLEAN_YES || (do_transitions == CONFIG_BOOLEAN_AUTO &&
+ (SctpActiveEstabs ||
+ SctpPassiveEstabs ||
+ SctpAborteds ||
+ SctpShutdowns ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_transitions = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_active = NULL,
+ *rd_passive = NULL,
+ *rd_aborted = NULL,
+ *rd_shutdown = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "sctp"
+ , "transitions"
+ , NULL
+ , "transitions"
+ , NULL
+ , "SCTP Association Transitions"
+ , "transitions/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SCTP_SNMP_NAME
+ , NETDATA_CHART_PRIO_SCTP + 10
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_active = rrddim_add(st, "SctpActiveEstabs", "active", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_passive = rrddim_add(st, "SctpPassiveEstabs", "passive", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_aborted = rrddim_add(st, "SctpAborteds", "aborted", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_shutdown = rrddim_add(st, "SctpShutdowns", "shutdown", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_active, SctpActiveEstabs);
+ rrddim_set_by_pointer(st, rd_passive, SctpPassiveEstabs);
+ rrddim_set_by_pointer(st, rd_aborted, SctpAborteds);
+ rrddim_set_by_pointer(st, rd_shutdown, SctpShutdowns);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_packets == CONFIG_BOOLEAN_YES || (do_packets == CONFIG_BOOLEAN_AUTO &&
+ (SctpInSCTPPacks ||
+ SctpOutSCTPPacks ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_packets = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "sctp"
+ , "packets"
+ , NULL
+ , "packets"
+ , NULL
+ , "SCTP Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SCTP_SNMP_NAME
+ , NETDATA_CHART_PRIO_SCTP + 20
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_received = rrddim_add(st, "SctpInSCTPPacks", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "SctpOutSCTPPacks", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_received, SctpInSCTPPacks);
+ rrddim_set_by_pointer(st, rd_sent, SctpOutSCTPPacks);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_packet_errors == CONFIG_BOOLEAN_YES || (do_packet_errors == CONFIG_BOOLEAN_AUTO &&
+ (SctpOutOfBlues ||
+ SctpChecksumErrors ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_packet_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_invalid = NULL,
+ *rd_csum = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "sctp"
+ , "packet_errors"
+ , NULL
+ , "packets"
+ , NULL
+ , "SCTP Packet Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SCTP_SNMP_NAME
+ , NETDATA_CHART_PRIO_SCTP + 30
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_invalid = rrddim_add(st, "SctpOutOfBlues", "invalid", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_csum = rrddim_add(st, "SctpChecksumErrors", "checksum", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_invalid, SctpOutOfBlues);
+ rrddim_set_by_pointer(st, rd_csum, SctpChecksumErrors);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_fragmentation == CONFIG_BOOLEAN_YES || (do_fragmentation == CONFIG_BOOLEAN_AUTO &&
+ (SctpFragUsrMsgs ||
+ SctpReasmUsrMsgs ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_fragmentation = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_fragmented = NULL,
+ *rd_reassembled = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "sctp"
+ , "fragmentation"
+ , NULL
+ , "fragmentation"
+ , NULL
+ , "SCTP Fragmentation"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SCTP_SNMP_NAME
+ , NETDATA_CHART_PRIO_SCTP + 40
+ , update_every
+ , RRDSET_TYPE_LINE);
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_reassembled = rrddim_add(st, "SctpReasmUsrMsgs", "reassembled", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_fragmented = rrddim_add(st, "SctpFragUsrMsgs", "fragmented", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_reassembled, SctpReasmUsrMsgs);
+ rrddim_set_by_pointer(st, rd_fragmented, SctpFragUsrMsgs);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_chunk_types == CONFIG_BOOLEAN_YES || (do_chunk_types == CONFIG_BOOLEAN_AUTO &&
+ (SctpInCtrlChunks ||
+ SctpInOrderChunks ||
+ SctpInUnorderChunks ||
+ SctpOutCtrlChunks ||
+ SctpOutOrderChunks ||
+ SctpOutUnorderChunks ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_chunk_types = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM
+ *rd_InCtrl = NULL,
+ *rd_InOrder = NULL,
+ *rd_InUnorder = NULL,
+ *rd_OutCtrl = NULL,
+ *rd_OutOrder = NULL,
+ *rd_OutUnorder = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "sctp"
+ , "chunks"
+ , NULL
+ , "chunks"
+ , NULL
+ , "SCTP Chunk Types"
+ , "chunks/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SCTP_SNMP_NAME
+ , NETDATA_CHART_PRIO_SCTP + 50
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_InCtrl = rrddim_add(st, "SctpInCtrlChunks", "InCtrl", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InOrder = rrddim_add(st, "SctpInOrderChunks", "InOrder", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InUnorder = rrddim_add(st, "SctpInUnorderChunks", "InUnorder", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutCtrl = rrddim_add(st, "SctpOutCtrlChunks", "OutCtrl", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutOrder = rrddim_add(st, "SctpOutOrderChunks", "OutOrder", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutUnorder = rrddim_add(st, "SctpOutUnorderChunks", "OutUnorder", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InCtrl, SctpInCtrlChunks);
+ rrddim_set_by_pointer(st, rd_InOrder, SctpInOrderChunks);
+ rrddim_set_by_pointer(st, rd_InUnorder, SctpInUnorderChunks);
+ rrddim_set_by_pointer(st, rd_OutCtrl, SctpOutCtrlChunks);
+ rrddim_set_by_pointer(st, rd_OutOrder, SctpOutOrderChunks);
+ rrddim_set_by_pointer(st, rd_OutUnorder, SctpOutUnorderChunks);
+ rrdset_done(st);
+ }
+
+ return 0;
+}
+
diff --git a/collectors/proc.plugin/proc_net_snmp.c b/collectors/proc.plugin/proc_net_snmp.c
new file mode 100644
index 0000000..b03a6ac
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_snmp.c
@@ -0,0 +1,1130 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+#define PLUGIN_PROC_MODULE_NET_SNMP_NAME "/proc/net/snmp"
+
+#define RRD_TYPE_NET_SNMP "ipv4"
+
+static struct proc_net_snmp {
+ // kernel_uint_t ip_Forwarding;
+ kernel_uint_t ip_DefaultTTL;
+ kernel_uint_t ip_InReceives;
+ kernel_uint_t ip_InHdrErrors;
+ kernel_uint_t ip_InAddrErrors;
+ kernel_uint_t ip_ForwDatagrams;
+ kernel_uint_t ip_InUnknownProtos;
+ kernel_uint_t ip_InDiscards;
+ kernel_uint_t ip_InDelivers;
+ kernel_uint_t ip_OutRequests;
+ kernel_uint_t ip_OutDiscards;
+ kernel_uint_t ip_OutNoRoutes;
+ kernel_uint_t ip_ReasmTimeout;
+ kernel_uint_t ip_ReasmReqds;
+ kernel_uint_t ip_ReasmOKs;
+ kernel_uint_t ip_ReasmFails;
+ kernel_uint_t ip_FragOKs;
+ kernel_uint_t ip_FragFails;
+ kernel_uint_t ip_FragCreates;
+
+ kernel_uint_t icmp_InMsgs;
+ kernel_uint_t icmp_OutMsgs;
+ kernel_uint_t icmp_InErrors;
+ kernel_uint_t icmp_OutErrors;
+ kernel_uint_t icmp_InCsumErrors;
+
+ kernel_uint_t icmpmsg_InEchoReps;
+ kernel_uint_t icmpmsg_OutEchoReps;
+ kernel_uint_t icmpmsg_InDestUnreachs;
+ kernel_uint_t icmpmsg_OutDestUnreachs;
+ kernel_uint_t icmpmsg_InRedirects;
+ kernel_uint_t icmpmsg_OutRedirects;
+ kernel_uint_t icmpmsg_InEchos;
+ kernel_uint_t icmpmsg_OutEchos;
+ kernel_uint_t icmpmsg_InRouterAdvert;
+ kernel_uint_t icmpmsg_OutRouterAdvert;
+ kernel_uint_t icmpmsg_InRouterSelect;
+ kernel_uint_t icmpmsg_OutRouterSelect;
+ kernel_uint_t icmpmsg_InTimeExcds;
+ kernel_uint_t icmpmsg_OutTimeExcds;
+ kernel_uint_t icmpmsg_InParmProbs;
+ kernel_uint_t icmpmsg_OutParmProbs;
+ kernel_uint_t icmpmsg_InTimestamps;
+ kernel_uint_t icmpmsg_OutTimestamps;
+ kernel_uint_t icmpmsg_InTimestampReps;
+ kernel_uint_t icmpmsg_OutTimestampReps;
+
+ //kernel_uint_t tcp_RtoAlgorithm;
+ //kernel_uint_t tcp_RtoMin;
+ //kernel_uint_t tcp_RtoMax;
+ ssize_t tcp_MaxConn;
+ kernel_uint_t tcp_ActiveOpens;
+ kernel_uint_t tcp_PassiveOpens;
+ kernel_uint_t tcp_AttemptFails;
+ kernel_uint_t tcp_EstabResets;
+ kernel_uint_t tcp_CurrEstab;
+ kernel_uint_t tcp_InSegs;
+ kernel_uint_t tcp_OutSegs;
+ kernel_uint_t tcp_RetransSegs;
+ kernel_uint_t tcp_InErrs;
+ kernel_uint_t tcp_OutRsts;
+ kernel_uint_t tcp_InCsumErrors;
+
+ kernel_uint_t udp_InDatagrams;
+ kernel_uint_t udp_NoPorts;
+ kernel_uint_t udp_InErrors;
+ kernel_uint_t udp_OutDatagrams;
+ kernel_uint_t udp_RcvbufErrors;
+ kernel_uint_t udp_SndbufErrors;
+ kernel_uint_t udp_InCsumErrors;
+ kernel_uint_t udp_IgnoredMulti;
+
+ kernel_uint_t udplite_InDatagrams;
+ kernel_uint_t udplite_NoPorts;
+ kernel_uint_t udplite_InErrors;
+ kernel_uint_t udplite_OutDatagrams;
+ kernel_uint_t udplite_RcvbufErrors;
+ kernel_uint_t udplite_SndbufErrors;
+ kernel_uint_t udplite_InCsumErrors;
+ kernel_uint_t udplite_IgnoredMulti;
+} snmp_root = { 0 };
+
+int do_proc_net_snmp(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+ static int do_ip_packets = -1, do_ip_fragsout = -1, do_ip_fragsin = -1, do_ip_errors = -1,
+ do_tcp_sockets = -1, do_tcp_packets = -1, do_tcp_errors = -1, do_tcp_handshake = -1, do_tcp_opens = -1,
+ do_udp_packets = -1, do_udp_errors = -1, do_icmp_packets = -1, do_icmpmsg = -1, do_udplite_packets = -1;
+ static uint32_t hash_ip = 0, hash_icmp = 0, hash_tcp = 0, hash_udp = 0, hash_icmpmsg = 0, hash_udplite = 0;
+
+ static ARL_BASE *arl_ip = NULL,
+ *arl_icmp = NULL,
+ *arl_icmpmsg = NULL,
+ *arl_tcp = NULL,
+ *arl_udp = NULL,
+ *arl_udplite = NULL;
+
+ static RRDVAR *tcp_max_connections_var = NULL;
+
+ if(unlikely(!arl_ip)) {
+ do_ip_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 packets", CONFIG_BOOLEAN_AUTO);
+ do_ip_fragsout = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 fragments sent", CONFIG_BOOLEAN_AUTO);
+ do_ip_fragsin = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 fragments assembly", CONFIG_BOOLEAN_AUTO);
+ do_ip_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 errors", CONFIG_BOOLEAN_AUTO);
+ do_tcp_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 TCP connections", CONFIG_BOOLEAN_AUTO);
+ do_tcp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 TCP packets", CONFIG_BOOLEAN_AUTO);
+ do_tcp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 TCP errors", CONFIG_BOOLEAN_AUTO);
+ do_tcp_opens = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 TCP opens", CONFIG_BOOLEAN_AUTO);
+ do_tcp_handshake = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 TCP handshake issues", CONFIG_BOOLEAN_AUTO);
+ do_udp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 UDP packets", CONFIG_BOOLEAN_AUTO);
+ do_udp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 UDP errors", CONFIG_BOOLEAN_AUTO);
+ do_icmp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 ICMP packets", CONFIG_BOOLEAN_AUTO);
+ do_icmpmsg = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 ICMP messages", CONFIG_BOOLEAN_AUTO);
+ do_udplite_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp", "ipv4 UDPLite packets", CONFIG_BOOLEAN_AUTO);
+
+ hash_ip = simple_hash("Ip");
+ hash_tcp = simple_hash("Tcp");
+ hash_udp = simple_hash("Udp");
+ hash_icmp = simple_hash("Icmp");
+ hash_icmpmsg = simple_hash("IcmpMsg");
+ hash_udplite = simple_hash("UdpLite");
+
+ arl_ip = arl_create("snmp/Ip", arl_callback_str2kernel_uint_t, 60);
+ // arl_expect(arl_ip, "Forwarding", &snmp_root.ip_Forwarding);
+ arl_expect(arl_ip, "DefaultTTL", &snmp_root.ip_DefaultTTL);
+ arl_expect(arl_ip, "InReceives", &snmp_root.ip_InReceives);
+ arl_expect(arl_ip, "InHdrErrors", &snmp_root.ip_InHdrErrors);
+ arl_expect(arl_ip, "InAddrErrors", &snmp_root.ip_InAddrErrors);
+ arl_expect(arl_ip, "ForwDatagrams", &snmp_root.ip_ForwDatagrams);
+ arl_expect(arl_ip, "InUnknownProtos", &snmp_root.ip_InUnknownProtos);
+ arl_expect(arl_ip, "InDiscards", &snmp_root.ip_InDiscards);
+ arl_expect(arl_ip, "InDelivers", &snmp_root.ip_InDelivers);
+ arl_expect(arl_ip, "OutRequests", &snmp_root.ip_OutRequests);
+ arl_expect(arl_ip, "OutDiscards", &snmp_root.ip_OutDiscards);
+ arl_expect(arl_ip, "OutNoRoutes", &snmp_root.ip_OutNoRoutes);
+ arl_expect(arl_ip, "ReasmTimeout", &snmp_root.ip_ReasmTimeout);
+ arl_expect(arl_ip, "ReasmReqds", &snmp_root.ip_ReasmReqds);
+ arl_expect(arl_ip, "ReasmOKs", &snmp_root.ip_ReasmOKs);
+ arl_expect(arl_ip, "ReasmFails", &snmp_root.ip_ReasmFails);
+ arl_expect(arl_ip, "FragOKs", &snmp_root.ip_FragOKs);
+ arl_expect(arl_ip, "FragFails", &snmp_root.ip_FragFails);
+ arl_expect(arl_ip, "FragCreates", &snmp_root.ip_FragCreates);
+
+ arl_icmp = arl_create("snmp/Icmp", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_icmp, "InMsgs", &snmp_root.icmp_InMsgs);
+ arl_expect(arl_icmp, "OutMsgs", &snmp_root.icmp_OutMsgs);
+ arl_expect(arl_icmp, "InErrors", &snmp_root.icmp_InErrors);
+ arl_expect(arl_icmp, "OutErrors", &snmp_root.icmp_OutErrors);
+ arl_expect(arl_icmp, "InCsumErrors", &snmp_root.icmp_InCsumErrors);
+
+ arl_icmpmsg = arl_create("snmp/Icmpmsg", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_icmpmsg, "InType0", &snmp_root.icmpmsg_InEchoReps);
+ arl_expect(arl_icmpmsg, "OutType0", &snmp_root.icmpmsg_OutEchoReps);
+ arl_expect(arl_icmpmsg, "InType3", &snmp_root.icmpmsg_InDestUnreachs);
+ arl_expect(arl_icmpmsg, "OutType3", &snmp_root.icmpmsg_OutDestUnreachs);
+ arl_expect(arl_icmpmsg, "InType5", &snmp_root.icmpmsg_InRedirects);
+ arl_expect(arl_icmpmsg, "OutType5", &snmp_root.icmpmsg_OutRedirects);
+ arl_expect(arl_icmpmsg, "InType8", &snmp_root.icmpmsg_InEchos);
+ arl_expect(arl_icmpmsg, "OutType8", &snmp_root.icmpmsg_OutEchos);
+ arl_expect(arl_icmpmsg, "InType9", &snmp_root.icmpmsg_InRouterAdvert);
+ arl_expect(arl_icmpmsg, "OutType9", &snmp_root.icmpmsg_OutRouterAdvert);
+ arl_expect(arl_icmpmsg, "InType10", &snmp_root.icmpmsg_InRouterSelect);
+ arl_expect(arl_icmpmsg, "OutType10", &snmp_root.icmpmsg_OutRouterSelect);
+ arl_expect(arl_icmpmsg, "InType11", &snmp_root.icmpmsg_InTimeExcds);
+ arl_expect(arl_icmpmsg, "OutType11", &snmp_root.icmpmsg_OutTimeExcds);
+ arl_expect(arl_icmpmsg, "InType12", &snmp_root.icmpmsg_InParmProbs);
+ arl_expect(arl_icmpmsg, "OutType12", &snmp_root.icmpmsg_OutParmProbs);
+ arl_expect(arl_icmpmsg, "InType13", &snmp_root.icmpmsg_InTimestamps);
+ arl_expect(arl_icmpmsg, "OutType13", &snmp_root.icmpmsg_OutTimestamps);
+ arl_expect(arl_icmpmsg, "InType14", &snmp_root.icmpmsg_InTimestampReps);
+ arl_expect(arl_icmpmsg, "OutType14", &snmp_root.icmpmsg_OutTimestampReps);
+
+ arl_tcp = arl_create("snmp/Tcp", arl_callback_str2kernel_uint_t, 60);
+ // arl_expect(arl_tcp, "RtoAlgorithm", &snmp_root.tcp_RtoAlgorithm);
+ // arl_expect(arl_tcp, "RtoMin", &snmp_root.tcp_RtoMin);
+ // arl_expect(arl_tcp, "RtoMax", &snmp_root.tcp_RtoMax);
+ arl_expect_custom(arl_tcp, "MaxConn", arl_callback_ssize_t, &snmp_root.tcp_MaxConn);
+ arl_expect(arl_tcp, "ActiveOpens", &snmp_root.tcp_ActiveOpens);
+ arl_expect(arl_tcp, "PassiveOpens", &snmp_root.tcp_PassiveOpens);
+ arl_expect(arl_tcp, "AttemptFails", &snmp_root.tcp_AttemptFails);
+ arl_expect(arl_tcp, "EstabResets", &snmp_root.tcp_EstabResets);
+ arl_expect(arl_tcp, "CurrEstab", &snmp_root.tcp_CurrEstab);
+ arl_expect(arl_tcp, "InSegs", &snmp_root.tcp_InSegs);
+ arl_expect(arl_tcp, "OutSegs", &snmp_root.tcp_OutSegs);
+ arl_expect(arl_tcp, "RetransSegs", &snmp_root.tcp_RetransSegs);
+ arl_expect(arl_tcp, "InErrs", &snmp_root.tcp_InErrs);
+ arl_expect(arl_tcp, "OutRsts", &snmp_root.tcp_OutRsts);
+ arl_expect(arl_tcp, "InCsumErrors", &snmp_root.tcp_InCsumErrors);
+
+ arl_udp = arl_create("snmp/Udp", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_udp, "InDatagrams", &snmp_root.udp_InDatagrams);
+ arl_expect(arl_udp, "NoPorts", &snmp_root.udp_NoPorts);
+ arl_expect(arl_udp, "InErrors", &snmp_root.udp_InErrors);
+ arl_expect(arl_udp, "OutDatagrams", &snmp_root.udp_OutDatagrams);
+ arl_expect(arl_udp, "RcvbufErrors", &snmp_root.udp_RcvbufErrors);
+ arl_expect(arl_udp, "SndbufErrors", &snmp_root.udp_SndbufErrors);
+ arl_expect(arl_udp, "InCsumErrors", &snmp_root.udp_InCsumErrors);
+ arl_expect(arl_udp, "IgnoredMulti", &snmp_root.udp_IgnoredMulti);
+
+ arl_udplite = arl_create("snmp/Udplite", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_udplite, "InDatagrams", &snmp_root.udplite_InDatagrams);
+ arl_expect(arl_udplite, "NoPorts", &snmp_root.udplite_NoPorts);
+ arl_expect(arl_udplite, "InErrors", &snmp_root.udplite_InErrors);
+ arl_expect(arl_udplite, "OutDatagrams", &snmp_root.udplite_OutDatagrams);
+ arl_expect(arl_udplite, "RcvbufErrors", &snmp_root.udplite_RcvbufErrors);
+ arl_expect(arl_udplite, "SndbufErrors", &snmp_root.udplite_SndbufErrors);
+ arl_expect(arl_udplite, "InCsumErrors", &snmp_root.udplite_InCsumErrors);
+ arl_expect(arl_udplite, "IgnoredMulti", &snmp_root.udplite_IgnoredMulti);
+
+ tcp_max_connections_var = rrdvar_custom_host_variable_create(localhost, "tcp_max_connections");
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/snmp");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/snmp", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+ size_t words, w;
+
+ for(l = 0; l < lines ;l++) {
+ char *key = procfile_lineword(ff, l, 0);
+ uint32_t hash = simple_hash(key);
+
+ if(unlikely(hash == hash_ip && strcmp(key, "Ip") == 0)) {
+ size_t h = l++;
+
+ if(strcmp(procfile_lineword(ff, l, 0), "Ip") != 0) {
+ error("Cannot read Ip line from /proc/net/snmp.");
+ break;
+ }
+
+ words = procfile_linewords(ff, l);
+ if(words < 3) {
+ error("Cannot read /proc/net/snmp Ip line. Expected 3+ params, read %zu.", words);
+ continue;
+ }
+
+ arl_begin(arl_ip);
+ for(w = 1; w < words ; w++) {
+ if (unlikely(arl_check(arl_ip, procfile_lineword(ff, h, w), procfile_lineword(ff, l, w)) != 0))
+ break;
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_packets == CONFIG_BOOLEAN_YES || (do_ip_packets == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.ip_OutRequests ||
+ snmp_root.ip_InReceives ||
+ snmp_root.ip_ForwDatagrams ||
+ snmp_root.ip_InDelivers ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_packets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InReceives = NULL,
+ *rd_OutRequests = NULL,
+ *rd_ForwDatagrams = NULL,
+ *rd_InDelivers = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "packets"
+ , NULL
+ , "packets"
+ , NULL
+ , "IPv4 Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InReceives = rrddim_add(st, "InReceives", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutRequests = rrddim_add(st, "OutRequests", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_ForwDatagrams = rrddim_add(st, "ForwDatagrams", "forwarded", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InDelivers = rrddim_add(st, "InDelivers", "delivered", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_OutRequests, (collected_number)snmp_root.ip_OutRequests);
+ rrddim_set_by_pointer(st, rd_InReceives, (collected_number)snmp_root.ip_InReceives);
+ rrddim_set_by_pointer(st, rd_ForwDatagrams, (collected_number)snmp_root.ip_ForwDatagrams);
+ rrddim_set_by_pointer(st, rd_InDelivers, (collected_number)snmp_root.ip_InDelivers);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_fragsout == CONFIG_BOOLEAN_YES || (do_ip_fragsout == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.ip_FragOKs ||
+ snmp_root.ip_FragFails ||
+ snmp_root.ip_FragCreates ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_fragsout = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_FragOKs = NULL,
+ *rd_FragFails = NULL,
+ *rd_FragCreates = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "fragsout"
+ , NULL
+ , "fragments"
+ , NULL
+ , "IPv4 Fragments Sent"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_FRAGMENTS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_FragOKs = rrddim_add(st, "FragOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_FragFails = rrddim_add(st, "FragFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_FragCreates = rrddim_add(st, "FragCreates", "created", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_FragOKs, (collected_number)snmp_root.ip_FragOKs);
+ rrddim_set_by_pointer(st, rd_FragFails, (collected_number)snmp_root.ip_FragFails);
+ rrddim_set_by_pointer(st, rd_FragCreates, (collected_number)snmp_root.ip_FragCreates);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_fragsin == CONFIG_BOOLEAN_YES || (do_ip_fragsin == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.ip_ReasmOKs ||
+ snmp_root.ip_ReasmFails ||
+ snmp_root.ip_ReasmReqds ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_fragsin = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_ReasmOKs = NULL,
+ *rd_ReasmFails = NULL,
+ *rd_ReasmReqds = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "fragsin"
+ , NULL
+ , "fragments"
+ , NULL
+ , "IPv4 Fragments Reassembly"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_FRAGMENTS + 1
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_ReasmOKs = rrddim_add(st, "ReasmOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_ReasmFails = rrddim_add(st, "ReasmFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_ReasmReqds = rrddim_add(st, "ReasmReqds", "all", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_ReasmOKs, (collected_number)snmp_root.ip_ReasmOKs);
+ rrddim_set_by_pointer(st, rd_ReasmFails, (collected_number)snmp_root.ip_ReasmFails);
+ rrddim_set_by_pointer(st, rd_ReasmReqds, (collected_number)snmp_root.ip_ReasmReqds);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_errors == CONFIG_BOOLEAN_YES || (do_ip_errors == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.ip_InDiscards ||
+ snmp_root.ip_OutDiscards ||
+ snmp_root.ip_InHdrErrors ||
+ snmp_root.ip_InAddrErrors ||
+ snmp_root.ip_InUnknownProtos ||
+ snmp_root.ip_OutNoRoutes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_errors = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InDiscards = NULL,
+ *rd_OutDiscards = NULL,
+ *rd_InHdrErrors = NULL,
+ *rd_OutNoRoutes = NULL,
+ *rd_InAddrErrors = NULL,
+ *rd_InUnknownProtos = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "errors"
+ , NULL
+ , "errors"
+ , NULL
+ , "IPv4 Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_InDiscards = rrddim_add(st, "InDiscards", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDiscards = rrddim_add(st, "OutDiscards", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rd_InHdrErrors = rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutNoRoutes = rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rd_InAddrErrors = rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InUnknownProtos = rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InDiscards, (collected_number)snmp_root.ip_InDiscards);
+ rrddim_set_by_pointer(st, rd_OutDiscards, (collected_number)snmp_root.ip_OutDiscards);
+ rrddim_set_by_pointer(st, rd_InHdrErrors, (collected_number)snmp_root.ip_InHdrErrors);
+ rrddim_set_by_pointer(st, rd_InAddrErrors, (collected_number)snmp_root.ip_InAddrErrors);
+ rrddim_set_by_pointer(st, rd_InUnknownProtos, (collected_number)snmp_root.ip_InUnknownProtos);
+ rrddim_set_by_pointer(st, rd_OutNoRoutes, (collected_number)snmp_root.ip_OutNoRoutes);
+ rrdset_done(st);
+ }
+ }
+ else if(unlikely(hash == hash_icmp && strcmp(key, "Icmp") == 0)) {
+ size_t h = l++;
+
+ if(strcmp(procfile_lineword(ff, l, 0), "Icmp") != 0) {
+ error("Cannot read Icmp line from /proc/net/snmp.");
+ break;
+ }
+
+ words = procfile_linewords(ff, l);
+ if(words < 3) {
+ error("Cannot read /proc/net/snmp Icmp line. Expected 3+ params, read %zu.", words);
+ continue;
+ }
+
+ arl_begin(arl_icmp);
+ for(w = 1; w < words ; w++) {
+ if (unlikely(arl_check(arl_icmp, procfile_lineword(ff, h, w), procfile_lineword(ff, l, w)) != 0))
+ break;
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_packets == CONFIG_BOOLEAN_YES || (do_icmp_packets == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.icmp_InMsgs ||
+ snmp_root.icmp_OutMsgs ||
+ snmp_root.icmp_InErrors ||
+ snmp_root.icmp_OutErrors ||
+ snmp_root.icmp_InCsumErrors ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_packets = CONFIG_BOOLEAN_YES;
+
+ {
+ static RRDSET *st_packets = NULL;
+ static RRDDIM *rd_InMsgs = NULL,
+ *rd_OutMsgs = NULL;
+
+ if(unlikely(!st_packets)) {
+ st_packets = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "icmp"
+ , NULL
+ , "icmp"
+ , NULL
+ , "IPv4 ICMP Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_ICMP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InMsgs = rrddim_add(st_packets, "InMsgs", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutMsgs = rrddim_add(st_packets, "OutMsgs", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_packets);
+
+ rrddim_set_by_pointer(st_packets, rd_InMsgs, (collected_number)snmp_root.icmp_InMsgs);
+ rrddim_set_by_pointer(st_packets, rd_OutMsgs, (collected_number)snmp_root.icmp_OutMsgs);
+
+ rrdset_done(st_packets);
+ }
+
+ {
+ static RRDSET *st_errors = NULL;
+ static RRDDIM *rd_InErrors = NULL,
+ *rd_OutErrors = NULL,
+ *rd_InCsumErrors = NULL;
+
+ if(unlikely(!st_errors)) {
+ st_errors = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "icmp_errors"
+ , NULL
+ , "icmp"
+ , NULL
+ , "IPv4 ICMP Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_ICMP + 1
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InErrors = rrddim_add(st_errors, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutErrors = rrddim_add(st_errors, "OutErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st_errors, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_errors);
+
+ rrddim_set_by_pointer(st_errors, rd_InErrors, (collected_number)snmp_root.icmp_InErrors);
+ rrddim_set_by_pointer(st_errors, rd_OutErrors, (collected_number)snmp_root.icmp_OutErrors);
+ rrddim_set_by_pointer(st_errors, rd_InCsumErrors, (collected_number)snmp_root.icmp_InCsumErrors);
+
+ rrdset_done(st_errors);
+ }
+ }
+ }
+ else if(unlikely(hash == hash_icmpmsg && strcmp(key, "IcmpMsg") == 0)) {
+ size_t h = l++;
+
+ if(strcmp(procfile_lineword(ff, l, 0), "IcmpMsg") != 0) {
+ error("Cannot read IcmpMsg line from /proc/net/snmp.");
+ break;
+ }
+
+ words = procfile_linewords(ff, l);
+ if(words < 2) {
+ error("Cannot read /proc/net/snmp IcmpMsg line. Expected 2+ params, read %zu.", words);
+ continue;
+ }
+
+ arl_begin(arl_icmpmsg);
+ for(w = 1; w < words ; w++) {
+ if (unlikely(arl_check(arl_icmpmsg, procfile_lineword(ff, h, w), procfile_lineword(ff, l, w)) != 0))
+ break;
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmpmsg == CONFIG_BOOLEAN_YES || (do_icmpmsg == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.icmpmsg_InEchoReps ||
+ snmp_root.icmpmsg_OutEchoReps ||
+ snmp_root.icmpmsg_InDestUnreachs ||
+ snmp_root.icmpmsg_OutDestUnreachs ||
+ snmp_root.icmpmsg_InRedirects ||
+ snmp_root.icmpmsg_OutRedirects ||
+ snmp_root.icmpmsg_InEchos ||
+ snmp_root.icmpmsg_OutEchos ||
+ snmp_root.icmpmsg_InRouterAdvert ||
+ snmp_root.icmpmsg_OutRouterAdvert ||
+ snmp_root.icmpmsg_InRouterSelect ||
+ snmp_root.icmpmsg_OutRouterSelect ||
+ snmp_root.icmpmsg_InTimeExcds ||
+ snmp_root.icmpmsg_OutTimeExcds ||
+ snmp_root.icmpmsg_InParmProbs ||
+ snmp_root.icmpmsg_OutParmProbs ||
+ snmp_root.icmpmsg_InTimestamps ||
+ snmp_root.icmpmsg_OutTimestamps ||
+ snmp_root.icmpmsg_InTimestampReps ||
+ snmp_root.icmpmsg_OutTimestampReps ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmpmsg = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InEchoReps = NULL,
+ *rd_OutEchoReps = NULL,
+ *rd_InDestUnreachs = NULL,
+ *rd_OutDestUnreachs = NULL,
+ *rd_InRedirects = NULL,
+ *rd_OutRedirects = NULL,
+ *rd_InEchos = NULL,
+ *rd_OutEchos = NULL,
+ *rd_InRouterAdvert = NULL,
+ *rd_OutRouterAdvert = NULL,
+ *rd_InRouterSelect = NULL,
+ *rd_OutRouterSelect = NULL,
+ *rd_InTimeExcds = NULL,
+ *rd_OutTimeExcds = NULL,
+ *rd_InParmProbs = NULL,
+ *rd_OutParmProbs = NULL,
+ *rd_InTimestamps = NULL,
+ *rd_OutTimestamps = NULL,
+ *rd_InTimestampReps = NULL,
+ *rd_OutTimestampReps = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "icmpmsg"
+ , NULL
+ , "icmp"
+ , NULL
+ , "IPv4 ICMP Messages"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_ICMP + 2
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InEchoReps = rrddim_add(st, "InType0", "InEchoReps", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutEchoReps = rrddim_add(st, "OutType0", "OutEchoReps", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InDestUnreachs = rrddim_add(st, "InType3", "InDestUnreachs", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDestUnreachs = rrddim_add(st, "OutType3", "OutDestUnreachs", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InRedirects = rrddim_add(st, "InType5", "InRedirects", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutRedirects = rrddim_add(st, "OutType5", "OutRedirects", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InEchos = rrddim_add(st, "InType8", "InEchos", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutEchos = rrddim_add(st, "OutType8", "OutEchos", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InRouterAdvert = rrddim_add(st, "InType9", "InRouterAdvert", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutRouterAdvert = rrddim_add(st, "OutType9", "OutRouterAdvert", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InRouterSelect = rrddim_add(st, "InType10", "InRouterSelect", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutRouterSelect = rrddim_add(st, "OutType10", "OutRouterSelect", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTimeExcds = rrddim_add(st, "InType11", "InTimeExcds", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutTimeExcds = rrddim_add(st, "OutType11", "OutTimeExcds", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InParmProbs = rrddim_add(st, "InType12", "InParmProbs", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutParmProbs = rrddim_add(st, "OutType12", "OutParmProbs", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTimestamps = rrddim_add(st, "InType13", "InTimestamps", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutTimestamps = rrddim_add(st, "OutType13", "OutTimestamps", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTimestampReps = rrddim_add(st, "InType14", "InTimestampReps", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutTimestampReps = rrddim_add(st, "OutType14", "OutTimestampReps", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InEchoReps, (collected_number)snmp_root.icmpmsg_InEchoReps);
+ rrddim_set_by_pointer(st, rd_OutEchoReps, (collected_number)snmp_root.icmpmsg_OutEchoReps);
+ rrddim_set_by_pointer(st, rd_InDestUnreachs, (collected_number)snmp_root.icmpmsg_InDestUnreachs);
+ rrddim_set_by_pointer(st, rd_OutDestUnreachs, (collected_number)snmp_root.icmpmsg_OutDestUnreachs);
+ rrddim_set_by_pointer(st, rd_InRedirects, (collected_number)snmp_root.icmpmsg_InRedirects);
+ rrddim_set_by_pointer(st, rd_OutRedirects, (collected_number)snmp_root.icmpmsg_OutRedirects);
+ rrddim_set_by_pointer(st, rd_InEchos, (collected_number)snmp_root.icmpmsg_InEchos);
+ rrddim_set_by_pointer(st, rd_OutEchos, (collected_number)snmp_root.icmpmsg_OutEchos);
+ rrddim_set_by_pointer(st, rd_InRouterAdvert, (collected_number)snmp_root.icmpmsg_InRouterAdvert);
+ rrddim_set_by_pointer(st, rd_OutRouterAdvert, (collected_number)snmp_root.icmpmsg_OutRouterAdvert);
+ rrddim_set_by_pointer(st, rd_InRouterSelect, (collected_number)snmp_root.icmpmsg_InRouterSelect);
+ rrddim_set_by_pointer(st, rd_OutRouterSelect, (collected_number)snmp_root.icmpmsg_OutRouterSelect);
+ rrddim_set_by_pointer(st, rd_InTimeExcds, (collected_number)snmp_root.icmpmsg_InTimeExcds);
+ rrddim_set_by_pointer(st, rd_OutTimeExcds, (collected_number)snmp_root.icmpmsg_OutTimeExcds);
+ rrddim_set_by_pointer(st, rd_InParmProbs, (collected_number)snmp_root.icmpmsg_InParmProbs);
+ rrddim_set_by_pointer(st, rd_OutParmProbs, (collected_number)snmp_root.icmpmsg_OutParmProbs);
+ rrddim_set_by_pointer(st, rd_InTimestamps, (collected_number)snmp_root.icmpmsg_InTimestamps);
+ rrddim_set_by_pointer(st, rd_OutTimestamps, (collected_number)snmp_root.icmpmsg_OutTimestamps);
+ rrddim_set_by_pointer(st, rd_InTimestampReps, (collected_number)snmp_root.icmpmsg_InTimestampReps);
+ rrddim_set_by_pointer(st, rd_OutTimestampReps, (collected_number)snmp_root.icmpmsg_OutTimestampReps);
+
+ rrdset_done(st);
+ }
+ }
+ else if(unlikely(hash == hash_tcp && strcmp(key, "Tcp") == 0)) {
+ size_t h = l++;
+
+ if(strcmp(procfile_lineword(ff, l, 0), "Tcp") != 0) {
+ error("Cannot read Tcp line from /proc/net/snmp.");
+ break;
+ }
+
+ words = procfile_linewords(ff, l);
+ if(words < 3) {
+ error("Cannot read /proc/net/snmp Tcp line. Expected 3+ params, read %zu.", words);
+ continue;
+ }
+
+ arl_begin(arl_tcp);
+ for(w = 1; w < words ; w++) {
+ if (unlikely(arl_check(arl_tcp, procfile_lineword(ff, h, w), procfile_lineword(ff, l, w)) != 0))
+ break;
+ }
+
+ // --------------------------------------------------------------------
+
+ // this is smart enough to update it, only when it is changed
+ rrdvar_custom_host_variable_set(localhost, tcp_max_connections_var, snmp_root.tcp_MaxConn);
+
+ // --------------------------------------------------------------------
+
+ // see http://net-snmp.sourceforge.net/docs/mibs/tcp.html
+ if(do_tcp_sockets == CONFIG_BOOLEAN_YES || (do_tcp_sockets == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.tcp_CurrEstab ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_CurrEstab = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "tcpsock"
+ , NULL
+ , "tcp"
+ , NULL
+ , "IPv4 TCP Connections"
+ , "active connections"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_TCP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_CurrEstab = rrddim_add(st, "CurrEstab", "connections", 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_CurrEstab, (collected_number)snmp_root.tcp_CurrEstab);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcp_packets == CONFIG_BOOLEAN_YES || (do_tcp_packets == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.tcp_InSegs ||
+ snmp_root.tcp_OutSegs ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_packets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InSegs = NULL,
+ *rd_OutSegs = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "tcppackets"
+ , NULL
+ , "tcp"
+ , NULL
+ , "IPv4 TCP Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_TCP + 4
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InSegs = rrddim_add(st, "InSegs", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutSegs = rrddim_add(st, "OutSegs", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InSegs, (collected_number)snmp_root.tcp_InSegs);
+ rrddim_set_by_pointer(st, rd_OutSegs, (collected_number)snmp_root.tcp_OutSegs);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcp_errors == CONFIG_BOOLEAN_YES || (do_tcp_errors == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.tcp_InErrs ||
+ snmp_root.tcp_InCsumErrors ||
+ snmp_root.tcp_RetransSegs ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_errors = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InErrs = NULL,
+ *rd_InCsumErrors = NULL,
+ *rd_RetransSegs = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "tcperrors"
+ , NULL
+ , "tcp"
+ , NULL
+ , "IPv4 TCP Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_TCP + 20
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_InErrs = rrddim_add(st, "InErrs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_RetransSegs = rrddim_add(st, "RetransSegs", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InErrs, (collected_number)snmp_root.tcp_InErrs);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, (collected_number)snmp_root.tcp_InCsumErrors);
+ rrddim_set_by_pointer(st, rd_RetransSegs, (collected_number)snmp_root.tcp_RetransSegs);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcp_opens == CONFIG_BOOLEAN_YES || (do_tcp_opens == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.tcp_ActiveOpens ||
+ snmp_root.tcp_PassiveOpens ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_opens = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_ActiveOpens = NULL,
+ *rd_PassiveOpens = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "tcpopens"
+ , NULL
+ , "tcp"
+ , NULL
+ , "IPv4 TCP Opens"
+ , "connections/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_TCP + 5
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_ActiveOpens = rrddim_add(st, "ActiveOpens", "active", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_PassiveOpens = rrddim_add(st, "PassiveOpens", "passive", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_ActiveOpens, (collected_number)snmp_root.tcp_ActiveOpens);
+ rrddim_set_by_pointer(st, rd_PassiveOpens, (collected_number)snmp_root.tcp_PassiveOpens);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_tcp_handshake == CONFIG_BOOLEAN_YES || (do_tcp_handshake == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.tcp_EstabResets ||
+ snmp_root.tcp_OutRsts ||
+ snmp_root.tcp_AttemptFails ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_handshake = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_EstabResets = NULL,
+ *rd_OutRsts = NULL,
+ *rd_AttemptFails = NULL,
+ *rd_TCPSynRetrans = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "tcphandshake"
+ , NULL
+ , "tcp"
+ , NULL
+ , "IPv4 TCP Handshake Issues"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_TCP + 30
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_EstabResets = rrddim_add(st, "EstabResets", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutRsts = rrddim_add(st, "OutRsts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_AttemptFails = rrddim_add(st, "AttemptFails", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_TCPSynRetrans = rrddim_add(st, "TCPSynRetrans", "SynRetrans", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_EstabResets, (collected_number)snmp_root.tcp_EstabResets);
+ rrddim_set_by_pointer(st, rd_OutRsts, (collected_number)snmp_root.tcp_OutRsts);
+ rrddim_set_by_pointer(st, rd_AttemptFails, (collected_number)snmp_root.tcp_AttemptFails);
+ rrddim_set_by_pointer(st, rd_TCPSynRetrans, tcpext_TCPSynRetrans);
+ rrdset_done(st);
+ }
+ }
+ else if(unlikely(hash == hash_udp && strcmp(key, "Udp") == 0)) {
+ size_t h = l++;
+
+ if(strcmp(procfile_lineword(ff, l, 0), "Udp") != 0) {
+ error("Cannot read Udp line from /proc/net/snmp.");
+ break;
+ }
+
+ words = procfile_linewords(ff, l);
+ if(words < 3) {
+ error("Cannot read /proc/net/snmp Udp line. Expected 3+ params, read %zu.", words);
+ continue;
+ }
+
+ arl_begin(arl_udp);
+ for(w = 1; w < words ; w++) {
+ if (unlikely(arl_check(arl_udp, procfile_lineword(ff, h, w), procfile_lineword(ff, l, w)) != 0))
+ break;
+ }
+
+ // --------------------------------------------------------------------
+
+ // see http://net-snmp.sourceforge.net/docs/mibs/udp.html
+ if(do_udp_packets == CONFIG_BOOLEAN_YES || (do_udp_packets == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.udp_InDatagrams ||
+ snmp_root.udp_OutDatagrams ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udp_packets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InDatagrams = NULL,
+ *rd_OutDatagrams = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "udppackets"
+ , NULL
+ , "udp"
+ , NULL
+ , "IPv4 UDP Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_UDP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InDatagrams = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDatagrams = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InDatagrams, (collected_number)snmp_root.udp_InDatagrams);
+ rrddim_set_by_pointer(st, rd_OutDatagrams, (collected_number)snmp_root.udp_OutDatagrams);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_udp_errors == CONFIG_BOOLEAN_YES || (do_udp_errors == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.udp_InErrors ||
+ snmp_root.udp_NoPorts ||
+ snmp_root.udp_RcvbufErrors ||
+ snmp_root.udp_SndbufErrors ||
+ snmp_root.udp_InCsumErrors ||
+ snmp_root.udp_IgnoredMulti ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udp_errors = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_RcvbufErrors = NULL,
+ *rd_SndbufErrors = NULL,
+ *rd_InErrors = NULL,
+ *rd_NoPorts = NULL,
+ *rd_InCsumErrors = NULL,
+ *rd_IgnoredMulti = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "udperrors"
+ , NULL
+ , "udp"
+ , NULL
+ , "IPv4 UDP Errors"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_UDP + 10
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_IgnoredMulti = rrddim_add(st, "IgnoredMulti", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InErrors, (collected_number)snmp_root.udp_InErrors);
+ rrddim_set_by_pointer(st, rd_NoPorts, (collected_number)snmp_root.udp_NoPorts);
+ rrddim_set_by_pointer(st, rd_RcvbufErrors, (collected_number)snmp_root.udp_RcvbufErrors);
+ rrddim_set_by_pointer(st, rd_SndbufErrors, (collected_number)snmp_root.udp_SndbufErrors);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, (collected_number)snmp_root.udp_InCsumErrors);
+ rrddim_set_by_pointer(st, rd_IgnoredMulti, (collected_number)snmp_root.udp_IgnoredMulti);
+ rrdset_done(st);
+ }
+ }
+ else if(unlikely(hash == hash_udplite && strcmp(key, "UdpLite") == 0)) {
+ size_t h = l++;
+
+ if(strcmp(procfile_lineword(ff, l, 0), "UdpLite") != 0) {
+ error("Cannot read UdpLite line from /proc/net/snmp.");
+ break;
+ }
+
+ words = procfile_linewords(ff, l);
+ if(words < 3) {
+ error("Cannot read /proc/net/snmp UdpLite line. Expected 3+ params, read %zu.", words);
+ continue;
+ }
+
+ arl_begin(arl_udplite);
+ for(w = 1; w < words ; w++) {
+ if (unlikely(arl_check(arl_udplite, procfile_lineword(ff, h, w), procfile_lineword(ff, l, w)) != 0))
+ break;
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_udplite_packets == CONFIG_BOOLEAN_YES || (do_udplite_packets == CONFIG_BOOLEAN_AUTO &&
+ (snmp_root.udplite_InDatagrams ||
+ snmp_root.udplite_OutDatagrams ||
+ snmp_root.udplite_NoPorts ||
+ snmp_root.udplite_InErrors ||
+ snmp_root.udplite_InCsumErrors ||
+ snmp_root.udplite_RcvbufErrors ||
+ snmp_root.udplite_SndbufErrors ||
+ snmp_root.udplite_IgnoredMulti ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udplite_packets = CONFIG_BOOLEAN_YES;
+
+ {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InDatagrams = NULL,
+ *rd_OutDatagrams = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "udplite"
+ , NULL
+ , "udplite"
+ , NULL
+ , "IPv4 UDPLite Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_UDPLITE
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InDatagrams = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDatagrams = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InDatagrams, (collected_number)snmp_root.udplite_InDatagrams);
+ rrddim_set_by_pointer(st, rd_OutDatagrams, (collected_number)snmp_root.udplite_OutDatagrams);
+ rrdset_done(st);
+ }
+
+ {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_RcvbufErrors = NULL,
+ *rd_SndbufErrors = NULL,
+ *rd_InErrors = NULL,
+ *rd_NoPorts = NULL,
+ *rd_InCsumErrors = NULL,
+ *rd_IgnoredMulti = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP
+ , "udplite_errors"
+ , NULL
+ , "udplite"
+ , NULL
+ , "IPv4 UDPLite Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP_NAME
+ , NETDATA_CHART_PRIO_IPV4_UDPLITE + 10
+ , update_every
+ , RRDSET_TYPE_LINE);
+
+ rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_IgnoredMulti = rrddim_add(st, "IgnoredMulti", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_NoPorts, (collected_number)snmp_root.udplite_NoPorts);
+ rrddim_set_by_pointer(st, rd_InErrors, (collected_number)snmp_root.udplite_InErrors);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, (collected_number)snmp_root.udplite_InCsumErrors);
+ rrddim_set_by_pointer(st, rd_RcvbufErrors, (collected_number)snmp_root.udplite_RcvbufErrors);
+ rrddim_set_by_pointer(st, rd_SndbufErrors, (collected_number)snmp_root.udplite_SndbufErrors);
+ rrddim_set_by_pointer(st, rd_IgnoredMulti, (collected_number)snmp_root.udplite_IgnoredMulti);
+ rrdset_done(st);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
diff --git a/collectors/proc.plugin/proc_net_snmp6.c b/collectors/proc.plugin/proc_net_snmp6.c
new file mode 100644
index 0000000..445e0dc
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_snmp6.c
@@ -0,0 +1,1293 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define RRD_TYPE_NET_SNMP6 "ipv6"
+#define PLUGIN_PROC_MODULE_NET_SNMP6_NAME "/proc/net/snmp6"
+
+int do_proc_net_snmp6(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+
+ static int do_ip_packets = -1,
+ do_ip_fragsout = -1,
+ do_ip_fragsin = -1,
+ do_ip_errors = -1,
+ do_udplite_packets = -1,
+ do_udplite_errors = -1,
+ do_udp_packets = -1,
+ do_udp_errors = -1,
+ do_bandwidth = -1,
+ do_mcast = -1,
+ do_bcast = -1,
+ do_mcast_p = -1,
+ do_icmp = -1,
+ do_icmp_redir = -1,
+ do_icmp_errors = -1,
+ do_icmp_echos = -1,
+ do_icmp_groupmemb = -1,
+ do_icmp_router = -1,
+ do_icmp_neighbor = -1,
+ do_icmp_mldv2 = -1,
+ do_icmp_types = -1,
+ do_ect = -1;
+
+ static ARL_BASE *arl_base = NULL;
+
+ static unsigned long long Ip6InReceives = 0ULL;
+ static unsigned long long Ip6InHdrErrors = 0ULL;
+ static unsigned long long Ip6InTooBigErrors = 0ULL;
+ static unsigned long long Ip6InNoRoutes = 0ULL;
+ static unsigned long long Ip6InAddrErrors = 0ULL;
+ static unsigned long long Ip6InUnknownProtos = 0ULL;
+ static unsigned long long Ip6InTruncatedPkts = 0ULL;
+ static unsigned long long Ip6InDiscards = 0ULL;
+ static unsigned long long Ip6InDelivers = 0ULL;
+ static unsigned long long Ip6OutForwDatagrams = 0ULL;
+ static unsigned long long Ip6OutRequests = 0ULL;
+ static unsigned long long Ip6OutDiscards = 0ULL;
+ static unsigned long long Ip6OutNoRoutes = 0ULL;
+ static unsigned long long Ip6ReasmTimeout = 0ULL;
+ static unsigned long long Ip6ReasmReqds = 0ULL;
+ static unsigned long long Ip6ReasmOKs = 0ULL;
+ static unsigned long long Ip6ReasmFails = 0ULL;
+ static unsigned long long Ip6FragOKs = 0ULL;
+ static unsigned long long Ip6FragFails = 0ULL;
+ static unsigned long long Ip6FragCreates = 0ULL;
+ static unsigned long long Ip6InMcastPkts = 0ULL;
+ static unsigned long long Ip6OutMcastPkts = 0ULL;
+ static unsigned long long Ip6InOctets = 0ULL;
+ static unsigned long long Ip6OutOctets = 0ULL;
+ static unsigned long long Ip6InMcastOctets = 0ULL;
+ static unsigned long long Ip6OutMcastOctets = 0ULL;
+ static unsigned long long Ip6InBcastOctets = 0ULL;
+ static unsigned long long Ip6OutBcastOctets = 0ULL;
+ static unsigned long long Ip6InNoECTPkts = 0ULL;
+ static unsigned long long Ip6InECT1Pkts = 0ULL;
+ static unsigned long long Ip6InECT0Pkts = 0ULL;
+ static unsigned long long Ip6InCEPkts = 0ULL;
+ static unsigned long long Icmp6InMsgs = 0ULL;
+ static unsigned long long Icmp6InErrors = 0ULL;
+ static unsigned long long Icmp6OutMsgs = 0ULL;
+ static unsigned long long Icmp6OutErrors = 0ULL;
+ static unsigned long long Icmp6InCsumErrors = 0ULL;
+ static unsigned long long Icmp6InDestUnreachs = 0ULL;
+ static unsigned long long Icmp6InPktTooBigs = 0ULL;
+ static unsigned long long Icmp6InTimeExcds = 0ULL;
+ static unsigned long long Icmp6InParmProblems = 0ULL;
+ static unsigned long long Icmp6InEchos = 0ULL;
+ static unsigned long long Icmp6InEchoReplies = 0ULL;
+ static unsigned long long Icmp6InGroupMembQueries = 0ULL;
+ static unsigned long long Icmp6InGroupMembResponses = 0ULL;
+ static unsigned long long Icmp6InGroupMembReductions = 0ULL;
+ static unsigned long long Icmp6InRouterSolicits = 0ULL;
+ static unsigned long long Icmp6InRouterAdvertisements = 0ULL;
+ static unsigned long long Icmp6InNeighborSolicits = 0ULL;
+ static unsigned long long Icmp6InNeighborAdvertisements = 0ULL;
+ static unsigned long long Icmp6InRedirects = 0ULL;
+ static unsigned long long Icmp6InMLDv2Reports = 0ULL;
+ static unsigned long long Icmp6OutDestUnreachs = 0ULL;
+ static unsigned long long Icmp6OutPktTooBigs = 0ULL;
+ static unsigned long long Icmp6OutTimeExcds = 0ULL;
+ static unsigned long long Icmp6OutParmProblems = 0ULL;
+ static unsigned long long Icmp6OutEchos = 0ULL;
+ static unsigned long long Icmp6OutEchoReplies = 0ULL;
+ static unsigned long long Icmp6OutGroupMembQueries = 0ULL;
+ static unsigned long long Icmp6OutGroupMembResponses = 0ULL;
+ static unsigned long long Icmp6OutGroupMembReductions = 0ULL;
+ static unsigned long long Icmp6OutRouterSolicits = 0ULL;
+ static unsigned long long Icmp6OutRouterAdvertisements = 0ULL;
+ static unsigned long long Icmp6OutNeighborSolicits = 0ULL;
+ static unsigned long long Icmp6OutNeighborAdvertisements = 0ULL;
+ static unsigned long long Icmp6OutRedirects = 0ULL;
+ static unsigned long long Icmp6OutMLDv2Reports = 0ULL;
+ static unsigned long long Icmp6InType1 = 0ULL;
+ static unsigned long long Icmp6InType128 = 0ULL;
+ static unsigned long long Icmp6InType129 = 0ULL;
+ static unsigned long long Icmp6InType136 = 0ULL;
+ static unsigned long long Icmp6OutType1 = 0ULL;
+ static unsigned long long Icmp6OutType128 = 0ULL;
+ static unsigned long long Icmp6OutType129 = 0ULL;
+ static unsigned long long Icmp6OutType133 = 0ULL;
+ static unsigned long long Icmp6OutType135 = 0ULL;
+ static unsigned long long Icmp6OutType143 = 0ULL;
+ static unsigned long long Udp6InDatagrams = 0ULL;
+ static unsigned long long Udp6NoPorts = 0ULL;
+ static unsigned long long Udp6InErrors = 0ULL;
+ static unsigned long long Udp6OutDatagrams = 0ULL;
+ static unsigned long long Udp6RcvbufErrors = 0ULL;
+ static unsigned long long Udp6SndbufErrors = 0ULL;
+ static unsigned long long Udp6InCsumErrors = 0ULL;
+ static unsigned long long Udp6IgnoredMulti = 0ULL;
+ static unsigned long long UdpLite6InDatagrams = 0ULL;
+ static unsigned long long UdpLite6NoPorts = 0ULL;
+ static unsigned long long UdpLite6InErrors = 0ULL;
+ static unsigned long long UdpLite6OutDatagrams = 0ULL;
+ static unsigned long long UdpLite6RcvbufErrors = 0ULL;
+ static unsigned long long UdpLite6SndbufErrors = 0ULL;
+ static unsigned long long UdpLite6InCsumErrors = 0ULL;
+
+ if(unlikely(!arl_base)) {
+ do_ip_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 packets", CONFIG_BOOLEAN_AUTO);
+ do_ip_fragsout = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments sent", CONFIG_BOOLEAN_AUTO);
+ do_ip_fragsin = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 fragments assembly", CONFIG_BOOLEAN_AUTO);
+ do_ip_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 errors", CONFIG_BOOLEAN_AUTO);
+ do_udp_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP packets", CONFIG_BOOLEAN_AUTO);
+ do_udp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDP errors", CONFIG_BOOLEAN_AUTO);
+ do_udplite_packets = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite packets", CONFIG_BOOLEAN_AUTO);
+ do_udplite_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ipv6 UDPlite errors", CONFIG_BOOLEAN_AUTO);
+ do_bandwidth = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_mcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_bcast = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "broadcast bandwidth", CONFIG_BOOLEAN_AUTO);
+ do_mcast_p = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "multicast packets", CONFIG_BOOLEAN_AUTO);
+ do_icmp = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp", CONFIG_BOOLEAN_AUTO);
+ do_icmp_redir = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp redirects", CONFIG_BOOLEAN_AUTO);
+ do_icmp_errors = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp errors", CONFIG_BOOLEAN_AUTO);
+ do_icmp_echos = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp echos", CONFIG_BOOLEAN_AUTO);
+ do_icmp_groupmemb = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp group membership", CONFIG_BOOLEAN_AUTO);
+ do_icmp_router = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp router", CONFIG_BOOLEAN_AUTO);
+ do_icmp_neighbor = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp neighbor", CONFIG_BOOLEAN_AUTO);
+ do_icmp_mldv2 = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp mldv2", CONFIG_BOOLEAN_AUTO);
+ do_icmp_types = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "icmp types", CONFIG_BOOLEAN_AUTO);
+ do_ect = config_get_boolean_ondemand("plugin:proc:/proc/net/snmp6", "ect", CONFIG_BOOLEAN_AUTO);
+
+ arl_base = arl_create("snmp6", NULL, 60);
+ arl_expect(arl_base, "Ip6InReceives", &Ip6InReceives);
+ arl_expect(arl_base, "Ip6InHdrErrors", &Ip6InHdrErrors);
+ arl_expect(arl_base, "Ip6InTooBigErrors", &Ip6InTooBigErrors);
+ arl_expect(arl_base, "Ip6InNoRoutes", &Ip6InNoRoutes);
+ arl_expect(arl_base, "Ip6InAddrErrors", &Ip6InAddrErrors);
+ arl_expect(arl_base, "Ip6InUnknownProtos", &Ip6InUnknownProtos);
+ arl_expect(arl_base, "Ip6InTruncatedPkts", &Ip6InTruncatedPkts);
+ arl_expect(arl_base, "Ip6InDiscards", &Ip6InDiscards);
+ arl_expect(arl_base, "Ip6InDelivers", &Ip6InDelivers);
+ arl_expect(arl_base, "Ip6OutForwDatagrams", &Ip6OutForwDatagrams);
+ arl_expect(arl_base, "Ip6OutRequests", &Ip6OutRequests);
+ arl_expect(arl_base, "Ip6OutDiscards", &Ip6OutDiscards);
+ arl_expect(arl_base, "Ip6OutNoRoutes", &Ip6OutNoRoutes);
+ arl_expect(arl_base, "Ip6ReasmTimeout", &Ip6ReasmTimeout);
+ arl_expect(arl_base, "Ip6ReasmReqds", &Ip6ReasmReqds);
+ arl_expect(arl_base, "Ip6ReasmOKs", &Ip6ReasmOKs);
+ arl_expect(arl_base, "Ip6ReasmFails", &Ip6ReasmFails);
+ arl_expect(arl_base, "Ip6FragOKs", &Ip6FragOKs);
+ arl_expect(arl_base, "Ip6FragFails", &Ip6FragFails);
+ arl_expect(arl_base, "Ip6FragCreates", &Ip6FragCreates);
+ arl_expect(arl_base, "Ip6InMcastPkts", &Ip6InMcastPkts);
+ arl_expect(arl_base, "Ip6OutMcastPkts", &Ip6OutMcastPkts);
+ arl_expect(arl_base, "Ip6InOctets", &Ip6InOctets);
+ arl_expect(arl_base, "Ip6OutOctets", &Ip6OutOctets);
+ arl_expect(arl_base, "Ip6InMcastOctets", &Ip6InMcastOctets);
+ arl_expect(arl_base, "Ip6OutMcastOctets", &Ip6OutMcastOctets);
+ arl_expect(arl_base, "Ip6InBcastOctets", &Ip6InBcastOctets);
+ arl_expect(arl_base, "Ip6OutBcastOctets", &Ip6OutBcastOctets);
+ arl_expect(arl_base, "Ip6InNoECTPkts", &Ip6InNoECTPkts);
+ arl_expect(arl_base, "Ip6InECT1Pkts", &Ip6InECT1Pkts);
+ arl_expect(arl_base, "Ip6InECT0Pkts", &Ip6InECT0Pkts);
+ arl_expect(arl_base, "Ip6InCEPkts", &Ip6InCEPkts);
+ arl_expect(arl_base, "Icmp6InMsgs", &Icmp6InMsgs);
+ arl_expect(arl_base, "Icmp6InErrors", &Icmp6InErrors);
+ arl_expect(arl_base, "Icmp6OutMsgs", &Icmp6OutMsgs);
+ arl_expect(arl_base, "Icmp6OutErrors", &Icmp6OutErrors);
+ arl_expect(arl_base, "Icmp6InCsumErrors", &Icmp6InCsumErrors);
+ arl_expect(arl_base, "Icmp6InDestUnreachs", &Icmp6InDestUnreachs);
+ arl_expect(arl_base, "Icmp6InPktTooBigs", &Icmp6InPktTooBigs);
+ arl_expect(arl_base, "Icmp6InTimeExcds", &Icmp6InTimeExcds);
+ arl_expect(arl_base, "Icmp6InParmProblems", &Icmp6InParmProblems);
+ arl_expect(arl_base, "Icmp6InEchos", &Icmp6InEchos);
+ arl_expect(arl_base, "Icmp6InEchoReplies", &Icmp6InEchoReplies);
+ arl_expect(arl_base, "Icmp6InGroupMembQueries", &Icmp6InGroupMembQueries);
+ arl_expect(arl_base, "Icmp6InGroupMembResponses", &Icmp6InGroupMembResponses);
+ arl_expect(arl_base, "Icmp6InGroupMembReductions", &Icmp6InGroupMembReductions);
+ arl_expect(arl_base, "Icmp6InRouterSolicits", &Icmp6InRouterSolicits);
+ arl_expect(arl_base, "Icmp6InRouterAdvertisements", &Icmp6InRouterAdvertisements);
+ arl_expect(arl_base, "Icmp6InNeighborSolicits", &Icmp6InNeighborSolicits);
+ arl_expect(arl_base, "Icmp6InNeighborAdvertisements", &Icmp6InNeighborAdvertisements);
+ arl_expect(arl_base, "Icmp6InRedirects", &Icmp6InRedirects);
+ arl_expect(arl_base, "Icmp6InMLDv2Reports", &Icmp6InMLDv2Reports);
+ arl_expect(arl_base, "Icmp6OutDestUnreachs", &Icmp6OutDestUnreachs);
+ arl_expect(arl_base, "Icmp6OutPktTooBigs", &Icmp6OutPktTooBigs);
+ arl_expect(arl_base, "Icmp6OutTimeExcds", &Icmp6OutTimeExcds);
+ arl_expect(arl_base, "Icmp6OutParmProblems", &Icmp6OutParmProblems);
+ arl_expect(arl_base, "Icmp6OutEchos", &Icmp6OutEchos);
+ arl_expect(arl_base, "Icmp6OutEchoReplies", &Icmp6OutEchoReplies);
+ arl_expect(arl_base, "Icmp6OutGroupMembQueries", &Icmp6OutGroupMembQueries);
+ arl_expect(arl_base, "Icmp6OutGroupMembResponses", &Icmp6OutGroupMembResponses);
+ arl_expect(arl_base, "Icmp6OutGroupMembReductions", &Icmp6OutGroupMembReductions);
+ arl_expect(arl_base, "Icmp6OutRouterSolicits", &Icmp6OutRouterSolicits);
+ arl_expect(arl_base, "Icmp6OutRouterAdvertisements", &Icmp6OutRouterAdvertisements);
+ arl_expect(arl_base, "Icmp6OutNeighborSolicits", &Icmp6OutNeighborSolicits);
+ arl_expect(arl_base, "Icmp6OutNeighborAdvertisements", &Icmp6OutNeighborAdvertisements);
+ arl_expect(arl_base, "Icmp6OutRedirects", &Icmp6OutRedirects);
+ arl_expect(arl_base, "Icmp6OutMLDv2Reports", &Icmp6OutMLDv2Reports);
+ arl_expect(arl_base, "Icmp6InType1", &Icmp6InType1);
+ arl_expect(arl_base, "Icmp6InType128", &Icmp6InType128);
+ arl_expect(arl_base, "Icmp6InType129", &Icmp6InType129);
+ arl_expect(arl_base, "Icmp6InType136", &Icmp6InType136);
+ arl_expect(arl_base, "Icmp6OutType1", &Icmp6OutType1);
+ arl_expect(arl_base, "Icmp6OutType128", &Icmp6OutType128);
+ arl_expect(arl_base, "Icmp6OutType129", &Icmp6OutType129);
+ arl_expect(arl_base, "Icmp6OutType133", &Icmp6OutType133);
+ arl_expect(arl_base, "Icmp6OutType135", &Icmp6OutType135);
+ arl_expect(arl_base, "Icmp6OutType143", &Icmp6OutType143);
+ arl_expect(arl_base, "Udp6InDatagrams", &Udp6InDatagrams);
+ arl_expect(arl_base, "Udp6NoPorts", &Udp6NoPorts);
+ arl_expect(arl_base, "Udp6InErrors", &Udp6InErrors);
+ arl_expect(arl_base, "Udp6OutDatagrams", &Udp6OutDatagrams);
+ arl_expect(arl_base, "Udp6RcvbufErrors", &Udp6RcvbufErrors);
+ arl_expect(arl_base, "Udp6SndbufErrors", &Udp6SndbufErrors);
+ arl_expect(arl_base, "Udp6InCsumErrors", &Udp6InCsumErrors);
+ arl_expect(arl_base, "Udp6IgnoredMulti", &Udp6IgnoredMulti);
+ arl_expect(arl_base, "UdpLite6InDatagrams", &UdpLite6InDatagrams);
+ arl_expect(arl_base, "UdpLite6NoPorts", &UdpLite6NoPorts);
+ arl_expect(arl_base, "UdpLite6InErrors", &UdpLite6InErrors);
+ arl_expect(arl_base, "UdpLite6OutDatagrams", &UdpLite6OutDatagrams);
+ arl_expect(arl_base, "UdpLite6RcvbufErrors", &UdpLite6RcvbufErrors);
+ arl_expect(arl_base, "UdpLite6SndbufErrors", &UdpLite6SndbufErrors);
+ arl_expect(arl_base, "UdpLite6InCsumErrors", &UdpLite6InCsumErrors);
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/snmp6");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/snmp6", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff))
+ return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ arl_begin(arl_base);
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) {
+ if(unlikely(words)) error("Cannot read /proc/net/snmp6 line %zu. Expected 2 params, read %zu.", l, words);
+ continue;
+ }
+
+ if(unlikely(arl_check(arl_base,
+ procfile_lineword(ff, l, 0),
+ procfile_lineword(ff, l, 1)))) break;
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_bandwidth == CONFIG_BOOLEAN_YES || (do_bandwidth == CONFIG_BOOLEAN_AUTO &&
+ (Ip6InOctets ||
+ Ip6OutOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_bandwidth = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "system"
+ , "ipv6"
+ , NULL
+ , "network"
+ , NULL
+ , "IPv6 Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_IPV6
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_received = rrddim_add(st, "InOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_received, Ip6InOctets);
+ rrddim_set_by_pointer(st, rd_sent, Ip6OutOctets);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_packets == CONFIG_BOOLEAN_YES || (do_ip_packets == CONFIG_BOOLEAN_AUTO &&
+ (Ip6InReceives ||
+ Ip6OutRequests ||
+ Ip6InDelivers ||
+ Ip6OutForwDatagrams ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_packets = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL,
+ *rd_forwarded = NULL,
+ *rd_delivers = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "packets"
+ , NULL
+ , "packets"
+ , NULL
+ , "IPv6 Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_received = rrddim_add(st, "InReceives", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutRequests", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_forwarded = rrddim_add(st, "OutForwDatagrams", "forwarded", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_delivers = rrddim_add(st, "InDelivers", "delivers", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_received, Ip6InReceives);
+ rrddim_set_by_pointer(st, rd_sent, Ip6OutRequests);
+ rrddim_set_by_pointer(st, rd_forwarded, Ip6OutForwDatagrams);
+ rrddim_set_by_pointer(st, rd_delivers, Ip6InDelivers);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_fragsout == CONFIG_BOOLEAN_YES || (do_ip_fragsout == CONFIG_BOOLEAN_AUTO &&
+ (Ip6FragOKs ||
+ Ip6FragFails ||
+ Ip6FragCreates ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_fragsout = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_ok = NULL,
+ *rd_failed = NULL,
+ *rd_all = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "fragsout"
+ , NULL
+ , "fragments6"
+ , NULL
+ , "IPv6 Fragments Sent"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_FRAGSOUT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_ok = rrddim_add(st, "FragOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_failed = rrddim_add(st, "FragFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_all = rrddim_add(st, "FragCreates", "all", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_ok, Ip6FragOKs);
+ rrddim_set_by_pointer(st, rd_failed, Ip6FragFails);
+ rrddim_set_by_pointer(st, rd_all, Ip6FragCreates);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_fragsin == CONFIG_BOOLEAN_YES || (do_ip_fragsin == CONFIG_BOOLEAN_AUTO &&
+ (Ip6ReasmOKs ||
+ Ip6ReasmFails ||
+ Ip6ReasmTimeout ||
+ Ip6ReasmReqds ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_fragsin = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_ok = NULL,
+ *rd_failed = NULL,
+ *rd_timeout = NULL,
+ *rd_all = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "fragsin"
+ , NULL
+ , "fragments6"
+ , NULL
+ , "IPv6 Fragments Reassembly"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_FRAGSIN
+ , update_every
+ , RRDSET_TYPE_LINE);
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_ok = rrddim_add(st, "ReasmOKs", "ok", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_failed = rrddim_add(st, "ReasmFails", "failed", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_timeout = rrddim_add(st, "ReasmTimeout", "timeout", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_all = rrddim_add(st, "ReasmReqds", "all", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_ok, Ip6ReasmOKs);
+ rrddim_set_by_pointer(st, rd_failed, Ip6ReasmFails);
+ rrddim_set_by_pointer(st, rd_timeout, Ip6ReasmTimeout);
+ rrddim_set_by_pointer(st, rd_all, Ip6ReasmReqds);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ip_errors == CONFIG_BOOLEAN_YES || (do_ip_errors == CONFIG_BOOLEAN_AUTO &&
+ (Ip6InDiscards ||
+ Ip6OutDiscards ||
+ Ip6InHdrErrors ||
+ Ip6InAddrErrors ||
+ Ip6InUnknownProtos ||
+ Ip6InTooBigErrors ||
+ Ip6InTruncatedPkts ||
+ Ip6InNoRoutes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ip_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InDiscards = NULL,
+ *rd_OutDiscards = NULL,
+ *rd_InHdrErrors = NULL,
+ *rd_InAddrErrors = NULL,
+ *rd_InUnknownProtos = NULL,
+ *rd_InTooBigErrors = NULL,
+ *rd_InTruncatedPkts = NULL,
+ *rd_InNoRoutes = NULL,
+ *rd_OutNoRoutes = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "errors"
+ , NULL
+ , "errors"
+ , NULL
+ , "IPv6 Errors"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_InDiscards = rrddim_add(st, "InDiscards", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDiscards = rrddim_add(st, "OutDiscards", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InHdrErrors = rrddim_add(st, "InHdrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InAddrErrors = rrddim_add(st, "InAddrErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InUnknownProtos = rrddim_add(st, "InUnknownProtos", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTooBigErrors = rrddim_add(st, "InTooBigErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTruncatedPkts = rrddim_add(st, "InTruncatedPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InNoRoutes = rrddim_add(st, "InNoRoutes", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutNoRoutes = rrddim_add(st, "OutNoRoutes", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InDiscards, Ip6InDiscards);
+ rrddim_set_by_pointer(st, rd_OutDiscards, Ip6OutDiscards);
+ rrddim_set_by_pointer(st, rd_InHdrErrors, Ip6InHdrErrors);
+ rrddim_set_by_pointer(st, rd_InAddrErrors, Ip6InAddrErrors);
+ rrddim_set_by_pointer(st, rd_InUnknownProtos, Ip6InUnknownProtos);
+ rrddim_set_by_pointer(st, rd_InTooBigErrors, Ip6InTooBigErrors);
+ rrddim_set_by_pointer(st, rd_InTruncatedPkts, Ip6InTruncatedPkts);
+ rrddim_set_by_pointer(st, rd_InNoRoutes, Ip6InNoRoutes);
+ rrddim_set_by_pointer(st, rd_OutNoRoutes, Ip6OutNoRoutes);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_udp_packets == CONFIG_BOOLEAN_YES || (do_udp_packets == CONFIG_BOOLEAN_AUTO &&
+ (Udp6InDatagrams ||
+ Udp6OutDatagrams ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udp_packets = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udppackets"
+ , NULL
+ , "udp6"
+ , NULL
+ , "IPv6 UDP Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDP_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_received = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_received, Udp6InDatagrams);
+ rrddim_set_by_pointer(st, rd_sent, Udp6OutDatagrams);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_udp_errors == CONFIG_BOOLEAN_YES || (do_udp_errors == CONFIG_BOOLEAN_AUTO &&
+ (Udp6InErrors ||
+ Udp6NoPorts ||
+ Udp6RcvbufErrors ||
+ Udp6SndbufErrors ||
+ Udp6InCsumErrors ||
+ Udp6IgnoredMulti ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udp_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_RcvbufErrors = NULL,
+ *rd_SndbufErrors = NULL,
+ *rd_InErrors = NULL,
+ *rd_NoPorts = NULL,
+ *rd_InCsumErrors = NULL,
+ *rd_IgnoredMulti = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udperrors"
+ , NULL
+ , "udp6"
+ , NULL
+ , "IPv6 UDP Errors"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDP_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_IgnoredMulti = rrddim_add(st, "IgnoredMulti", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_RcvbufErrors, Udp6RcvbufErrors);
+ rrddim_set_by_pointer(st, rd_SndbufErrors, Udp6SndbufErrors);
+ rrddim_set_by_pointer(st, rd_InErrors, Udp6InErrors);
+ rrddim_set_by_pointer(st, rd_NoPorts, Udp6NoPorts);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, Udp6InCsumErrors);
+ rrddim_set_by_pointer(st, rd_IgnoredMulti, Udp6IgnoredMulti);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_udplite_packets == CONFIG_BOOLEAN_YES || (do_udplite_packets == CONFIG_BOOLEAN_AUTO &&
+ (UdpLite6InDatagrams ||
+ UdpLite6OutDatagrams ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udplite_packets = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_received = NULL,
+ *rd_sent = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udplitepackets"
+ , NULL
+ , "udplite6"
+ , NULL
+ , "IPv6 UDPlite Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDPLITE_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_received = rrddim_add(st, "InDatagrams", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_sent = rrddim_add(st, "OutDatagrams", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_received, UdpLite6InDatagrams);
+ rrddim_set_by_pointer(st, rd_sent, UdpLite6OutDatagrams);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_udplite_errors == CONFIG_BOOLEAN_YES || (do_udplite_errors == CONFIG_BOOLEAN_AUTO &&
+ (UdpLite6InErrors ||
+ UdpLite6NoPorts ||
+ UdpLite6RcvbufErrors ||
+ UdpLite6SndbufErrors ||
+ Udp6InCsumErrors ||
+ UdpLite6InCsumErrors ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udplite_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_RcvbufErrors = NULL,
+ *rd_SndbufErrors = NULL,
+ *rd_InErrors = NULL,
+ *rd_NoPorts = NULL,
+ *rd_InCsumErrors = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "udpliteerrors"
+ , NULL
+ , "udplite6"
+ , NULL
+ , "IPv6 UDP Lite Errors"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDPLITE_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_RcvbufErrors = rrddim_add(st, "RcvbufErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_SndbufErrors = rrddim_add(st, "SndbufErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_NoPorts = rrddim_add(st, "NoPorts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InErrors, UdpLite6InErrors);
+ rrddim_set_by_pointer(st, rd_NoPorts, UdpLite6NoPorts);
+ rrddim_set_by_pointer(st, rd_RcvbufErrors, UdpLite6RcvbufErrors);
+ rrddim_set_by_pointer(st, rd_SndbufErrors, UdpLite6SndbufErrors);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, UdpLite6InCsumErrors);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_mcast == CONFIG_BOOLEAN_YES || (do_mcast == CONFIG_BOOLEAN_AUTO &&
+ (Ip6OutMcastOctets ||
+ Ip6InMcastOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_mcast = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Ip6InMcastOctets = NULL,
+ *rd_Ip6OutMcastOctets = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "mcast"
+ , NULL
+ , "multicast6"
+ , NULL
+ , "IPv6 Multicast Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_MCAST
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_Ip6InMcastOctets = rrddim_add(st, "InMcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_Ip6OutMcastOctets = rrddim_add(st, "OutMcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_Ip6InMcastOctets, Ip6InMcastOctets);
+ rrddim_set_by_pointer(st, rd_Ip6OutMcastOctets, Ip6OutMcastOctets);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_bcast == CONFIG_BOOLEAN_YES || (do_bcast == CONFIG_BOOLEAN_AUTO &&
+ (Ip6OutBcastOctets ||
+ Ip6InBcastOctets ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_bcast = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Ip6InBcastOctets = NULL,
+ *rd_Ip6OutBcastOctets = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "bcast"
+ , NULL
+ , "broadcast6"
+ , NULL
+ , "IPv6 Broadcast Bandwidth"
+ , "kilobits/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_BCAST
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_Ip6InBcastOctets = rrddim_add(st, "InBcastOctets", "received", 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ rd_Ip6OutBcastOctets = rrddim_add(st, "OutBcastOctets", "sent", -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_Ip6InBcastOctets, Ip6InBcastOctets);
+ rrddim_set_by_pointer(st, rd_Ip6OutBcastOctets, Ip6OutBcastOctets);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_mcast_p == CONFIG_BOOLEAN_YES || (do_mcast_p == CONFIG_BOOLEAN_AUTO &&
+ (Ip6OutMcastPkts ||
+ Ip6InMcastPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_mcast_p = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Ip6InMcastPkts = NULL,
+ *rd_Ip6OutMcastPkts = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "mcastpkts"
+ , NULL
+ , "multicast6"
+ , NULL
+ , "IPv6 Multicast Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_MCAST_PACKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_Ip6InMcastPkts = rrddim_add(st, "InMcastPkts", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_Ip6OutMcastPkts = rrddim_add(st, "OutMcastPkts", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_Ip6InMcastPkts, Ip6InMcastPkts);
+ rrddim_set_by_pointer(st, rd_Ip6OutMcastPkts, Ip6OutMcastPkts);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp == CONFIG_BOOLEAN_YES || (do_icmp == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InMsgs ||
+ Icmp6OutMsgs ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Icmp6InMsgs = NULL,
+ *rd_Icmp6OutMsgs = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmp"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Messages"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_Icmp6InMsgs = rrddim_add(st, "InMsgs", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_Icmp6OutMsgs = rrddim_add(st, "OutMsgs", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_Icmp6InMsgs, Icmp6InMsgs);
+ rrddim_set_by_pointer(st, rd_Icmp6OutMsgs, Icmp6OutMsgs);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_redir == CONFIG_BOOLEAN_YES || (do_icmp_redir == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InRedirects ||
+ Icmp6OutRedirects ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_redir = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_Icmp6InRedirects = NULL,
+ *rd_Icmp6OutRedirects = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpredir"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Redirects"
+ , "redirects/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_REDIR
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_Icmp6InRedirects = rrddim_add(st, "InRedirects", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_Icmp6OutRedirects = rrddim_add(st, "OutRedirects", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_Icmp6InRedirects, Icmp6InRedirects);
+ rrddim_set_by_pointer(st, rd_Icmp6OutRedirects, Icmp6OutRedirects);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_errors == CONFIG_BOOLEAN_YES || (do_icmp_errors == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InErrors ||
+ Icmp6OutErrors ||
+ Icmp6InCsumErrors ||
+ Icmp6InDestUnreachs ||
+ Icmp6InPktTooBigs ||
+ Icmp6InTimeExcds ||
+ Icmp6InParmProblems ||
+ Icmp6OutDestUnreachs ||
+ Icmp6OutPktTooBigs ||
+ Icmp6OutTimeExcds ||
+ Icmp6OutParmProblems ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_errors = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InErrors = NULL,
+ *rd_OutErrors = NULL,
+ *rd_InCsumErrors = NULL,
+ *rd_InDestUnreachs = NULL,
+ *rd_InPktTooBigs = NULL,
+ *rd_InTimeExcds = NULL,
+ *rd_InParmProblems = NULL,
+ *rd_OutDestUnreachs = NULL,
+ *rd_OutPktTooBigs = NULL,
+ *rd_OutTimeExcds = NULL,
+ *rd_OutParmProblems = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmperrors"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Errors"
+ , "errors/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InErrors = rrddim_add(st, "InErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutErrors = rrddim_add(st, "OutErrors", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCsumErrors = rrddim_add(st, "InCsumErrors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InDestUnreachs = rrddim_add(st, "InDestUnreachs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InPktTooBigs = rrddim_add(st, "InPktTooBigs", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InTimeExcds = rrddim_add(st, "InTimeExcds", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InParmProblems = rrddim_add(st, "InParmProblems", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutDestUnreachs = rrddim_add(st, "OutDestUnreachs", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutPktTooBigs = rrddim_add(st, "OutPktTooBigs", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutTimeExcds = rrddim_add(st, "OutTimeExcds", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutParmProblems = rrddim_add(st, "OutParmProblems", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InErrors, Icmp6InErrors);
+ rrddim_set_by_pointer(st, rd_OutErrors, Icmp6OutErrors);
+ rrddim_set_by_pointer(st, rd_InCsumErrors, Icmp6InCsumErrors);
+ rrddim_set_by_pointer(st, rd_InDestUnreachs, Icmp6InDestUnreachs);
+ rrddim_set_by_pointer(st, rd_InPktTooBigs, Icmp6InPktTooBigs);
+ rrddim_set_by_pointer(st, rd_InTimeExcds, Icmp6InTimeExcds);
+ rrddim_set_by_pointer(st, rd_InParmProblems, Icmp6InParmProblems);
+ rrddim_set_by_pointer(st, rd_OutDestUnreachs, Icmp6OutDestUnreachs);
+ rrddim_set_by_pointer(st, rd_OutPktTooBigs, Icmp6OutPktTooBigs);
+ rrddim_set_by_pointer(st, rd_OutTimeExcds, Icmp6OutTimeExcds);
+ rrddim_set_by_pointer(st, rd_OutParmProblems, Icmp6OutParmProblems);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_echos == CONFIG_BOOLEAN_YES || (do_icmp_echos == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InEchos ||
+ Icmp6OutEchos ||
+ Icmp6InEchoReplies ||
+ Icmp6OutEchoReplies ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_echos = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InEchos = NULL,
+ *rd_OutEchos = NULL,
+ *rd_InEchoReplies = NULL,
+ *rd_OutEchoReplies = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpechos"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Echo"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_ECHOS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InEchos = rrddim_add(st, "InEchos", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutEchos = rrddim_add(st, "OutEchos", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InEchoReplies = rrddim_add(st, "InEchoReplies", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutEchoReplies = rrddim_add(st, "OutEchoReplies", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InEchos, Icmp6InEchos);
+ rrddim_set_by_pointer(st, rd_OutEchos, Icmp6OutEchos);
+ rrddim_set_by_pointer(st, rd_InEchoReplies, Icmp6InEchoReplies);
+ rrddim_set_by_pointer(st, rd_OutEchoReplies, Icmp6OutEchoReplies);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_groupmemb == CONFIG_BOOLEAN_YES || (do_icmp_groupmemb == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InGroupMembQueries ||
+ Icmp6OutGroupMembQueries ||
+ Icmp6InGroupMembResponses ||
+ Icmp6OutGroupMembResponses ||
+ Icmp6InGroupMembReductions ||
+ Icmp6OutGroupMembReductions ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_groupmemb = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InQueries = NULL,
+ *rd_OutQueries = NULL,
+ *rd_InResponses = NULL,
+ *rd_OutResponses = NULL,
+ *rd_InReductions = NULL,
+ *rd_OutReductions = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "groupmemb"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Group Membership"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_GROUPMEMB
+ , update_every
+ , RRDSET_TYPE_LINE);
+
+ rd_InQueries = rrddim_add(st, "InQueries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutQueries = rrddim_add(st, "OutQueries", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InResponses = rrddim_add(st, "InResponses", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutResponses = rrddim_add(st, "OutResponses", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InReductions = rrddim_add(st, "InReductions", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutReductions = rrddim_add(st, "OutReductions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InQueries, Icmp6InGroupMembQueries);
+ rrddim_set_by_pointer(st, rd_OutQueries, Icmp6OutGroupMembQueries);
+ rrddim_set_by_pointer(st, rd_InResponses, Icmp6InGroupMembResponses);
+ rrddim_set_by_pointer(st, rd_OutResponses, Icmp6OutGroupMembResponses);
+ rrddim_set_by_pointer(st, rd_InReductions, Icmp6InGroupMembReductions);
+ rrddim_set_by_pointer(st, rd_OutReductions, Icmp6OutGroupMembReductions);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_router == CONFIG_BOOLEAN_YES || (do_icmp_router == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InRouterSolicits ||
+ Icmp6OutRouterSolicits ||
+ Icmp6InRouterAdvertisements ||
+ Icmp6OutRouterAdvertisements ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_router = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InSolicits = NULL,
+ *rd_OutSolicits = NULL,
+ *rd_InAdvertisements = NULL,
+ *rd_OutAdvertisements = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmprouter"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 Router Messages"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_ROUTER
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InSolicits = rrddim_add(st, "InSolicits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutSolicits = rrddim_add(st, "OutSolicits", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InAdvertisements = rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutAdvertisements = rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InSolicits, Icmp6InRouterSolicits);
+ rrddim_set_by_pointer(st, rd_OutSolicits, Icmp6OutRouterSolicits);
+ rrddim_set_by_pointer(st, rd_InAdvertisements, Icmp6InRouterAdvertisements);
+ rrddim_set_by_pointer(st, rd_OutAdvertisements, Icmp6OutRouterAdvertisements);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_neighbor == CONFIG_BOOLEAN_YES || (do_icmp_neighbor == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InNeighborSolicits ||
+ Icmp6OutNeighborSolicits ||
+ Icmp6InNeighborAdvertisements ||
+ Icmp6OutNeighborAdvertisements ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_neighbor = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InSolicits = NULL,
+ *rd_OutSolicits = NULL,
+ *rd_InAdvertisements = NULL,
+ *rd_OutAdvertisements = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpneighbor"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 Neighbor Messages"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_NEIGHBOR
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InSolicits = rrddim_add(st, "InSolicits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutSolicits = rrddim_add(st, "OutSolicits", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InAdvertisements = rrddim_add(st, "InAdvertisements", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutAdvertisements = rrddim_add(st, "OutAdvertisements", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InSolicits, Icmp6InNeighborSolicits);
+ rrddim_set_by_pointer(st, rd_OutSolicits, Icmp6OutNeighborSolicits);
+ rrddim_set_by_pointer(st, rd_InAdvertisements, Icmp6InNeighborAdvertisements);
+ rrddim_set_by_pointer(st, rd_OutAdvertisements, Icmp6OutNeighborAdvertisements);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_mldv2 == CONFIG_BOOLEAN_YES || (do_icmp_mldv2 == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InMLDv2Reports ||
+ Icmp6OutMLDv2Reports ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_mldv2 = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InMLDv2Reports = NULL,
+ *rd_OutMLDv2Reports = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmpmldv2"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP MLDv2 Reports"
+ , "reports/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_LDV2
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InMLDv2Reports = rrddim_add(st, "InMLDv2Reports", "received", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutMLDv2Reports = rrddim_add(st, "OutMLDv2Reports", "sent", -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InMLDv2Reports, Icmp6InMLDv2Reports);
+ rrddim_set_by_pointer(st, rd_OutMLDv2Reports, Icmp6OutMLDv2Reports);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_icmp_types == CONFIG_BOOLEAN_YES || (do_icmp_types == CONFIG_BOOLEAN_AUTO &&
+ (Icmp6InType1 ||
+ Icmp6InType128 ||
+ Icmp6InType129 ||
+ Icmp6InType136 ||
+ Icmp6OutType1 ||
+ Icmp6OutType128 ||
+ Icmp6OutType129 ||
+ Icmp6OutType133 ||
+ Icmp6OutType135 ||
+ Icmp6OutType143 ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_icmp_types = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InType1 = NULL,
+ *rd_InType128 = NULL,
+ *rd_InType129 = NULL,
+ *rd_InType136 = NULL,
+ *rd_OutType1 = NULL,
+ *rd_OutType128 = NULL,
+ *rd_OutType129 = NULL,
+ *rd_OutType133 = NULL,
+ *rd_OutType135 = NULL,
+ *rd_OutType143 = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "icmptypes"
+ , NULL
+ , "icmp6"
+ , NULL
+ , "IPv6 ICMP Types"
+ , "messages/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ICMP_TYPES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InType1 = rrddim_add(st, "InType1", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InType128 = rrddim_add(st, "InType128", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InType129 = rrddim_add(st, "InType129", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InType136 = rrddim_add(st, "InType136", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType1 = rrddim_add(st, "OutType1", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType128 = rrddim_add(st, "OutType128", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType129 = rrddim_add(st, "OutType129", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType133 = rrddim_add(st, "OutType133", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType135 = rrddim_add(st, "OutType135", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_OutType143 = rrddim_add(st, "OutType143", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InType1, Icmp6InType1);
+ rrddim_set_by_pointer(st, rd_InType128, Icmp6InType128);
+ rrddim_set_by_pointer(st, rd_InType129, Icmp6InType129);
+ rrddim_set_by_pointer(st, rd_InType136, Icmp6InType136);
+ rrddim_set_by_pointer(st, rd_OutType1, Icmp6OutType1);
+ rrddim_set_by_pointer(st, rd_OutType128, Icmp6OutType128);
+ rrddim_set_by_pointer(st, rd_OutType129, Icmp6OutType129);
+ rrddim_set_by_pointer(st, rd_OutType133, Icmp6OutType133);
+ rrddim_set_by_pointer(st, rd_OutType135, Icmp6OutType135);
+ rrddim_set_by_pointer(st, rd_OutType143, Icmp6OutType143);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ect == CONFIG_BOOLEAN_YES || (do_ect == CONFIG_BOOLEAN_AUTO &&
+ (Ip6InNoECTPkts ||
+ Ip6InECT1Pkts ||
+ Ip6InECT0Pkts ||
+ Ip6InCEPkts ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ect = CONFIG_BOOLEAN_YES;
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_InNoECTPkts = NULL,
+ *rd_InECT1Pkts = NULL,
+ *rd_InECT0Pkts = NULL,
+ *rd_InCEPkts = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_SNMP6
+ , "ect"
+ , NULL
+ , "packets"
+ , NULL
+ , "IPv6 ECT Packets"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SNMP6_NAME
+ , NETDATA_CHART_PRIO_IPV6_ECT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_InNoECTPkts = rrddim_add(st, "InNoECTPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InECT1Pkts = rrddim_add(st, "InECT1Pkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InECT0Pkts = rrddim_add(st, "InECT0Pkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_InCEPkts = rrddim_add(st, "InCEPkts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_InNoECTPkts, Ip6InNoECTPkts);
+ rrddim_set_by_pointer(st, rd_InECT1Pkts, Ip6InECT1Pkts);
+ rrddim_set_by_pointer(st, rd_InECT0Pkts, Ip6InECT0Pkts);
+ rrddim_set_by_pointer(st, rd_InCEPkts, Ip6InCEPkts);
+ rrdset_done(st);
+ }
+
+ return 0;
+}
+
diff --git a/collectors/proc.plugin/proc_net_sockstat.c b/collectors/proc.plugin/proc_net_sockstat.c
new file mode 100644
index 0000000..994cbad
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_sockstat.c
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME "/proc/net/sockstat"
+
+static struct proc_net_sockstat {
+ kernel_uint_t sockets_used;
+
+ kernel_uint_t tcp_inuse;
+ kernel_uint_t tcp_orphan;
+ kernel_uint_t tcp_tw;
+ kernel_uint_t tcp_alloc;
+ kernel_uint_t tcp_mem;
+
+ kernel_uint_t udp_inuse;
+ kernel_uint_t udp_mem;
+
+ kernel_uint_t udplite_inuse;
+
+ kernel_uint_t raw_inuse;
+
+ kernel_uint_t frag_inuse;
+ kernel_uint_t frag_memory;
+} sockstat_root = { 0 };
+
+
+static int read_tcp_mem(void) {
+ static char *filename = NULL;
+ static RRDVAR *tcp_mem_low_threshold = NULL,
+ *tcp_mem_pressure_threshold = NULL,
+ *tcp_mem_high_threshold = NULL;
+
+ if(unlikely(!tcp_mem_low_threshold)) {
+ tcp_mem_low_threshold = rrdvar_custom_host_variable_create(localhost, "tcp_mem_low");
+ tcp_mem_pressure_threshold = rrdvar_custom_host_variable_create(localhost, "tcp_mem_pressure");
+ tcp_mem_high_threshold = rrdvar_custom_host_variable_create(localhost, "tcp_mem_high");
+ }
+
+ if(unlikely(!filename)) {
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%s/proc/sys/net/ipv4/tcp_mem", netdata_configured_host_prefix);
+ filename = strdupz(buffer);
+ }
+
+ char buffer[200 + 1], *start, *end;
+ if(read_file(filename, buffer, 200) != 0) return 1;
+ buffer[200] = '\0';
+
+ unsigned long long low = 0, pressure = 0, high = 0;
+
+ start = buffer;
+ low = strtoull(start, &end, 10);
+
+ start = end;
+ pressure = strtoull(start, &end, 10);
+
+ start = end;
+ high = strtoull(start, &end, 10);
+
+ // fprintf(stderr, "TCP MEM low = %llu, pressure = %llu, high = %llu\n", low, pressure, high);
+
+ rrdvar_custom_host_variable_set(localhost, tcp_mem_low_threshold, low * sysconf(_SC_PAGESIZE) / 1024.0);
+ rrdvar_custom_host_variable_set(localhost, tcp_mem_pressure_threshold, pressure * sysconf(_SC_PAGESIZE) / 1024.0);
+ rrdvar_custom_host_variable_set(localhost, tcp_mem_high_threshold, high * sysconf(_SC_PAGESIZE) / 1024.0);
+
+ return 0;
+}
+
+static kernel_uint_t read_tcp_max_orphans(void) {
+ static char *filename = NULL;
+ static RRDVAR *tcp_max_orphans_var = NULL;
+
+ if(unlikely(!filename)) {
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%s/proc/sys/net/ipv4/tcp_max_orphans", netdata_configured_host_prefix);
+ filename = strdupz(buffer);
+ }
+
+ unsigned long long tcp_max_orphans = 0;
+ if(read_single_number_file(filename, &tcp_max_orphans) == 0) {
+
+ if(unlikely(!tcp_max_orphans_var))
+ tcp_max_orphans_var = rrdvar_custom_host_variable_create(localhost, "tcp_max_orphans");
+
+ rrdvar_custom_host_variable_set(localhost, tcp_max_orphans_var, tcp_max_orphans);
+ return tcp_max_orphans;
+ }
+
+ return 0;
+}
+
+int do_proc_net_sockstat(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+
+ static uint32_t hash_sockets = 0,
+ hash_raw = 0,
+ hash_frag = 0,
+ hash_tcp = 0,
+ hash_udp = 0,
+ hash_udplite = 0;
+
+ static long long update_constants_every = 60, update_constants_count = 0;
+
+ static ARL_BASE *arl_sockets = NULL;
+ static ARL_BASE *arl_tcp = NULL;
+ static ARL_BASE *arl_udp = NULL;
+ static ARL_BASE *arl_udplite = NULL;
+ static ARL_BASE *arl_raw = NULL;
+ static ARL_BASE *arl_frag = NULL;
+
+ static int do_sockets = -1, do_tcp_sockets = -1, do_tcp_mem = -1, do_udp_sockets = -1, do_udp_mem = -1, do_udplite_sockets = -1, do_raw_sockets = -1, do_frag_sockets = -1, do_frag_mem = -1;
+
+ static char *keys[7] = { NULL };
+ static uint32_t hashes[7] = { 0 };
+ static ARL_BASE *bases[7] = { NULL };
+
+ if(unlikely(!arl_sockets)) {
+ do_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 sockets", CONFIG_BOOLEAN_AUTO);
+ do_tcp_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 TCP sockets", CONFIG_BOOLEAN_AUTO);
+ do_tcp_mem = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 TCP memory", CONFIG_BOOLEAN_AUTO);
+ do_udp_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 UDP sockets", CONFIG_BOOLEAN_AUTO);
+ do_udp_mem = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 UDP memory", CONFIG_BOOLEAN_AUTO);
+ do_udplite_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 UDPLITE sockets", CONFIG_BOOLEAN_AUTO);
+ do_raw_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 RAW sockets", CONFIG_BOOLEAN_AUTO);
+ do_frag_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 FRAG sockets", CONFIG_BOOLEAN_AUTO);
+ do_frag_mem = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat", "ipv4 FRAG memory", CONFIG_BOOLEAN_AUTO);
+
+ update_constants_every = config_get_number("plugin:proc:/proc/net/sockstat", "update constants every", update_constants_every);
+ update_constants_count = update_constants_every;
+
+ arl_sockets = arl_create("sockstat/sockets", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_sockets, "used", &sockstat_root.sockets_used);
+
+ arl_tcp = arl_create("sockstat/TCP", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_tcp, "inuse", &sockstat_root.tcp_inuse);
+ arl_expect(arl_tcp, "orphan", &sockstat_root.tcp_orphan);
+ arl_expect(arl_tcp, "tw", &sockstat_root.tcp_tw);
+ arl_expect(arl_tcp, "alloc", &sockstat_root.tcp_alloc);
+ arl_expect(arl_tcp, "mem", &sockstat_root.tcp_mem);
+
+ arl_udp = arl_create("sockstat/UDP", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_udp, "inuse", &sockstat_root.udp_inuse);
+ arl_expect(arl_udp, "mem", &sockstat_root.udp_mem);
+
+ arl_udplite = arl_create("sockstat/UDPLITE", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_udplite, "inuse", &sockstat_root.udplite_inuse);
+
+ arl_raw = arl_create("sockstat/RAW", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_raw, "inuse", &sockstat_root.raw_inuse);
+
+ arl_frag = arl_create("sockstat/FRAG", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_frag, "inuse", &sockstat_root.frag_inuse);
+ arl_expect(arl_frag, "memory", &sockstat_root.frag_memory);
+
+ hash_sockets = simple_hash("sockets");
+ hash_tcp = simple_hash("TCP");
+ hash_udp = simple_hash("UDP");
+ hash_udplite = simple_hash("UDPLITE");
+ hash_raw = simple_hash("RAW");
+ hash_frag = simple_hash("FRAG");
+
+ keys[0] = "sockets"; hashes[0] = hash_sockets; bases[0] = arl_sockets;
+ keys[1] = "TCP"; hashes[1] = hash_tcp; bases[1] = arl_tcp;
+ keys[2] = "UDP"; hashes[2] = hash_udp; bases[2] = arl_udp;
+ keys[3] = "UDPLITE"; hashes[3] = hash_udplite; bases[3] = arl_udplite;
+ keys[4] = "RAW"; hashes[4] = hash_raw; bases[4] = arl_raw;
+ keys[5] = "FRAG"; hashes[5] = hash_frag; bases[5] = arl_frag;
+ keys[6] = NULL; // terminator
+ }
+
+ update_constants_count += update_every;
+ if(unlikely(update_constants_count > update_constants_every)) {
+ read_tcp_max_orphans();
+ read_tcp_mem();
+ update_constants_count = 0;
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/sockstat");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/sockstat", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ char *key = procfile_lineword(ff, l, 0);
+ uint32_t hash = simple_hash(key);
+
+ int k;
+ for(k = 0; keys[k] ; k++) {
+ if(unlikely(hash == hashes[k] && strcmp(key, keys[k]) == 0)) {
+ // fprintf(stderr, "KEY: '%s', l=%zu, w=1, words=%zu\n", key, l, words);
+ ARL_BASE *arl = bases[k];
+ arl_begin(arl);
+ size_t w = 1;
+
+ while(w + 1 < words) {
+ char *name = procfile_lineword(ff, l, w); w++;
+ char *value = procfile_lineword(ff, l, w); w++;
+ // fprintf(stderr, " > NAME '%s', VALUE '%s', l=%zu, w=%zu, words=%zu\n", name, value, l, w, words);
+ if(unlikely(arl_check(arl, name, value) != 0))
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_sockets == CONFIG_BOOLEAN_YES || (do_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.sockets_used ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_used = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_sockets"
+ , NULL
+ , "sockets"
+ , NULL
+ , "IPv4 Sockets Used"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_SOCKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_used = rrddim_add(st, "used", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_used, (collected_number)sockstat_root.sockets_used);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_tcp_sockets == CONFIG_BOOLEAN_YES || (do_tcp_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.tcp_inuse ||
+ sockstat_root.tcp_orphan ||
+ sockstat_root.tcp_tw ||
+ sockstat_root.tcp_alloc ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL,
+ *rd_orphan = NULL,
+ *rd_timewait = NULL,
+ *rd_alloc = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_tcp_sockets"
+ , NULL
+ , "tcp"
+ , NULL
+ , "IPv4 TCP Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_TCP_SOCKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_alloc = rrddim_add(st, "alloc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_orphan = rrddim_add(st, "orphan", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_timewait = rrddim_add(st, "timewait", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat_root.tcp_inuse);
+ rrddim_set_by_pointer(st, rd_orphan, (collected_number)sockstat_root.tcp_orphan);
+ rrddim_set_by_pointer(st, rd_timewait, (collected_number)sockstat_root.tcp_tw);
+ rrddim_set_by_pointer(st, rd_alloc, (collected_number)sockstat_root.tcp_alloc);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_tcp_mem == CONFIG_BOOLEAN_YES || (do_tcp_mem == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.tcp_mem || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_mem = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_mem = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_tcp_mem"
+ , NULL
+ , "tcp"
+ , NULL
+ , "IPv4 TCP Sockets Memory"
+ , "KiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_TCP_MEM
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_mem = rrddim_add(st, "mem", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_mem, (collected_number)sockstat_root.tcp_mem);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_udp_sockets == CONFIG_BOOLEAN_YES || (do_udp_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.udp_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udp_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_udp_sockets"
+ , NULL
+ , "udp"
+ , NULL
+ , "IPv4 UDP Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_UDP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat_root.udp_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_udp_mem == CONFIG_BOOLEAN_YES || (do_udp_mem == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.udp_mem ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udp_mem = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_mem = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_udp_mem"
+ , NULL
+ , "udp"
+ , NULL
+ , "IPv4 UDP Sockets Memory"
+ , "KiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_UDP_MEM
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_mem = rrddim_add(st, "mem", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_mem, (collected_number)sockstat_root.udp_mem);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_udplite_sockets == CONFIG_BOOLEAN_YES || (do_udplite_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.udplite_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udplite_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_udplite_sockets"
+ , NULL
+ , "udplite"
+ , NULL
+ , "IPv4 UDPLITE Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_UDPLITE
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat_root.udplite_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_raw_sockets == CONFIG_BOOLEAN_YES || (do_raw_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.raw_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_raw_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_raw_sockets"
+ , NULL
+ , "raw"
+ , NULL
+ , "IPv4 RAW Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_RAW
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat_root.raw_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_frag_sockets == CONFIG_BOOLEAN_YES || (do_frag_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.frag_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_frag_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_frag_sockets"
+ , NULL
+ , "fragments"
+ , NULL
+ , "IPv4 FRAG Sockets"
+ , "fragments"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_FRAGMENTS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat_root.frag_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_frag_mem == CONFIG_BOOLEAN_YES || (do_frag_mem == CONFIG_BOOLEAN_AUTO &&
+ (sockstat_root.frag_memory ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_frag_mem = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_mem = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv4"
+ , "sockstat_frag_mem"
+ , NULL
+ , "fragments"
+ , NULL
+ , "IPv4 FRAG Sockets Memory"
+ , "KiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT_NAME
+ , NETDATA_CHART_PRIO_IPV4_FRAGMENTS_MEM
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_mem = rrddim_add(st, "mem", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_mem, (collected_number)sockstat_root.frag_memory);
+ rrdset_done(st);
+ }
+
+ return 0;
+}
+
diff --git a/collectors/proc.plugin/proc_net_sockstat6.c b/collectors/proc.plugin/proc_net_sockstat6.c
new file mode 100644
index 0000000..ce8c9e0
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_sockstat6.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_NET_SOCKSTAT6_NAME "/proc/net/sockstat6"
+
+static struct proc_net_sockstat6 {
+ kernel_uint_t tcp6_inuse;
+ kernel_uint_t udp6_inuse;
+ kernel_uint_t udplite6_inuse;
+ kernel_uint_t raw6_inuse;
+ kernel_uint_t frag6_inuse;
+} sockstat6_root = { 0 };
+
+int do_proc_net_sockstat6(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+
+ static uint32_t hash_raw = 0,
+ hash_frag = 0,
+ hash_tcp = 0,
+ hash_udp = 0,
+ hash_udplite = 0;
+
+ static ARL_BASE *arl_tcp = NULL;
+ static ARL_BASE *arl_udp = NULL;
+ static ARL_BASE *arl_udplite = NULL;
+ static ARL_BASE *arl_raw = NULL;
+ static ARL_BASE *arl_frag = NULL;
+
+ static int do_tcp_sockets = -1, do_udp_sockets = -1, do_udplite_sockets = -1, do_raw_sockets = -1, do_frag_sockets = -1;
+
+ static char *keys[6] = { NULL };
+ static uint32_t hashes[6] = { 0 };
+ static ARL_BASE *bases[6] = { NULL };
+
+ if(unlikely(!arl_tcp)) {
+ do_tcp_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat6", "ipv6 TCP sockets", CONFIG_BOOLEAN_AUTO);
+ do_udp_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat6", "ipv6 UDP sockets", CONFIG_BOOLEAN_AUTO);
+ do_udplite_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat6", "ipv6 UDPLITE sockets", CONFIG_BOOLEAN_AUTO);
+ do_raw_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat6", "ipv6 RAW sockets", CONFIG_BOOLEAN_AUTO);
+ do_frag_sockets = config_get_boolean_ondemand("plugin:proc:/proc/net/sockstat6", "ipv6 FRAG sockets", CONFIG_BOOLEAN_AUTO);
+
+ arl_tcp = arl_create("sockstat6/TCP6", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_tcp, "inuse", &sockstat6_root.tcp6_inuse);
+
+ arl_udp = arl_create("sockstat6/UDP6", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_udp, "inuse", &sockstat6_root.udp6_inuse);
+
+ arl_udplite = arl_create("sockstat6/UDPLITE6", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_udplite, "inuse", &sockstat6_root.udplite6_inuse);
+
+ arl_raw = arl_create("sockstat6/RAW6", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_raw, "inuse", &sockstat6_root.raw6_inuse);
+
+ arl_frag = arl_create("sockstat6/FRAG6", arl_callback_str2kernel_uint_t, 60);
+ arl_expect(arl_frag, "inuse", &sockstat6_root.frag6_inuse);
+
+ hash_tcp = simple_hash("TCP6");
+ hash_udp = simple_hash("UDP6");
+ hash_udplite = simple_hash("UDPLITE6");
+ hash_raw = simple_hash("RAW6");
+ hash_frag = simple_hash("FRAG6");
+
+ keys[0] = "TCP6"; hashes[0] = hash_tcp; bases[0] = arl_tcp;
+ keys[1] = "UDP6"; hashes[1] = hash_udp; bases[1] = arl_udp;
+ keys[2] = "UDPLITE6"; hashes[2] = hash_udplite; bases[2] = arl_udplite;
+ keys[3] = "RAW6"; hashes[3] = hash_raw; bases[3] = arl_raw;
+ keys[4] = "FRAG6"; hashes[4] = hash_frag; bases[4] = arl_frag;
+ keys[5] = NULL; // terminator
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/sockstat6");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/sockstat6", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ char *key = procfile_lineword(ff, l, 0);
+ uint32_t hash = simple_hash(key);
+
+ int k;
+ for(k = 0; keys[k] ; k++) {
+ if(unlikely(hash == hashes[k] && strcmp(key, keys[k]) == 0)) {
+ // fprintf(stderr, "KEY: '%s', l=%zu, w=1, words=%zu\n", key, l, words);
+ ARL_BASE *arl = bases[k];
+ arl_begin(arl);
+ size_t w = 1;
+
+ while(w + 1 < words) {
+ char *name = procfile_lineword(ff, l, w); w++;
+ char *value = procfile_lineword(ff, l, w); w++;
+ // fprintf(stderr, " > NAME '%s', VALUE '%s', l=%zu, w=%zu, words=%zu\n", name, value, l, w, words);
+ if(unlikely(arl_check(arl, name, value) != 0))
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_tcp_sockets == CONFIG_BOOLEAN_YES || (do_tcp_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat6_root.tcp6_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_tcp_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv6"
+ , "sockstat6_tcp_sockets"
+ , NULL
+ , "tcp6"
+ , NULL
+ , "IPv6 TCP Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT6_NAME
+ , NETDATA_CHART_PRIO_IPV6_TCP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat6_root.tcp6_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_udp_sockets == CONFIG_BOOLEAN_YES || (do_udp_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat6_root.udp6_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udp_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv6"
+ , "sockstat6_udp_sockets"
+ , NULL
+ , "udp6"
+ , NULL
+ , "IPv6 UDP Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT6_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDP
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat6_root.udp6_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_udplite_sockets == CONFIG_BOOLEAN_YES || (do_udplite_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat6_root.udplite6_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_udplite_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv6"
+ , "sockstat6_udplite_sockets"
+ , NULL
+ , "udplite6"
+ , NULL
+ , "IPv6 UDPLITE Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT6_NAME
+ , NETDATA_CHART_PRIO_IPV6_UDPLITE
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat6_root.udplite6_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_raw_sockets == CONFIG_BOOLEAN_YES || (do_raw_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat6_root.raw6_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_raw_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv6"
+ , "sockstat6_raw_sockets"
+ , NULL
+ , "raw6"
+ , NULL
+ , "IPv6 RAW Sockets"
+ , "sockets"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT6_NAME
+ , NETDATA_CHART_PRIO_IPV6_RAW
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat6_root.raw6_inuse);
+ rrdset_done(st);
+ }
+
+ // ------------------------------------------------------------------------
+
+ if(do_frag_sockets == CONFIG_BOOLEAN_YES || (do_frag_sockets == CONFIG_BOOLEAN_AUTO &&
+ (sockstat6_root.frag6_inuse ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_frag_sockets = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_inuse = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "ipv6"
+ , "sockstat6_frag_sockets"
+ , NULL
+ , "fragments6"
+ , NULL
+ , "IPv6 FRAG Sockets"
+ , "fragments"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOCKSTAT6_NAME
+ , NETDATA_CHART_PRIO_IPV6_FRAGMENTS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_inuse = rrddim_add(st, "inuse", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inuse, (collected_number)sockstat6_root.frag6_inuse);
+ rrdset_done(st);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_softnet_stat.c b/collectors/proc.plugin/proc_net_softnet_stat.c
new file mode 100644
index 0000000..a29cccc
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_softnet_stat.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_NET_SOFTNET_NAME "/proc/net/softnet_stat"
+
+static inline char *softnet_column_name(size_t column) {
+ switch(column) {
+ // https://github.com/torvalds/linux/blob/a7fd20d1c476af4563e66865213474a2f9f473a4/net/core/net-procfs.c#L161-L166
+ case 0: return "processed";
+ case 1: return "dropped";
+ case 2: return "squeezed";
+ case 9: return "received_rps";
+ case 10: return "flow_limit_count";
+ default: return NULL;
+ }
+}
+
+int do_proc_net_softnet_stat(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+ static int do_per_core = -1;
+ static size_t allocated_lines = 0, allocated_columns = 0;
+ static uint32_t *data = NULL;
+
+ if(unlikely(do_per_core == -1)) do_per_core = config_get_boolean("plugin:proc:/proc/net/softnet_stat", "softnet_stat per core", 1);
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/softnet_stat");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/softnet_stat", "filename to monitor", filename), " \t", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+ size_t words = procfile_linewords(ff, 0), w;
+
+ if(unlikely(!lines || !words)) {
+ error("Cannot read /proc/net/softnet_stat, %zu lines and %zu columns reported.", lines, words);
+ return 1;
+ }
+
+ if(unlikely(lines > 200)) lines = 200;
+ if(unlikely(words > 50)) words = 50;
+
+ if(unlikely(!data || lines > allocated_lines || words > allocated_columns)) {
+ freez(data);
+ allocated_lines = lines;
+ allocated_columns = words;
+ data = mallocz((allocated_lines + 1) * allocated_columns * sizeof(uint32_t));
+ }
+
+ // initialize to zero
+ memset(data, 0, (allocated_lines + 1) * allocated_columns * sizeof(uint32_t));
+
+ // parse the values
+ for(l = 0; l < lines ;l++) {
+ words = procfile_linewords(ff, l);
+ if(unlikely(!words)) continue;
+
+ if(unlikely(words > allocated_columns))
+ words = allocated_columns;
+
+ for(w = 0; w < words ; w++) {
+ if(unlikely(softnet_column_name(w))) {
+ uint32_t t = (uint32_t)strtoul(procfile_lineword(ff, l, w), NULL, 16);
+ data[w] += t;
+ data[((l + 1) * allocated_columns) + w] = t;
+ }
+ }
+ }
+
+ if(unlikely(data[(lines * allocated_columns)] == 0))
+ lines--;
+
+ RRDSET *st;
+
+ // --------------------------------------------------------------------
+
+ st = rrdset_find_active_bytype_localhost("system", "softnet_stat");
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "system"
+ , "softnet_stat"
+ , NULL
+ , "softnet_stat"
+ , "system.softnet_stat"
+ , "System softnet_stat"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOFTNET_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_SOFTNET_STAT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ for(w = 0; w < allocated_columns ;w++)
+ if(unlikely(softnet_column_name(w)))
+ rrddim_add(st, softnet_column_name(w), NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ for(w = 0; w < allocated_columns ;w++)
+ if(unlikely(softnet_column_name(w)))
+ rrddim_set(st, softnet_column_name(w), data[w]);
+
+ rrdset_done(st);
+
+ if(do_per_core) {
+ for(l = 0; l < lines ;l++) {
+ char id[50+1];
+ snprintfz(id, 50, "cpu%zu_softnet_stat", l);
+
+ st = rrdset_find_active_bytype_localhost("cpu", id);
+ if(unlikely(!st)) {
+ char title[100+1];
+ snprintfz(title, 100, "CPU%zu softnet_stat", l);
+
+ st = rrdset_create_localhost(
+ "cpu"
+ , id
+ , NULL
+ , "softnet_stat"
+ , "cpu.softnet_stat"
+ , title
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_NET_SOFTNET_NAME
+ , NETDATA_CHART_PRIO_SOFTNET_PER_CORE + l
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ for(w = 0; w < allocated_columns ;w++)
+ if(unlikely(softnet_column_name(w)))
+ rrddim_add(st, softnet_column_name(w), NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ for(w = 0; w < allocated_columns ;w++)
+ if(unlikely(softnet_column_name(w)))
+ rrddim_set(st, softnet_column_name(w), data[((l + 1) * allocated_columns) + w]);
+
+ rrdset_done(st);
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_stat_conntrack.c b/collectors/proc.plugin/proc_net_stat_conntrack.c
new file mode 100644
index 0000000..642e33f
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_stat_conntrack.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define RRD_TYPE_NET_STAT_NETFILTER "netfilter"
+#define RRD_TYPE_NET_STAT_CONNTRACK "conntrack"
+#define PLUGIN_PROC_MODULE_CONNTRACK_NAME "/proc/net/stat/nf_conntrack"
+
+int do_proc_net_stat_conntrack(int update_every, usec_t dt) {
+ static procfile *ff = NULL;
+ static int do_sockets = -1, do_new = -1, do_changes = -1, do_expect = -1, do_search = -1, do_errors = -1;
+ static usec_t get_max_every = 10 * USEC_PER_SEC, usec_since_last_max = 0;
+ static int read_full = 1;
+ static char *nf_conntrack_filename, *nf_conntrack_count_filename, *nf_conntrack_max_filename;
+ static RRDVAR *rrdvar_max = NULL;
+
+ unsigned long long aentries = 0, asearched = 0, afound = 0, anew = 0, ainvalid = 0, aignore = 0, adelete = 0, adelete_list = 0,
+ ainsert = 0, ainsert_failed = 0, adrop = 0, aearly_drop = 0, aicmp_error = 0, aexpect_new = 0, aexpect_create = 0, aexpect_delete = 0, asearch_restart = 0;
+
+ if(unlikely(do_sockets == -1)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/stat/nf_conntrack");
+ nf_conntrack_filename = config_get("plugin:proc:/proc/net/stat/nf_conntrack", "filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sys/net/netfilter/nf_conntrack_max");
+ nf_conntrack_max_filename = config_get("plugin:proc:/proc/sys/net/netfilter/nf_conntrack_max", "filename to monitor", filename);
+ usec_since_last_max = get_max_every = config_get_number("plugin:proc:/proc/sys/net/netfilter/nf_conntrack_max", "read every seconds", 10) * USEC_PER_SEC;
+
+ read_full = 1;
+ ff = procfile_open(nf_conntrack_filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if(!ff) read_full = 0;
+
+ do_new = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter new connections", read_full);
+ do_changes = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection changes", read_full);
+ do_expect = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection expectations", read_full);
+ do_search = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connection searches", read_full);
+ do_errors = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter errors", read_full);
+
+ do_sockets = 1;
+ if(!read_full) {
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sys/net/netfilter/nf_conntrack_count");
+ nf_conntrack_count_filename = config_get("plugin:proc:/proc/sys/net/netfilter/nf_conntrack_count", "filename to monitor", filename);
+
+ if(read_single_number_file(nf_conntrack_count_filename, &aentries))
+ do_sockets = 0;
+ }
+
+ do_sockets = config_get_boolean("plugin:proc:/proc/net/stat/nf_conntrack", "netfilter connections", do_sockets);
+
+ if(!do_sockets && !read_full)
+ return 1;
+
+ rrdvar_max = rrdvar_custom_host_variable_create(localhost, "netfilter_conntrack_max");
+ }
+
+ if(likely(read_full)) {
+ if(unlikely(!ff)) {
+ ff = procfile_open(nf_conntrack_filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ for(l = 1; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 17)) {
+ if(unlikely(words)) error("Cannot read /proc/net/stat/nf_conntrack line. Expected 17 params, read %zu.", words);
+ continue;
+ }
+
+ unsigned long long tentries = 0, tsearched = 0, tfound = 0, tnew = 0, tinvalid = 0, tignore = 0, tdelete = 0, tdelete_list = 0, tinsert = 0, tinsert_failed = 0, tdrop = 0, tearly_drop = 0, ticmp_error = 0, texpect_new = 0, texpect_create = 0, texpect_delete = 0, tsearch_restart = 0;
+
+ tentries = strtoull(procfile_lineword(ff, l, 0), NULL, 16);
+ tsearched = strtoull(procfile_lineword(ff, l, 1), NULL, 16);
+ tfound = strtoull(procfile_lineword(ff, l, 2), NULL, 16);
+ tnew = strtoull(procfile_lineword(ff, l, 3), NULL, 16);
+ tinvalid = strtoull(procfile_lineword(ff, l, 4), NULL, 16);
+ tignore = strtoull(procfile_lineword(ff, l, 5), NULL, 16);
+ tdelete = strtoull(procfile_lineword(ff, l, 6), NULL, 16);
+ tdelete_list = strtoull(procfile_lineword(ff, l, 7), NULL, 16);
+ tinsert = strtoull(procfile_lineword(ff, l, 8), NULL, 16);
+ tinsert_failed = strtoull(procfile_lineword(ff, l, 9), NULL, 16);
+ tdrop = strtoull(procfile_lineword(ff, l, 10), NULL, 16);
+ tearly_drop = strtoull(procfile_lineword(ff, l, 11), NULL, 16);
+ ticmp_error = strtoull(procfile_lineword(ff, l, 12), NULL, 16);
+ texpect_new = strtoull(procfile_lineword(ff, l, 13), NULL, 16);
+ texpect_create = strtoull(procfile_lineword(ff, l, 14), NULL, 16);
+ texpect_delete = strtoull(procfile_lineword(ff, l, 15), NULL, 16);
+ tsearch_restart = strtoull(procfile_lineword(ff, l, 16), NULL, 16);
+
+ if(unlikely(!aentries)) aentries = tentries;
+
+ // sum all the cpus together
+ asearched += tsearched; // conntrack.search
+ afound += tfound; // conntrack.search
+ anew += tnew; // conntrack.new
+ ainvalid += tinvalid; // conntrack.new
+ aignore += tignore; // conntrack.new
+ adelete += tdelete; // conntrack.changes
+ adelete_list += tdelete_list; // conntrack.changes
+ ainsert += tinsert; // conntrack.changes
+ ainsert_failed += tinsert_failed; // conntrack.errors
+ adrop += tdrop; // conntrack.errors
+ aearly_drop += tearly_drop; // conntrack.errors
+ aicmp_error += ticmp_error; // conntrack.errors
+ aexpect_new += texpect_new; // conntrack.expect
+ aexpect_create += texpect_create; // conntrack.expect
+ aexpect_delete += texpect_delete; // conntrack.expect
+ asearch_restart += tsearch_restart; // conntrack.search
+ }
+ }
+ else {
+ if(unlikely(read_single_number_file(nf_conntrack_count_filename, &aentries)))
+ return 0; // we return 0, so that we will retry to open it next time
+ }
+
+ usec_since_last_max += dt;
+ if(unlikely(rrdvar_max && usec_since_last_max >= get_max_every)) {
+ usec_since_last_max = 0;
+
+ unsigned long long max;
+ if(likely(!read_single_number_file(nf_conntrack_max_filename, &max)))
+ rrdvar_custom_host_variable_set(localhost, rrdvar_max, max);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_sockets) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_connections = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_sockets"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Connections"
+ , "active connections"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_CONNTRACK_NAME
+ , NETDATA_CHART_PRIO_NETFILTER_SOCKETS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_connections = rrddim_add(st, "connections", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_connections, aentries);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_new) {
+ static RRDSET *st = NULL;
+ static RRDDIM
+ *rd_new = NULL,
+ *rd_ignore = NULL,
+ *rd_invalid = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_new"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker New Connections"
+ , "connections/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_CONNTRACK_NAME
+ , NETDATA_CHART_PRIO_NETFILTER_NEW
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_new = rrddim_add(st, "new", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_ignore = rrddim_add(st, "ignore", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_invalid = rrddim_add(st, "invalid", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_new, anew);
+ rrddim_set_by_pointer(st, rd_ignore, aignore);
+ rrddim_set_by_pointer(st, rd_invalid, ainvalid);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_changes) {
+ static RRDSET *st = NULL;
+ static RRDDIM
+ *rd_inserted = NULL,
+ *rd_deleted = NULL,
+ *rd_delete_list = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_changes"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Changes"
+ , "changes/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_CONNTRACK_NAME
+ , NETDATA_CHART_PRIO_NETFILTER_CHANGES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_inserted = rrddim_add(st, "inserted", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_deleted = rrddim_add(st, "deleted", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_delete_list = rrddim_add(st, "delete_list", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_inserted, ainsert);
+ rrddim_set_by_pointer(st, rd_deleted, adelete);
+ rrddim_set_by_pointer(st, rd_delete_list, adelete_list);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_expect) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_created = NULL,
+ *rd_deleted = NULL,
+ *rd_new = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_expect"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Expectations"
+ , "expectations/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_CONNTRACK_NAME
+ , NETDATA_CHART_PRIO_NETFILTER_EXPECT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_created = rrddim_add(st, "created", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_deleted = rrddim_add(st, "deleted", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_new = rrddim_add(st, "new", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_created, aexpect_create);
+ rrddim_set_by_pointer(st, rd_deleted, aexpect_delete);
+ rrddim_set_by_pointer(st, rd_new, aexpect_new);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_search) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_searched = NULL,
+ *rd_restarted = NULL,
+ *rd_found = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_search"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Searches"
+ , "searches/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_CONNTRACK_NAME
+ , NETDATA_CHART_PRIO_NETFILTER_SEARCH
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_searched = rrddim_add(st, "searched", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_restarted = rrddim_add(st, "restarted", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_found = rrddim_add(st, "found", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_searched, asearched);
+ rrddim_set_by_pointer(st, rd_restarted, asearch_restart);
+ rrddim_set_by_pointer(st, rd_found, afound);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_errors) {
+ static RRDSET *st = NULL;
+ static RRDDIM *rd_icmp_error = NULL,
+ *rd_insert_failed = NULL,
+ *rd_drop = NULL,
+ *rd_early_drop = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_errors"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Errors"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_CONNTRACK_NAME
+ , NETDATA_CHART_PRIO_NETFILTER_ERRORS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
+
+ rd_icmp_error = rrddim_add(st, "icmp_error", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_insert_failed = rrddim_add(st, "insert_failed", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_drop = rrddim_add(st, "drop", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_early_drop = rrddim_add(st, "early_drop", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd_icmp_error, aicmp_error);
+ rrddim_set_by_pointer(st, rd_insert_failed, ainsert_failed);
+ rrddim_set_by_pointer(st, rd_drop, adrop);
+ rrddim_set_by_pointer(st, rd_early_drop, aearly_drop);
+ rrdset_done(st);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_stat_synproxy.c b/collectors/proc.plugin/proc_net_stat_synproxy.c
new file mode 100644
index 0000000..f5030f9
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_stat_synproxy.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_SYNPROXY_NAME "/proc/net/stat/synproxy"
+
+#define RRD_TYPE_NET_STAT_NETFILTER "netfilter"
+#define RRD_TYPE_NET_STAT_SYNPROXY "synproxy"
+
+int do_proc_net_stat_synproxy(int update_every, usec_t dt) {
+ (void)dt;
+
+ static int do_entries = -1, do_cookies = -1, do_syns = -1, do_reopened = -1;
+ static procfile *ff = NULL;
+
+ if(unlikely(do_entries == -1)) {
+ do_entries = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY entries", CONFIG_BOOLEAN_AUTO);
+ do_cookies = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY cookies", CONFIG_BOOLEAN_AUTO);
+ do_syns = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY SYN received", CONFIG_BOOLEAN_AUTO);
+ do_reopened = config_get_boolean_ondemand("plugin:proc:/proc/net/stat/synproxy", "SYNPROXY connections reopened", CONFIG_BOOLEAN_AUTO);
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/stat/synproxy");
+ ff = procfile_open(config_get("plugin:proc:/proc/net/stat/synproxy", "filename to monitor", filename), " \t,:|", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff))
+ return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ // make sure we have 3 lines
+ size_t lines = procfile_lines(ff), l;
+ if(unlikely(lines < 2)) {
+ error("/proc/net/stat/synproxy has %zu lines, expected no less than 2. Disabling it.", lines);
+ return 1;
+ }
+
+ unsigned long long entries = 0, syn_received = 0, cookie_invalid = 0, cookie_valid = 0, cookie_retrans = 0, conn_reopened = 0;
+
+ // synproxy gives its values per CPU
+ for(l = 1; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 6))
+ continue;
+
+ entries += strtoull(procfile_lineword(ff, l, 0), NULL, 16);
+ syn_received += strtoull(procfile_lineword(ff, l, 1), NULL, 16);
+ cookie_invalid += strtoull(procfile_lineword(ff, l, 2), NULL, 16);
+ cookie_valid += strtoull(procfile_lineword(ff, l, 3), NULL, 16);
+ cookie_retrans += strtoull(procfile_lineword(ff, l, 4), NULL, 16);
+ conn_reopened += strtoull(procfile_lineword(ff, l, 5), NULL, 16);
+ }
+
+ unsigned long long events = entries + syn_received + cookie_invalid + cookie_valid + cookie_retrans + conn_reopened;
+
+ // --------------------------------------------------------------------
+
+ if(do_entries == CONFIG_BOOLEAN_YES || (do_entries == CONFIG_BOOLEAN_AUTO &&
+ (events || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_entries = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_SYNPROXY "_entries"
+ , NULL
+ , RRD_TYPE_NET_STAT_SYNPROXY
+ , NULL
+ , "SYNPROXY Entries Used"
+ , "entries"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_SYNPROXY_NAME
+ , NETDATA_CHART_PRIO_SYNPROXY_ENTRIES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrddim_add(st, "entries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "entries", entries);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_syns == CONFIG_BOOLEAN_YES || (do_syns == CONFIG_BOOLEAN_AUTO &&
+ (events || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_syns = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_SYNPROXY "_syn_received"
+ , NULL
+ , RRD_TYPE_NET_STAT_SYNPROXY
+ , NULL
+ , "SYNPROXY SYN Packets received"
+ , "packets/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_SYNPROXY_NAME
+ , NETDATA_CHART_PRIO_SYNPROXY_SYN_RECEIVED
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrddim_add(st, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "received", syn_received);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_reopened == CONFIG_BOOLEAN_YES || (do_reopened == CONFIG_BOOLEAN_AUTO &&
+ (events || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_reopened = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_SYNPROXY "_conn_reopened"
+ , NULL
+ , RRD_TYPE_NET_STAT_SYNPROXY
+ , NULL
+ , "SYNPROXY Connections Reopened"
+ , "connections/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_SYNPROXY_NAME
+ , NETDATA_CHART_PRIO_SYNPROXY_CONN_OPEN
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrddim_add(st, "reopened", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "reopened", conn_reopened);
+ rrdset_done(st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_cookies == CONFIG_BOOLEAN_YES || (do_cookies == CONFIG_BOOLEAN_AUTO &&
+ (events || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_cookies = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st = NULL;
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_SYNPROXY "_cookies"
+ , NULL
+ , RRD_TYPE_NET_STAT_SYNPROXY
+ , NULL
+ , "SYNPROXY TCP Cookies"
+ , "cookies/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_SYNPROXY_NAME
+ , NETDATA_CHART_PRIO_SYNPROXY_COOKIES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrddim_add(st, "valid", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(st, "invalid", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(st, "retransmits", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st);
+
+ rrddim_set(st, "valid", cookie_valid);
+ rrddim_set(st, "invalid", cookie_invalid);
+ rrddim_set(st, "retransmits", cookie_retrans);
+ rrdset_done(st);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_net_wireless.c b/collectors/proc.plugin/proc_net_wireless.c
new file mode 100644
index 0000000..32a53c6
--- /dev/null
+++ b/collectors/proc.plugin/proc_net_wireless.c
@@ -0,0 +1,453 @@
+#include <stdbool.h>
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_NETWIRELESS_NAME "/proc/net/wireless"
+
+#define CONFIG_SECTION_PLUGIN_PROC_NETWIRELESS "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_NETWIRELESS_NAME
+
+
+static struct netwireless {
+ char *name;
+ uint32_t hash;
+
+ //flags
+ bool configured;
+ struct timeval updated;
+
+ int do_status;
+ int do_quality;
+ int do_discarded_packets;
+ int do_missed_beacon;
+
+ // Data collected
+ // status
+ kernel_uint_t status;
+
+ // Quality
+ calculated_number link;
+ calculated_number level;
+ calculated_number noise;
+
+ // Discarded packets
+ kernel_uint_t nwid;
+ kernel_uint_t crypt;
+ kernel_uint_t frag;
+ kernel_uint_t retry;
+ kernel_uint_t misc;
+
+ // missed beacon
+ kernel_uint_t missed_beacon;
+
+ const char *chart_id_net_status;
+ const char *chart_id_net_link;
+ const char *chart_id_net_level;
+ const char *chart_id_net_noise;
+ const char *chart_id_net_discarded_packets;
+ const char *chart_id_net_missed_beacon;
+
+ const char *chart_family;
+
+ // charts
+ // satus
+ RRDSET *st_status;
+
+ // Quality
+ RRDSET *st_link;
+ RRDSET *st_level;
+ RRDSET *st_noise;
+
+ // Discarded Packets
+ RRDSET *st_discarded_packets;
+ // Missed beacon
+ RRDSET *st_missed_beacon;
+
+ // Dimensions
+ // status
+ RRDDIM *rd_status;
+
+ // Quality
+ RRDDIM *rd_link;
+ RRDDIM *rd_level;
+ RRDDIM *rd_noise;
+
+ // Discarded packets
+ RRDDIM *rd_nwid;
+ RRDDIM *rd_crypt;
+ RRDDIM *rd_frag;
+ RRDDIM *rd_retry;
+ RRDDIM *rd_misc;
+
+ // missed beacon
+ RRDDIM *rd_missed_beacon;
+
+ struct netwireless *next;
+} *netwireless_root = NULL;
+
+static void netwireless_free_st(struct netwireless *wireless_dev)
+{
+ if (wireless_dev->st_status) rrdset_is_obsolete(wireless_dev->st_status);
+ if (wireless_dev->st_link) rrdset_is_obsolete(wireless_dev->st_link);
+ if (wireless_dev->st_level) rrdset_is_obsolete(wireless_dev->st_level);
+ if (wireless_dev->st_noise) rrdset_is_obsolete(wireless_dev->st_noise);
+ if (wireless_dev->st_discarded_packets) rrdset_is_obsolete(wireless_dev->st_discarded_packets);
+ if (wireless_dev->st_missed_beacon) rrdset_is_obsolete(wireless_dev->st_missed_beacon);
+
+ wireless_dev->st_status = NULL;
+ wireless_dev->st_link = NULL;
+ wireless_dev->st_level = NULL;
+ wireless_dev->st_noise = NULL;
+ wireless_dev->st_discarded_packets = NULL;
+ wireless_dev->st_missed_beacon = NULL;
+}
+
+static void netwireless_free(struct netwireless *wireless_dev)
+{
+ wireless_dev->next = NULL;
+ freez((void *)wireless_dev->name);
+ netwireless_free_st(wireless_dev);
+ freez((void *)wireless_dev->chart_id_net_status);
+ freez((void *)wireless_dev->chart_id_net_link);
+ freez((void *)wireless_dev->chart_id_net_level);
+ freez((void *)wireless_dev->chart_id_net_noise);
+ freez((void *)wireless_dev->chart_id_net_discarded_packets);
+ freez((void *)wireless_dev->chart_id_net_missed_beacon);
+
+ freez((void *)wireless_dev);
+}
+
+static void netwireless_cleanup(struct timeval *timestamp)
+{
+ struct netwireless *previous = NULL;
+ struct netwireless *current;
+ // search it, from begining to the end
+ for (current = netwireless_root; current;) {
+
+ if (timercmp(&current->updated, timestamp, <)) {
+ struct netwireless *to_free = current;
+ current = current->next;
+ netwireless_free(to_free);
+
+ if (previous) {
+ previous->next = current;
+ } else {
+ netwireless_root = current;
+ }
+ } else {
+ previous = current;
+ current = current->next;
+ }
+ }
+}
+
+// finds an existing interface or creates a new entry
+static struct netwireless *find_or_create_wireless(const char *name)
+{
+ struct netwireless *wireless;
+ uint32_t hash = simple_hash(name);
+
+ // search it, from begining to the end
+ for (wireless = netwireless_root ; wireless ; wireless = wireless->next) {
+ if (unlikely(hash == wireless->hash && !strcmp(name, wireless->name))) {
+ return wireless;
+ }
+ }
+
+ // create a new one
+ wireless = callocz(1, sizeof(struct netwireless));
+ wireless->name = strdupz(name);
+ wireless->hash = hash;
+
+ // link it to the end
+ if (netwireless_root) {
+ struct netwireless *last_node;
+ for (last_node = netwireless_root; last_node->next ; last_node = last_node->next);
+
+ last_node->next = wireless;
+ } else
+ netwireless_root = wireless;
+
+ return wireless;
+}
+
+static void configure_device(int do_status, int do_quality, int do_discarded_packets, int do_missed,
+ struct netwireless *wireless_dev) {
+ wireless_dev->do_status = do_status;
+ wireless_dev->do_quality = do_quality;
+ wireless_dev->do_discarded_packets = do_discarded_packets;
+ wireless_dev->do_missed_beacon = do_missed;
+ wireless_dev->configured = true;
+
+ char buffer[RRD_ID_LENGTH_MAX + 1];
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s_status", wireless_dev->name);
+ wireless_dev->chart_id_net_status = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s_link_quality", wireless_dev->name);
+ wireless_dev->chart_id_net_link = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s_signal_level", wireless_dev->name);
+ wireless_dev->chart_id_net_level = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s_noise_level", wireless_dev->name);
+ wireless_dev->chart_id_net_noise = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s_discarded_packets", wireless_dev->name);
+ wireless_dev->chart_id_net_discarded_packets = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s_missed_beacon", wireless_dev->name);
+ wireless_dev->chart_id_net_missed_beacon = strdupz(buffer);
+}
+
+int do_proc_net_wireless(int update_every, usec_t dt)
+{
+ UNUSED(dt);
+ static procfile *ff = NULL;
+ static int do_status, do_quality = -1, do_discarded_packets, do_beacon;
+ static char *proc_net_wireless_filename = NULL;
+
+ if (unlikely(do_quality == -1)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/net/wireless");
+
+ proc_net_wireless_filename = config_get(CONFIG_SECTION_PLUGIN_PROC_NETWIRELESS,"filename to monitor",
+ filename);
+
+ do_status = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETWIRELESS,
+ "status for all interfaces", CONFIG_BOOLEAN_AUTO);
+
+ do_quality = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETWIRELESS,
+ "quality for all interfaces", CONFIG_BOOLEAN_AUTO);
+
+ do_discarded_packets = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETWIRELESS,
+ "discarded packets for all interfaces",
+ CONFIG_BOOLEAN_AUTO);
+
+ do_beacon = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETWIRELESS,
+ "missed beacon for all interface", CONFIG_BOOLEAN_AUTO);
+ }
+
+ if (unlikely(!ff)) {
+ ff = procfile_open(proc_net_wireless_filename, " \t,|", PROCFILE_FLAG_DEFAULT);
+ if (unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if (unlikely(!ff)) return 1;
+
+ size_t lines = procfile_lines(ff);
+ struct timeval timestamp;
+ size_t l;
+ gettimeofday(&timestamp, NULL);
+ for (l = 2; l < lines; l++) {
+ if (unlikely(procfile_linewords(ff, l) < 11)) continue;
+
+ char *name = procfile_lineword(ff, l, 0);
+ size_t len = strlen(name);
+ if (name[len - 1] == ':') name[len - 1] = '\0';
+
+ struct netwireless *wireless_dev = find_or_create_wireless(name);
+
+ if (unlikely(!wireless_dev->configured)) {
+ configure_device(do_status, do_quality, do_discarded_packets, do_beacon, wireless_dev);
+ }
+
+ if (likely(do_status != CONFIG_BOOLEAN_NO)) {
+ wireless_dev->status = str2kernel_uint_t(procfile_lineword(ff, l, 1));
+
+ if (unlikely(!wireless_dev->st_status)) {
+ wireless_dev->st_status = rrdset_create_localhost("wireless",
+ wireless_dev->chart_id_net_status,
+ NULL,
+ wireless_dev->name,
+ "wireless.status",
+ "Internal status reported by interface.",
+ "status",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_NETWIRELESS_NAME,
+ NETDATA_CHART_PRIO_WIRELESS_IFACE,
+ update_every,
+ RRDSET_TYPE_LINE);
+ rrdset_flag_set(wireless_dev->st_status, RRDSET_FLAG_DETAIL);
+
+ wireless_dev->rd_status = rrddim_add(wireless_dev->st_status, "status", NULL, 1,
+ 1, RRD_ALGORITHM_ABSOLUTE);
+ } else {
+ rrdset_next(wireless_dev->st_status);
+ }
+
+ rrddim_set_by_pointer(wireless_dev->st_status, wireless_dev->rd_status,
+ (collected_number)wireless_dev->status);
+ rrdset_done(wireless_dev->st_status);
+ }
+
+ if (likely(do_quality != CONFIG_BOOLEAN_NO)) {
+ wireless_dev->link = str2ld(procfile_lineword(ff, l, 2), NULL);
+ wireless_dev->level = str2ld(procfile_lineword(ff, l, 3), NULL);
+ wireless_dev->noise = str2ld(procfile_lineword(ff, l, 4), NULL);
+
+ if (unlikely(!wireless_dev->st_link)) {
+ wireless_dev->st_link = rrdset_create_localhost("wireless",
+ wireless_dev->chart_id_net_link,
+ NULL,
+ wireless_dev->name,
+ "wireless.link_quality",
+ "Overall quality of the link. This is an aggregate value, and depends on the driver and hardware.",
+ "value",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_NETWIRELESS_NAME,
+ NETDATA_CHART_PRIO_WIRELESS_IFACE + 1,
+ update_every,
+ RRDSET_TYPE_LINE);
+ rrdset_flag_set(wireless_dev->st_link, RRDSET_FLAG_DETAIL);
+
+ wireless_dev->rd_link = rrddim_add(wireless_dev->st_link, "link_quality", NULL, 1, 1,
+ RRD_ALGORITHM_ABSOLUTE);
+ } else {
+ rrdset_next(wireless_dev->st_link);
+ }
+
+ if (unlikely(!wireless_dev->st_level)) {
+ wireless_dev->st_level = rrdset_create_localhost("wireless",
+ wireless_dev->chart_id_net_level,
+ NULL,
+ wireless_dev->name,
+ "wireless.signal_level",
+ "The signal level is the wireless signal power level received by the wireless client. The closer the value is to 0, the stronger the signal.",
+ "dBm",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_NETWIRELESS_NAME,
+ NETDATA_CHART_PRIO_WIRELESS_IFACE + 2,
+ update_every,
+ RRDSET_TYPE_LINE);
+ rrdset_flag_set(wireless_dev->st_level, RRDSET_FLAG_DETAIL);
+
+ wireless_dev->rd_level = rrddim_add(wireless_dev->st_level, "signal_level", NULL, 1, 1,
+ RRD_ALGORITHM_ABSOLUTE);
+ } else {
+ rrdset_next(wireless_dev->st_level);
+ }
+
+ if (unlikely(!wireless_dev->st_noise)) {
+ wireless_dev->st_noise = rrdset_create_localhost("wireless",
+ wireless_dev->chart_id_net_noise,
+ NULL,
+ wireless_dev->name,
+ "wireless.noise_level",
+ "The noise level indicates the amount of background noise in your environment. The closer the value to 0, the greater the noise level.",
+ "dBm",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_NETWIRELESS_NAME,
+ NETDATA_CHART_PRIO_WIRELESS_IFACE + 3,
+ update_every,
+ RRDSET_TYPE_LINE);
+ rrdset_flag_set(wireless_dev->st_noise, RRDSET_FLAG_DETAIL);
+
+ wireless_dev->rd_noise = rrddim_add(wireless_dev->st_noise, "noise_level", NULL, 1, 1,
+ RRD_ALGORITHM_ABSOLUTE);
+ } else {
+ rrdset_next(wireless_dev->st_noise);
+ }
+
+ rrddim_set_by_pointer(wireless_dev->st_link, wireless_dev->rd_link,
+ (collected_number)wireless_dev->link);
+ rrdset_done(wireless_dev->st_link);
+
+ rrddim_set_by_pointer(wireless_dev->st_level, wireless_dev->rd_level,
+ (collected_number)wireless_dev->level);
+ rrdset_done(wireless_dev->st_level);
+
+ rrddim_set_by_pointer(wireless_dev->st_noise, wireless_dev->rd_noise,
+ (collected_number)wireless_dev->noise);
+ rrdset_done(wireless_dev->st_noise);
+ }
+
+ if (likely(do_discarded_packets)) {
+ wireless_dev->nwid = str2kernel_uint_t(procfile_lineword(ff, l, 5));
+ wireless_dev->crypt = str2kernel_uint_t(procfile_lineword(ff, l, 6));
+ wireless_dev->frag = str2kernel_uint_t(procfile_lineword(ff, l, 7));
+ wireless_dev->retry = str2kernel_uint_t(procfile_lineword(ff, l, 8));
+ wireless_dev->misc = str2kernel_uint_t(procfile_lineword(ff, l, 9));
+
+ if (unlikely(!wireless_dev->st_discarded_packets)) {
+ wireless_dev->st_discarded_packets = rrdset_create_localhost("wireless",
+ wireless_dev->chart_id_net_discarded_packets,
+ NULL,
+ wireless_dev->name,
+ "wireless.discarded_packets",
+ "Packet discarded in the wireless adapter due to \"wireless\" specific problems.",
+ "packets/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_NETWIRELESS_NAME,
+ NETDATA_CHART_PRIO_WIRELESS_IFACE + 4,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_flag_set(wireless_dev->st_discarded_packets, RRDSET_FLAG_DETAIL);
+
+ wireless_dev->rd_nwid = rrddim_add(wireless_dev->st_discarded_packets, "nwid", NULL, 1,
+ 1, RRD_ALGORITHM_INCREMENTAL);
+ wireless_dev->rd_crypt = rrddim_add(wireless_dev->st_discarded_packets, "crypt", NULL, 1,
+ 1, RRD_ALGORITHM_INCREMENTAL);
+ wireless_dev->rd_frag = rrddim_add(wireless_dev->st_discarded_packets, "frag", NULL, 1,
+ 1, RRD_ALGORITHM_INCREMENTAL);
+ wireless_dev->rd_retry = rrddim_add(wireless_dev->st_discarded_packets, "retry", NULL, 1,
+ 1, RRD_ALGORITHM_INCREMENTAL);
+ wireless_dev->rd_misc = rrddim_add(wireless_dev->st_discarded_packets, "misc", NULL, 1,
+ 1, RRD_ALGORITHM_INCREMENTAL);
+ } else {
+ rrdset_next(wireless_dev->st_discarded_packets);
+ }
+
+ rrddim_set_by_pointer(wireless_dev->st_discarded_packets, wireless_dev->rd_nwid,
+ (collected_number)wireless_dev->nwid);
+
+ rrddim_set_by_pointer(wireless_dev->st_discarded_packets, wireless_dev->rd_crypt,
+ (collected_number)wireless_dev->crypt);
+
+ rrddim_set_by_pointer(wireless_dev->st_discarded_packets, wireless_dev->rd_frag,
+ (collected_number)wireless_dev->frag);
+
+ rrddim_set_by_pointer(wireless_dev->st_discarded_packets, wireless_dev->rd_retry,
+ (collected_number)wireless_dev->retry);
+
+ rrddim_set_by_pointer(wireless_dev->st_discarded_packets, wireless_dev->rd_misc,
+ (collected_number)wireless_dev->misc);
+
+ rrdset_done(wireless_dev->st_discarded_packets);
+ }
+
+ if (likely(do_beacon)) {
+ wireless_dev->missed_beacon = str2kernel_uint_t(procfile_lineword(ff, l, 10));
+
+ if (unlikely(!wireless_dev->st_missed_beacon)) {
+ wireless_dev->st_missed_beacon = rrdset_create_localhost("wireless",
+ wireless_dev->chart_id_net_missed_beacon,
+ NULL,
+ wireless_dev->name,
+ "wireless.missed_beacons",
+ "Number of missed beacons.",
+ "frames/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_NETWIRELESS_NAME,
+ NETDATA_CHART_PRIO_WIRELESS_IFACE + 5,
+ update_every,
+ RRDSET_TYPE_LINE);
+ rrdset_flag_set(wireless_dev->st_missed_beacon, RRDSET_FLAG_DETAIL);
+
+ wireless_dev->rd_missed_beacon = rrddim_add(wireless_dev->st_missed_beacon, "missed_beacons",
+ NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ } else {
+ rrdset_next(wireless_dev->st_missed_beacon);
+ }
+
+ rrddim_set_by_pointer(wireless_dev->st_missed_beacon, wireless_dev->rd_missed_beacon,
+ (collected_number)wireless_dev->missed_beacon);
+ rrdset_done(wireless_dev->st_missed_beacon);
+ }
+
+ wireless_dev->updated = timestamp;
+ }
+
+ netwireless_cleanup(&timestamp);
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_pagetypeinfo.c b/collectors/proc.plugin/proc_pagetypeinfo.c
new file mode 100644
index 0000000..6b6c6c4
--- /dev/null
+++ b/collectors/proc.plugin/proc_pagetypeinfo.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+// For ULONG_MAX
+#include <limits.h>
+
+#define PLUGIN_PROC_MODULE_PAGETYPEINFO_NAME "/proc/pagetypeinfo"
+#define CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_PAGETYPEINFO_NAME
+
+// Zone struct is pglist_data, in include/linux/mmzone.h
+// MAX_NR_ZONES is from __MAX_NR_ZONE, which is the last value of the enum.
+#define MAX_PAGETYPE_ORDER 11
+
+// Names are in mm/page_alloc.c :: migratetype_names. Max size = 10.
+#define MAX_ZONETYPE_NAME 16
+#define MAX_PAGETYPE_NAME 16
+
+// Defined in include/linux/mmzone.h as __MAX_NR_ZONE (last enum of zone_type)
+#define MAX_ZONETYPE 6
+// Defined in include/linux/mmzone.h as MIGRATE_TYPES (last enum of migratetype)
+#define MAX_PAGETYPE 7
+
+
+//
+// /proc/pagetypeinfo is declared in mm/vmstat.c :: init_mm_internals
+//
+
+// One line of /proc/pagetypeinfo
+struct pageline {
+ int node;
+ char *zone;
+ char *type;
+ int line;
+ uint64_t free_pages_size[MAX_PAGETYPE_ORDER];
+ RRDDIM *rd[MAX_PAGETYPE_ORDER];
+};
+
+// Sum of all orders
+struct systemorder {
+ uint64_t size;
+ RRDDIM *rd;
+};
+
+
+static inline uint64_t pageline_total_count(struct pageline *p) {
+ uint64_t sum = 0, o;
+ for (o=0; o<MAX_PAGETYPE_ORDER; o++)
+ sum += p->free_pages_size[o];
+ return sum;
+}
+
+// Check if a line of /proc/pagetypeinfo is valid to use
+// Free block lines starts by "Node" && 4th col is "type"
+#define pagetypeinfo_line_valid(ff, l) (strncmp(procfile_lineword(ff, l, 0), "Node", 4) == 0 && strncmp(procfile_lineword(ff, l, 4), "type", 4) == 0)
+
+// Dimension name from the order
+#define dim_name(s, o, pagesize) (snprintfz(s, 16,"%ldKB (%lu)", (1 << o) * pagesize / 1024, o))
+
+int do_proc_pagetypeinfo(int update_every, usec_t dt) {
+ (void)dt;
+
+ // Config
+ static int do_global, do_detail;
+ static SIMPLE_PATTERN *filter_types = NULL;
+
+ // Counters from parsing the file, that doesn't change after boot
+ static struct systemorder systemorders[MAX_PAGETYPE_ORDER] = {};
+ static struct pageline* pagelines = NULL;
+ static long pagesize = 0;
+ static size_t pageorders_cnt = 0, pagelines_cnt = 0, ff_lines = 0;
+
+ // Handle
+ static procfile *ff = NULL;
+ static char ff_path[FILENAME_MAX + 1];
+
+ // RRD Sets
+ static RRDSET *st_order = NULL;
+ static RRDSET **st_nodezonetype = NULL;
+
+ // Local temp variables
+ size_t l, o, p;
+ struct pageline *pgl = NULL;
+
+ // --------------------------------------------------------------------
+ // Startup: Init arch and open /proc/pagetypeinfo
+ if (unlikely(!pagesize)) {
+ pagesize = sysconf(_SC_PAGESIZE);
+ }
+
+ if(unlikely(!ff)) {
+ snprintfz(ff_path, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, PLUGIN_PROC_MODULE_PAGETYPEINFO_NAME);
+ ff = procfile_open(config_get(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "filename to monitor", ff_path), " \t:", PROCFILE_FLAG_DEFAULT);
+
+ if(unlikely(!ff)) {
+ strncpyz(ff_path, PLUGIN_PROC_MODULE_PAGETYPEINFO_NAME, FILENAME_MAX);
+ ff = procfile_open(PLUGIN_PROC_MODULE_PAGETYPEINFO_NAME, " \t,", PROCFILE_FLAG_DEFAULT);
+ }
+ }
+ if(unlikely(!ff))
+ return 1;
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ // --------------------------------------------------------------------
+ // Init: find how many Nodes, Zones and Types
+ if(unlikely(pagelines_cnt == 0)) {
+ size_t nodenumlast = -1;
+ char *zonenamelast = NULL;
+
+ ff_lines = procfile_lines(ff);
+ if(unlikely(!ff_lines)) {
+ error("PLUGIN: PROC_PAGETYPEINFO: Cannot read %s, zero lines reported.", ff_path);
+ return 1;
+ }
+
+ // Configuration
+ do_global = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "enable system summary", CONFIG_BOOLEAN_YES);
+ do_detail = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "enable detail per-type", CONFIG_BOOLEAN_AUTO);
+ filter_types = simple_pattern_create(
+ config_get(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "hide charts id matching", "")
+ , NULL
+ , SIMPLE_PATTERN_SUFFIX
+ );
+
+ pagelines_cnt = 0;
+
+ // Pass 1: how many lines would be valid
+ for (l = 4; l < ff_lines; l++) {
+ if (!pagetypeinfo_line_valid(ff, l))
+ continue;
+
+ pagelines_cnt++;
+ }
+ if (pagelines_cnt == 0) {
+ error("PLUGIN: PROC_PAGETYPEINFO: Unable to parse any valid line in %s", ff_path);
+ return 1;
+ }
+
+ // 4th line is the "Free pages count per migrate type at order". Just substract these 8 words.
+ pageorders_cnt = procfile_linewords(ff, 3);
+ if (pageorders_cnt < 9) {
+ error("PLUGIN: PROC_PAGETYPEINFO: Unable to parse Line 4 of %s", ff_path);
+ return 1;
+ }
+
+ pageorders_cnt -= 9;
+
+ if (pageorders_cnt > MAX_PAGETYPE_ORDER) {
+ error("PLUGIN: PROC_PAGETYPEINFO: pageorder found (%lu) is higher than max %d", pageorders_cnt, MAX_PAGETYPE_ORDER);
+ return 1;
+ }
+
+ // Init pagelines from scanned lines
+ if (!pagelines) {
+ pagelines = callocz(pagelines_cnt, sizeof(struct pageline));
+ if (!pagelines) {
+ error("PLUGIN: PROC_PAGETYPEINFO: Cannot allocate %lu pagelines of %lu B", pagelines_cnt, sizeof(struct pageline));
+ return 1;
+ }
+ }
+
+ // Pass 2: Scan the file again, with details
+ p = 0;
+ for (l=4; l < ff_lines; l++) {
+
+ if (!pagetypeinfo_line_valid(ff, l))
+ continue;
+
+ size_t nodenum = strtoul(procfile_lineword(ff, l, 1), NULL, 10);
+ char *zonename = procfile_lineword(ff, l, 3);
+ char *typename = procfile_lineword(ff, l, 5);
+
+ // We changed node or zone
+ if (nodenum != nodenumlast || !zonenamelast || strncmp(zonename, zonenamelast, 6) != 0) {
+ zonenamelast = zonename;
+ }
+
+ // Populate the line
+ pgl = &pagelines[p];
+
+ pgl->line = l;
+ pgl->node = nodenum;
+ pgl->type = typename;
+ pgl->zone = zonename;
+ for (o = 0; o < pageorders_cnt; o++)
+ pgl->free_pages_size[o] = str2uint64_t(procfile_lineword(ff, l, o+6)) * 1 << o;
+
+ p++;
+ }
+
+ // Init the RRD graphs
+
+ // Per-Order: sum of all node, zone, type Grouped by order
+ if (do_global != CONFIG_BOOLEAN_NO) {
+ st_order = rrdset_create_localhost(
+ "mem"
+ , "pagetype_global"
+ , NULL
+ , "pagetype"
+ , NULL
+ , "System orders available"
+ , "B"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_PAGETYPEINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_PAGEFRAG
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ for (o = 0; o < pageorders_cnt; o++) {
+ char id[3+1];
+ snprintfz(id, 3, "%lu", o);
+
+ char name[20+1];
+ dim_name(name, o, pagesize);
+
+ systemorders[o].rd = rrddim_add(st_order, id, name, pagesize, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ }
+
+
+ // Per-Numa Node & Zone & Type (full detail). Only if sum(line) > 0
+ st_nodezonetype = callocz(pagelines_cnt, sizeof(RRDSET *));
+ for (p = 0; p < pagelines_cnt; p++) {
+ pgl = &pagelines[p];
+
+ // Skip invalid, refused or empty pagelines if not explicitely requested
+ if (!pgl
+ || do_detail == CONFIG_BOOLEAN_NO
+ || (do_detail == CONFIG_BOOLEAN_AUTO && pageline_total_count(pgl) == 0 && netdata_zero_metrics_enabled != CONFIG_BOOLEAN_YES))
+ continue;
+
+ // "pagetype Node" + NUMA-NodeId + ZoneName + TypeName
+ char setid[13+1+2+1+MAX_ZONETYPE_NAME+1+MAX_PAGETYPE_NAME+1];
+ snprintfz(setid, 13+1+2+1+MAX_ZONETYPE_NAME+1+MAX_PAGETYPE_NAME, "pagetype_Node%d_%s_%s", pgl->node, pgl->zone, pgl->type);
+
+ // Skip explicitely refused charts
+ if (simple_pattern_matches(filter_types, setid))
+ continue;
+
+ // "Node" + NUMA-NodeID + ZoneName + TypeName
+ char setname[4+1+MAX_ZONETYPE_NAME+1+MAX_PAGETYPE_NAME +1];
+ snprintfz(setname, MAX_ZONETYPE_NAME + MAX_PAGETYPE_NAME, "Node %d %s %s",
+ pgl->node, pgl->zone, pgl->type);
+
+ st_nodezonetype[p] = rrdset_create_localhost(
+ "mem"
+ , setid
+ , NULL
+ , "pagetype"
+ , NULL
+ , setname
+ , "B"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_PAGETYPEINFO_NAME
+ , NETDATA_CHART_PRIO_MEM_PAGEFRAG + 1 + p
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ for (o = 0; o < pageorders_cnt; o++) {
+ char dimid[3+1];
+ snprintfz(dimid, 3, "%lu", o);
+ char dimname[20+1];
+ dim_name(dimname, o, pagesize);
+
+ pgl->rd[o] = rrddim_add(st_nodezonetype[p], dimid, dimname, pagesize, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------
+ // Update pagelines
+
+ // Process each line
+ p = 0;
+ for (l=4; l<ff_lines; l++) {
+
+ if (!pagetypeinfo_line_valid(ff, l))
+ continue;
+
+ size_t words = procfile_linewords(ff, l);
+
+ if (words != 7+pageorders_cnt) {
+ error("PLUGIN: PROC_PAGETYPEINFO: Unable to read line %lu, %lu words found instead of %lu", l+1, words, 7+pageorders_cnt);
+ break;
+ }
+
+ for (o = 0; o < pageorders_cnt; o++) {
+ // Reset counter
+ if (p == 0)
+ systemorders[o].size = 0;
+
+ // Update orders of the current line
+ pagelines[p].free_pages_size[o] = str2uint64_t(procfile_lineword(ff, l, o+6)) * 1 << o;
+
+ // Update sum by order
+ systemorders[o].size += pagelines[p].free_pages_size[o];
+ }
+
+ p++;
+ }
+
+ // --------------------------------------------------------------------
+ // update RRD values
+
+ // Global system per order
+ if (st_order) {
+ rrdset_next(st_order);
+ for (o = 0; o < pageorders_cnt; o++) {
+ rrddim_set_by_pointer(st_order, systemorders[o].rd, systemorders[o].size);
+ }
+ rrdset_done(st_order);
+ }
+
+ // Per Node-Zone-Type
+ if (do_detail) {
+ for (p = 0; p < pagelines_cnt; p++) {
+ // Skip empty graphs
+ if (!st_nodezonetype[p])
+ continue;
+
+ rrdset_next(st_nodezonetype[p]);
+ for (o = 0; o < pageorders_cnt; o++)
+ rrddim_set_by_pointer(st_nodezonetype[p], pagelines[p].rd[o], pagelines[p].free_pages_size[o]);
+
+ rrdset_done(st_nodezonetype[p]);
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_pressure.c b/collectors/proc.plugin/proc_pressure.c
new file mode 100644
index 0000000..4a40b4a
--- /dev/null
+++ b/collectors/proc.plugin/proc_pressure.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_PRESSURE_NAME "/proc/pressure"
+#define CONFIG_SECTION_PLUGIN_PROC_PRESSURE "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_PRESSURE_NAME
+
+// linux calculates this every 2 seconds, see kernel/sched/psi.c PSI_FREQ
+#define MIN_PRESSURE_UPDATE_EVERY 2
+
+
+static struct pressure resources[PRESSURE_NUM_RESOURCES] = {
+ {
+ .some = { .id = "cpu_pressure", .title = "CPU Pressure" },
+ },
+ {
+ .some = { .id = "memory_some_pressure", .title = "Memory Pressure" },
+ .full = { .id = "memory_full_pressure", .title = "Memory Full Pressure" },
+ },
+ {
+ .some = { .id = "io_some_pressure", .title = "I/O Pressure" },
+ .full = { .id = "io_full_pressure", .title = "I/O Full Pressure" },
+ },
+};
+
+static struct {
+ procfile *pf;
+ const char *name; // metric file name
+ const char *family; // webui section name
+ int section_priority;
+} resource_info[PRESSURE_NUM_RESOURCES] = {
+ { .name = "cpu", .family = "cpu", .section_priority = NETDATA_CHART_PRIO_SYSTEM_CPU },
+ { .name = "memory", .family = "ram", .section_priority = NETDATA_CHART_PRIO_SYSTEM_RAM },
+ { .name = "io", .family = "disk", .section_priority = NETDATA_CHART_PRIO_SYSTEM_IO },
+};
+
+void update_pressure_chart(struct pressure_chart *chart) {
+ rrddim_set_by_pointer(chart->st, chart->rd10, (collected_number)(chart->value10 * 100));
+ rrddim_set_by_pointer(chart->st, chart->rd60, (collected_number) (chart->value60 * 100));
+ rrddim_set_by_pointer(chart->st, chart->rd300, (collected_number) (chart->value300 * 100));
+
+ rrdset_done(chart->st);
+}
+
+int do_proc_pressure(int update_every, usec_t dt) {
+ int fail_count = 0;
+ int i;
+
+ static usec_t next_pressure_dt = 0;
+ static char *base_path = NULL;
+
+ update_every = (update_every < MIN_PRESSURE_UPDATE_EVERY) ? MIN_PRESSURE_UPDATE_EVERY : update_every;
+
+ if (next_pressure_dt <= dt) {
+ next_pressure_dt = update_every * USEC_PER_SEC;
+ } else {
+ next_pressure_dt -= dt;
+ return 0;
+ }
+
+ if (unlikely(!base_path)) {
+ base_path = config_get(CONFIG_SECTION_PLUGIN_PROC_PRESSURE, "base path of pressure metrics", "/proc/pressure");
+ }
+
+ for (i = 0; i < PRESSURE_NUM_RESOURCES; i++) {
+ procfile *ff = resource_info[i].pf;
+ int do_some = resources[i].some.enabled, do_full = resources[i].full.enabled;
+
+ if (unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ char config_key[CONFIG_MAX_NAME + 1];
+
+ snprintfz(filename
+ , FILENAME_MAX
+ , "%s%s/%s"
+ , netdata_configured_host_prefix
+ , base_path
+ , resource_info[i].name);
+
+ snprintfz(config_key, CONFIG_MAX_NAME, "enable %s some pressure", resource_info[i].name);
+ do_some = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_PRESSURE, config_key, CONFIG_BOOLEAN_YES);
+ resources[i].some.enabled = do_some;
+ if (resources[i].full.id) {
+ snprintfz(config_key, CONFIG_MAX_NAME, "enable %s full pressure", resource_info[i].name);
+ do_full = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_PRESSURE, config_key, CONFIG_BOOLEAN_YES);
+ resources[i].full.enabled = do_full;
+ }
+
+ ff = procfile_open(filename, " =", PROCFILE_FLAG_DEFAULT);
+ if (unlikely(!ff)) {
+ error("Cannot read pressure information from %s.", filename);
+ fail_count++;
+ continue;
+ }
+ }
+
+ ff = procfile_readall(ff);
+ resource_info[i].pf = ff;
+ if (unlikely(!ff)) {
+ fail_count++;
+ continue;
+ }
+
+ size_t lines = procfile_lines(ff);
+ if (unlikely(lines < 1)) {
+ error("%s has no lines.", procfile_filename(ff));
+ fail_count++;
+ continue;
+ }
+
+ struct pressure_chart *chart;
+ if (do_some) {
+ chart = &resources[i].some;
+ if (unlikely(!chart->st)) {
+ chart->st = rrdset_create_localhost(
+ "system"
+ , chart->id
+ , NULL
+ , resource_info[i].family
+ , NULL
+ , chart->title
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_PRESSURE_NAME
+ , resource_info[i].section_priority + 40
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ chart->rd10 = rrddim_add(chart->st, "some 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ chart->rd60 = rrddim_add(chart->st, "some 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ chart->rd300 = rrddim_add(chart->st, "some 300", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ } else {
+ rrdset_next(chart->st);
+ }
+
+ chart->value10 = strtod(procfile_lineword(ff, 0, 2), NULL);
+ chart->value60 = strtod(procfile_lineword(ff, 0, 4), NULL);
+ chart->value300 = strtod(procfile_lineword(ff, 0, 6), NULL);
+ update_pressure_chart(chart);
+ }
+
+ if (do_full && lines > 2) {
+ chart = &resources[i].full;
+ if (unlikely(!chart->st)) {
+ chart->st = rrdset_create_localhost(
+ "system"
+ , chart->id
+ , NULL
+ , resource_info[i].family
+ , NULL
+ , chart->title
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_PRESSURE_NAME
+ , resource_info[i].section_priority + 45
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ chart->rd10 = rrddim_add(chart->st, "full 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ chart->rd60 = rrddim_add(chart->st, "full 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ chart->rd300 = rrddim_add(chart->st, "full 300", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+ } else {
+ rrdset_next(chart->st);
+ }
+
+ chart->value10 = strtod(procfile_lineword(ff, 1, 2), NULL);
+ chart->value60 = strtod(procfile_lineword(ff, 1, 4), NULL);
+ chart->value300 = strtod(procfile_lineword(ff, 1, 6), NULL);
+ update_pressure_chart(chart);
+ }
+ }
+
+ if (PRESSURE_NUM_RESOURCES == fail_count) {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_pressure.h b/collectors/proc.plugin/proc_pressure.h
new file mode 100644
index 0000000..3330218
--- /dev/null
+++ b/collectors/proc.plugin/proc_pressure.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_PROC_PRESSURE_H
+#define NETDATA_PROC_PRESSURE_H
+
+#define PRESSURE_NUM_RESOURCES 3
+
+struct pressure {
+ int updated;
+ char *filename;
+
+ struct pressure_chart {
+ int enabled;
+
+ const char *id;
+ const char *title;
+
+ double value10;
+ double value60;
+ double value300;
+
+ RRDSET *st;
+ RRDDIM *rd10;
+ RRDDIM *rd60;
+ RRDDIM *rd300;
+ } some, full;
+};
+
+extern void update_pressure_chart(struct pressure_chart *chart);
+
+#endif //NETDATA_PROC_PRESSURE_H
diff --git a/collectors/proc.plugin/proc_self_mountinfo.c b/collectors/proc.plugin/proc_self_mountinfo.c
new file mode 100644
index 0000000..3f17ccc
--- /dev/null
+++ b/collectors/proc.plugin/proc_self_mountinfo.c
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+// ----------------------------------------------------------------------------
+// taken from gnulib/mountlist.c
+
+#ifndef ME_REMOTE
+/* A file system is "remote" if its Fs_name contains a ':'
+ or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')
+ or Fs_name is equal to "-hosts" (used by autofs to mount remote fs). */
+# define ME_REMOTE(Fs_name, Fs_type) \
+ (strchr (Fs_name, ':') != NULL \
+ || ((Fs_name)[0] == '/' \
+ && (Fs_name)[1] == '/' \
+ && (strcmp (Fs_type, "smbfs") == 0 \
+ || strcmp (Fs_type, "cifs") == 0)) \
+ || (strcmp("-hosts", Fs_name) == 0))
+#endif
+
+#define ME_DUMMY_0(Fs_name, Fs_type) \
+ (strcmp (Fs_type, "autofs") == 0 \
+ || strcmp (Fs_type, "proc") == 0 \
+ || strcmp (Fs_type, "subfs") == 0 \
+ /* for Linux 2.6/3.x */ \
+ || strcmp (Fs_type, "debugfs") == 0 \
+ || strcmp (Fs_type, "devpts") == 0 \
+ || strcmp (Fs_type, "fusectl") == 0 \
+ || strcmp (Fs_type, "mqueue") == 0 \
+ || strcmp (Fs_type, "rpc_pipefs") == 0 \
+ || strcmp (Fs_type, "sysfs") == 0 \
+ /* FreeBSD, Linux 2.4 */ \
+ || strcmp (Fs_type, "devfs") == 0 \
+ /* for NetBSD 3.0 */ \
+ || strcmp (Fs_type, "kernfs") == 0 \
+ /* for Irix 6.5 */ \
+ || strcmp (Fs_type, "ignore") == 0)
+
+/* Historically, we have marked as "dummy" any file system of type "none",
+ but now that programs like du need to know about bind-mounted directories,
+ we grant an exception to any with "bind" in its list of mount options.
+ I.e., those are *not* dummy entries. */
+# define ME_DUMMY(Fs_name, Fs_type) \
+ (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0)
+
+// ----------------------------------------------------------------------------
+
+// find the mount info with the given major:minor
+// in the supplied linked list of mountinfo structures
+struct mountinfo *mountinfo_find(struct mountinfo *root, unsigned long major, unsigned long minor) {
+ struct mountinfo *mi;
+
+ for(mi = root; mi ; mi = mi->next)
+ if(unlikely(mi->major == major && mi->minor == minor))
+ return mi;
+
+ return NULL;
+}
+
+// find the mount info with the given filesystem and mount_source
+// in the supplied linked list of mountinfo structures
+struct mountinfo *mountinfo_find_by_filesystem_mount_source(struct mountinfo *root, const char *filesystem, const char *mount_source) {
+ struct mountinfo *mi;
+ uint32_t filesystem_hash = simple_hash(filesystem), mount_source_hash = simple_hash(mount_source);
+
+ for(mi = root; mi ; mi = mi->next)
+ if(unlikely(mi->filesystem
+ && mi->mount_source
+ && mi->filesystem_hash == filesystem_hash
+ && mi->mount_source_hash == mount_source_hash
+ && !strcmp(mi->filesystem, filesystem)
+ && !strcmp(mi->mount_source, mount_source)))
+ return mi;
+
+ return NULL;
+}
+
+struct mountinfo *mountinfo_find_by_filesystem_super_option(struct mountinfo *root, const char *filesystem, const char *super_options) {
+ struct mountinfo *mi;
+ uint32_t filesystem_hash = simple_hash(filesystem);
+
+ size_t solen = strlen(super_options);
+
+ for(mi = root; mi ; mi = mi->next)
+ if(unlikely(mi->filesystem
+ && mi->super_options
+ && mi->filesystem_hash == filesystem_hash
+ && !strcmp(mi->filesystem, filesystem))) {
+
+ // super_options is a comma separated list
+ char *s = mi->super_options, *e;
+ while(*s) {
+ e = s + 1;
+ while(*e && *e != ',') e++;
+
+ size_t len = e - s;
+ if(unlikely(len == solen && !strncmp(s, super_options, len)))
+ return mi;
+
+ if(*e == ',') s = ++e;
+ else s = e;
+ }
+ }
+
+ return NULL;
+}
+
+static void mountinfo_free(struct mountinfo *mi) {
+ freez(mi->root);
+ freez(mi->mount_point);
+ freez(mi->mount_options);
+ freez(mi->persistent_id);
+/*
+ if(mi->optional_fields_count) {
+ int i;
+ for(i = 0; i < mi->optional_fields_count ; i++)
+ free(*mi->optional_fields[i]);
+ }
+ free(mi->optional_fields);
+*/
+ freez(mi->filesystem);
+ freez(mi->mount_source);
+ freez(mi->super_options);
+ freez(mi);
+}
+
+// free a linked list of mountinfo structures
+void mountinfo_free_all(struct mountinfo *mi) {
+ while(mi) {
+ struct mountinfo *t = mi;
+ mi = mi->next;
+
+ mountinfo_free(t);
+ }
+}
+
+static char *strdupz_decoding_octal(const char *string) {
+ char *buffer = strdupz(string);
+
+ char *d = buffer;
+ const char *s = string;
+
+ while(*s) {
+ if(unlikely(*s == '\\')) {
+ s++;
+ if(likely(isdigit(*s) && isdigit(s[1]) && isdigit(s[2]))) {
+ char c = *s++ - '0';
+ c <<= 3;
+ c |= *s++ - '0';
+ c <<= 3;
+ c |= *s++ - '0';
+ *d++ = c;
+ }
+ else *d++ = '_';
+ }
+ else *d++ = *s++;
+ }
+ *d = '\0';
+
+ return buffer;
+}
+
+static inline int is_read_only(const char *s) {
+ if(!s) return 0;
+
+ size_t len = strlen(s);
+ if(len < 2) return 0;
+ if(len == 2) {
+ if(!strcmp(s, "ro")) return 1;
+ return 0;
+ }
+ if(!strncmp(s, "ro,", 3)) return 1;
+ if(!strncmp(&s[len - 3], ",ro", 3)) return 1;
+ if(strstr(s, ",ro,")) return 1;
+ return 0;
+}
+
+// read the whole mountinfo into a linked list
+struct mountinfo *mountinfo_read(int do_statvfs) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/proc/self/mountinfo", netdata_configured_host_prefix);
+ procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) {
+ snprintfz(filename, FILENAME_MAX, "%s/proc/1/mountinfo", netdata_configured_host_prefix);
+ ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return NULL;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return NULL;
+
+ struct mountinfo *root = NULL, *last = NULL, *mi = NULL;
+
+ unsigned long l, lines = procfile_lines(ff);
+ for(l = 0; l < lines ;l++) {
+ if(unlikely(procfile_linewords(ff, l) < 5))
+ continue;
+
+ mi = mallocz(sizeof(struct mountinfo));
+
+ unsigned long w = 0;
+ mi->id = str2ul(procfile_lineword(ff, l, w)); w++;
+ mi->parentid = str2ul(procfile_lineword(ff, l, w)); w++;
+
+ char *major = procfile_lineword(ff, l, w), *minor; w++;
+ for(minor = major; *minor && *minor != ':' ;minor++) ;
+
+ if(unlikely(!*minor)) {
+ error("Cannot parse major:minor on '%s' at line %lu of '%s'", major, l + 1, filename);
+ freez(mi);
+ continue;
+ }
+
+ *minor = '\0';
+ minor++;
+
+ mi->flags = 0;
+
+ mi->major = str2ul(major);
+ mi->minor = str2ul(minor);
+
+ mi->root = strdupz(procfile_lineword(ff, l, w)); w++;
+ mi->root_hash = simple_hash(mi->root);
+
+ mi->mount_point = strdupz_decoding_octal(procfile_lineword(ff, l, w)); w++;
+ mi->mount_point_hash = simple_hash(mi->mount_point);
+
+ mi->persistent_id = strdupz(mi->mount_point);
+ netdata_fix_chart_id(mi->persistent_id);
+ mi->persistent_id_hash = simple_hash(mi->persistent_id);
+
+ mi->mount_options = strdupz(procfile_lineword(ff, l, w)); w++;
+
+ if(unlikely(is_read_only(mi->mount_options)))
+ mi->flags |= MOUNTINFO_READONLY;
+
+ // count the optional fields
+/*
+ unsigned long wo = w;
+*/
+ mi->optional_fields_count = 0;
+ char *s = procfile_lineword(ff, l, w);
+ while(*s && *s != '-') {
+ w++;
+ s = procfile_lineword(ff, l, w);
+ mi->optional_fields_count++;
+ }
+
+/*
+ if(unlikely(mi->optional_fields_count)) {
+ // we have some optional fields
+ // read them into a new array of pointers;
+
+ mi->optional_fields = mallocz(mi->optional_fields_count * sizeof(char *));
+
+ int i;
+ for(i = 0; i < mi->optional_fields_count ; i++) {
+ *mi->optional_fields[wo] = strdupz(procfile_lineword(ff, l, w));
+ wo++;
+ }
+ }
+ else
+ mi->optional_fields = NULL;
+*/
+
+ if(likely(*s == '-')) {
+ w++;
+
+ mi->filesystem = strdupz(procfile_lineword(ff, l, w)); w++;
+ mi->filesystem_hash = simple_hash(mi->filesystem);
+
+ mi->mount_source = strdupz_decoding_octal(procfile_lineword(ff, l, w)); w++;
+ mi->mount_source_hash = simple_hash(mi->mount_source);
+
+ mi->super_options = strdupz(procfile_lineword(ff, l, w)); w++;
+
+ if(unlikely(is_read_only(mi->super_options)))
+ mi->flags |= MOUNTINFO_READONLY;
+
+ if(unlikely(ME_DUMMY(mi->mount_source, mi->filesystem)))
+ mi->flags |= MOUNTINFO_IS_DUMMY;
+
+ if(unlikely(ME_REMOTE(mi->mount_source, mi->filesystem)))
+ mi->flags |= MOUNTINFO_IS_REMOTE;
+
+ // mark as BIND the duplicates (i.e. same filesystem + same source)
+ if(do_statvfs) {
+ struct stat buf;
+ if(unlikely(stat(mi->mount_point, &buf) == -1)) {
+ mi->st_dev = 0;
+ mi->flags |= MOUNTINFO_NO_STAT;
+ }
+ else {
+ mi->st_dev = buf.st_dev;
+
+ struct mountinfo *mt;
+ for(mt = root; mt; mt = mt->next) {
+ if(unlikely(mt->st_dev == mi->st_dev && !(mt->flags & MOUNTINFO_IS_SAME_DEV))) {
+ if(strlen(mi->mount_point) < strlen(mt->mount_point))
+ mt->flags |= MOUNTINFO_IS_SAME_DEV;
+ else
+ mi->flags |= MOUNTINFO_IS_SAME_DEV;
+ }
+ }
+ }
+ }
+ else {
+ mi->st_dev = 0;
+ }
+ }
+ else {
+ mi->filesystem = NULL;
+ mi->filesystem_hash = 0;
+
+ mi->mount_source = NULL;
+ mi->mount_source_hash = 0;
+
+ mi->super_options = NULL;
+
+ mi->st_dev = 0;
+ }
+
+ // check if it has size
+ if(do_statvfs && !(mi->flags & MOUNTINFO_IS_DUMMY)) {
+ struct statvfs buff_statvfs;
+ if(unlikely(statvfs(mi->mount_point, &buff_statvfs) < 0)) {
+ mi->flags |= MOUNTINFO_NO_STAT;
+ }
+ else if(unlikely(!buff_statvfs.f_blocks /* || !buff_statvfs.f_files */)) {
+ mi->flags |= MOUNTINFO_NO_SIZE;
+ }
+ }
+
+ // link it
+ if(unlikely(!root))
+ root = mi;
+ else
+ last->next = mi;
+
+ last = mi;
+ mi->next = NULL;
+
+/*
+#ifdef NETDATA_INTERNAL_CHECKS
+ fprintf(stderr, "MOUNTINFO: %ld %ld %lu:%lu root '%s', persistent id '%s', mount point '%s', mount options '%s', filesystem '%s', mount source '%s', super options '%s'%s%s%s%s%s%s\n",
+ mi->id,
+ mi->parentid,
+ mi->major,
+ mi->minor,
+ mi->root,
+ mi->persistent_id,
+ (mi->mount_point)?mi->mount_point:"",
+ (mi->mount_options)?mi->mount_options:"",
+ (mi->filesystem)?mi->filesystem:"",
+ (mi->mount_source)?mi->mount_source:"",
+ (mi->super_options)?mi->super_options:"",
+ (mi->flags & MOUNTINFO_IS_DUMMY)?" DUMMY":"",
+ (mi->flags & MOUNTINFO_IS_BIND)?" BIND":"",
+ (mi->flags & MOUNTINFO_IS_REMOTE)?" REMOTE":"",
+ (mi->flags & MOUNTINFO_NO_STAT)?" NOSTAT":"",
+ (mi->flags & MOUNTINFO_NO_SIZE)?" NOSIZE":"",
+ (mi->flags & MOUNTINFO_IS_SAME_DEV)?" SAMEDEV":""
+ );
+#endif
+*/
+ }
+
+/* find if the mount options have "bind" in them
+ {
+ FILE *fp = setmntent(MOUNTED, "r");
+ if (fp != NULL) {
+ struct mntent mntbuf;
+ struct mntent *mnt;
+ char buf[4096 + 1];
+
+ while ((mnt = getmntent_r(fp, &mntbuf, buf, 4096))) {
+ char *bind = hasmntopt(mnt, "bind");
+ if(unlikely(bind)) {
+ struct mountinfo *mi;
+ for(mi = root; mi ; mi = mi->next) {
+ if(unlikely(strcmp(mnt->mnt_dir, mi->mount_point) == 0)) {
+ fprintf(stderr, "Mount point '%s' is BIND\n", mi->mount_point);
+ mi->flags |= MOUNTINFO_IS_BIND;
+ break;
+ }
+ }
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ if(unlikely(!mi)) {
+ error("Mount point '%s' not found in /proc/self/mountinfo", mnt->mnt_dir);
+ }
+#endif
+ }
+ }
+ endmntent(fp);
+ }
+ }
+*/
+
+ procfile_close(ff);
+ return root;
+}
diff --git a/collectors/proc.plugin/proc_self_mountinfo.h b/collectors/proc.plugin/proc_self_mountinfo.h
new file mode 100644
index 0000000..15d63c7
--- /dev/null
+++ b/collectors/proc.plugin/proc_self_mountinfo.h
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_PROC_SELF_MOUNTINFO_H
+#define NETDATA_PROC_SELF_MOUNTINFO_H 1
+
+#define MOUNTINFO_IS_DUMMY 0x00000001
+#define MOUNTINFO_IS_REMOTE 0x00000002
+#define MOUNTINFO_IS_BIND 0x00000004
+#define MOUNTINFO_IS_SAME_DEV 0x00000008
+#define MOUNTINFO_NO_STAT 0x00000010
+#define MOUNTINFO_NO_SIZE 0x00000020
+#define MOUNTINFO_READONLY 0x00000040
+
+struct mountinfo {
+ long id; // mount ID: unique identifier of the mount (may be reused after umount(2)).
+ long parentid; // parent ID: ID of parent mount (or of self for the top of the mount tree).
+ unsigned long major; // major:minor: value of st_dev for files on filesystem (see stat(2)).
+ unsigned long minor;
+
+ char *persistent_id; // a calculated persistent id for the mount point
+ uint32_t persistent_id_hash;
+
+ char *root; // root: root of the mount within the filesystem.
+ uint32_t root_hash;
+
+ char *mount_point; // mount point: mount point relative to the process's root.
+ uint32_t mount_point_hash;
+
+ char *mount_options; // mount options: per-mount options.
+
+ int optional_fields_count;
+/*
+ char ***optional_fields; // optional fields: zero or more fields of the form "tag[:value]".
+*/
+ char *filesystem; // filesystem type: name of filesystem in the form "type[.subtype]".
+ uint32_t filesystem_hash;
+
+ char *mount_source; // mount source: filesystem-specific information or "none".
+ uint32_t mount_source_hash;
+
+ char *super_options; // super options: per-superblock options.
+
+ uint32_t flags;
+
+ dev_t st_dev; // id of device as given by stat()
+
+ struct mountinfo *next;
+};
+
+extern struct mountinfo *mountinfo_find(struct mountinfo *root, unsigned long major, unsigned long minor);
+extern struct mountinfo *mountinfo_find_by_filesystem_mount_source(struct mountinfo *root, const char *filesystem, const char *mount_source);
+extern struct mountinfo *mountinfo_find_by_filesystem_super_option(struct mountinfo *root, const char *filesystem, const char *super_options);
+
+extern void mountinfo_free_all(struct mountinfo *mi);
+extern struct mountinfo *mountinfo_read(int do_statvfs);
+
+#endif /* NETDATA_PROC_SELF_MOUNTINFO_H */ \ No newline at end of file
diff --git a/collectors/proc.plugin/proc_softirqs.c b/collectors/proc.plugin/proc_softirqs.c
new file mode 100644
index 0000000..d68c69b
--- /dev/null
+++ b/collectors/proc.plugin/proc_softirqs.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_SOFTIRQS_NAME "/proc/softirqs"
+
+#define MAX_INTERRUPT_NAME 50
+
+struct cpu_interrupt {
+ unsigned long long value;
+ RRDDIM *rd;
+};
+
+struct interrupt {
+ int used;
+ char *id;
+ char name[MAX_INTERRUPT_NAME + 1];
+ RRDDIM *rd;
+ unsigned long long total;
+ struct cpu_interrupt cpu[];
+};
+
+// since each interrupt is variable in size
+// we use this to calculate its record size
+#define recordsize(cpus) (sizeof(struct interrupt) + ((cpus) * sizeof(struct cpu_interrupt)))
+
+// given a base, get a pointer to each record
+#define irrindex(base, line, cpus) ((struct interrupt *)&((char *)(base))[(line) * recordsize(cpus)])
+
+static inline struct interrupt *get_interrupts_array(size_t lines, int cpus) {
+ static struct interrupt *irrs = NULL;
+ static size_t allocated = 0;
+
+ if(unlikely(lines != allocated)) {
+ uint32_t l;
+ int c;
+
+ irrs = (struct interrupt *)reallocz(irrs, lines * recordsize(cpus));
+
+ // reset all interrupt RRDDIM pointers as any line could have shifted
+ for(l = 0; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ irr->rd = NULL;
+ irr->name[0] = '\0';
+ for(c = 0; c < cpus ;c++)
+ irr->cpu[c].rd = NULL;
+ }
+
+ allocated = lines;
+ }
+
+ return irrs;
+}
+
+int do_proc_softirqs(int update_every, usec_t dt) {
+ (void)dt;
+ static procfile *ff = NULL;
+ static int cpus = -1, do_per_core = CONFIG_BOOLEAN_INVALID;
+ struct interrupt *irrs = NULL;
+
+ if(unlikely(do_per_core == CONFIG_BOOLEAN_INVALID))
+ do_per_core = config_get_boolean_ondemand("plugin:proc:/proc/softirqs", "interrupts per core", CONFIG_BOOLEAN_AUTO);
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/softirqs");
+ ff = procfile_open(config_get("plugin:proc:/proc/softirqs", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+ size_t words = procfile_linewords(ff, 0);
+
+ if(unlikely(!lines)) {
+ error("Cannot read /proc/softirqs, zero lines reported.");
+ return 1;
+ }
+
+ // find how many CPUs are there
+ if(unlikely(cpus == -1)) {
+ uint32_t w;
+ cpus = 0;
+ for(w = 0; w < words ; w++) {
+ if(likely(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0))
+ cpus++;
+ }
+ }
+
+ if(unlikely(!cpus)) {
+ error("PLUGIN: PROC_SOFTIRQS: Cannot find the number of CPUs in /proc/softirqs");
+ return 1;
+ }
+
+ // allocate the size we need;
+ irrs = get_interrupts_array(lines, cpus);
+ irrs[0].used = 0;
+
+ // loop through all lines
+ for(l = 1; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ irr->used = 0;
+ irr->total = 0;
+
+ words = procfile_linewords(ff, l);
+ if(unlikely(!words)) continue;
+
+ irr->id = procfile_lineword(ff, l, 0);
+ if(unlikely(!irr->id || !irr->id[0])) continue;
+
+ int c;
+ for(c = 0; c < cpus ;c++) {
+ if(likely((c + 1) < (int)words))
+ irr->cpu[c].value = str2ull(procfile_lineword(ff, l, (uint32_t)(c + 1)));
+ else
+ irr->cpu[c].value = 0;
+
+ irr->total += irr->cpu[c].value;
+ }
+
+ strncpyz(irr->name, irr->id, MAX_INTERRUPT_NAME);
+
+ irr->used = 1;
+ }
+
+ // --------------------------------------------------------------------
+
+ static RRDSET *st_system_softirqs = NULL;
+ if(unlikely(!st_system_softirqs))
+ st_system_softirqs = rrdset_create_localhost(
+ "system"
+ , "softirqs"
+ , NULL
+ , "softirqs"
+ , NULL
+ , "System softirqs"
+ , "softirqs/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_SOFTIRQS_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_SOFTIRQS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ else
+ rrdset_next(st_system_softirqs);
+
+ for(l = 0; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+
+ if(irr->used && irr->total) {
+ // some interrupt may have changed without changing the total number of lines
+ // if the same number of interrupts have been added and removed between two
+ // calls of this function.
+ if(unlikely(!irr->rd || strncmp(irr->name, irr->rd->name, MAX_INTERRUPT_NAME) != 0)) {
+ irr->rd = rrddim_add(st_system_softirqs, irr->id, irr->name, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_set_name(st_system_softirqs, irr->rd, irr->name);
+
+ // also reset per cpu RRDDIMs to avoid repeating strncmp() in the per core loop
+ if(likely(do_per_core != CONFIG_BOOLEAN_NO)) {
+ int c;
+ for(c = 0; c < cpus; c++) irr->cpu[c].rd = NULL;
+ }
+ }
+
+ rrddim_set_by_pointer(st_system_softirqs, irr->rd, irr->total);
+ }
+ }
+
+ rrdset_done(st_system_softirqs);
+
+ // --------------------------------------------------------------------
+
+ if(do_per_core != CONFIG_BOOLEAN_NO) {
+ static RRDSET **core_st = NULL;
+ static int old_cpus = 0;
+
+ if(old_cpus < cpus) {
+ core_st = reallocz(core_st, sizeof(RRDSET *) * cpus);
+ memset(&core_st[old_cpus], 0, sizeof(RRDSET *) * (cpus - old_cpus));
+ old_cpus = cpus;
+ }
+
+ int c;
+
+ for(c = 0; c < cpus ; c++) {
+ if(unlikely(!core_st[c])) {
+ // find if everything is just zero
+ unsigned long long core_sum = 0;
+
+ for (l = 0; l < lines; l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+ if (unlikely(!irr->used)) continue;
+ core_sum += irr->cpu[c].value;
+ }
+
+ if (unlikely(core_sum == 0)) continue; // try next core
+
+ char id[50 + 1];
+ snprintfz(id, 50, "cpu%d_softirqs", c);
+
+ char title[100 + 1];
+ snprintfz(title, 100, "CPU%d softirqs", c);
+
+ core_st[c] = rrdset_create_localhost(
+ "cpu"
+ , id
+ , NULL
+ , "softirqs"
+ , "cpu.softirqs"
+ , title
+ , "softirqs/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_SOFTIRQS_NAME
+ , NETDATA_CHART_PRIO_SOFTIRQS_PER_CORE + c
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+ }
+ else
+ rrdset_next(core_st[c]);
+
+ for(l = 0; l < lines ;l++) {
+ struct interrupt *irr = irrindex(irrs, l, cpus);
+
+ if(irr->used && (do_per_core == CONFIG_BOOLEAN_YES || irr->cpu[c].value)) {
+ if(unlikely(!irr->cpu[c].rd)) {
+ irr->cpu[c].rd = rrddim_add(core_st[c], irr->id, irr->name, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_set_name(core_st[c], irr->cpu[c].rd, irr->name);
+ }
+
+ rrddim_set_by_pointer(core_st[c], irr->cpu[c].rd, irr->cpu[c].value);
+ }
+ }
+
+ rrdset_done(core_st[c]);
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_spl_kstat_zfs.c b/collectors/proc.plugin/proc_spl_kstat_zfs.c
new file mode 100644
index 0000000..32ff36b
--- /dev/null
+++ b/collectors/proc.plugin/proc_spl_kstat_zfs.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+#include "zfs_common.h"
+
+#define ZFS_PROC_ARCSTATS "/proc/spl/kstat/zfs/arcstats"
+
+extern struct arcstats arcstats;
+
+int do_proc_spl_kstat_zfs_arcstats(int update_every, usec_t dt) {
+ (void)dt;
+
+ static int show_zero_charts = 0, do_zfs_stats = 0;
+ static procfile *ff = NULL;
+ static char *dirname = NULL;
+ static ARL_BASE *arl_base = NULL;
+
+ arcstats.l2exist = -1;
+
+ if(unlikely(!arl_base)) {
+ arl_base = arl_create("arcstats", NULL, 60);
+
+ arl_expect(arl_base, "hits", &arcstats.hits);
+ arl_expect(arl_base, "misses", &arcstats.misses);
+ arl_expect(arl_base, "demand_data_hits", &arcstats.demand_data_hits);
+ arl_expect(arl_base, "demand_data_misses", &arcstats.demand_data_misses);
+ arl_expect(arl_base, "demand_metadata_hits", &arcstats.demand_metadata_hits);
+ arl_expect(arl_base, "demand_metadata_misses", &arcstats.demand_metadata_misses);
+ arl_expect(arl_base, "prefetch_data_hits", &arcstats.prefetch_data_hits);
+ arl_expect(arl_base, "prefetch_data_misses", &arcstats.prefetch_data_misses);
+ arl_expect(arl_base, "prefetch_metadata_hits", &arcstats.prefetch_metadata_hits);
+ arl_expect(arl_base, "prefetch_metadata_misses", &arcstats.prefetch_metadata_misses);
+ arl_expect(arl_base, "mru_hits", &arcstats.mru_hits);
+ arl_expect(arl_base, "mru_ghost_hits", &arcstats.mru_ghost_hits);
+ arl_expect(arl_base, "mfu_hits", &arcstats.mfu_hits);
+ arl_expect(arl_base, "mfu_ghost_hits", &arcstats.mfu_ghost_hits);
+ arl_expect(arl_base, "deleted", &arcstats.deleted);
+ arl_expect(arl_base, "mutex_miss", &arcstats.mutex_miss);
+ arl_expect(arl_base, "evict_skip", &arcstats.evict_skip);
+ arl_expect(arl_base, "evict_not_enough", &arcstats.evict_not_enough);
+ arl_expect(arl_base, "evict_l2_cached", &arcstats.evict_l2_cached);
+ arl_expect(arl_base, "evict_l2_eligible", &arcstats.evict_l2_eligible);
+ arl_expect(arl_base, "evict_l2_ineligible", &arcstats.evict_l2_ineligible);
+ arl_expect(arl_base, "evict_l2_skip", &arcstats.evict_l2_skip);
+ arl_expect(arl_base, "hash_elements", &arcstats.hash_elements);
+ arl_expect(arl_base, "hash_elements_max", &arcstats.hash_elements_max);
+ arl_expect(arl_base, "hash_collisions", &arcstats.hash_collisions);
+ arl_expect(arl_base, "hash_chains", &arcstats.hash_chains);
+ arl_expect(arl_base, "hash_chain_max", &arcstats.hash_chain_max);
+ arl_expect(arl_base, "p", &arcstats.p);
+ arl_expect(arl_base, "c", &arcstats.c);
+ arl_expect(arl_base, "c_min", &arcstats.c_min);
+ arl_expect(arl_base, "c_max", &arcstats.c_max);
+ arl_expect(arl_base, "size", &arcstats.size);
+ arl_expect(arl_base, "hdr_size", &arcstats.hdr_size);
+ arl_expect(arl_base, "data_size", &arcstats.data_size);
+ arl_expect(arl_base, "metadata_size", &arcstats.metadata_size);
+ arl_expect(arl_base, "other_size", &arcstats.other_size);
+ arl_expect(arl_base, "anon_size", &arcstats.anon_size);
+ arl_expect(arl_base, "anon_evictable_data", &arcstats.anon_evictable_data);
+ arl_expect(arl_base, "anon_evictable_metadata", &arcstats.anon_evictable_metadata);
+ arl_expect(arl_base, "mru_size", &arcstats.mru_size);
+ arl_expect(arl_base, "mru_evictable_data", &arcstats.mru_evictable_data);
+ arl_expect(arl_base, "mru_evictable_metadata", &arcstats.mru_evictable_metadata);
+ arl_expect(arl_base, "mru_ghost_size", &arcstats.mru_ghost_size);
+ arl_expect(arl_base, "mru_ghost_evictable_data", &arcstats.mru_ghost_evictable_data);
+ arl_expect(arl_base, "mru_ghost_evictable_metadata", &arcstats.mru_ghost_evictable_metadata);
+ arl_expect(arl_base, "mfu_size", &arcstats.mfu_size);
+ arl_expect(arl_base, "mfu_evictable_data", &arcstats.mfu_evictable_data);
+ arl_expect(arl_base, "mfu_evictable_metadata", &arcstats.mfu_evictable_metadata);
+ arl_expect(arl_base, "mfu_ghost_size", &arcstats.mfu_ghost_size);
+ arl_expect(arl_base, "mfu_ghost_evictable_data", &arcstats.mfu_ghost_evictable_data);
+ arl_expect(arl_base, "mfu_ghost_evictable_metadata", &arcstats.mfu_ghost_evictable_metadata);
+ arl_expect(arl_base, "l2_hits", &arcstats.l2_hits);
+ arl_expect(arl_base, "l2_misses", &arcstats.l2_misses);
+ arl_expect(arl_base, "l2_feeds", &arcstats.l2_feeds);
+ arl_expect(arl_base, "l2_rw_clash", &arcstats.l2_rw_clash);
+ arl_expect(arl_base, "l2_read_bytes", &arcstats.l2_read_bytes);
+ arl_expect(arl_base, "l2_write_bytes", &arcstats.l2_write_bytes);
+ arl_expect(arl_base, "l2_writes_sent", &arcstats.l2_writes_sent);
+ arl_expect(arl_base, "l2_writes_done", &arcstats.l2_writes_done);
+ arl_expect(arl_base, "l2_writes_error", &arcstats.l2_writes_error);
+ arl_expect(arl_base, "l2_writes_lock_retry", &arcstats.l2_writes_lock_retry);
+ arl_expect(arl_base, "l2_evict_lock_retry", &arcstats.l2_evict_lock_retry);
+ arl_expect(arl_base, "l2_evict_reading", &arcstats.l2_evict_reading);
+ arl_expect(arl_base, "l2_evict_l1cached", &arcstats.l2_evict_l1cached);
+ arl_expect(arl_base, "l2_free_on_write", &arcstats.l2_free_on_write);
+ arl_expect(arl_base, "l2_cdata_free_on_write", &arcstats.l2_cdata_free_on_write);
+ arl_expect(arl_base, "l2_abort_lowmem", &arcstats.l2_abort_lowmem);
+ arl_expect(arl_base, "l2_cksum_bad", &arcstats.l2_cksum_bad);
+ arl_expect(arl_base, "l2_io_error", &arcstats.l2_io_error);
+ arl_expect(arl_base, "l2_size", &arcstats.l2_size);
+ arl_expect(arl_base, "l2_asize", &arcstats.l2_asize);
+ arl_expect(arl_base, "l2_hdr_size", &arcstats.l2_hdr_size);
+ arl_expect(arl_base, "l2_compress_successes", &arcstats.l2_compress_successes);
+ arl_expect(arl_base, "l2_compress_zeros", &arcstats.l2_compress_zeros);
+ arl_expect(arl_base, "l2_compress_failures", &arcstats.l2_compress_failures);
+ arl_expect(arl_base, "memory_throttle_count", &arcstats.memory_throttle_count);
+ arl_expect(arl_base, "duplicate_buffers", &arcstats.duplicate_buffers);
+ arl_expect(arl_base, "duplicate_buffers_size", &arcstats.duplicate_buffers_size);
+ arl_expect(arl_base, "duplicate_reads", &arcstats.duplicate_reads);
+ arl_expect(arl_base, "memory_direct_count", &arcstats.memory_direct_count);
+ arl_expect(arl_base, "memory_indirect_count", &arcstats.memory_indirect_count);
+ arl_expect(arl_base, "arc_no_grow", &arcstats.arc_no_grow);
+ arl_expect(arl_base, "arc_tempreserve", &arcstats.arc_tempreserve);
+ arl_expect(arl_base, "arc_loaned_bytes", &arcstats.arc_loaned_bytes);
+ arl_expect(arl_base, "arc_prune", &arcstats.arc_prune);
+ arl_expect(arl_base, "arc_meta_used", &arcstats.arc_meta_used);
+ arl_expect(arl_base, "arc_meta_limit", &arcstats.arc_meta_limit);
+ arl_expect(arl_base, "arc_meta_max", &arcstats.arc_meta_max);
+ arl_expect(arl_base, "arc_meta_min", &arcstats.arc_meta_min);
+ arl_expect(arl_base, "arc_need_free", &arcstats.arc_need_free);
+ arl_expect(arl_base, "arc_sys_free", &arcstats.arc_sys_free);
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, ZFS_PROC_ARCSTATS);
+ ff = procfile_open(config_get("plugin:proc:" ZFS_PROC_ARCSTATS, "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff))
+ return 1;
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/spl/kstat/zfs");
+ dirname = config_get("plugin:proc:" ZFS_PROC_ARCSTATS, "directory to monitor", filename);
+
+ show_zero_charts = config_get_boolean_ondemand("plugin:proc:" ZFS_PROC_ARCSTATS, "show zero charts", CONFIG_BOOLEAN_NO);
+ if(show_zero_charts == CONFIG_BOOLEAN_AUTO && netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES)
+ show_zero_charts = CONFIG_BOOLEAN_YES;
+ if(unlikely(show_zero_charts == CONFIG_BOOLEAN_YES))
+ do_zfs_stats = 1;
+ }
+
+ // check if any pools exist
+ if(likely(!do_zfs_stats)) {
+ DIR *dir = opendir(dirname);
+ if(unlikely(!dir)) {
+ error("Cannot read directory '%s'", dirname);
+ return 1;
+ }
+
+ struct dirent *de = NULL;
+ while(likely(de = readdir(dir))) {
+ if(likely(de->d_type == DT_DIR
+ && (
+ (de->d_name[0] == '.' && de->d_name[1] == '\0')
+ || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
+ )))
+ continue;
+
+ if(unlikely(de->d_type == DT_LNK || de->d_type == DT_DIR)) {
+ do_zfs_stats = 1;
+ break;
+ }
+ }
+
+ closedir(dir);
+ }
+
+ // do not show ZFS filesystem metrics if there haven't been any pools in the system yet
+ if(unlikely(!do_zfs_stats))
+ return 0;
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff))
+ return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ arl_begin(arl_base);
+
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 3)) {
+ if(unlikely(words)) error("Cannot read " ZFS_PROC_ARCSTATS " line %zu. Expected 3 params, read %zu.", l, words);
+ continue;
+ }
+
+ const char *key = procfile_lineword(ff, l, 0);
+ const char *value = procfile_lineword(ff, l, 2);
+
+ if(unlikely(arcstats.l2exist == -1)) {
+ if(key[0] == 'l' && key[1] == '2' && key[2] == '_')
+ arcstats.l2exist = 1;
+ }
+
+ if(unlikely(arl_check(arl_base, key, value))) break;
+ }
+
+ if(unlikely(arcstats.l2exist == -1))
+ arcstats.l2exist = 0;
+
+ generate_charts_arcstats(PLUGIN_PROC_NAME, ZFS_PROC_ARCSTATS, show_zero_charts, update_every);
+ generate_charts_arc_summary(PLUGIN_PROC_NAME, ZFS_PROC_ARCSTATS, show_zero_charts, update_every);
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_stat.c b/collectors/proc.plugin/proc_stat.c
new file mode 100644
index 0000000..373a067
--- /dev/null
+++ b/collectors/proc.plugin/proc_stat.c
@@ -0,0 +1,1069 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_STAT_NAME "/proc/stat"
+
+struct per_core_single_number_file {
+ unsigned char found:1;
+ const char *filename;
+ int fd;
+ collected_number value;
+ RRDDIM *rd;
+};
+
+struct last_ticks {
+ collected_number frequency;
+ collected_number ticks;
+};
+
+// This is an extension of struct per_core_single_number_file at CPU_FREQ_INDEX.
+// Either scaling_cur_freq or time_in_state file is used at one time.
+struct per_core_time_in_state_file {
+ const char *filename;
+ procfile *ff;
+ size_t last_ticks_len;
+ struct last_ticks *last_ticks;
+};
+
+#define CORE_THROTTLE_COUNT_INDEX 0
+#define PACKAGE_THROTTLE_COUNT_INDEX 1
+#define CPU_FREQ_INDEX 2
+#define PER_CORE_FILES 3
+
+struct cpu_chart {
+ const char *id;
+
+ RRDSET *st;
+ RRDDIM *rd_user;
+ RRDDIM *rd_nice;
+ RRDDIM *rd_system;
+ RRDDIM *rd_idle;
+ RRDDIM *rd_iowait;
+ RRDDIM *rd_irq;
+ RRDDIM *rd_softirq;
+ RRDDIM *rd_steal;
+ RRDDIM *rd_guest;
+ RRDDIM *rd_guest_nice;
+
+ struct per_core_single_number_file files[PER_CORE_FILES];
+
+ struct per_core_time_in_state_file time_in_state_files;
+};
+
+static int keep_per_core_fds_open = CONFIG_BOOLEAN_YES;
+static int keep_cpuidle_fds_open = CONFIG_BOOLEAN_YES;
+
+static int read_per_core_files(struct cpu_chart *all_cpu_charts, size_t len, size_t index) {
+ char buf[50 + 1];
+ size_t x, files_read = 0, files_nonzero = 0;
+
+ for(x = 0; x < len ; x++) {
+ struct per_core_single_number_file *f = &all_cpu_charts[x].files[index];
+
+ f->found = 0;
+
+ if(unlikely(!f->filename))
+ continue;
+
+ if(unlikely(f->fd == -1)) {
+ f->fd = open(f->filename, O_RDONLY);
+ if (unlikely(f->fd == -1)) {
+ error("Cannot open file '%s'", f->filename);
+ continue;
+ }
+ }
+
+ ssize_t ret = read(f->fd, buf, 50);
+ if(unlikely(ret < 0)) {
+ // cannot read that file
+
+ error("Cannot read file '%s'", f->filename);
+ close(f->fd);
+ f->fd = -1;
+ continue;
+ }
+ else {
+ // successful read
+
+ // terminate the buffer
+ buf[ret] = '\0';
+
+ if(unlikely(keep_per_core_fds_open != CONFIG_BOOLEAN_YES)) {
+ close(f->fd);
+ f->fd = -1;
+ }
+ else if(lseek(f->fd, 0, SEEK_SET) == -1) {
+ error("Cannot seek in file '%s'", f->filename);
+ close(f->fd);
+ f->fd = -1;
+ }
+ }
+
+ files_read++;
+ f->found = 1;
+
+ f->value = str2ll(buf, NULL);
+ if(likely(f->value != 0))
+ files_nonzero++;
+ }
+
+ if(files_read == 0)
+ return -1;
+
+ if(files_nonzero == 0)
+ return 0;
+
+ return (int)files_nonzero;
+}
+
+static int read_per_core_time_in_state_files(struct cpu_chart *all_cpu_charts, size_t len, size_t index) {
+ size_t x, files_read = 0, files_nonzero = 0;
+
+ for(x = 0; x < len ; x++) {
+ struct per_core_single_number_file *f = &all_cpu_charts[x].files[index];
+ struct per_core_time_in_state_file *tsf = &all_cpu_charts[x].time_in_state_files;
+
+ f->found = 0;
+
+ if(unlikely(!tsf->filename))
+ continue;
+
+ if(unlikely(!tsf->ff)) {
+ tsf->ff = procfile_open(tsf->filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!tsf->ff))
+ {
+ error("Cannot open file '%s'", tsf->filename);
+ continue;
+ }
+ }
+
+ tsf->ff = procfile_readall(tsf->ff);
+ if(unlikely(!tsf->ff)) {
+ error("Cannot read file '%s'", tsf->filename);
+ procfile_close(tsf->ff);
+ tsf->ff = NULL;
+ continue;
+ }
+ else {
+ // successful read
+
+ size_t lines = procfile_lines(tsf->ff), l;
+ size_t words;
+ unsigned long long total_ticks_since_last = 0, avg_freq = 0;
+
+ // Check if there is at least one frequency in time_in_state
+ if (procfile_word(tsf->ff, 0)[0] == '\0') {
+ if(unlikely(keep_per_core_fds_open != CONFIG_BOOLEAN_YES)) {
+ procfile_close(tsf->ff);
+ tsf->ff = NULL;
+ }
+ // TODO: Is there a better way to avoid spikes than calculating the average over
+ // the whole period under schedutil governor?
+ // freez(tsf->last_ticks);
+ // tsf->last_ticks = NULL;
+ // tsf->last_ticks_len = 0;
+ continue;
+ }
+
+ if (unlikely(tsf->last_ticks_len < lines || tsf->last_ticks == NULL)) {
+ tsf->last_ticks = reallocz(tsf->last_ticks, sizeof(struct last_ticks) * lines);
+ memset(tsf->last_ticks, 0, sizeof(struct last_ticks) * lines);
+ tsf->last_ticks_len = lines;
+ }
+
+ f->value = 0;
+
+ for(l = 0; l < lines - 1 ;l++) {
+ unsigned long long frequency = 0, ticks = 0, ticks_since_last = 0;
+
+ words = procfile_linewords(tsf->ff, l);
+ if(unlikely(words < 2)) {
+ error("Cannot read time_in_state line. Expected 2 params, read %zu.", words);
+ continue;
+ }
+ frequency = str2ull(procfile_lineword(tsf->ff, l, 0));
+ ticks = str2ull(procfile_lineword(tsf->ff, l, 1));
+
+ // It is assumed that frequencies are static and sorted
+ ticks_since_last = ticks - tsf->last_ticks[l].ticks;
+ tsf->last_ticks[l].frequency = frequency;
+ tsf->last_ticks[l].ticks = ticks;
+
+ total_ticks_since_last += ticks_since_last;
+ avg_freq += frequency * ticks_since_last;
+
+ }
+
+ if (likely(total_ticks_since_last)) {
+ avg_freq /= total_ticks_since_last;
+ f->value = avg_freq;
+ }
+
+ if(unlikely(keep_per_core_fds_open != CONFIG_BOOLEAN_YES)) {
+ procfile_close(tsf->ff);
+ tsf->ff = NULL;
+ }
+ }
+
+ files_read++;
+
+ f->found = 1;
+
+ if(likely(f->value != 0))
+ files_nonzero++;
+ }
+
+ if(unlikely(files_read == 0))
+ return -1;
+
+ if(unlikely(files_nonzero == 0))
+ return 0;
+
+ return (int)files_nonzero;
+}
+
+static void chart_per_core_files(struct cpu_chart *all_cpu_charts, size_t len, size_t index, RRDSET *st, collected_number multiplier, collected_number divisor, RRD_ALGORITHM algorithm) {
+ size_t x;
+ for(x = 0; x < len ; x++) {
+ struct per_core_single_number_file *f = &all_cpu_charts[x].files[index];
+
+ if(unlikely(!f->found))
+ continue;
+
+ if(unlikely(!f->rd))
+ f->rd = rrddim_add(st, all_cpu_charts[x].id, NULL, multiplier, divisor, algorithm);
+
+ rrddim_set_by_pointer(st, f->rd, f->value);
+ }
+}
+
+struct cpuidle_state {
+ char *name;
+
+ char *time_filename;
+ int time_fd;
+
+ collected_number value;
+
+ RRDDIM *rd;
+};
+
+struct per_core_cpuidle_chart {
+ RRDSET *st;
+
+ RRDDIM *active_time_rd;
+ collected_number active_time;
+ collected_number last_active_time;
+
+ struct cpuidle_state *cpuidle_state;
+ size_t cpuidle_state_len;
+ int rescan_cpu_states;
+};
+
+static void* wake_cpu_thread(void* core) {
+ pthread_t thread;
+ cpu_set_t cpu_set;
+ static size_t cpu_wakeups = 0;
+ static int errors = 0;
+
+ CPU_ZERO(&cpu_set);
+ CPU_SET(*(int*)core, &cpu_set);
+
+ thread = pthread_self();
+ if(unlikely(pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpu_set))) {
+ if(unlikely(errors < 8)) {
+ error("Cannot set CPU affinity for core %d", *(int*)core);
+ errors++;
+ }
+ else if(unlikely(errors < 9)) {
+ error("CPU affinity errors are disabled");
+ errors++;
+ }
+ }
+
+ // Make the CPU core do something to force it to update its idle counters
+ cpu_wakeups++;
+
+ return 0;
+}
+
+static int read_schedstat(char *schedstat_filename, struct per_core_cpuidle_chart **cpuidle_charts_address, size_t *schedstat_cores_found) {
+ static size_t cpuidle_charts_len = 0;
+ static procfile *ff = NULL;
+ struct per_core_cpuidle_chart *cpuidle_charts = *cpuidle_charts_address;
+ size_t cores_found = 0;
+
+ if(unlikely(!ff)) {
+ ff = procfile_open(schedstat_filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 1;
+
+ size_t lines = procfile_lines(ff), l;
+ size_t words;
+
+ for(l = 0; l < lines ;l++) {
+ char *row_key = procfile_lineword(ff, l, 0);
+
+ // faster strncmp(row_key, "cpu", 3) == 0
+ if(likely(row_key[0] == 'c' && row_key[1] == 'p' && row_key[2] == 'u')) {
+ words = procfile_linewords(ff, l);
+ if(unlikely(words < 10)) {
+ error("Cannot read /proc/schedstat cpu line. Expected 9 params, read %zu.", words);
+ return 1;
+ }
+ cores_found++;
+
+ size_t core = str2ul(&row_key[3]);
+ if(unlikely(core >= cores_found)) {
+ error("Core %zu found but no more than %zu cores were expected.", core, cores_found);
+ return 1;
+ }
+
+ if(unlikely(cpuidle_charts_len < cores_found)) {
+ cpuidle_charts = reallocz(cpuidle_charts, sizeof(struct per_core_cpuidle_chart) * cores_found);
+ *cpuidle_charts_address = cpuidle_charts;
+ memset(cpuidle_charts + cpuidle_charts_len, 0, sizeof(struct per_core_cpuidle_chart) * (cores_found - cpuidle_charts_len));
+ cpuidle_charts_len = cores_found;
+ }
+
+ cpuidle_charts[core].active_time = str2ull(procfile_lineword(ff, l, 7)) / 1000;
+ }
+ }
+
+ *schedstat_cores_found = cores_found;
+ return 0;
+}
+
+static int read_one_state(char *buf, const char *filename, int *fd) {
+ ssize_t ret = read(*fd, buf, 50);
+
+ if(unlikely(ret <= 0)) {
+ // cannot read that file
+ error("Cannot read file '%s'", filename);
+ close(*fd);
+ *fd = -1;
+ return 0;
+ }
+ else {
+ // successful read
+
+ // terminate the buffer
+ buf[ret - 1] = '\0';
+
+ if(unlikely(keep_cpuidle_fds_open != CONFIG_BOOLEAN_YES)) {
+ close(*fd);
+ *fd = -1;
+ }
+ else if(lseek(*fd, 0, SEEK_SET) == -1) {
+ error("Cannot seek in file '%s'", filename);
+ close(*fd);
+ *fd = -1;
+ }
+ }
+
+ return 1;
+}
+
+static int read_cpuidle_states(char *cpuidle_name_filename , char *cpuidle_time_filename, struct per_core_cpuidle_chart *cpuidle_charts, size_t core) {
+ char filename[FILENAME_MAX + 1];
+ static char next_state_filename[FILENAME_MAX + 1];
+ struct stat stbuf;
+ struct per_core_cpuidle_chart *cc = &cpuidle_charts[core];
+ size_t state;
+
+ if(unlikely(!cc->cpuidle_state_len || cc->rescan_cpu_states)) {
+ int state_file_found = 1; // check at least one state
+
+ if(cc->cpuidle_state_len) {
+ for(state = 0; state < cc->cpuidle_state_len; state++) {
+ freez(cc->cpuidle_state[state].name);
+
+ freez(cc->cpuidle_state[state].time_filename);
+ close(cc->cpuidle_state[state].time_fd);
+ cc->cpuidle_state[state].time_fd = -1;
+ }
+
+ freez(cc->cpuidle_state);
+ cc->cpuidle_state = NULL;
+ cc->cpuidle_state_len = 0;
+
+ cc->active_time_rd = NULL;
+ cc->st = NULL;
+ }
+
+ while(likely(state_file_found)) {
+ snprintfz(filename, FILENAME_MAX, cpuidle_name_filename, core, cc->cpuidle_state_len);
+ if (stat(filename, &stbuf) == 0)
+ cc->cpuidle_state_len++;
+ else
+ state_file_found = 0;
+ }
+ snprintfz(next_state_filename, FILENAME_MAX, cpuidle_name_filename, core, cc->cpuidle_state_len);
+
+ if(likely(cc->cpuidle_state_len))
+ cc->cpuidle_state = callocz(cc->cpuidle_state_len, sizeof(struct cpuidle_state));
+
+ for(state = 0; state < cc->cpuidle_state_len; state++) {
+ char name_buf[50 + 1];
+ snprintfz(filename, FILENAME_MAX, cpuidle_name_filename, core, state);
+
+ int fd = open(filename, O_RDONLY, 0666);
+ if(unlikely(fd == -1)) {
+ error("Cannot open file '%s'", filename);
+ cc->rescan_cpu_states = 1;
+ return 1;
+ }
+
+ ssize_t r = read(fd, name_buf, 50);
+ if(unlikely(r < 1)) {
+ error("Cannot read file '%s'", filename);
+ close(fd);
+ cc->rescan_cpu_states = 1;
+ return 1;
+ }
+
+ name_buf[r - 1] = '\0'; // erase extra character
+ cc->cpuidle_state[state].name = strdupz(trim(name_buf));
+ close(fd);
+
+ snprintfz(filename, FILENAME_MAX, cpuidle_time_filename, core, state);
+ cc->cpuidle_state[state].time_filename = strdupz(filename);
+ cc->cpuidle_state[state].time_fd = -1;
+ }
+
+ cc->rescan_cpu_states = 0;
+ }
+
+ for(state = 0; state < cc->cpuidle_state_len; state++) {
+
+ struct cpuidle_state *cs = &cc->cpuidle_state[state];
+
+ if(unlikely(cs->time_fd == -1)) {
+ cs->time_fd = open(cs->time_filename, O_RDONLY);
+ if (unlikely(cs->time_fd == -1)) {
+ error("Cannot open file '%s'", cs->time_filename);
+ cc->rescan_cpu_states = 1;
+ return 1;
+ }
+ }
+
+ char time_buf[50 + 1];
+ if(likely(read_one_state(time_buf, cs->time_filename, &cs->time_fd))) {
+ cs->value = str2ll(time_buf, NULL);
+ }
+ else {
+ cc->rescan_cpu_states = 1;
+ return 1;
+ }
+ }
+
+ // check if the number of states was increased
+ if(unlikely(stat(next_state_filename, &stbuf) == 0)) {
+ cc->rescan_cpu_states = 1;
+ return 1;
+ }
+
+ return 0;
+}
+
+int do_proc_stat(int update_every, usec_t dt) {
+ (void)dt;
+
+ static struct cpu_chart *all_cpu_charts = NULL;
+ static size_t all_cpu_charts_size = 0;
+ static procfile *ff = NULL;
+ static int do_cpu = -1, do_cpu_cores = -1, do_interrupts = -1, do_context = -1, do_forks = -1, do_processes = -1,
+ do_core_throttle_count = -1, do_package_throttle_count = -1, do_cpu_freq = -1, do_cpuidle = -1;
+ static uint32_t hash_intr, hash_ctxt, hash_processes, hash_procs_running, hash_procs_blocked;
+ static char *core_throttle_count_filename = NULL, *package_throttle_count_filename = NULL, *scaling_cur_freq_filename = NULL,
+ *time_in_state_filename = NULL, *schedstat_filename = NULL, *cpuidle_name_filename = NULL, *cpuidle_time_filename = NULL;
+ static RRDVAR *cpus_var = NULL;
+ static int accurate_freq_avail = 0, accurate_freq_is_used = 0;
+ size_t cores_found = (size_t)processors;
+
+ if(unlikely(do_cpu == -1)) {
+ do_cpu = config_get_boolean("plugin:proc:/proc/stat", "cpu utilization", CONFIG_BOOLEAN_YES);
+ do_cpu_cores = config_get_boolean("plugin:proc:/proc/stat", "per cpu core utilization", CONFIG_BOOLEAN_YES);
+ do_interrupts = config_get_boolean("plugin:proc:/proc/stat", "cpu interrupts", CONFIG_BOOLEAN_YES);
+ do_context = config_get_boolean("plugin:proc:/proc/stat", "context switches", CONFIG_BOOLEAN_YES);
+ do_forks = config_get_boolean("plugin:proc:/proc/stat", "processes started", CONFIG_BOOLEAN_YES);
+ do_processes = config_get_boolean("plugin:proc:/proc/stat", "processes running", CONFIG_BOOLEAN_YES);
+
+ // give sane defaults based on the number of processors
+ if(unlikely(processors > 50)) {
+ // the system has too many processors
+ keep_per_core_fds_open = CONFIG_BOOLEAN_NO;
+ do_core_throttle_count = CONFIG_BOOLEAN_NO;
+ do_package_throttle_count = CONFIG_BOOLEAN_NO;
+ do_cpu_freq = CONFIG_BOOLEAN_NO;
+ do_cpuidle = CONFIG_BOOLEAN_NO;
+ }
+ else {
+ // the system has a reasonable number of processors
+ keep_per_core_fds_open = CONFIG_BOOLEAN_YES;
+ do_core_throttle_count = CONFIG_BOOLEAN_AUTO;
+ do_package_throttle_count = CONFIG_BOOLEAN_NO;
+ do_cpu_freq = CONFIG_BOOLEAN_YES;
+ do_cpuidle = CONFIG_BOOLEAN_YES;
+ }
+ if(unlikely(processors > 24)) {
+ // the system has too many processors
+ keep_cpuidle_fds_open = CONFIG_BOOLEAN_NO;
+ }
+ else {
+ // the system has a reasonable number of processors
+ keep_cpuidle_fds_open = CONFIG_BOOLEAN_YES;
+ }
+
+ keep_per_core_fds_open = config_get_boolean("plugin:proc:/proc/stat", "keep per core files open", keep_per_core_fds_open);
+ keep_cpuidle_fds_open = config_get_boolean("plugin:proc:/proc/stat", "keep cpuidle files open", keep_cpuidle_fds_open);
+ do_core_throttle_count = config_get_boolean_ondemand("plugin:proc:/proc/stat", "core_throttle_count", do_core_throttle_count);
+ do_package_throttle_count = config_get_boolean_ondemand("plugin:proc:/proc/stat", "package_throttle_count", do_package_throttle_count);
+ do_cpu_freq = config_get_boolean_ondemand("plugin:proc:/proc/stat", "cpu frequency", do_cpu_freq);
+ do_cpuidle = config_get_boolean_ondemand("plugin:proc:/proc/stat", "cpu idle states", do_cpuidle);
+
+ hash_intr = simple_hash("intr");
+ hash_ctxt = simple_hash("ctxt");
+ hash_processes = simple_hash("processes");
+ hash_procs_running = simple_hash("procs_running");
+ hash_procs_blocked = simple_hash("procs_blocked");
+
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/thermal_throttle/core_throttle_count");
+ core_throttle_count_filename = config_get("plugin:proc:/proc/stat", "core_throttle_count filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/thermal_throttle/package_throttle_count");
+ package_throttle_count_filename = config_get("plugin:proc:/proc/stat", "package_throttle_count filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/cpufreq/scaling_cur_freq");
+ scaling_cur_freq_filename = config_get("plugin:proc:/proc/stat", "scaling_cur_freq filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/cpufreq/stats/time_in_state");
+ time_in_state_filename = config_get("plugin:proc:/proc/stat", "time_in_state filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/schedstat");
+ schedstat_filename = config_get("plugin:proc:/proc/stat", "schedstat filename to monitor", filename);
+
+ if(do_cpuidle != CONFIG_BOOLEAN_NO) {
+ struct stat stbuf;
+
+ if (stat(schedstat_filename, &stbuf))
+ do_cpuidle = CONFIG_BOOLEAN_NO;
+ }
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/cpu%zu/cpuidle/state%zu/name");
+ cpuidle_name_filename = config_get("plugin:proc:/proc/stat", "cpuidle name filename to monitor", filename);
+
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/cpu%zu/cpuidle/state%zu/time");
+ cpuidle_time_filename = config_get("plugin:proc:/proc/stat", "cpuidle time filename to monitor", filename);
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/stat");
+ ff = procfile_open(config_get("plugin:proc:/proc/stat", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+ size_t words;
+
+ unsigned long long processes = 0, running = 0 , blocked = 0;
+
+ for(l = 0; l < lines ;l++) {
+ char *row_key = procfile_lineword(ff, l, 0);
+ uint32_t hash = simple_hash(row_key);
+
+ // faster strncmp(row_key, "cpu", 3) == 0
+ if(likely(row_key[0] == 'c' && row_key[1] == 'p' && row_key[2] == 'u')) {
+ words = procfile_linewords(ff, l);
+ if(unlikely(words < 9)) {
+ error("Cannot read /proc/stat cpu line. Expected 9 params, read %zu.", words);
+ continue;
+ }
+
+ size_t core = (row_key[3] == '\0') ? 0 : str2ul(&row_key[3]) + 1;
+ if(likely(core > 0)) cores_found = core;
+
+ if(likely((core == 0 && do_cpu) || (core > 0 && do_cpu_cores))) {
+ char *id;
+ unsigned long long user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0, steal = 0, guest = 0, guest_nice = 0;
+
+ id = row_key;
+ user = str2ull(procfile_lineword(ff, l, 1));
+ nice = str2ull(procfile_lineword(ff, l, 2));
+ system = str2ull(procfile_lineword(ff, l, 3));
+ idle = str2ull(procfile_lineword(ff, l, 4));
+ iowait = str2ull(procfile_lineword(ff, l, 5));
+ irq = str2ull(procfile_lineword(ff, l, 6));
+ softirq = str2ull(procfile_lineword(ff, l, 7));
+ steal = str2ull(procfile_lineword(ff, l, 8));
+
+ guest = str2ull(procfile_lineword(ff, l, 9));
+ user -= guest;
+
+ guest_nice = str2ull(procfile_lineword(ff, l, 10));
+ nice -= guest_nice;
+
+ char *title, *type, *context, *family;
+ long priority;
+
+ if(unlikely(core >= all_cpu_charts_size)) {
+ size_t old_cpu_charts_size = all_cpu_charts_size;
+ all_cpu_charts_size = core + 1;
+ all_cpu_charts = reallocz(all_cpu_charts, sizeof(struct cpu_chart) * all_cpu_charts_size);
+ memset(&all_cpu_charts[old_cpu_charts_size], 0, sizeof(struct cpu_chart) * (all_cpu_charts_size - old_cpu_charts_size));
+ }
+ struct cpu_chart *cpu_chart = &all_cpu_charts[core];
+
+ if(unlikely(!cpu_chart->st)) {
+ cpu_chart->id = strdupz(id);
+
+ if(unlikely(core == 0)) {
+ title = "Total CPU utilization";
+ type = "system";
+ context = "system.cpu";
+ family = id;
+ priority = NETDATA_CHART_PRIO_SYSTEM_CPU;
+ }
+ else {
+ title = "Core utilization";
+ type = "cpu";
+ context = "cpu.cpu";
+ family = "utilization";
+ priority = NETDATA_CHART_PRIO_CPU_PER_CORE;
+
+ char filename[FILENAME_MAX + 1];
+ struct stat stbuf;
+
+ if(do_core_throttle_count != CONFIG_BOOLEAN_NO) {
+ snprintfz(filename, FILENAME_MAX, core_throttle_count_filename, id);
+ if (stat(filename, &stbuf) == 0) {
+ cpu_chart->files[CORE_THROTTLE_COUNT_INDEX].filename = strdupz(filename);
+ cpu_chart->files[CORE_THROTTLE_COUNT_INDEX].fd = -1;
+ do_core_throttle_count = CONFIG_BOOLEAN_YES;
+ }
+ }
+
+ if(do_package_throttle_count != CONFIG_BOOLEAN_NO) {
+ snprintfz(filename, FILENAME_MAX, package_throttle_count_filename, id);
+ if (stat(filename, &stbuf) == 0) {
+ cpu_chart->files[PACKAGE_THROTTLE_COUNT_INDEX].filename = strdupz(filename);
+ cpu_chart->files[PACKAGE_THROTTLE_COUNT_INDEX].fd = -1;
+ do_package_throttle_count = CONFIG_BOOLEAN_YES;
+ }
+ }
+
+ if(do_cpu_freq != CONFIG_BOOLEAN_NO) {
+
+ snprintfz(filename, FILENAME_MAX, scaling_cur_freq_filename, id);
+
+ if (stat(filename, &stbuf) == 0) {
+ cpu_chart->files[CPU_FREQ_INDEX].filename = strdupz(filename);
+ cpu_chart->files[CPU_FREQ_INDEX].fd = -1;
+ do_cpu_freq = CONFIG_BOOLEAN_YES;
+ }
+
+ snprintfz(filename, FILENAME_MAX, time_in_state_filename, id);
+
+ if (stat(filename, &stbuf) == 0) {
+ cpu_chart->time_in_state_files.filename = strdupz(filename);
+ cpu_chart->time_in_state_files.ff = NULL;
+ do_cpu_freq = CONFIG_BOOLEAN_YES;
+ accurate_freq_avail = 1;
+ }
+ }
+ }
+
+ cpu_chart->st = rrdset_create_localhost(
+ type
+ , id
+ , NULL
+ , family
+ , context
+ , title
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , priority + core
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ long multiplier = 1;
+ long divisor = 1; // sysconf(_SC_CLK_TCK);
+
+ cpu_chart->rd_guest_nice = rrddim_add(cpu_chart->st, "guest_nice", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_guest = rrddim_add(cpu_chart->st, "guest", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_steal = rrddim_add(cpu_chart->st, "steal", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_softirq = rrddim_add(cpu_chart->st, "softirq", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_irq = rrddim_add(cpu_chart->st, "irq", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_user = rrddim_add(cpu_chart->st, "user", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_system = rrddim_add(cpu_chart->st, "system", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_nice = rrddim_add(cpu_chart->st, "nice", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_iowait = rrddim_add(cpu_chart->st, "iowait", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ cpu_chart->rd_idle = rrddim_add(cpu_chart->st, "idle", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rrddim_hide(cpu_chart->st, "idle");
+
+ if(unlikely(core == 0 && cpus_var == NULL))
+ cpus_var = rrdvar_custom_host_variable_create(localhost, "active_processors");
+ }
+ else rrdset_next(cpu_chart->st);
+
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_user, user);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_nice, nice);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_system, system);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_idle, idle);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_iowait, iowait);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_irq, irq);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_softirq, softirq);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_steal, steal);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_guest, guest);
+ rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_guest_nice, guest_nice);
+ rrdset_done(cpu_chart->st);
+ }
+ }
+ else if(unlikely(hash == hash_intr && strcmp(row_key, "intr") == 0)) {
+ if(likely(do_interrupts)) {
+ static RRDSET *st_intr = NULL;
+ static RRDDIM *rd_interrupts = NULL;
+ unsigned long long value = str2ull(procfile_lineword(ff, l, 1));
+
+ if(unlikely(!st_intr)) {
+ st_intr = rrdset_create_localhost(
+ "system"
+ , "intr"
+ , NULL
+ , "interrupts"
+ , NULL
+ , "CPU Interrupts"
+ , "interrupts/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_INTR
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_intr, RRDSET_FLAG_DETAIL);
+
+ rd_interrupts = rrddim_add(st_intr, "interrupts", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_intr);
+
+ rrddim_set_by_pointer(st_intr, rd_interrupts, value);
+ rrdset_done(st_intr);
+ }
+ }
+ else if(unlikely(hash == hash_ctxt && strcmp(row_key, "ctxt") == 0)) {
+ if(likely(do_context)) {
+ static RRDSET *st_ctxt = NULL;
+ static RRDDIM *rd_switches = NULL;
+ unsigned long long value = str2ull(procfile_lineword(ff, l, 1));
+
+ if(unlikely(!st_ctxt)) {
+ st_ctxt = rrdset_create_localhost(
+ "system"
+ , "ctxt"
+ , NULL
+ , "processes"
+ , NULL
+ , "CPU Context Switches"
+ , "context switches/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_CTXT
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_switches = rrddim_add(st_ctxt, "switches", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_ctxt);
+
+ rrddim_set_by_pointer(st_ctxt, rd_switches, value);
+ rrdset_done(st_ctxt);
+ }
+ }
+ else if(unlikely(hash == hash_processes && !processes && strcmp(row_key, "processes") == 0)) {
+ processes = str2ull(procfile_lineword(ff, l, 1));
+ }
+ else if(unlikely(hash == hash_procs_running && !running && strcmp(row_key, "procs_running") == 0)) {
+ running = str2ull(procfile_lineword(ff, l, 1));
+ }
+ else if(unlikely(hash == hash_procs_blocked && !blocked && strcmp(row_key, "procs_blocked") == 0)) {
+ blocked = str2ull(procfile_lineword(ff, l, 1));
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_forks)) {
+ static RRDSET *st_forks = NULL;
+ static RRDDIM *rd_started = NULL;
+
+ if(unlikely(!st_forks)) {
+ st_forks = rrdset_create_localhost(
+ "system"
+ , "forks"
+ , NULL
+ , "processes"
+ , NULL
+ , "Started Processes"
+ , "processes/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_FORKS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st_forks, RRDSET_FLAG_DETAIL);
+
+ rd_started = rrddim_add(st_forks, "started", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_forks);
+
+ rrddim_set_by_pointer(st_forks, rd_started, processes);
+ rrdset_done(st_forks);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_processes)) {
+ static RRDSET *st_processes = NULL;
+ static RRDDIM *rd_running = NULL;
+ static RRDDIM *rd_blocked = NULL;
+
+ if(unlikely(!st_processes)) {
+ st_processes = rrdset_create_localhost(
+ "system"
+ , "processes"
+ , NULL
+ , "processes"
+ , NULL
+ , "System Processes"
+ , "processes"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_PROCESSES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_running = rrddim_add(st_processes, "running", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_blocked = rrddim_add(st_processes, "blocked", NULL, -1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st_processes);
+
+ rrddim_set_by_pointer(st_processes, rd_running, running);
+ rrddim_set_by_pointer(st_processes, rd_blocked, blocked);
+ rrdset_done(st_processes);
+ }
+
+ if(likely(all_cpu_charts_size > 1)) {
+ if(likely(do_core_throttle_count != CONFIG_BOOLEAN_NO)) {
+ int r = read_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CORE_THROTTLE_COUNT_INDEX);
+ if(likely(r != -1 && (do_core_throttle_count == CONFIG_BOOLEAN_YES || r > 0))) {
+ do_core_throttle_count = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_core_throttle_count = NULL;
+
+ if (unlikely(!st_core_throttle_count))
+ st_core_throttle_count = rrdset_create_localhost(
+ "cpu"
+ , "core_throttling"
+ , NULL
+ , "throttling"
+ , "cpu.core_throttling"
+ , "Core Thermal Throttling Events"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_CORE_THROTTLING
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ else
+ rrdset_next(st_core_throttle_count);
+
+ chart_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CORE_THROTTLE_COUNT_INDEX, st_core_throttle_count, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrdset_done(st_core_throttle_count);
+ }
+ }
+
+ if(likely(do_package_throttle_count != CONFIG_BOOLEAN_NO)) {
+ int r = read_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, PACKAGE_THROTTLE_COUNT_INDEX);
+ if(likely(r != -1 && (do_package_throttle_count == CONFIG_BOOLEAN_YES || r > 0))) {
+ do_package_throttle_count = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_package_throttle_count = NULL;
+
+ if(unlikely(!st_package_throttle_count))
+ st_package_throttle_count = rrdset_create_localhost(
+ "cpu"
+ , "package_throttling"
+ , NULL
+ , "throttling"
+ , "cpu.package_throttling"
+ , "Package Thermal Throttling Events"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_PACKAGE_THROTTLING
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ else
+ rrdset_next(st_package_throttle_count);
+
+ chart_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, PACKAGE_THROTTLE_COUNT_INDEX, st_package_throttle_count, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrdset_done(st_package_throttle_count);
+ }
+ }
+
+ if(likely(do_cpu_freq != CONFIG_BOOLEAN_NO)) {
+ char filename[FILENAME_MAX + 1];
+ int r = 0;
+
+ if (accurate_freq_avail) {
+ r = read_per_core_time_in_state_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CPU_FREQ_INDEX);
+ if(r > 0 && !accurate_freq_is_used) {
+ accurate_freq_is_used = 1;
+ snprintfz(filename, FILENAME_MAX, time_in_state_filename, "cpu*");
+ info("cpufreq is using %s", filename);
+ }
+ }
+ if (r < 1) {
+ r = read_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CPU_FREQ_INDEX);
+ if(accurate_freq_is_used) {
+ accurate_freq_is_used = 0;
+ snprintfz(filename, FILENAME_MAX, scaling_cur_freq_filename, "cpu*");
+ info("cpufreq fell back to %s", filename);
+ }
+ }
+
+ if(likely(r != -1 && (do_cpu_freq == CONFIG_BOOLEAN_YES || r > 0))) {
+ do_cpu_freq = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_scaling_cur_freq = NULL;
+
+ if(unlikely(!st_scaling_cur_freq))
+ st_scaling_cur_freq = rrdset_create_localhost(
+ "cpu"
+ , "cpufreq"
+ , NULL
+ , "cpufreq"
+ , "cpufreq.cpufreq"
+ , "Current CPU Frequency"
+ , "MHz"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_CPUFREQ_SCALING_CUR_FREQ
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ else
+ rrdset_next(st_scaling_cur_freq);
+
+ chart_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CPU_FREQ_INDEX, st_scaling_cur_freq, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ rrdset_done(st_scaling_cur_freq);
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ static struct per_core_cpuidle_chart *cpuidle_charts = NULL;
+ size_t schedstat_cores_found = 0;
+
+ if(likely(do_cpuidle != CONFIG_BOOLEAN_NO && !read_schedstat(schedstat_filename, &cpuidle_charts, &schedstat_cores_found))) {
+ int cpu_states_updated = 0;
+ size_t core, state;
+
+
+ // proc.plugin runs on Linux systems only. Multi-platform compatibility is not needed here,
+ // so bare pthread functions are used to avoid unneeded overheads.
+ for(core = 0; core < schedstat_cores_found; core++) {
+ if(unlikely(!(cpuidle_charts[core].active_time - cpuidle_charts[core].last_active_time))) {
+ pthread_t thread;
+ cpu_set_t global_cpu_set;
+
+ if (likely(!pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &global_cpu_set))) {
+ if (unlikely(!CPU_ISSET(core, &global_cpu_set))) {
+ continue;
+ }
+ }
+ else
+ error("Cannot read current process affinity");
+
+ // These threads are very ephemeral and don't need to have a specific name
+ if(unlikely(pthread_create(&thread, NULL, wake_cpu_thread, (void *)&core)))
+ error("Cannot create wake_cpu_thread");
+ else if(unlikely(pthread_join(thread, NULL)))
+ error("Cannot join wake_cpu_thread");
+ cpu_states_updated = 1;
+ }
+ }
+
+ if(unlikely(!cpu_states_updated || !read_schedstat(schedstat_filename, &cpuidle_charts, &schedstat_cores_found))) {
+ for(core = 0; core < schedstat_cores_found; core++) {
+ cpuidle_charts[core].last_active_time = cpuidle_charts[core].active_time;
+
+ int r = read_cpuidle_states(cpuidle_name_filename, cpuidle_time_filename, cpuidle_charts, core);
+ if(likely(r != -1 && (do_cpuidle == CONFIG_BOOLEAN_YES || r > 0))) {
+ do_cpuidle = CONFIG_BOOLEAN_YES;
+
+ char cpuidle_chart_id[RRD_ID_LENGTH_MAX + 1];
+ snprintfz(cpuidle_chart_id, RRD_ID_LENGTH_MAX, "cpu%zu_cpuidle", core);
+
+ if(unlikely(!cpuidle_charts[core].st)) {
+ cpuidle_charts[core].st = rrdset_create_localhost(
+ "cpu"
+ , cpuidle_chart_id
+ , NULL
+ , "cpuidle"
+ , "cpuidle.cpuidle"
+ , "C-state residency time"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_STAT_NAME
+ , NETDATA_CHART_PRIO_CPUIDLE + core
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ char cpuidle_dim_id[RRD_ID_LENGTH_MAX + 1];
+ snprintfz(cpuidle_dim_id, RRD_ID_LENGTH_MAX, "cpu%zu_active_time", core);
+ cpuidle_charts[core].active_time_rd = rrddim_add(cpuidle_charts[core].st, cpuidle_dim_id, "C0 (active)", 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ for(state = 0; state < cpuidle_charts[core].cpuidle_state_len; state++) {
+ snprintfz(cpuidle_dim_id, RRD_ID_LENGTH_MAX, "cpu%zu_cpuidle_state%zu_time", core, state);
+ cpuidle_charts[core].cpuidle_state[state].rd = rrddim_add(cpuidle_charts[core].st, cpuidle_dim_id,
+ cpuidle_charts[core].cpuidle_state[state].name,
+ 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ }
+ else
+ rrdset_next(cpuidle_charts[core].st);
+
+ rrddim_set_by_pointer(cpuidle_charts[core].st, cpuidle_charts[core].active_time_rd, cpuidle_charts[core].active_time);
+ for(state = 0; state < cpuidle_charts[core].cpuidle_state_len; state++) {
+ rrddim_set_by_pointer(cpuidle_charts[core].st, cpuidle_charts[core].cpuidle_state[state].rd, cpuidle_charts[core].cpuidle_state[state].value);
+ }
+ rrdset_done(cpuidle_charts[core].st);
+ }
+ }
+ }
+ }
+
+ if(cpus_var)
+ rrdvar_custom_host_variable_set(localhost, cpus_var, cores_found);
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c b/collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c
new file mode 100644
index 0000000..20d2116
--- /dev/null
+++ b/collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+int do_proc_sys_kernel_random_entropy_avail(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/sys/kernel/random/entropy_avail");
+ ff = procfile_open(config_get("plugin:proc:/proc/sys/kernel/random/entropy_avail", "filename to monitor", filename), "", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ unsigned long long entropy = str2ull(procfile_lineword(ff, 0, 0));
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd = NULL;
+
+ if(unlikely(!st)) {
+ st = rrdset_create_localhost(
+ "system"
+ , "entropy"
+ , NULL
+ , "entropy"
+ , NULL
+ , "Available Entropy"
+ , "entropy"
+ , PLUGIN_PROC_NAME
+ , "/proc/sys/kernel/random/entropy_avail"
+ , NETDATA_CHART_PRIO_SYSTEM_ENTROPY
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd = rrddim_add(st, "entropy", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd, entropy);
+ rrdset_done(st);
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_uptime.c b/collectors/proc.plugin/proc_uptime.c
new file mode 100644
index 0000000..28b00e0
--- /dev/null
+++ b/collectors/proc.plugin/proc_uptime.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+int do_proc_uptime(int update_every, usec_t dt) {
+ (void)dt;
+
+ static char *uptime_filename = NULL;
+ if(!uptime_filename) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/uptime");
+
+ uptime_filename = config_get("plugin:proc:/proc/uptime", "filename to monitor", filename);
+ }
+
+ static RRDSET *st = NULL;
+ static RRDDIM *rd = NULL;
+
+ if(unlikely(!st)) {
+
+ st = rrdset_create_localhost(
+ "system"
+ , "uptime"
+ , NULL
+ , "uptime"
+ , NULL
+ , "System Uptime"
+ , "seconds"
+ , PLUGIN_PROC_NAME
+ , "/proc/uptime"
+ , NETDATA_CHART_PRIO_SYSTEM_UPTIME
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd = rrddim_add(st, "uptime", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st);
+
+ rrddim_set_by_pointer(st, rd, uptime_msec(uptime_filename));
+
+ rrdset_done(st);
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/proc_vmstat.c b/collectors/proc.plugin/proc_vmstat.c
new file mode 100644
index 0000000..7def02d
--- /dev/null
+++ b/collectors/proc.plugin/proc_vmstat.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_VMSTAT_NAME "/proc/vmstat"
+
+int do_proc_vmstat(int update_every, usec_t dt) {
+ (void)dt;
+
+ static procfile *ff = NULL;
+ static int do_swapio = -1, do_io = -1, do_pgfaults = -1, do_numa = -1;
+ static int has_numa = -1;
+
+ static ARL_BASE *arl_base = NULL;
+ static unsigned long long numa_foreign = 0ULL;
+ static unsigned long long numa_hint_faults = 0ULL;
+ static unsigned long long numa_hint_faults_local = 0ULL;
+ static unsigned long long numa_huge_pte_updates = 0ULL;
+ static unsigned long long numa_interleave = 0ULL;
+ static unsigned long long numa_local = 0ULL;
+ static unsigned long long numa_other = 0ULL;
+ static unsigned long long numa_pages_migrated = 0ULL;
+ static unsigned long long numa_pte_updates = 0ULL;
+ static unsigned long long pgfault = 0ULL;
+ static unsigned long long pgmajfault = 0ULL;
+ static unsigned long long pgpgin = 0ULL;
+ static unsigned long long pgpgout = 0ULL;
+ static unsigned long long pswpin = 0ULL;
+ static unsigned long long pswpout = 0ULL;
+
+ if(unlikely(!arl_base)) {
+ do_swapio = config_get_boolean_ondemand("plugin:proc:/proc/vmstat", "swap i/o", CONFIG_BOOLEAN_AUTO);
+ do_io = config_get_boolean("plugin:proc:/proc/vmstat", "disk i/o", 1);
+ do_pgfaults = config_get_boolean("plugin:proc:/proc/vmstat", "memory page faults", 1);
+ do_numa = config_get_boolean_ondemand("plugin:proc:/proc/vmstat", "system-wide numa metric summary", CONFIG_BOOLEAN_AUTO);
+
+
+ arl_base = arl_create("vmstat", NULL, 60);
+ arl_expect(arl_base, "pgfault", &pgfault);
+ arl_expect(arl_base, "pgmajfault", &pgmajfault);
+ arl_expect(arl_base, "pgpgin", &pgpgin);
+ arl_expect(arl_base, "pgpgout", &pgpgout);
+ arl_expect(arl_base, "pswpin", &pswpin);
+ arl_expect(arl_base, "pswpout", &pswpout);
+
+ if(do_numa == CONFIG_BOOLEAN_YES || (do_numa == CONFIG_BOOLEAN_AUTO &&
+ (get_numa_node_count() >= 2 ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ arl_expect(arl_base, "numa_foreign", &numa_foreign);
+ arl_expect(arl_base, "numa_hint_faults_local", &numa_hint_faults_local);
+ arl_expect(arl_base, "numa_hint_faults", &numa_hint_faults);
+ arl_expect(arl_base, "numa_huge_pte_updates", &numa_huge_pte_updates);
+ arl_expect(arl_base, "numa_interleave", &numa_interleave);
+ arl_expect(arl_base, "numa_local", &numa_local);
+ arl_expect(arl_base, "numa_other", &numa_other);
+ arl_expect(arl_base, "numa_pages_migrated", &numa_pages_migrated);
+ arl_expect(arl_base, "numa_pte_updates", &numa_pte_updates);
+ }
+ else {
+ // Do not expect numa metrics when they are not needed.
+ // By not adding them, the ARL will stop processing the file
+ // when all the expected metrics are collected.
+ // Also ARL will not parse their values.
+ has_numa = 0;
+ do_numa = CONFIG_BOOLEAN_NO;
+ }
+ }
+
+ if(unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/vmstat");
+ ff = procfile_open(config_get("plugin:proc:/proc/vmstat", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time
+
+ size_t lines = procfile_lines(ff), l;
+
+ arl_begin(arl_base);
+ for(l = 0; l < lines ;l++) {
+ size_t words = procfile_linewords(ff, l);
+ if(unlikely(words < 2)) {
+ if(unlikely(words)) error("Cannot read /proc/vmstat line %zu. Expected 2 params, read %zu.", l, words);
+ continue;
+ }
+
+ if(unlikely(arl_check(arl_base,
+ procfile_lineword(ff, l, 0),
+ procfile_lineword(ff, l, 1)))) break;
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_swapio == CONFIG_BOOLEAN_YES || (do_swapio == CONFIG_BOOLEAN_AUTO &&
+ (pswpin || pswpout ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_swapio = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_swapio = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_swapio)) {
+ st_swapio = rrdset_create_localhost(
+ "system"
+ , "swapio"
+ , NULL
+ , "swap"
+ , NULL
+ , "Swap I/O"
+ , "KiB/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_VMSTAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_SWAPIO
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_in = rrddim_add(st_swapio, "in", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_swapio, "out", NULL, -sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_swapio);
+
+ rrddim_set_by_pointer(st_swapio, rd_in, pswpin);
+ rrddim_set_by_pointer(st_swapio, rd_out, pswpout);
+ rrdset_done(st_swapio);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_io) {
+ static RRDSET *st_io = NULL;
+ static RRDDIM *rd_in = NULL, *rd_out = NULL;
+
+ if(unlikely(!st_io)) {
+ st_io = rrdset_create_localhost(
+ "system"
+ , "pgpgio"
+ , NULL
+ , "disk"
+ , NULL
+ , "Memory Paged from/to disk"
+ , "KiB/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_VMSTAT_NAME
+ , NETDATA_CHART_PRIO_SYSTEM_PGPGIO
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_in = rrddim_add(st_io, "in", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_out = rrddim_add(st_io, "out", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_io);
+
+ rrddim_set_by_pointer(st_io, rd_in, pgpgin);
+ rrddim_set_by_pointer(st_io, rd_out, pgpgout);
+ rrdset_done(st_io);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_pgfaults) {
+ static RRDSET *st_pgfaults = NULL;
+ static RRDDIM *rd_minor = NULL, *rd_major = NULL;
+
+ if(unlikely(!st_pgfaults)) {
+ st_pgfaults = rrdset_create_localhost(
+ "mem"
+ , "pgfaults"
+ , NULL
+ , "system"
+ , NULL
+ , "Memory Page Faults"
+ , "faults/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_VMSTAT_NAME
+ , NETDATA_CHART_PRIO_MEM_SYSTEM_PGFAULTS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_pgfaults, RRDSET_FLAG_DETAIL);
+
+ rd_minor = rrddim_add(st_pgfaults, "minor", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_major = rrddim_add(st_pgfaults, "major", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_pgfaults);
+
+ rrddim_set_by_pointer(st_pgfaults, rd_minor, pgfault);
+ rrddim_set_by_pointer(st_pgfaults, rd_major, pgmajfault);
+ rrdset_done(st_pgfaults);
+ }
+
+ // --------------------------------------------------------------------
+
+ // Ondemand criteria for NUMA. Since this won't change at run time, we
+ // check it only once. We check whether the node count is >= 2 because
+ // single-node systems have uninteresting statistics (since all accesses
+ // are local).
+ if(unlikely(has_numa == -1))
+
+ has_numa = (numa_local || numa_foreign || numa_interleave || numa_other || numa_pte_updates ||
+ numa_huge_pte_updates || numa_hint_faults || numa_hint_faults_local || numa_pages_migrated) ? 1 : 0;
+
+ if(do_numa == CONFIG_BOOLEAN_YES || (do_numa == CONFIG_BOOLEAN_AUTO && has_numa)) {
+ do_numa = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_numa = NULL;
+ static RRDDIM *rd_local = NULL, *rd_foreign = NULL, *rd_interleave = NULL, *rd_other = NULL, *rd_pte_updates = NULL, *rd_huge_pte_updates = NULL, *rd_hint_faults = NULL, *rd_hint_faults_local = NULL, *rd_pages_migrated = NULL;
+
+ if(unlikely(!st_numa)) {
+ st_numa = rrdset_create_localhost(
+ "mem"
+ , "numa"
+ , NULL
+ , "numa"
+ , NULL
+ , "NUMA events"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_VMSTAT_NAME
+ , NETDATA_CHART_PRIO_MEM_NUMA
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(st_numa, RRDSET_FLAG_DETAIL);
+
+ // These depend on CONFIG_NUMA in the kernel.
+ rd_local = rrddim_add(st_numa, "local", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_foreign = rrddim_add(st_numa, "foreign", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_interleave = rrddim_add(st_numa, "interleave", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_other = rrddim_add(st_numa, "other", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ // The following stats depend on CONFIG_NUMA_BALANCING in the
+ // kernel.
+ rd_pte_updates = rrddim_add(st_numa, "pte_updates", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_huge_pte_updates = rrddim_add(st_numa, "huge_pte_updates", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_hint_faults = rrddim_add(st_numa, "hint_faults", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_hint_faults_local = rrddim_add(st_numa, "hint_faults_local", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pages_migrated = rrddim_add(st_numa, "pages_migrated", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else rrdset_next(st_numa);
+
+ rrddim_set_by_pointer(st_numa, rd_local, numa_local);
+ rrddim_set_by_pointer(st_numa, rd_foreign, numa_foreign);
+ rrddim_set_by_pointer(st_numa, rd_interleave, numa_interleave);
+ rrddim_set_by_pointer(st_numa, rd_other, numa_other);
+
+ rrddim_set_by_pointer(st_numa, rd_pte_updates, numa_pte_updates);
+ rrddim_set_by_pointer(st_numa, rd_huge_pte_updates, numa_huge_pte_updates);
+ rrddim_set_by_pointer(st_numa, rd_hint_faults, numa_hint_faults);
+ rrddim_set_by_pointer(st_numa, rd_hint_faults_local, numa_hint_faults_local);
+ rrddim_set_by_pointer(st_numa, rd_pages_migrated, numa_pages_migrated);
+
+ rrdset_done(st_numa);
+ }
+
+ return 0;
+}
+
diff --git a/collectors/proc.plugin/sys_block_zram.c b/collectors/proc.plugin/sys_block_zram.c
new file mode 100644
index 0000000..170c720
--- /dev/null
+++ b/collectors/proc.plugin/sys_block_zram.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_ZRAM_NAME "/sys/block/zram"
+#define rrdset_obsolete_and_pointer_null(st) do { if(st) { rrdset_is_obsolete(st); (st) = NULL; } } while(st)
+
+typedef struct mm_stat {
+ unsigned long long orig_data_size;
+ unsigned long long compr_data_size;
+ unsigned long long mem_used_total;
+ unsigned long long mem_limit;
+ unsigned long long mem_used_max;
+ unsigned long long same_pages;
+ unsigned long long pages_compacted;
+} MM_STAT;
+
+typedef struct zram_device {
+ procfile *file;
+
+ RRDSET *st_usage;
+ RRDDIM *rd_compr_data_size;
+ RRDDIM *rd_metadata_size;
+
+ RRDSET *st_savings;
+ RRDDIM *rd_original_size;
+ RRDDIM *rd_savings_size;
+
+ RRDSET *st_comp_ratio;
+ RRDDIM *rd_comp_ratio;
+
+ RRDSET *st_alloc_efficiency;
+ RRDDIM *rd_alloc_efficiency;
+} ZRAM_DEVICE;
+
+ // --------------------------------------------------------------------
+
+static int try_get_zram_major_number(procfile *file) {
+ size_t i;
+ unsigned int lines = procfile_lines(file);
+ int id = -1;
+ char *name = NULL;
+ for (i = 0; i < lines; i++)
+ {
+ if (procfile_linewords(file, i) < 2)
+ continue;
+ name = procfile_lineword(file, i, 1);
+ if (strcmp(name, "zram") == 0)
+ {
+ id = str2i(procfile_lineword(file, i, 0));
+ if (id == 0)
+ return -1;
+ return id;
+ }
+ }
+ return -1;
+}
+
+static inline void init_rrd(const char *name, ZRAM_DEVICE *d, int update_every) {
+ char chart_name[RRD_ID_LENGTH_MAX + 1];
+
+ snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_usage.%s", name);
+ d->st_usage = rrdset_create_localhost(
+ "mem"
+ , chart_name
+ , chart_name
+ , name
+ , "mem.zram_usage"
+ , "ZRAM Memory Usage"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_ZRAM_NAME
+ , NETDATA_CHART_PRIO_MEM_ZRAM
+ , update_every
+ , RRDSET_TYPE_AREA);
+ d->rd_compr_data_size = rrddim_add(d->st_usage, "compressed", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_metadata_size = rrddim_add(d->st_usage, "metadata", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+
+ snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_savings.%s", name);
+ d->st_savings = rrdset_create_localhost(
+ "mem"
+ , chart_name
+ , chart_name
+ , name
+ , "mem.zram_savings"
+ , "ZRAM Memory Savings"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_ZRAM_NAME
+ , NETDATA_CHART_PRIO_MEM_ZRAM_SAVINGS
+ , update_every
+ , RRDSET_TYPE_AREA);
+ d->rd_savings_size = rrddim_add(d->st_savings, "savings", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ d->rd_original_size = rrddim_add(d->st_savings, "original", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+
+ snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_ratio.%s", name);
+ d->st_comp_ratio = rrdset_create_localhost(
+ "mem"
+ , chart_name
+ , chart_name
+ , name
+ , "mem.zram_ratio"
+ , "ZRAM Compression Ratio (original to compressed)"
+ , "ratio"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_ZRAM_NAME
+ , NETDATA_CHART_PRIO_MEM_ZRAM_RATIO
+ , update_every
+ , RRDSET_TYPE_LINE);
+ d->rd_comp_ratio = rrddim_add(d->st_comp_ratio, "ratio", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE);
+
+ snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_efficiency.%s", name);
+ d->st_alloc_efficiency = rrdset_create_localhost(
+ "mem"
+ , chart_name
+ , chart_name
+ , name
+ , "mem.zram_efficiency"
+ , "ZRAM Efficiency"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_ZRAM_NAME
+ , NETDATA_CHART_PRIO_MEM_ZRAM_EFFICIENCY
+ , update_every
+ , RRDSET_TYPE_LINE);
+ d->rd_alloc_efficiency = rrddim_add(d->st_alloc_efficiency, "percent", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+}
+
+static int init_devices(DICTIONARY *devices, unsigned int zram_id, int update_every) {
+ int count = 0;
+ DIR *dir = opendir("/dev");
+ struct dirent *de;
+ struct stat st;
+ char filename[FILENAME_MAX + 1];
+ procfile *ff = NULL;
+ ZRAM_DEVICE device;
+
+ if (unlikely(!dir))
+ return 0;
+ while ((de = readdir(dir)))
+ {
+ snprintfz(filename, FILENAME_MAX, "/dev/%s", de->d_name);
+ if (unlikely(stat(filename, &st) != 0))
+ {
+ error("ZRAM : Unable to stat %s: %s", filename, strerror(errno));
+ continue;
+ }
+ if (major(st.st_rdev) == zram_id)
+ {
+ info("ZRAM : Found device %s", filename);
+ snprintfz(filename, FILENAME_MAX, "/sys/block/%s/mm_stat", de->d_name);
+ ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ if (ff == NULL)
+ {
+ error("ZRAM : Failed to open %s: %s", filename, strerror(errno));
+ continue;
+ }
+ device.file = ff;
+ init_rrd(de->d_name, &device, update_every);
+ dictionary_set(devices, de->d_name, &device, sizeof(ZRAM_DEVICE));
+ count++;
+ }
+ }
+ closedir(dir);
+ return count;
+}
+
+static void free_device(DICTIONARY *dict, char *name)
+{
+ ZRAM_DEVICE *d = (ZRAM_DEVICE*)dictionary_get(dict, name);
+ info("ZRAM : Disabling monitoring of device %s", name);
+ rrdset_obsolete_and_pointer_null(d->st_usage);
+ rrdset_obsolete_and_pointer_null(d->st_savings);
+ rrdset_obsolete_and_pointer_null(d->st_alloc_efficiency);
+ rrdset_obsolete_and_pointer_null(d->st_comp_ratio);
+ dictionary_del(dict, name);
+}
+ // --------------------------------------------------------------------
+
+static inline int read_mm_stat(procfile *ff, MM_STAT *stats) {
+ ff = procfile_readall(ff);
+ if (!ff)
+ return -1;
+ if (procfile_lines(ff) < 1) {
+ procfile_close(ff);
+ return -1;
+ }
+ if (procfile_linewords(ff, 0) < 7) {
+ procfile_close(ff);
+ return -1;
+ }
+
+ stats->orig_data_size = str2ull(procfile_word(ff, 0));
+ stats->compr_data_size = str2ull(procfile_word(ff, 1));
+ stats->mem_used_total = str2ull(procfile_word(ff, 2));
+ stats->mem_limit = str2ull(procfile_word(ff, 3));
+ stats->mem_used_max = str2ull(procfile_word(ff, 4));
+ stats->same_pages = str2ull(procfile_word(ff, 5));
+ stats->pages_compacted = str2ull(procfile_word(ff, 6));
+ return 0;
+}
+
+static inline int _collect_zram_metrics(char* name, ZRAM_DEVICE *d, int advance, DICTIONARY* dict) {
+ MM_STAT mm;
+ int value;
+ if (unlikely(read_mm_stat(d->file, &mm) < 0))
+ {
+ free_device(dict, name);
+ return -1;
+ }
+
+ if (likely(advance))
+ {
+ rrdset_next(d->st_usage);
+ rrdset_next(d->st_savings);
+ rrdset_next(d->st_comp_ratio);
+ rrdset_next(d->st_alloc_efficiency);
+ }
+ // zram_usage
+ rrddim_set_by_pointer(d->st_usage, d->rd_compr_data_size, mm.compr_data_size);
+ rrddim_set_by_pointer(d->st_usage, d->rd_metadata_size, mm.mem_used_total - mm.compr_data_size);
+ rrdset_done(d->st_usage);
+ // zram_savings
+ rrddim_set_by_pointer(d->st_savings, d->rd_savings_size, mm.compr_data_size - mm.orig_data_size);
+ rrddim_set_by_pointer(d->st_savings, d->rd_original_size, mm.orig_data_size);
+ rrdset_done(d->st_savings);
+ // zram_ratio
+ value = mm.compr_data_size == 0 ? 1 : mm.orig_data_size * 100 / mm.compr_data_size;
+ rrddim_set_by_pointer(d->st_comp_ratio, d->rd_comp_ratio, value);
+ rrdset_done(d->st_comp_ratio);
+ // zram_efficiency
+ value = mm.mem_used_total == 0 ? 100 : (mm.compr_data_size * 1000000 / mm.mem_used_total);
+ rrddim_set_by_pointer(d->st_alloc_efficiency, d->rd_alloc_efficiency, value);
+ rrdset_done(d->st_alloc_efficiency);
+ return 0;
+}
+
+static int collect_first_zram_metrics(char *name, void *entry, void *data) {
+ // collect without calling rrdset_next (init only)
+ return _collect_zram_metrics(name, (ZRAM_DEVICE *)entry, 0, (DICTIONARY *)data);
+}
+
+static int collect_zram_metrics(char *name, void *entry, void *data) {
+ (void)name;
+ // collect with calling rrdset_next
+ return _collect_zram_metrics(name, (ZRAM_DEVICE *)entry, 1, (DICTIONARY *)data);
+}
+
+ // --------------------------------------------------------------------
+
+int do_sys_block_zram(int update_every, usec_t dt) {
+ (void)dt;
+ static procfile *ff = NULL;
+ static DICTIONARY *devices = NULL;
+ static int initialized = 0;
+ static int device_count = 0;
+ int zram_id = -1;
+ if (unlikely(!initialized))
+ {
+ initialized = 1;
+ ff = procfile_open("/proc/devices", " \t:", PROCFILE_FLAG_DEFAULT);
+ if (ff == NULL)
+ {
+ error("Cannot read /proc/devices");
+ return 1;
+ }
+ ff = procfile_readall(ff);
+ if (!ff)
+ return 1;
+ zram_id = try_get_zram_major_number(ff);
+ if (zram_id == -1)
+ {
+ if (ff != NULL)
+ procfile_close(ff);
+ return 1;
+ }
+ procfile_close(ff);
+
+ devices = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED);
+ device_count = init_devices(devices, (unsigned int)zram_id, update_every);
+ if (device_count < 1)
+ return 1;
+ dictionary_get_all_name_value(devices, collect_first_zram_metrics, devices);
+ }
+ else
+ {
+ if (unlikely(device_count < 1))
+ return 1;
+ dictionary_get_all_name_value(devices, collect_zram_metrics, devices);
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/collectors/proc.plugin/sys_class_infiniband.c b/collectors/proc.plugin/sys_class_infiniband.c
new file mode 100644
index 0000000..46f40f2
--- /dev/null
+++ b/collectors/proc.plugin/sys_class_infiniband.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Heavily inspired from proc_net_dev.c
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_INFINIBAND_NAME "/sys/class/infiniband"
+#define CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND \
+ "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_INFINIBAND_NAME
+
+// ib_device::name[IB_DEVICE_NAME_MAX(64)] + "-" + ib_device::phys_port_cnt[u8 = 3 chars]
+#define IBNAME_MAX 68
+
+// ----------------------------------------------------------------------------
+// infiniband & omnipath standard counters
+
+// I use macro as there's no single file acting as summary, but a lot of different files, so can't use helpers like
+// procfile(). Also, omnipath generates other counters, that are not provided by infiniband
+#define FOREACH_COUNTER(GEN, ...) \
+ FOREACH_COUNTER_BYTES(GEN, __VA_ARGS__) \
+ FOREACH_COUNTER_PACKETS(GEN, __VA_ARGS__) \
+ FOREACH_COUNTER_ERRORS(GEN, __VA_ARGS__)
+
+#define FOREACH_COUNTER_BYTES(GEN, ...) \
+ GEN(port_rcv_data, bytes, "Received", 1, __VA_ARGS__) \
+ GEN(port_xmit_data, bytes, "Sent", -1, __VA_ARGS__)
+
+#define FOREACH_COUNTER_PACKETS(GEN, ...) \
+ GEN(port_rcv_packets, packets, "Received", 1, __VA_ARGS__) \
+ GEN(port_xmit_packets, packets, "Sent", -1, __VA_ARGS__) \
+ GEN(multicast_rcv_packets, packets, "Mcast rcvd", 1, __VA_ARGS__) \
+ GEN(multicast_xmit_packets, packets, "Mcast sent", -1, __VA_ARGS__) \
+ GEN(unicast_rcv_packets, packets, "Ucast rcvd", 1, __VA_ARGS__) \
+ GEN(unicast_xmit_packets, packets, "Ucast sent", -1, __VA_ARGS__)
+
+#define FOREACH_COUNTER_ERRORS(GEN, ...) \
+ GEN(port_rcv_errors, errors, "Pkts malformated", 1, __VA_ARGS__) \
+ GEN(port_rcv_constraint_errors, errors, "Pkts rcvd discarded ", 1, __VA_ARGS__) \
+ GEN(port_xmit_discards, errors, "Pkts sent discarded", 1, __VA_ARGS__) \
+ GEN(port_xmit_wait, errors, "Tick Wait to send", 1, __VA_ARGS__) \
+ GEN(VL15_dropped, errors, "Pkts missed ressource", 1, __VA_ARGS__) \
+ GEN(excessive_buffer_overrun_errors, errors, "Buffer overrun", 1, __VA_ARGS__) \
+ GEN(link_downed, errors, "Link Downed", 1, __VA_ARGS__) \
+ GEN(link_error_recovery, errors, "Link recovered", 1, __VA_ARGS__) \
+ GEN(local_link_integrity_errors, errors, "Link integrity err", 1, __VA_ARGS__) \
+ GEN(symbol_error, errors, "Link minor errors", 1, __VA_ARGS__) \
+ GEN(port_rcv_remote_physical_errors, errors, "Pkts rcvd with EBP", 1, __VA_ARGS__) \
+ GEN(port_rcv_switch_relay_errors, errors, "Pkts rcvd discarded by switch", 1, __VA_ARGS__) \
+ GEN(port_xmit_constraint_errors, errors, "Pkts sent discarded by switch", 1, __VA_ARGS__)
+
+//
+// Hardware Counters
+//
+
+// IMPORTANT: These are vendor-specific fields.
+// If you want to add a new vendor, search this for for 'VENDORS:' keyword and
+// add your definition as 'VENDOR-<key>:' where <key> if the string part that
+// is shown in /sys/class/infiniband/<key>X_Y
+// EG: for Mellanox, shown as mlx0_1, it's 'mlx'
+// for Intel, shown as hfi1_1, it's 'hfi'
+
+// VENDORS: List of implemented hardware vendors
+#define FOREACH_HWCOUNTER_NAME(GEN, ...) GEN(mlx, __VA_ARGS__)
+
+// VENDOR-MLX: HW Counters for Mellanox ConnectX Devices
+#define FOREACH_HWCOUNTER_MLX(GEN, ...) \
+ FOREACH_HWCOUNTER_MLX_PACKETS(GEN, __VA_ARGS__) \
+ FOREACH_HWCOUNTER_MLX_ERRORS(GEN, __VA_ARGS__)
+
+#define FOREACH_HWCOUNTER_MLX_PACKETS(GEN, ...) \
+ GEN(np_cnp_sent, hwpackets, "RoCEv2 Congestion sent", 1, __VA_ARGS__) \
+ GEN(np_ecn_marked_roce_packets, hwpackets, "RoCEv2 Congestion rcvd", -1, __VA_ARGS__) \
+ GEN(rp_cnp_handled, hwpackets, "IB Congestion handled", 1, __VA_ARGS__) \
+ GEN(rx_atomic_requests, hwpackets, "ATOMIC req. rcvd", 1, __VA_ARGS__) \
+ GEN(rx_dct_connect, hwpackets, "Connection req. rcvd", 1, __VA_ARGS__) \
+ GEN(rx_read_requests, hwpackets, "Read req. rcvd", 1, __VA_ARGS__) \
+ GEN(rx_write_requests, hwpackets, "Write req. rcvd", 1, __VA_ARGS__) \
+ GEN(roce_adp_retrans, hwpackets, "RoCE retrans adaptive", 1, __VA_ARGS__) \
+ GEN(roce_adp_retrans_to, hwpackets, "RoCE retrans timeout", 1, __VA_ARGS__) \
+ GEN(roce_slow_restart, hwpackets, "RoCE slow restart", 1, __VA_ARGS__) \
+ GEN(roce_slow_restart_cnps, hwpackets, "RoCE slow restart congestion", 1, __VA_ARGS__) \
+ GEN(roce_slow_restart_trans, hwpackets, "RoCE slow restart count", 1, __VA_ARGS__)
+
+#define FOREACH_HWCOUNTER_MLX_ERRORS(GEN, ...) \
+ GEN(duplicate_request, hwerrors, "Duplicated packets", -1, __VA_ARGS__) \
+ GEN(implied_nak_seq_err, hwerrors, "Pkt Seq Num gap", 1, __VA_ARGS__) \
+ GEN(local_ack_timeout_err, hwerrors, "Ack timer expired", 1, __VA_ARGS__) \
+ GEN(out_of_buffer, hwerrors, "Drop missing buffer", 1, __VA_ARGS__) \
+ GEN(out_of_sequence, hwerrors, "Drop out of sequence", 1, __VA_ARGS__) \
+ GEN(packet_seq_err, hwerrors, "NAK sequence rcvd", 1, __VA_ARGS__) \
+ GEN(req_cqe_error, hwerrors, "CQE err Req", 1, __VA_ARGS__) \
+ GEN(resp_cqe_error, hwerrors, "CQE err Resp", 1, __VA_ARGS__) \
+ GEN(req_cqe_flush_error, hwerrors, "CQE Flushed err Req", 1, __VA_ARGS__) \
+ GEN(resp_cqe_flush_error, hwerrors, "CQE Flushed err Resp", 1, __VA_ARGS__) \
+ GEN(req_remote_access_errors, hwerrors, "Remote access err Req", 1, __VA_ARGS__) \
+ GEN(resp_remote_access_errors, hwerrors, "Remote access err Resp", 1, __VA_ARGS__) \
+ GEN(req_remote_invalid_request, hwerrors, "Remote invalid req", 1, __VA_ARGS__) \
+ GEN(resp_local_length_error, hwerrors, "Local length err Resp", 1, __VA_ARGS__) \
+ GEN(rnr_nak_retry_err, hwerrors, "RNR NAK Packets", 1, __VA_ARGS__) \
+ GEN(rp_cnp_ignored, hwerrors, "CNP Pkts ignored", 1, __VA_ARGS__) \
+ GEN(rx_icrc_encapsulated, hwerrors, "RoCE ICRC Errors", 1, __VA_ARGS__)
+
+// Common definitions used more than once
+#define GEN_RRD_DIM_ADD(NAME, GRP, DESC, DIR, PORT) \
+ GEN_RRD_DIM_ADD_CUSTOM(NAME, GRP, DESC, DIR, PORT, 1, 1, RRD_ALGORITHM_INCREMENTAL)
+
+#define GEN_RRD_DIM_ADD_CUSTOM(NAME, GRP, DESC, DIR, PORT, MULT, DIV, ALGO) \
+ PORT->rd_##NAME = rrddim_add(PORT->st_##GRP, DESC, NULL, DIR * MULT, DIV, ALGO);
+
+#define GEN_RRD_DIM_ADD_HW(NAME, GRP, DESC, DIR, PORT, HW) \
+ HW->rd_##NAME = rrddim_add(PORT->st_##GRP, DESC, NULL, DIR, 1, RRD_ALGORITHM_INCREMENTAL);
+
+#define GEN_RRD_DIM_SETP(NAME, GRP, DESC, DIR, PORT) \
+ rrddim_set_by_pointer(PORT->st_##GRP, PORT->rd_##NAME, (collected_number)PORT->NAME);
+
+#define GEN_RRD_DIM_SETP_HW(NAME, GRP, DESC, DIR, PORT, HW) \
+ rrddim_set_by_pointer(PORT->st_##GRP, HW->rd_##NAME, (collected_number)HW->NAME);
+
+// https://community.mellanox.com/s/article/understanding-mlx5-linux-counters-and-status-parameters
+// https://community.mellanox.com/s/article/infiniband-port-counters
+static struct ibport {
+ char *name;
+ char *counters_path;
+ char *hwcounters_path;
+ int len;
+
+ // flags
+ int configured;
+ int enabled;
+ int discovered;
+
+ int do_bytes;
+ int do_packets;
+ int do_errors;
+ int do_hwpackets;
+ int do_hwerrors;
+
+ const char *chart_type_bytes;
+ const char *chart_type_packets;
+ const char *chart_type_errors;
+ const char *chart_type_hwpackets;
+ const char *chart_type_hwerrors;
+
+ const char *chart_id_bytes;
+ const char *chart_id_packets;
+ const char *chart_id_errors;
+ const char *chart_id_hwpackets;
+ const char *chart_id_hwerrors;
+
+ const char *chart_family;
+
+ unsigned long priority;
+
+ // Port details using drivers/infiniband/core/sysfs.c :: rate_show()
+ RRDDIM *rd_speed;
+ uint64_t speed;
+ uint64_t width;
+
+// Stats from /$device/ports/$portid/counters
+// as drivers/infiniband/hw/qib/qib_verbs.h
+// All uint64 except vl15_dropped, local_link_integrity_errors, excessive_buffer_overrun_errors uint32
+// Will generate 2 elements for each counter:
+// - uint64_t to store the value
+// - char* to store the filename path
+// - RRDDIM* to store the RRD Dimension
+#define GEN_DEF_COUNTER(NAME, ...) \
+ uint64_t NAME; \
+ char *file_##NAME; \
+ RRDDIM *rd_##NAME;
+ FOREACH_COUNTER(GEN_DEF_COUNTER)
+
+// Vendor specific hwcounters from /$device/ports/$portid/hw_counters
+// We will generate one struct pointer per vendor to avoid future casting
+#define GEN_DEF_HWCOUNTER_PTR(VENDOR, ...) struct ibporthw_##VENDOR *hwcounters_##VENDOR;
+ FOREACH_HWCOUNTER_NAME(GEN_DEF_HWCOUNTER_PTR)
+
+ // Function pointer to the "infiniband_hwcounters_parse_<vendor>" function
+ void (*hwcounters_parse)(struct ibport *);
+ void (*hwcounters_dorrd)(struct ibport *);
+
+ // charts and dim
+ RRDSET *st_bytes;
+ RRDSET *st_packets;
+ RRDSET *st_errors;
+ RRDSET *st_hwpackets;
+ RRDSET *st_hwerrors;
+
+ RRDSETVAR *stv_speed;
+
+ usec_t speed_last_collected_usec;
+
+ struct ibport *next;
+} *ibport_root = NULL, *ibport_last_used = NULL;
+
+// VENDORS: reading / calculation functions
+#define GEN_DEF_HWCOUNTER(NAME, ...) \
+ uint64_t NAME; \
+ char *file_##NAME; \
+ RRDDIM *rd_##NAME;
+
+#define GEN_DO_HWCOUNTER_READ(NAME, GRP, DESC, DIR, PORT, HW, ...) \
+ if (HW->file_##NAME) { \
+ if (read_single_number_file(HW->file_##NAME, (unsigned long long *)&HW->NAME)) { \
+ error("cannot read iface '%s' hwcounter '" #HW "'", PORT->name); \
+ HW->file_##NAME = NULL; \
+ } \
+ }
+
+// VENDOR-MLX: Mellanox
+struct ibporthw_mlx {
+ FOREACH_HWCOUNTER_MLX(GEN_DEF_HWCOUNTER)
+};
+void infiniband_hwcounters_parse_mlx(struct ibport *port)
+{
+ if (port->do_hwerrors != CONFIG_BOOLEAN_NO)
+ FOREACH_HWCOUNTER_MLX_ERRORS(GEN_DO_HWCOUNTER_READ, port, port->hwcounters_mlx)
+ if (port->do_hwpackets != CONFIG_BOOLEAN_NO)
+ FOREACH_HWCOUNTER_MLX_PACKETS(GEN_DO_HWCOUNTER_READ, port, port->hwcounters_mlx)
+}
+void infiniband_hwcounters_dorrd_mlx(struct ibport *port)
+{
+ if (port->do_hwerrors != CONFIG_BOOLEAN_NO) {
+ FOREACH_HWCOUNTER_MLX_ERRORS(GEN_RRD_DIM_SETP_HW, port, port->hwcounters_mlx)
+ rrdset_done(port->st_hwerrors);
+ }
+ if (port->do_hwpackets != CONFIG_BOOLEAN_NO) {
+ FOREACH_HWCOUNTER_MLX_PACKETS(GEN_RRD_DIM_SETP_HW, port, port->hwcounters_mlx)
+ rrdset_done(port->st_hwpackets);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+static struct ibport *get_ibport(const char *dev, const char *port)
+{
+ struct ibport *p;
+
+ char name[IBNAME_MAX + 1];
+ snprintfz(name, IBNAME_MAX, "%s-%s", dev, port);
+
+ // search it, resuming from the last position in sequence
+ for (p = ibport_last_used; p; p = p->next) {
+ if (unlikely(!strcmp(name, p->name))) {
+ ibport_last_used = p->next;
+ return p;
+ }
+ }
+
+ // new round, from the beginning to the last position used this time
+ for (p = ibport_root; p != ibport_last_used; p = p->next) {
+ if (unlikely(!strcmp(name, p->name))) {
+ ibport_last_used = p->next;
+ return p;
+ }
+ }
+
+ // create a new one
+ p = callocz(1, sizeof(struct ibport));
+ p->name = strdupz(name);
+ p->len = strlen(p->name);
+
+ p->chart_type_bytes = strdupz("infiniband_cnt_bytes");
+ p->chart_type_packets = strdupz("infiniband_cnt_packets");
+ p->chart_type_errors = strdupz("infiniband_cnt_errors");
+ p->chart_type_hwpackets = strdupz("infiniband_hwc_packets");
+ p->chart_type_hwerrors = strdupz("infiniband_hwc_errors");
+
+ char buffer[RRD_ID_LENGTH_MAX + 1];
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "ib_cntbytes_%s", p->name);
+ p->chart_id_bytes = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "ib_cntpackets_%s", p->name);
+ p->chart_id_packets = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "ib_cnterrors_%s", p->name);
+ p->chart_id_errors = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "ib_hwcntpackets_%s", p->name);
+ p->chart_id_hwpackets = strdupz(buffer);
+
+ snprintfz(buffer, RRD_ID_LENGTH_MAX, "ib_hwcnterrors_%s", p->name);
+ p->chart_id_hwerrors = strdupz(buffer);
+
+ p->chart_family = strdupz(p->name);
+ p->priority = NETDATA_CHART_PRIO_INFINIBAND;
+
+ // Link current ibport to last one in the list
+ if (ibport_root) {
+ struct ibport *t;
+ for (t = ibport_root; t->next; t = t->next)
+ ;
+ t->next = p;
+ } else
+ ibport_root = p;
+
+ return p;
+}
+
+int do_sys_class_infiniband(int update_every, usec_t dt)
+{
+ (void)dt;
+ static SIMPLE_PATTERN *disabled_list = NULL;
+ static int initialized = 0;
+ static int enable_new_ports = -1, enable_only_active = CONFIG_BOOLEAN_YES;
+ static int do_bytes = -1, do_packets = -1, do_errors = -1, do_hwpackets = -1, do_hwerrors = -1;
+ static char *sys_class_infiniband_dirname = NULL;
+
+ static long long int dt_to_refresh_ports = 0, last_refresh_ports_usec = 0;
+
+ if (unlikely(enable_new_ports == -1)) {
+ char dirname[FILENAME_MAX + 1];
+
+ snprintfz(dirname, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/class/infiniband");
+ sys_class_infiniband_dirname =
+ config_get(CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "dirname to monitor", dirname);
+
+ do_bytes = config_get_boolean_ondemand(
+ CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "bandwidth counters", CONFIG_BOOLEAN_YES);
+ do_packets = config_get_boolean_ondemand(
+ CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "packets counters", CONFIG_BOOLEAN_YES);
+ do_errors = config_get_boolean_ondemand(
+ CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "errors counters", CONFIG_BOOLEAN_YES);
+ do_hwpackets = config_get_boolean_ondemand(
+ CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "hardware packets counters", CONFIG_BOOLEAN_AUTO);
+ do_hwerrors = config_get_boolean_ondemand(
+ CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "hardware errors counters", CONFIG_BOOLEAN_AUTO);
+
+ enable_only_active = config_get_boolean_ondemand(
+ CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "monitor only active ports", CONFIG_BOOLEAN_AUTO);
+ disabled_list = simple_pattern_create(
+ config_get(CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "disable by default interfaces matching", ""), NULL,
+ SIMPLE_PATTERN_EXACT);
+
+ dt_to_refresh_ports =
+ config_get_number(CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "refresh ports state every seconds", 30) *
+ USEC_PER_SEC;
+ if (dt_to_refresh_ports < 0)
+ dt_to_refresh_ports = 0;
+ }
+
+ // init listing of /sys/class/infiniband/ (or rediscovery)
+ if (unlikely(!initialized) || unlikely(last_refresh_ports_usec >= dt_to_refresh_ports)) {
+ // If folder does not exists, return 1 to disable
+ DIR *devices_dir = opendir(sys_class_infiniband_dirname);
+ if (unlikely(!devices_dir))
+ return 1;
+
+ // Work on all device available
+ struct dirent *dev_dent;
+ while ((dev_dent = readdir(devices_dir))) {
+ // Skip special folders
+ if (!strcmp(dev_dent->d_name, "..") || !strcmp(dev_dent->d_name, "."))
+ continue;
+
+ // /sys/class/infiniband/<dev>/ports
+ char ports_dirname[FILENAME_MAX + 1];
+ snprintfz(ports_dirname, FILENAME_MAX, "%s/%s/%s", sys_class_infiniband_dirname, dev_dent->d_name, "ports");
+
+ DIR *ports_dir = opendir(ports_dirname);
+ if (unlikely(!ports_dir))
+ continue;
+
+ struct dirent *port_dent;
+ while ((port_dent = readdir(ports_dir))) {
+ // Skip special folders
+ if (!strcmp(port_dent->d_name, "..") || !strcmp(port_dent->d_name, "."))
+ continue;
+
+ char buffer[FILENAME_MAX + 1];
+
+ // Check if counters are availablea (mandatory)
+ // /sys/class/infiniband/<device>/ports/<port>/counters
+ char counters_dirname[FILENAME_MAX + 1];
+ snprintfz(counters_dirname, FILENAME_MAX, "%s/%s/%s", ports_dirname, port_dent->d_name, "counters");
+ DIR *counters_dir = opendir(counters_dirname);
+ // Standard counters are mandatory
+ if (!counters_dir)
+ continue;
+ closedir(counters_dir);
+
+ // Hardware Counters are optionnal, used later
+ char hwcounters_dirname[FILENAME_MAX + 1];
+ snprintfz(
+ hwcounters_dirname, FILENAME_MAX, "%s/%s/%s", ports_dirname, port_dent->d_name, "hw_counters");
+
+ // Get new or existing ibport
+ struct ibport *p = get_ibport(dev_dent->d_name, port_dent->d_name);
+ if (!p)
+ continue;
+
+ // Prepare configuration
+ if (!p->configured) {
+ p->configured = 1;
+
+ // Enable by default, will be filtered out later
+ p->enabled = 1;
+
+ p->counters_path = strdupz(counters_dirname);
+ p->hwcounters_path = strdupz(hwcounters_dirname);
+
+ snprintfz(buffer, FILENAME_MAX, "plugin:proc:/sys/class/infiniband:%s", p->name);
+
+ // Standard counters
+ p->do_bytes = config_get_boolean_ondemand(buffer, "bytes", do_bytes);
+ p->do_packets = config_get_boolean_ondemand(buffer, "packets", do_packets);
+ p->do_errors = config_get_boolean_ondemand(buffer, "errors", do_errors);
+
+// Gen filename allocation and concatenation
+#define GEN_DO_COUNTER_NAME(NAME, GRP, DESC, DIR, PORT, ...) \
+ PORT->file_##NAME = callocz(1, strlen(PORT->counters_path) + sizeof(#NAME) + 3); \
+ strcat(PORT->file_##NAME, PORT->counters_path); \
+ strcat(PORT->file_##NAME, "/" #NAME);
+ FOREACH_COUNTER(GEN_DO_COUNTER_NAME, p)
+
+ // Check HW Counters vendor dependent
+ DIR *hwcounters_dir = opendir(hwcounters_dirname);
+ if (hwcounters_dir) {
+ // By default set standard
+ p->do_hwpackets = config_get_boolean_ondemand(buffer, "hwpackets", do_hwpackets);
+ p->do_hwerrors = config_get_boolean_ondemand(buffer, "hwerrors", do_hwerrors);
+
+// VENDORS: Set your own
+
+// Allocate the chars to the filenames
+#define GEN_DO_HWCOUNTER_NAME(NAME, GRP, DESC, DIR, PORT, HW, ...) \
+ HW->file_##NAME = callocz(1, strlen(PORT->hwcounters_path) + sizeof(#NAME) + 3); \
+ strcat(HW->file_##NAME, PORT->hwcounters_path); \
+ strcat(HW->file_##NAME, "/" #NAME);
+
+ // VENDOR-MLX: Mellanox
+ if (strncmp(dev_dent->d_name, "mlx", 3) == 0) {
+ // Allocate the vendor specific struct
+ p->hwcounters_mlx = callocz(1, sizeof(struct ibporthw_mlx));
+
+ FOREACH_HWCOUNTER_MLX(GEN_DO_HWCOUNTER_NAME, p, p->hwcounters_mlx)
+
+ // Set the function pointer for hwcounter parsing
+ p->hwcounters_parse = &infiniband_hwcounters_parse_mlx;
+ p->hwcounters_dorrd = &infiniband_hwcounters_dorrd_mlx;
+ }
+
+ // VENDOR: Unknown
+ else {
+ p->do_hwpackets = CONFIG_BOOLEAN_NO;
+ p->do_hwerrors = CONFIG_BOOLEAN_NO;
+ }
+ closedir(hwcounters_dir);
+ }
+ }
+
+ // Check port state to keep activation
+ if (enable_only_active) {
+ snprintfz(buffer, FILENAME_MAX, "%s/%s/%s", ports_dirname, port_dent->d_name, "state");
+ unsigned long long active;
+ // File is "1: DOWN" or "4: ACTIVE", but str2ull will stop on first non-decimal char
+ read_single_number_file(buffer, &active);
+
+ // Want "IB_PORT_ACTIVE" == "4", as defined by drivers/infiniband/core/sysfs.c::state_show()
+ if (active == 4)
+ p->enabled = 1;
+ else
+ p->enabled = 0;
+ }
+
+ if (p->enabled)
+ p->enabled = !simple_pattern_matches(disabled_list, p->name);
+
+ // Check / Update the link speed & width frm "rate" file
+ // Sample output: "100 Gb/sec (4X EDR)"
+ snprintfz(buffer, FILENAME_MAX, "%s/%s/%s", ports_dirname, port_dent->d_name, "rate");
+ char buffer_rate[65];
+ if (read_file(buffer, buffer_rate, 64)) {
+ error("Unable to read '%s'", buffer);
+ p->width = 1;
+ } else {
+ char *buffer_width = strstr(buffer_rate, "(");
+ buffer_width++;
+ // str2ull will stop on first non-decimal value
+ p->speed = str2ull(buffer_rate);
+ p->width = str2ull(buffer_width);
+ }
+
+ if (!p->discovered)
+ info(
+ "Infiniband card %s port %s at speed %lu width %lu", dev_dent->d_name, port_dent->d_name,
+ p->speed, p->width);
+
+ p->discovered = 1;
+ }
+ closedir(ports_dir);
+ }
+ closedir(devices_dir);
+
+ initialized = 1;
+ last_refresh_ports_usec = 0;
+ }
+ last_refresh_ports_usec += dt;
+
+ // Update all ports values
+ struct ibport *port;
+ for (port = ibport_root; port; port = port->next) {
+ if (!port->enabled)
+ continue;
+ //
+ // Read values from system to struct
+ //
+
+// counter from file and place it in ibport struct
+#define GEN_DO_COUNTER_READ(NAME, GRP, DESC, DIR, PORT, ...) \
+ if (PORT->file_##NAME) { \
+ if (read_single_number_file(PORT->file_##NAME, (unsigned long long *)&PORT->NAME)) { \
+ error("cannot read iface '%s' counter '" #NAME "'", PORT->name); \
+ PORT->file_##NAME = NULL; \
+ } \
+ }
+
+ // Update charts
+ if (port->do_bytes != CONFIG_BOOLEAN_NO) {
+ // Read values from sysfs
+ FOREACH_COUNTER_BYTES(GEN_DO_COUNTER_READ, port)
+
+ // First creation of RRD Set (charts)
+ if (unlikely(!port->st_bytes)) {
+ port->st_bytes = rrdset_create_localhost(
+ "Infiniband",
+ port->chart_id_bytes,
+ NULL,
+ port->chart_family,
+ "ib.bytes",
+ "Bandwidth usage",
+ "kilobits/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_INFINIBAND_NAME,
+ port->priority + 1,
+ update_every,
+ RRDSET_TYPE_AREA);
+ // Create Dimensions
+ rrdset_flag_set(port->st_bytes, RRDSET_FLAG_DETAIL);
+ // On this chart, we want to have a KB/s so the dashboard will autoscale it
+ // The reported values are also per-lane, so we must multiply it by the width
+ FOREACH_COUNTER_BYTES(GEN_RRD_DIM_ADD_CUSTOM, port, 8 * port->width, 1024, RRD_ALGORITHM_INCREMENTAL)
+
+ port->stv_speed = rrdsetvar_custom_chart_variable_create(port->st_bytes, "link_speed");
+ } else
+ rrdset_next(port->st_bytes);
+
+ // Link read values to dimensions
+ FOREACH_COUNTER_BYTES(GEN_RRD_DIM_SETP, port)
+
+ // For link speed set only variable
+ rrdsetvar_custom_chart_variable_set(port->stv_speed, port->speed);
+
+ rrdset_done(port->st_bytes);
+ }
+
+ if (port->do_packets != CONFIG_BOOLEAN_NO) {
+ // Read values from sysfs
+ FOREACH_COUNTER_PACKETS(GEN_DO_COUNTER_READ, port)
+
+ // First creation of RRD Set (charts)
+ if (unlikely(!port->st_packets)) {
+ port->st_packets = rrdset_create_localhost(
+ "Infiniband",
+ port->chart_id_packets,
+ NULL,
+ port->chart_family,
+ "ib.packets",
+ "Packets Statistics",
+ "packets/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_INFINIBAND_NAME,
+ port->priority + 2,
+ update_every,
+ RRDSET_TYPE_AREA);
+ // Create Dimensions
+ rrdset_flag_set(port->st_packets, RRDSET_FLAG_DETAIL);
+ FOREACH_COUNTER_PACKETS(GEN_RRD_DIM_ADD, port)
+ } else
+ rrdset_next(port->st_packets);
+
+ // Link read values to dimensions
+ FOREACH_COUNTER_PACKETS(GEN_RRD_DIM_SETP, port)
+ rrdset_done(port->st_packets);
+ }
+
+ if (port->do_errors != CONFIG_BOOLEAN_NO) {
+ // Read values from sysfs
+ FOREACH_COUNTER_ERRORS(GEN_DO_COUNTER_READ, port)
+
+ // First creation of RRD Set (charts)
+ if (unlikely(!port->st_errors)) {
+ port->st_errors = rrdset_create_localhost(
+ "Infiniband",
+ port->chart_id_errors,
+ NULL,
+ port->chart_family,
+ "ib.errors",
+ "Error Counters",
+ "errors/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_INFINIBAND_NAME,
+ port->priority + 3,
+ update_every,
+ RRDSET_TYPE_LINE);
+ // Create Dimensions
+ rrdset_flag_set(port->st_errors, RRDSET_FLAG_DETAIL);
+ FOREACH_COUNTER_ERRORS(GEN_RRD_DIM_ADD, port)
+ } else
+ rrdset_next(port->st_errors);
+
+ // Link read values to dimensions
+ FOREACH_COUNTER_ERRORS(GEN_RRD_DIM_SETP, port)
+ rrdset_done(port->st_errors);
+ }
+
+ //
+ // HW Counters
+ //
+
+ // Call the function for parsing and setting hwcounters
+ if (port->hwcounters_parse && port->hwcounters_dorrd) {
+ // Read all values (done by vendor-specific function)
+ (*port->hwcounters_parse)(port);
+
+ if (port->do_hwerrors != CONFIG_BOOLEAN_NO) {
+ // First creation of RRD Set (charts)
+ if (unlikely(!port->st_hwerrors)) {
+ port->st_hwerrors = rrdset_create_localhost(
+ "Infiniband",
+ port->chart_id_hwerrors,
+ NULL,
+ port->chart_family,
+ "ib.hwerrors",
+ "Hardware Errors",
+ "errors/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_INFINIBAND_NAME,
+ port->priority + 4,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_flag_set(port->st_hwerrors, RRDSET_FLAG_DETAIL);
+
+ // VENDORS: Set your selection
+
+ // VENDOR: Mellanox
+ if (strncmp(port->name, "mlx", 3) == 0) {
+ FOREACH_HWCOUNTER_MLX_ERRORS(GEN_RRD_DIM_ADD_HW, port, port->hwcounters_mlx)
+ }
+
+ // Unknown vendor, should not happen
+ else {
+ error(
+ "Unmanaged vendor for '%s', do_hwerrors should have been set to no. Please report this bug",
+ port->name);
+ port->do_hwerrors = CONFIG_BOOLEAN_NO;
+ }
+ } else
+ rrdset_next(port->st_hwerrors);
+ }
+
+ if (port->do_hwpackets != CONFIG_BOOLEAN_NO) {
+ // First creation of RRD Set (charts)
+ if (unlikely(!port->st_hwpackets)) {
+ port->st_hwpackets = rrdset_create_localhost(
+ "Infiniband",
+ port->chart_id_hwpackets,
+ NULL,
+ port->chart_family,
+ "ib.hwpackets",
+ "Hardware Packets Statistics",
+ "packets/s",
+ PLUGIN_PROC_NAME,
+ PLUGIN_PROC_MODULE_INFINIBAND_NAME,
+ port->priority + 5,
+ update_every,
+ RRDSET_TYPE_LINE);
+
+ rrdset_flag_set(port->st_hwpackets, RRDSET_FLAG_DETAIL);
+
+ // VENDORS: Set your selection
+
+ // VENDOR: Mellanox
+ if (strncmp(port->name, "mlx", 3) == 0) {
+ FOREACH_HWCOUNTER_MLX_PACKETS(GEN_RRD_DIM_ADD_HW, port, port->hwcounters_mlx)
+ }
+
+ // Unknown vendor, should not happen
+ else {
+ error(
+ "Unmanaged vendor for '%s', do_hwpackets should have been set to no. Please report this bug",
+ port->name);
+ port->do_hwpackets = CONFIG_BOOLEAN_NO;
+ }
+ } else
+ rrdset_next(port->st_hwpackets);
+ }
+
+ // Update values to rrd (done by vendor-specific function)
+ (*port->hwcounters_dorrd)(port);
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/sys_class_power_supply.c b/collectors/proc.plugin/sys_class_power_supply.c
new file mode 100644
index 0000000..c558a38
--- /dev/null
+++ b/collectors/proc.plugin/sys_class_power_supply.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME "/sys/class/power_supply"
+
+const char *ps_property_names[] = { "charge", "energy", "voltage"};
+const char *ps_property_titles[] = {"Battery charge", "Battery energy", "Power supply voltage"};
+const char *ps_property_units[] = { "Ah", "Wh", "V"};
+
+const char *ps_property_dim_names[] = {"empty_design", "empty", "now", "full", "full_design",
+ "empty_design", "empty", "now", "full", "full_design",
+ "min_design", "min", "now", "max", "max_design"};
+
+struct ps_property_dim {
+ char *name;
+ char *filename;
+ int fd;
+
+ RRDDIM *rd;
+ unsigned long long value;
+ int always_zero;
+
+ struct ps_property_dim *next;
+};
+
+struct ps_property {
+ char *name;
+ char *title;
+ char *units;
+
+ RRDSET *st;
+
+ struct ps_property_dim *property_dim_root;
+
+ struct ps_property *next;
+};
+
+struct capacity {
+ char *filename;
+ int fd;
+
+ RRDSET *st;
+ RRDDIM *rd;
+ unsigned long long value;
+};
+
+struct power_supply {
+ char *name;
+ uint32_t hash;
+ int found;
+
+ struct capacity *capacity;
+
+ struct ps_property *property_root;
+
+ struct power_supply *next;
+};
+
+static struct power_supply *power_supply_root = NULL;
+static int files_num = 0;
+
+void power_supply_free(struct power_supply *ps) {
+ if(likely(ps)) {
+
+ // free capacity structure
+ if(likely(ps->capacity)) {
+ if(likely(ps->capacity->st)) rrdset_is_obsolete(ps->capacity->st);
+ freez(ps->capacity->filename);
+ if(likely(ps->capacity->fd != -1)) close(ps->capacity->fd);
+ files_num--;
+ freez(ps->capacity);
+ }
+ freez(ps->name);
+
+ struct ps_property *pr = ps->property_root;
+ while(likely(pr)) {
+
+ // free dimensions
+ struct ps_property_dim *pd = pr->property_dim_root;
+ while(likely(pd)) {
+ freez(pd->name);
+ freez(pd->filename);
+ if(likely(pd->fd != -1)) close(pd->fd);
+ files_num--;
+ struct ps_property_dim *d = pd;
+ pd = pd->next;
+ freez(d);
+ }
+
+ // free properties
+ if(likely(pr->st)) rrdset_is_obsolete(pr->st);
+ freez(pr->name);
+ freez(pr->title);
+ freez(pr->units);
+ struct ps_property *p = pr;
+ pr = pr->next;
+ freez(p);
+ }
+
+ // remove power supply from linked list
+ if(likely(ps == power_supply_root)) {
+ power_supply_root = ps->next;
+ }
+ else {
+ struct power_supply *last;
+ for(last = power_supply_root; last && last->next != ps; last = last->next);
+ if(likely(last)) last->next = ps->next;
+ }
+
+ freez(ps);
+ }
+}
+
+int do_sys_class_power_supply(int update_every, usec_t dt) {
+ (void)dt;
+ static int do_capacity = -1, do_property[3] = {-1};
+ static int keep_fds_open = CONFIG_BOOLEAN_NO, keep_fds_open_config = -1;
+ static char *dirname = NULL;
+
+ if(unlikely(do_capacity == -1)) {
+ do_capacity = config_get_boolean("plugin:proc:/sys/class/power_supply", "battery capacity", CONFIG_BOOLEAN_YES);
+ do_property[0] = config_get_boolean("plugin:proc:/sys/class/power_supply", "battery charge", CONFIG_BOOLEAN_NO);
+ do_property[1] = config_get_boolean("plugin:proc:/sys/class/power_supply", "battery energy", CONFIG_BOOLEAN_NO);
+ do_property[2] = config_get_boolean("plugin:proc:/sys/class/power_supply", "power supply voltage", CONFIG_BOOLEAN_NO);
+
+ keep_fds_open_config = config_get_boolean_ondemand("plugin:proc:/sys/class/power_supply", "keep files open", CONFIG_BOOLEAN_AUTO);
+
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/class/power_supply");
+ dirname = config_get("plugin:proc:/sys/class/power_supply", "directory to monitor", filename);
+ }
+
+ DIR *dir = opendir(dirname);
+ if(unlikely(!dir)) {
+ error("Cannot read directory '%s'", dirname);
+ return 1;
+ }
+
+ struct dirent *de = NULL;
+ while(likely(de = readdir(dir))) {
+ if(likely(de->d_type == DT_DIR
+ && (
+ (de->d_name[0] == '.' && de->d_name[1] == '\0')
+ || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
+ )))
+ continue;
+
+ if(likely(de->d_type == DT_LNK || de->d_type == DT_DIR)) {
+ uint32_t hash = simple_hash(de->d_name);
+
+ struct power_supply *ps;
+ for(ps = power_supply_root; ps; ps = ps->next) {
+ if(unlikely(ps->hash == hash && !strcmp(ps->name, de->d_name))) {
+ ps->found = 1;
+ break;
+ }
+ }
+
+ // allocate memory for power supply and initialize it
+ if(unlikely(!ps)) {
+ ps = callocz(sizeof(struct power_supply), 1);
+ ps->name = strdupz(de->d_name);
+ ps->hash = simple_hash(de->d_name);
+ ps->found = 1;
+ ps->next = power_supply_root;
+ power_supply_root = ps;
+
+ struct stat stbuf;
+ if(likely(do_capacity != CONFIG_BOOLEAN_NO)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/%s/%s", dirname, de->d_name, "capacity");
+ if (stat(filename, &stbuf) == 0) {
+ ps->capacity = callocz(sizeof(struct capacity), 1);
+ ps->capacity->filename = strdupz(filename);
+ ps->capacity->fd = -1;
+ files_num++;
+ }
+ }
+
+ // allocate memory and initialize structures for every property and file found
+ size_t pr_idx, pd_idx;
+ size_t prev_idx = 3; // there is no property with this index
+
+ for(pr_idx = 0; pr_idx < 3; pr_idx++) {
+ if(unlikely(do_property[pr_idx] != CONFIG_BOOLEAN_NO)) {
+ struct ps_property *pr = NULL;
+ int min_value_found = 0, max_value_found = 0;
+
+ for(pd_idx = pr_idx * 5; pd_idx < pr_idx * 5 + 5; pd_idx++) {
+
+ // check if file exists
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s/%s/%s_%s", dirname, de->d_name,
+ ps_property_names[pr_idx], ps_property_dim_names[pd_idx]);
+ if (stat(filename, &stbuf) == 0) {
+
+ if(unlikely(pd_idx == pr_idx * 5 + 1))
+ min_value_found = 1;
+ if(unlikely(pd_idx == pr_idx * 5 + 3))
+ max_value_found = 1;
+
+ // add chart
+ if(unlikely(prev_idx != pr_idx)) {
+ pr = callocz(sizeof(struct ps_property), 1);
+ pr->name = strdupz(ps_property_names[pr_idx]);
+ pr->title = strdupz(ps_property_titles[pr_idx]);
+ pr->units = strdupz(ps_property_units[pr_idx]);
+ prev_idx = pr_idx;
+ pr->next = ps->property_root;
+ ps->property_root = pr;
+ }
+
+ // add dimension
+ struct ps_property_dim *pd;
+ pd= callocz(sizeof(struct ps_property_dim), 1);
+ pd->name = strdupz(ps_property_dim_names[pd_idx]);
+ pd->filename = strdupz(filename);
+ pd->fd = -1;
+ files_num++;
+ pd->next = pr->property_dim_root;
+ pr->property_dim_root = pd;
+ }
+ }
+
+ // create a zero empty/min dimension
+ if(unlikely(max_value_found && !min_value_found)) {
+ struct ps_property_dim *pd;
+ pd= callocz(sizeof(struct ps_property_dim), 1);
+ pd->name = strdupz(ps_property_dim_names[pr_idx * 5 + 1]);
+ pd->always_zero = 1;
+ pd->next = pr->property_dim_root;
+ pr->property_dim_root = pd;
+ }
+ }
+ }
+ }
+
+ // read capacity file
+ if(likely(ps->capacity)) {
+ char buffer[30 + 1];
+
+ if(unlikely(ps->capacity->fd == -1)) {
+ ps->capacity->fd = open(ps->capacity->filename, O_RDONLY, 0666);
+ if(unlikely(ps->capacity->fd == -1)) {
+ error("Cannot open file '%s'", ps->capacity->filename);
+ power_supply_free(ps);
+ ps = NULL;
+ }
+ }
+
+ if (ps)
+ {
+ ssize_t r = read(ps->capacity->fd, buffer, 30);
+ if(unlikely(r < 1)) {
+ error("Cannot read file '%s'", ps->capacity->filename);
+ power_supply_free(ps);
+ ps = NULL;
+ }
+ else {
+ buffer[r] = '\0';
+ ps->capacity->value = str2ull(buffer);
+
+ if(unlikely(!keep_fds_open)) {
+ close(ps->capacity->fd);
+ ps->capacity->fd = -1;
+ }
+ else if(unlikely(lseek(ps->capacity->fd, 0, SEEK_SET) == -1)) {
+ error("Cannot seek in file '%s'", ps->capacity->filename);
+ close(ps->capacity->fd);
+ ps->capacity->fd = -1;
+ }
+ }
+ }
+ }
+
+ // read property files
+ int read_error = 0;
+ struct ps_property *pr;
+ if (ps)
+ {
+ for(pr = ps->property_root; pr && !read_error; pr = pr->next) {
+ struct ps_property_dim *pd;
+ for(pd = pr->property_dim_root; pd; pd = pd->next) {
+ if(likely(!pd->always_zero)) {
+ char buffer[30 + 1];
+
+ if(unlikely(pd->fd == -1)) {
+ pd->fd = open(pd->filename, O_RDONLY, 0666);
+ if(unlikely(pd->fd == -1)) {
+ error("Cannot open file '%s'", pd->filename);
+ read_error = 1;
+ power_supply_free(ps);
+ break;
+ }
+ }
+
+ ssize_t r = read(pd->fd, buffer, 30);
+ if(unlikely(r < 1)) {
+ error("Cannot read file '%s'", pd->filename);
+ read_error = 1;
+ power_supply_free(ps);
+ break;
+ }
+ buffer[r] = '\0';
+ pd->value = str2ull(buffer);
+
+ if(unlikely(!keep_fds_open)) {
+ close(pd->fd);
+ pd->fd = -1;
+ }
+ else if(unlikely(lseek(pd->fd, 0, SEEK_SET) == -1)) {
+ error("Cannot seek in file '%s'", pd->filename);
+ close(pd->fd);
+ pd->fd = -1;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ closedir(dir);
+
+ keep_fds_open = keep_fds_open_config;
+ if(likely(keep_fds_open_config == CONFIG_BOOLEAN_AUTO)) {
+ if(unlikely(files_num > 32))
+ keep_fds_open = CONFIG_BOOLEAN_NO;
+ else
+ keep_fds_open = CONFIG_BOOLEAN_YES;
+ }
+
+ // --------------------------------------------------------------------
+
+ struct power_supply *ps = power_supply_root;
+ while(unlikely(ps)) {
+ if(unlikely(!ps->found)) {
+ struct power_supply *f = ps;
+ ps = ps->next;
+ power_supply_free(f);
+ continue;
+ }
+
+ if(likely(ps->capacity)) {
+ if(unlikely(!ps->capacity->st)) {
+ ps->capacity->st = rrdset_create_localhost(
+ "powersupply_capacity"
+ , ps->name
+ , NULL
+ , ps->name
+ , "powersupply.capacity"
+ , "Battery capacity"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME
+ , NETDATA_CHART_PRIO_POWER_SUPPLY_CAPACITY
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ }
+ else
+ rrdset_next(ps->capacity->st);
+
+ if(unlikely(!ps->capacity->rd)) ps->capacity->rd = rrddim_add(ps->capacity->st, "capacity", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rrddim_set_by_pointer(ps->capacity->st, ps->capacity->rd, ps->capacity->value);
+
+ rrdset_done(ps->capacity->st);
+ }
+
+ struct ps_property *pr;
+ for(pr = ps->property_root; pr; pr = pr->next) {
+ if(unlikely(!pr->st)) {
+ char id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
+ snprintfz(id, RRD_ID_LENGTH_MAX, "powersupply_%s", pr->name);
+ snprintfz(context, RRD_ID_LENGTH_MAX, "powersupply.%s", pr->name);
+
+ pr->st = rrdset_create_localhost(
+ id
+ , ps->name
+ , NULL
+ , ps->name
+ , context
+ , pr->title
+ , pr->units
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_POWER_SUPPLY_NAME
+ , NETDATA_CHART_PRIO_POWER_SUPPLY_CAPACITY
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ }
+ else
+ rrdset_next(pr->st);
+
+ struct ps_property_dim *pd;
+ for(pd = pr->property_dim_root; pd; pd = pd->next) {
+ if(unlikely(!pd->rd)) pd->rd = rrddim_add(pr->st, pd->name, NULL, 1, 1000000, RRD_ALGORITHM_ABSOLUTE);
+ rrddim_set_by_pointer(pr->st, pd->rd, pd->value);
+ }
+
+ rrdset_done(pr->st);
+ }
+
+ ps->found = 0;
+ ps = ps->next;
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/sys_devices_system_edac_mc.c b/collectors/proc.plugin/sys_devices_system_edac_mc.c
new file mode 100644
index 0000000..b111483
--- /dev/null
+++ b/collectors/proc.plugin/sys_devices_system_edac_mc.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+struct mc {
+ char *name;
+ char ce_updated;
+ char ue_updated;
+
+ char *ce_count_filename;
+ char *ue_count_filename;
+
+ procfile *ce_ff;
+ procfile *ue_ff;
+
+ collected_number ce_count;
+ collected_number ue_count;
+
+ RRDDIM *ce_rd;
+ RRDDIM *ue_rd;
+
+ struct mc *next;
+};
+static struct mc *mc_root = NULL;
+
+static void find_all_mc() {
+ char name[FILENAME_MAX + 1];
+ snprintfz(name, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/edac/mc");
+ char *dirname = config_get("plugin:proc:/sys/devices/system/edac/mc", "directory to monitor", name);
+
+ DIR *dir = opendir(dirname);
+ if(unlikely(!dir)) {
+ error("Cannot read ECC memory errors directory '%s'", dirname);
+ return;
+ }
+
+ struct dirent *de = NULL;
+ while((de = readdir(dir))) {
+ if(de->d_type == DT_DIR && de->d_name[0] == 'm' && de->d_name[1] == 'c' && isdigit(de->d_name[2])) {
+ struct mc *m = callocz(1, sizeof(struct mc));
+ m->name = strdupz(de->d_name);
+
+ struct stat st;
+
+ snprintfz(name, FILENAME_MAX, "%s/%s/ce_count", dirname, de->d_name);
+ if(stat(name, &st) != -1)
+ m->ce_count_filename = strdupz(name);
+
+ snprintfz(name, FILENAME_MAX, "%s/%s/ue_count", dirname, de->d_name);
+ if(stat(name, &st) != -1)
+ m->ue_count_filename = strdupz(name);
+
+ if(!m->ce_count_filename && !m->ue_count_filename) {
+ freez(m->name);
+ freez(m);
+ }
+ else {
+ m->next = mc_root;
+ mc_root = m;
+ }
+ }
+ }
+
+ closedir(dir);
+}
+
+int do_proc_sys_devices_system_edac_mc(int update_every, usec_t dt) {
+ (void)dt;
+
+ if(unlikely(mc_root == NULL)) {
+ find_all_mc();
+ if(unlikely(mc_root == NULL))
+ return 1;
+ }
+
+ static int do_ce = -1, do_ue = -1;
+ calculated_number ce_sum = 0, ue_sum = 0;
+ struct mc *m;
+
+ if(unlikely(do_ce == -1)) {
+ do_ce = config_get_boolean_ondemand("plugin:proc:/sys/devices/system/edac/mc", "enable ECC memory correctable errors", CONFIG_BOOLEAN_AUTO);
+ do_ue = config_get_boolean_ondemand("plugin:proc:/sys/devices/system/edac/mc", "enable ECC memory uncorrectable errors", CONFIG_BOOLEAN_AUTO);
+ }
+
+ if(do_ce != CONFIG_BOOLEAN_NO) {
+ for(m = mc_root; m; m = m->next) {
+ if(m->ce_count_filename) {
+ m->ce_updated = 0;
+
+ if(unlikely(!m->ce_ff)) {
+ m->ce_ff = procfile_open(m->ce_count_filename, " \t", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!m->ce_ff))
+ continue;
+ }
+
+ m->ce_ff = procfile_readall(m->ce_ff);
+ if(unlikely(!m->ce_ff || procfile_lines(m->ce_ff) < 1 || procfile_linewords(m->ce_ff, 0) < 1))
+ continue;
+
+ m->ce_count = str2ull(procfile_lineword(m->ce_ff, 0, 0));
+ ce_sum += m->ce_count;
+ m->ce_updated = 1;
+ }
+ }
+ }
+
+ if(do_ue != CONFIG_BOOLEAN_NO) {
+ for(m = mc_root; m; m = m->next) {
+ if(m->ue_count_filename) {
+ m->ue_updated = 0;
+
+ if(unlikely(!m->ue_ff)) {
+ m->ue_ff = procfile_open(m->ue_count_filename, " \t", PROCFILE_FLAG_DEFAULT);
+ if(unlikely(!m->ue_ff))
+ continue;
+ }
+
+ m->ue_ff = procfile_readall(m->ue_ff);
+ if(unlikely(!m->ue_ff || procfile_lines(m->ue_ff) < 1 || procfile_linewords(m->ue_ff, 0) < 1))
+ continue;
+
+ m->ue_count = str2ull(procfile_lineword(m->ue_ff, 0, 0));
+ ue_sum += m->ue_count;
+ m->ue_updated = 1;
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ce == CONFIG_BOOLEAN_YES || (do_ce == CONFIG_BOOLEAN_AUTO &&
+ (ce_sum > 0 || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ce = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *ce_st = NULL;
+
+ if(unlikely(!ce_st)) {
+ ce_st = rrdset_create_localhost(
+ "mem"
+ , "ecc_ce"
+ , NULL
+ , "ecc"
+ , NULL
+ , "ECC Memory Correctable Errors"
+ , "errors"
+ , PLUGIN_PROC_NAME
+ , "/sys/devices/system/edac/mc"
+ , NETDATA_CHART_PRIO_MEM_HW_ECC_CE
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ }
+ else
+ rrdset_next(ce_st);
+
+ for(m = mc_root; m; m = m->next) {
+ if (m->ce_count_filename && m->ce_updated) {
+ if(unlikely(!m->ce_rd))
+ m->ce_rd = rrddim_add(ce_st, m->name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(ce_st, m->ce_rd, m->ce_count);
+ }
+ }
+
+ rrdset_done(ce_st);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(do_ue == CONFIG_BOOLEAN_YES || (do_ue == CONFIG_BOOLEAN_AUTO &&
+ (ue_sum > 0 || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_ue = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *ue_st = NULL;
+
+ if(unlikely(!ue_st)) {
+ ue_st = rrdset_create_localhost(
+ "mem"
+ , "ecc_ue"
+ , NULL
+ , "ecc"
+ , NULL
+ , "ECC Memory Uncorrectable Errors"
+ , "errors"
+ , PLUGIN_PROC_NAME
+ , "/sys/devices/system/edac/mc"
+ , NETDATA_CHART_PRIO_MEM_HW_ECC_UE
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+ }
+ else
+ rrdset_next(ue_st);
+
+ for(m = mc_root; m; m = m->next) {
+ if (m->ue_count_filename && m->ue_updated) {
+ if(unlikely(!m->ue_rd))
+ m->ue_rd = rrddim_add(ue_st, m->name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ rrddim_set_by_pointer(ue_st, m->ue_rd, m->ue_count);
+ }
+ }
+
+ rrdset_done(ue_st);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/sys_devices_system_node.c b/collectors/proc.plugin/sys_devices_system_node.c
new file mode 100644
index 0000000..ff408ed
--- /dev/null
+++ b/collectors/proc.plugin/sys_devices_system_node.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+struct node {
+ char *name;
+ char *numastat_filename;
+ procfile *numastat_ff;
+ RRDSET *numastat_st;
+ struct node *next;
+};
+static struct node *numa_root = NULL;
+
+static int find_all_nodes() {
+ int numa_node_count = 0;
+ char name[FILENAME_MAX + 1];
+ snprintfz(name, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/node");
+ char *dirname = config_get("plugin:proc:/sys/devices/system/node", "directory to monitor", name);
+
+ DIR *dir = opendir(dirname);
+ if(!dir) {
+ error("Cannot read NUMA node directory '%s'", dirname);
+ return 0;
+ }
+
+ struct dirent *de = NULL;
+ while((de = readdir(dir))) {
+ if(de->d_type != DT_DIR)
+ continue;
+
+ if(strncmp(de->d_name, "node", 4) != 0)
+ continue;
+
+ if(!isdigit(de->d_name[4]))
+ continue;
+
+ numa_node_count++;
+
+ struct node *m = callocz(1, sizeof(struct node));
+ m->name = strdupz(de->d_name);
+
+ struct stat st;
+
+ snprintfz(name, FILENAME_MAX, "%s/%s/numastat", dirname, de->d_name);
+ if(stat(name, &st) == -1) {
+ freez(m->name);
+ freez(m);
+ continue;
+ }
+
+ m->numastat_filename = strdupz(name);
+
+ m->next = numa_root;
+ numa_root = m;
+ }
+
+ closedir(dir);
+
+ return numa_node_count;
+}
+
+int do_proc_sys_devices_system_node(int update_every, usec_t dt) {
+ (void)dt;
+
+ static uint32_t hash_local_node = 0, hash_numa_foreign = 0, hash_interleave_hit = 0, hash_other_node = 0, hash_numa_hit = 0, hash_numa_miss = 0;
+ static int do_numastat = -1, numa_node_count = 0;
+ struct node *m;
+
+ if(unlikely(numa_root == NULL)) {
+ numa_node_count = find_all_nodes();
+ if(unlikely(numa_root == NULL))
+ return 1;
+ }
+
+ if(unlikely(do_numastat == -1)) {
+ do_numastat = config_get_boolean_ondemand("plugin:proc:/sys/devices/system/node", "enable per-node numa metrics", CONFIG_BOOLEAN_AUTO);
+
+ hash_local_node = simple_hash("local_node");
+ hash_numa_foreign = simple_hash("numa_foreign");
+ hash_interleave_hit = simple_hash("interleave_hit");
+ hash_other_node = simple_hash("other_node");
+ hash_numa_hit = simple_hash("numa_hit");
+ hash_numa_miss = simple_hash("numa_miss");
+ }
+
+ if(do_numastat == CONFIG_BOOLEAN_YES || (do_numastat == CONFIG_BOOLEAN_AUTO &&
+ (numa_node_count >= 2 || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ for(m = numa_root; m; m = m->next) {
+ if(m->numastat_filename) {
+
+ if(unlikely(!m->numastat_ff)) {
+ m->numastat_ff = procfile_open(m->numastat_filename, " ", PROCFILE_FLAG_DEFAULT);
+
+ if(unlikely(!m->numastat_ff))
+ continue;
+ }
+
+ m->numastat_ff = procfile_readall(m->numastat_ff);
+ if(unlikely(!m->numastat_ff || procfile_lines(m->numastat_ff) < 1 || procfile_linewords(m->numastat_ff, 0) < 1))
+ continue;
+
+ if(unlikely(!m->numastat_st)) {
+ m->numastat_st = rrdset_create_localhost(
+ "mem"
+ , m->name
+ , NULL
+ , "numa"
+ , NULL
+ , "NUMA events"
+ , "events/s"
+ , PLUGIN_PROC_NAME
+ , "/sys/devices/system/node"
+ , NETDATA_CHART_PRIO_MEM_NUMA_NODES
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rrdset_flag_set(m->numastat_st, RRDSET_FLAG_DETAIL);
+
+ rrddim_add(m->numastat_st, "numa_hit", "hit", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(m->numastat_st, "numa_miss", "miss", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(m->numastat_st, "local_node", "local", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(m->numastat_st, "numa_foreign", "foreign", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(m->numastat_st, "interleave_hit", "interleave", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rrddim_add(m->numastat_st, "other_node", "other", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ }
+ else rrdset_next(m->numastat_st);
+
+ size_t lines = procfile_lines(m->numastat_ff), l;
+ for(l = 0; l < lines; l++) {
+ size_t words = procfile_linewords(m->numastat_ff, l);
+
+ if(unlikely(words < 2)) {
+ if(unlikely(words))
+ error("Cannot read %s numastat line %zu. Expected 2 params, read %zu.", m->name, l, words);
+ continue;
+ }
+
+ char *name = procfile_lineword(m->numastat_ff, l, 0);
+ char *value = procfile_lineword(m->numastat_ff, l, 1);
+
+ if (unlikely(!name || !*name || !value || !*value))
+ continue;
+
+ uint32_t hash = simple_hash(name);
+ if(likely(
+ (hash == hash_numa_hit && !strcmp(name, "numa_hit"))
+ || (hash == hash_numa_miss && !strcmp(name, "numa_miss"))
+ || (hash == hash_local_node && !strcmp(name, "local_node"))
+ || (hash == hash_numa_foreign && !strcmp(name, "numa_foreign"))
+ || (hash == hash_interleave_hit && !strcmp(name, "interleave_hit"))
+ || (hash == hash_other_node && !strcmp(name, "other_node"))
+ ))
+ rrddim_set(m->numastat_st, name, (collected_number)str2kernel_uint_t(value));
+ }
+
+ rrdset_done(m->numastat_st);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/sys_fs_btrfs.c b/collectors/proc.plugin/sys_fs_btrfs.c
new file mode 100644
index 0000000..4e58a1a
--- /dev/null
+++ b/collectors/proc.plugin/sys_fs_btrfs.c
@@ -0,0 +1,734 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_BTRFS_NAME "/sys/fs/btrfs"
+
+typedef struct btrfs_disk {
+ char *name;
+ uint32_t hash;
+ int exists;
+
+ char *size_filename;
+ char *hw_sector_size_filename;
+ unsigned long long size;
+ unsigned long long hw_sector_size;
+
+ struct btrfs_disk *next;
+} BTRFS_DISK;
+
+typedef struct btrfs_node {
+ int exists;
+ int logged_error;
+
+ char *id;
+ uint32_t hash;
+
+ char *label;
+
+ // unsigned long long int sectorsize;
+ // unsigned long long int nodesize;
+ // unsigned long long int quota_override;
+
+ #define declare_btrfs_allocation_section_field(SECTION, FIELD) \
+ char *allocation_ ## SECTION ## _ ## FIELD ## _filename; \
+ unsigned long long int allocation_ ## SECTION ## _ ## FIELD;
+
+ #define declare_btrfs_allocation_field(FIELD) \
+ char *allocation_ ## FIELD ## _filename; \
+ unsigned long long int allocation_ ## FIELD;
+
+ RRDSET *st_allocation_disks;
+ RRDDIM *rd_allocation_disks_unallocated;
+ RRDDIM *rd_allocation_disks_data_used;
+ RRDDIM *rd_allocation_disks_data_free;
+ RRDDIM *rd_allocation_disks_metadata_used;
+ RRDDIM *rd_allocation_disks_metadata_free;
+ RRDDIM *rd_allocation_disks_system_used;
+ RRDDIM *rd_allocation_disks_system_free;
+ unsigned long long all_disks_total;
+
+ RRDSET *st_allocation_data;
+ RRDDIM *rd_allocation_data_free;
+ RRDDIM *rd_allocation_data_used;
+ declare_btrfs_allocation_section_field(data, total_bytes)
+ declare_btrfs_allocation_section_field(data, bytes_used)
+ declare_btrfs_allocation_section_field(data, disk_total)
+ declare_btrfs_allocation_section_field(data, disk_used)
+
+ RRDSET *st_allocation_metadata;
+ RRDDIM *rd_allocation_metadata_free;
+ RRDDIM *rd_allocation_metadata_used;
+ RRDDIM *rd_allocation_metadata_reserved;
+ declare_btrfs_allocation_section_field(metadata, total_bytes)
+ declare_btrfs_allocation_section_field(metadata, bytes_used)
+ declare_btrfs_allocation_section_field(metadata, disk_total)
+ declare_btrfs_allocation_section_field(metadata, disk_used)
+ //declare_btrfs_allocation_field(global_rsv_reserved)
+ declare_btrfs_allocation_field(global_rsv_size)
+
+ RRDSET *st_allocation_system;
+ RRDDIM *rd_allocation_system_free;
+ RRDDIM *rd_allocation_system_used;
+ declare_btrfs_allocation_section_field(system, total_bytes)
+ declare_btrfs_allocation_section_field(system, bytes_used)
+ declare_btrfs_allocation_section_field(system, disk_total)
+ declare_btrfs_allocation_section_field(system, disk_used)
+
+ BTRFS_DISK *disks;
+
+ struct btrfs_node *next;
+} BTRFS_NODE;
+
+static BTRFS_NODE *nodes = NULL;
+
+static inline void btrfs_free_disk(BTRFS_DISK *d) {
+ freez(d->name);
+ freez(d->size_filename);
+ freez(d->hw_sector_size_filename);
+ freez(d);
+}
+
+static inline void btrfs_free_node(BTRFS_NODE *node) {
+ // info("BTRFS: destroying '%s'", node->id);
+
+ if(node->st_allocation_disks)
+ rrdset_is_obsolete(node->st_allocation_disks);
+
+ if(node->st_allocation_data)
+ rrdset_is_obsolete(node->st_allocation_data);
+
+ if(node->st_allocation_metadata)
+ rrdset_is_obsolete(node->st_allocation_metadata);
+
+ if(node->st_allocation_system)
+ rrdset_is_obsolete(node->st_allocation_system);
+
+ freez(node->allocation_data_bytes_used_filename);
+ freez(node->allocation_data_total_bytes_filename);
+
+ freez(node->allocation_metadata_bytes_used_filename);
+ freez(node->allocation_metadata_total_bytes_filename);
+
+ freez(node->allocation_system_bytes_used_filename);
+ freez(node->allocation_system_total_bytes_filename);
+
+ while(node->disks) {
+ BTRFS_DISK *d = node->disks;
+ node->disks = node->disks->next;
+ btrfs_free_disk(d);
+ }
+
+ freez(node->label);
+ freez(node->id);
+ freez(node);
+}
+
+static inline int find_btrfs_disks(BTRFS_NODE *node, const char *path) {
+ char filename[FILENAME_MAX + 1];
+
+ node->all_disks_total = 0;
+
+ BTRFS_DISK *d;
+ for(d = node->disks ; d ; d = d->next)
+ d->exists = 0;
+
+ DIR *dir = opendir(path);
+ if (!dir) {
+ if(!node->logged_error) {
+ error("BTRFS: Cannot open directory '%s'.", path);
+ node->logged_error = 1;
+ }
+ return 1;
+ }
+ node->logged_error = 0;
+
+ struct dirent *de = NULL;
+ while ((de = readdir(dir))) {
+ if (de->d_type != DT_LNK
+ || !strcmp(de->d_name, ".")
+ || !strcmp(de->d_name, "..")
+ ) {
+ // info("BTRFS: ignoring '%s'", de->d_name);
+ continue;
+ }
+
+ uint32_t hash = simple_hash(de->d_name);
+
+ // --------------------------------------------------------------------
+ // search for it
+
+ for(d = node->disks ; d ; d = d->next) {
+ if(hash == d->hash && !strcmp(de->d_name, d->name))
+ break;
+ }
+
+ // --------------------------------------------------------------------
+ // did we find it?
+
+ if(!d) {
+ d = callocz(sizeof(BTRFS_DISK), 1);
+
+ d->name = strdupz(de->d_name);
+ d->hash = simple_hash(d->name);
+
+ snprintfz(filename, FILENAME_MAX, "%s/%s/size", path, de->d_name);
+ d->size_filename = strdupz(filename);
+
+ // for bcache
+ snprintfz(filename, FILENAME_MAX, "%s/%s/bcache/../queue/hw_sector_size", path, de->d_name);
+ struct stat sb;
+ if(stat(filename, &sb) == -1) {
+ // for disks
+ snprintfz(filename, FILENAME_MAX, "%s/%s/queue/hw_sector_size", path, de->d_name);
+ if(stat(filename, &sb) == -1)
+ // for partitions
+ snprintfz(filename, FILENAME_MAX, "%s/%s/../queue/hw_sector_size", path, de->d_name);
+ }
+
+ d->hw_sector_size_filename = strdupz(filename);
+
+ // link it
+ d->next = node->disks;
+ node->disks = d;
+ }
+
+ d->exists = 1;
+
+
+ // --------------------------------------------------------------------
+ // update the values
+
+ if(read_single_number_file(d->size_filename, &d->size) != 0) {
+ error("BTRFS: failed to read '%s'", d->size_filename);
+ d->exists = 0;
+ continue;
+ }
+
+ if(read_single_number_file(d->hw_sector_size_filename, &d->hw_sector_size) != 0) {
+ error("BTRFS: failed to read '%s'", d->hw_sector_size_filename);
+ d->exists = 0;
+ continue;
+ }
+
+ node->all_disks_total += d->size * d->hw_sector_size;
+ }
+ closedir(dir);
+
+ // ------------------------------------------------------------------------
+ // cleanup
+
+ BTRFS_DISK *last = NULL;
+ d = node->disks;
+
+ while(d) {
+ if(unlikely(!d->exists)) {
+ if(unlikely(node->disks == d)) {
+ node->disks = d->next;
+ btrfs_free_disk(d);
+ d = node->disks;
+ last = NULL;
+ }
+ else {
+ last->next = d->next;
+ btrfs_free_disk(d);
+ d = last->next;
+ }
+
+ continue;
+ }
+
+ last = d;
+ d = d->next;
+ }
+
+ return 0;
+}
+
+
+static inline int find_all_btrfs_pools(const char *path) {
+ static int logged_error = 0;
+ char filename[FILENAME_MAX + 1];
+
+ BTRFS_NODE *node;
+ for(node = nodes ; node ; node = node->next)
+ node->exists = 0;
+
+ DIR *dir = opendir(path);
+ if (!dir) {
+ if(!logged_error) {
+ error("BTRFS: Cannot open directory '%s'.", path);
+ logged_error = 1;
+ }
+ return 1;
+ }
+ logged_error = 0;
+
+ struct dirent *de = NULL;
+ while ((de = readdir(dir))) {
+ if(de->d_type != DT_DIR
+ || !strcmp(de->d_name, ".")
+ || !strcmp(de->d_name, "..")
+ || !strcmp(de->d_name, "features")
+ ) {
+ // info("BTRFS: ignoring '%s'", de->d_name);
+ continue;
+ }
+
+ uint32_t hash = simple_hash(de->d_name);
+
+ // search for it
+ for(node = nodes ; node ; node = node->next) {
+ if(hash == node->hash && !strcmp(de->d_name, node->id))
+ break;
+ }
+
+ // did we find it?
+ if(node) {
+ // info("BTRFS: already exists '%s'", de->d_name);
+ node->exists = 1;
+
+ // update the disk sizes
+ snprintfz(filename, FILENAME_MAX, "%s/%s/devices", path, de->d_name);
+ find_btrfs_disks(node, filename);
+
+ continue;
+ }
+
+ // info("BTRFS: adding '%s'", de->d_name);
+
+ // not found, create it
+ node = callocz(sizeof(BTRFS_NODE), 1);
+
+ node->id = strdupz(de->d_name);
+ node->hash = simple_hash(node->id);
+ node->exists = 1;
+
+ {
+ char label[FILENAME_MAX + 1] = "";
+
+ snprintfz(filename, FILENAME_MAX, "%s/%s/label", path, de->d_name);
+ if(read_file(filename, label, FILENAME_MAX) != 0) {
+ error("BTRFS: failed to read '%s'", filename);
+ btrfs_free_node(node);
+ continue;
+ }
+
+ char *s = label;
+ if (s[0])
+ s = trim(label);
+
+ if(s && s[0])
+ node->label = strdupz(s);
+ else
+ node->label = strdupz(node->id);
+ }
+
+ //snprintfz(filename, FILENAME_MAX, "%s/%s/sectorsize", path, de->d_name);
+ //if(read_single_number_file(filename, &node->sectorsize) != 0) {
+ // error("BTRFS: failed to read '%s'", filename);
+ // btrfs_free_node(node);
+ // continue;
+ //}
+
+ //snprintfz(filename, FILENAME_MAX, "%s/%s/nodesize", path, de->d_name);
+ //if(read_single_number_file(filename, &node->nodesize) != 0) {
+ // error("BTRFS: failed to read '%s'", filename);
+ // btrfs_free_node(node);
+ // continue;
+ //}
+
+ //snprintfz(filename, FILENAME_MAX, "%s/%s/quota_override", path, de->d_name);
+ //if(read_single_number_file(filename, &node->quota_override) != 0) {
+ // error("BTRFS: failed to read '%s'", filename);
+ // btrfs_free_node(node);
+ // continue;
+ //}
+
+ // --------------------------------------------------------------------
+ // macros to simplify our life
+
+ #define init_btrfs_allocation_field(FIELD) {\
+ snprintfz(filename, FILENAME_MAX, "%s/%s/allocation/" #FIELD, path, de->d_name); \
+ if(read_single_number_file(filename, &node->allocation_ ## FIELD) != 0) {\
+ error("BTRFS: failed to read '%s'", filename);\
+ btrfs_free_node(node);\
+ continue;\
+ }\
+ if(!node->allocation_ ## FIELD ## _filename)\
+ node->allocation_ ## FIELD ## _filename = strdupz(filename);\
+ }
+
+ #define init_btrfs_allocation_section_field(SECTION, FIELD) {\
+ snprintfz(filename, FILENAME_MAX, "%s/%s/allocation/" #SECTION "/" #FIELD, path, de->d_name); \
+ if(read_single_number_file(filename, &node->allocation_ ## SECTION ## _ ## FIELD) != 0) {\
+ error("BTRFS: failed to read '%s'", filename);\
+ btrfs_free_node(node);\
+ continue;\
+ }\
+ if(!node->allocation_ ## SECTION ## _ ## FIELD ## _filename)\
+ node->allocation_ ## SECTION ## _ ## FIELD ## _filename = strdupz(filename);\
+ }
+
+ // --------------------------------------------------------------------
+ // allocation/data
+
+ init_btrfs_allocation_section_field(data, total_bytes);
+ init_btrfs_allocation_section_field(data, bytes_used);
+ init_btrfs_allocation_section_field(data, disk_total);
+ init_btrfs_allocation_section_field(data, disk_used);
+
+
+ // --------------------------------------------------------------------
+ // allocation/metadata
+
+ init_btrfs_allocation_section_field(metadata, total_bytes);
+ init_btrfs_allocation_section_field(metadata, bytes_used);
+ init_btrfs_allocation_section_field(metadata, disk_total);
+ init_btrfs_allocation_section_field(metadata, disk_used);
+
+ init_btrfs_allocation_field(global_rsv_size);
+ // init_btrfs_allocation_field(global_rsv_reserved);
+
+
+ // --------------------------------------------------------------------
+ // allocation/system
+
+ init_btrfs_allocation_section_field(system, total_bytes);
+ init_btrfs_allocation_section_field(system, bytes_used);
+ init_btrfs_allocation_section_field(system, disk_total);
+ init_btrfs_allocation_section_field(system, disk_used);
+
+
+ // --------------------------------------------------------------------
+ // find all disks related to this node
+ // and collect their sizes
+
+ snprintfz(filename, FILENAME_MAX, "%s/%s/devices", path, de->d_name);
+ find_btrfs_disks(node, filename);
+
+
+ // --------------------------------------------------------------------
+ // link it
+
+ // info("BTRFS: linking '%s'", node->id);
+ node->next = nodes;
+ nodes = node;
+ }
+ closedir(dir);
+
+
+ // ------------------------------------------------------------------------
+ // cleanup
+
+ BTRFS_NODE *last = NULL;
+ node = nodes;
+
+ while(node) {
+ if(unlikely(!node->exists)) {
+ if(unlikely(nodes == node)) {
+ nodes = node->next;
+ btrfs_free_node(node);
+ node = nodes;
+ last = NULL;
+ }
+ else {
+ last->next = node->next;
+ btrfs_free_node(node);
+ node = last->next;
+ }
+
+ continue;
+ }
+
+ last = node;
+ node = node->next;
+ }
+
+ return 0;
+}
+
+int do_sys_fs_btrfs(int update_every, usec_t dt) {
+ static int initialized = 0
+ , do_allocation_disks = CONFIG_BOOLEAN_AUTO
+ , do_allocation_system = CONFIG_BOOLEAN_AUTO
+ , do_allocation_data = CONFIG_BOOLEAN_AUTO
+ , do_allocation_metadata = CONFIG_BOOLEAN_AUTO;
+
+ static usec_t refresh_delta = 0, refresh_every = 60 * USEC_PER_SEC;
+ static char *btrfs_path = NULL;
+
+ (void)dt;
+
+ if(unlikely(!initialized)) {
+ initialized = 1;
+
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/fs/btrfs");
+ btrfs_path = config_get("plugin:proc:/sys/fs/btrfs", "path to monitor", filename);
+
+ refresh_every = config_get_number("plugin:proc:/sys/fs/btrfs", "check for btrfs changes every", refresh_every / USEC_PER_SEC) * USEC_PER_SEC;
+ refresh_delta = refresh_every;
+
+ do_allocation_disks = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "physical disks allocation", do_allocation_disks);
+ do_allocation_data = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "data allocation", do_allocation_data);
+ do_allocation_metadata = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "metadata allocation", do_allocation_metadata);
+ do_allocation_system = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "system allocation", do_allocation_system);
+ }
+
+ refresh_delta += dt;
+ if(refresh_delta >= refresh_every) {
+ refresh_delta = 0;
+ find_all_btrfs_pools(btrfs_path);
+ }
+
+ BTRFS_NODE *node;
+ for(node = nodes; node ; node = node->next) {
+ // --------------------------------------------------------------------
+ // allocation/system
+
+ #define collect_btrfs_allocation_field(FIELD) \
+ read_single_number_file(node->allocation_ ## FIELD ## _filename, &node->allocation_ ## FIELD)
+
+ #define collect_btrfs_allocation_section_field(SECTION, FIELD) \
+ read_single_number_file(node->allocation_ ## SECTION ## _ ## FIELD ## _filename, &node->allocation_ ## SECTION ## _ ## FIELD)
+
+ if(do_allocation_disks != CONFIG_BOOLEAN_NO) {
+ if( collect_btrfs_allocation_section_field(data, disk_total) != 0
+ || collect_btrfs_allocation_section_field(data, disk_used) != 0
+ || collect_btrfs_allocation_section_field(metadata, disk_total) != 0
+ || collect_btrfs_allocation_section_field(metadata, disk_used) != 0
+ || collect_btrfs_allocation_section_field(system, disk_total) != 0
+ || collect_btrfs_allocation_section_field(system, disk_used) != 0) {
+ error("BTRFS: failed to collect physical disks allocation for '%s'", node->id);
+ // make it refresh btrfs at the next iteration
+ refresh_delta = refresh_every;
+ continue;
+ }
+ }
+
+ if(do_allocation_data != CONFIG_BOOLEAN_NO) {
+ if (collect_btrfs_allocation_section_field(data, total_bytes) != 0
+ || collect_btrfs_allocation_section_field(data, bytes_used) != 0) {
+ error("BTRFS: failed to collect allocation/data for '%s'", node->id);
+ // make it refresh btrfs at the next iteration
+ refresh_delta = refresh_every;
+ continue;
+ }
+ }
+
+ if(do_allocation_metadata != CONFIG_BOOLEAN_NO) {
+ if (collect_btrfs_allocation_section_field(metadata, total_bytes) != 0
+ || collect_btrfs_allocation_section_field(metadata, bytes_used) != 0
+ || collect_btrfs_allocation_field(global_rsv_size) != 0
+ ) {
+ error("BTRFS: failed to collect allocation/metadata for '%s'", node->id);
+ // make it refresh btrfs at the next iteration
+ refresh_delta = refresh_every;
+ continue;
+ }
+ }
+
+ if(do_allocation_system != CONFIG_BOOLEAN_NO) {
+ if (collect_btrfs_allocation_section_field(system, total_bytes) != 0
+ || collect_btrfs_allocation_section_field(system, bytes_used) != 0) {
+ error("BTRFS: failed to collect allocation/system for '%s'", node->id);
+ // make it refresh btrfs at the next iteration
+ refresh_delta = refresh_every;
+ continue;
+ }
+ }
+
+ // --------------------------------------------------------------------
+ // allocation/disks
+
+ if(do_allocation_disks == CONFIG_BOOLEAN_YES || (do_allocation_disks == CONFIG_BOOLEAN_AUTO &&
+ ((node->all_disks_total && node->allocation_data_disk_total) ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_allocation_disks = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!node->st_allocation_disks)) {
+ char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1];
+
+ snprintf(id, RRD_ID_LENGTH_MAX, "disk_%s", node->id);
+ snprintf(name, RRD_ID_LENGTH_MAX, "disk_%s", node->label);
+ snprintf(title, 200, "BTRFS Physical Disk Allocation for %s", node->label);
+
+ netdata_fix_chart_id(id);
+ netdata_fix_chart_name(name);
+
+ node->st_allocation_disks = rrdset_create_localhost(
+ "btrfs"
+ , id
+ , name
+ , node->label
+ , "btrfs.disk"
+ , title
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_BTRFS_NAME
+ , NETDATA_CHART_PRIO_BTRFS_DISK
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ node->rd_allocation_disks_unallocated = rrddim_add(node->st_allocation_disks, "unallocated", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_disks_data_free = rrddim_add(node->st_allocation_disks, "data_free", "data free", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_disks_data_used = rrddim_add(node->st_allocation_disks, "data_used", "data used", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_disks_metadata_free = rrddim_add(node->st_allocation_disks, "meta_free", "meta free", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_disks_metadata_used = rrddim_add(node->st_allocation_disks, "meta_used", "meta used", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_disks_system_free = rrddim_add(node->st_allocation_disks, "sys_free", "sys free", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_disks_system_used = rrddim_add(node->st_allocation_disks, "sys_used", "sys used", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(node->st_allocation_disks);
+
+ // unsigned long long disk_used = node->allocation_data_disk_used + node->allocation_metadata_disk_used + node->allocation_system_disk_used;
+ unsigned long long disk_total = node->allocation_data_disk_total + node->allocation_metadata_disk_total + node->allocation_system_disk_total;
+ unsigned long long disk_unallocated = node->all_disks_total - disk_total;
+
+ rrddim_set_by_pointer(node->st_allocation_disks, node->rd_allocation_disks_unallocated, disk_unallocated);
+ rrddim_set_by_pointer(node->st_allocation_disks, node->rd_allocation_disks_data_used, node->allocation_data_disk_used);
+ rrddim_set_by_pointer(node->st_allocation_disks, node->rd_allocation_disks_data_free, node->allocation_data_disk_total - node->allocation_data_disk_used);
+ rrddim_set_by_pointer(node->st_allocation_disks, node->rd_allocation_disks_metadata_used, node->allocation_metadata_disk_used);
+ rrddim_set_by_pointer(node->st_allocation_disks, node->rd_allocation_disks_metadata_free, node->allocation_metadata_disk_total - node->allocation_metadata_disk_used);
+ rrddim_set_by_pointer(node->st_allocation_disks, node->rd_allocation_disks_system_used, node->allocation_system_disk_used);
+ rrddim_set_by_pointer(node->st_allocation_disks, node->rd_allocation_disks_system_free, node->allocation_system_disk_total - node->allocation_system_disk_used);
+ rrdset_done(node->st_allocation_disks);
+ }
+
+
+ // --------------------------------------------------------------------
+ // allocation/data
+
+ if(do_allocation_data == CONFIG_BOOLEAN_YES || (do_allocation_data == CONFIG_BOOLEAN_AUTO &&
+ (node->allocation_data_total_bytes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_allocation_data = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!node->st_allocation_data)) {
+ char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1];
+
+ snprintf(id, RRD_ID_LENGTH_MAX, "data_%s", node->id);
+ snprintf(name, RRD_ID_LENGTH_MAX, "data_%s", node->label);
+ snprintf(title, 200, "BTRFS Data Allocation for %s", node->label);
+
+ netdata_fix_chart_id(id);
+ netdata_fix_chart_name(name);
+
+ node->st_allocation_data = rrdset_create_localhost(
+ "btrfs"
+ , id
+ , name
+ , node->label
+ , "btrfs.data"
+ , title
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_BTRFS_NAME
+ , NETDATA_CHART_PRIO_BTRFS_DATA
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ node->rd_allocation_data_free = rrddim_add(node->st_allocation_data, "free", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_data_used = rrddim_add(node->st_allocation_data, "used", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(node->st_allocation_data);
+
+ rrddim_set_by_pointer(node->st_allocation_data, node->rd_allocation_data_free, node->allocation_data_total_bytes - node->allocation_data_bytes_used);
+ rrddim_set_by_pointer(node->st_allocation_data, node->rd_allocation_data_used, node->allocation_data_bytes_used);
+ rrdset_done(node->st_allocation_data);
+ }
+
+ // --------------------------------------------------------------------
+ // allocation/metadata
+
+ if(do_allocation_metadata == CONFIG_BOOLEAN_YES || (do_allocation_metadata == CONFIG_BOOLEAN_AUTO &&
+ (node->allocation_metadata_total_bytes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_allocation_metadata = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!node->st_allocation_metadata)) {
+ char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1];
+
+ snprintf(id, RRD_ID_LENGTH_MAX, "metadata_%s", node->id);
+ snprintf(name, RRD_ID_LENGTH_MAX, "metadata_%s", node->label);
+ snprintf(title, 200, "BTRFS Metadata Allocation for %s", node->label);
+
+ netdata_fix_chart_id(id);
+ netdata_fix_chart_name(name);
+
+ node->st_allocation_metadata = rrdset_create_localhost(
+ "btrfs"
+ , id
+ , name
+ , node->label
+ , "btrfs.metadata"
+ , title
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_BTRFS_NAME
+ , NETDATA_CHART_PRIO_BTRFS_METADATA
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ node->rd_allocation_metadata_free = rrddim_add(node->st_allocation_metadata, "free", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_metadata_used = rrddim_add(node->st_allocation_metadata, "used", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_metadata_reserved = rrddim_add(node->st_allocation_metadata, "reserved", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(node->st_allocation_metadata);
+
+ rrddim_set_by_pointer(node->st_allocation_metadata, node->rd_allocation_metadata_free, node->allocation_metadata_total_bytes - node->allocation_metadata_bytes_used - node->allocation_global_rsv_size);
+ rrddim_set_by_pointer(node->st_allocation_metadata, node->rd_allocation_metadata_used, node->allocation_metadata_bytes_used);
+ rrddim_set_by_pointer(node->st_allocation_metadata, node->rd_allocation_metadata_reserved, node->allocation_global_rsv_size);
+ rrdset_done(node->st_allocation_metadata);
+ }
+
+ // --------------------------------------------------------------------
+ // allocation/system
+
+ if(do_allocation_system == CONFIG_BOOLEAN_YES || (do_allocation_system == CONFIG_BOOLEAN_AUTO &&
+ (node->allocation_system_total_bytes ||
+ netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) {
+ do_allocation_system = CONFIG_BOOLEAN_YES;
+
+ if(unlikely(!node->st_allocation_system)) {
+ char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1];
+
+ snprintf(id, RRD_ID_LENGTH_MAX, "system_%s", node->id);
+ snprintf(name, RRD_ID_LENGTH_MAX, "system_%s", node->label);
+ snprintf(title, 200, "BTRFS System Allocation for %s", node->label);
+
+ netdata_fix_chart_id(id);
+ netdata_fix_chart_name(name);
+
+ node->st_allocation_system = rrdset_create_localhost(
+ "btrfs"
+ , id
+ , name
+ , node->label
+ , "btrfs.system"
+ , title
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_BTRFS_NAME
+ , NETDATA_CHART_PRIO_BTRFS_SYSTEM
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ node->rd_allocation_system_free = rrddim_add(node->st_allocation_system, "free", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ node->rd_allocation_system_used = rrddim_add(node->st_allocation_system, "used", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else rrdset_next(node->st_allocation_system);
+
+ rrddim_set_by_pointer(node->st_allocation_system, node->rd_allocation_system_free, node->allocation_system_total_bytes - node->allocation_system_bytes_used);
+ rrddim_set_by_pointer(node->st_allocation_system, node->rd_allocation_system_used, node->allocation_system_bytes_used);
+ rrdset_done(node->st_allocation_system);
+ }
+ }
+
+ return 0;
+}
+
diff --git a/collectors/proc.plugin/sys_kernel_mm_ksm.c b/collectors/proc.plugin/sys_kernel_mm_ksm.c
new file mode 100644
index 0000000..a0e5690
--- /dev/null
+++ b/collectors/proc.plugin/sys_kernel_mm_ksm.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "plugin_proc.h"
+
+#define PLUGIN_PROC_MODULE_KSM_NAME "/sys/kernel/mm/ksm"
+
+typedef struct ksm_name_value {
+ char filename[FILENAME_MAX + 1];
+ unsigned long long value;
+} KSM_NAME_VALUE;
+
+#define PAGES_SHARED 0
+#define PAGES_SHARING 1
+#define PAGES_UNSHARED 2
+#define PAGES_VOLATILE 3
+#define PAGES_TO_SCAN 4
+
+KSM_NAME_VALUE values[] = {
+ [PAGES_SHARED] = { "/sys/kernel/mm/ksm/pages_shared", 0ULL },
+ [PAGES_SHARING] = { "/sys/kernel/mm/ksm/pages_sharing", 0ULL },
+ [PAGES_UNSHARED] = { "/sys/kernel/mm/ksm/pages_unshared", 0ULL },
+ [PAGES_VOLATILE] = { "/sys/kernel/mm/ksm/pages_volatile", 0ULL },
+ // [PAGES_TO_SCAN] = { "/sys/kernel/mm/ksm/pages_to_scan", 0ULL },
+};
+
+int do_sys_kernel_mm_ksm(int update_every, usec_t dt) {
+ (void)dt;
+ static procfile *ff_pages_shared = NULL, *ff_pages_sharing = NULL, *ff_pages_unshared = NULL, *ff_pages_volatile = NULL/*, *ff_pages_to_scan = NULL*/;
+ static unsigned long page_size = 0;
+
+ if(unlikely(page_size == 0))
+ page_size = (unsigned long)sysconf(_SC_PAGESIZE);
+
+ if(unlikely(!ff_pages_shared)) {
+ snprintfz(values[PAGES_SHARED].filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/kernel/mm/ksm/pages_shared");
+ snprintfz(values[PAGES_SHARED].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_shared", values[PAGES_SHARED].filename));
+ ff_pages_shared = procfile_open(values[PAGES_SHARED].filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ }
+
+ if(unlikely(!ff_pages_sharing)) {
+ snprintfz(values[PAGES_SHARING].filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/kernel/mm/ksm/pages_sharing");
+ snprintfz(values[PAGES_SHARING].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_sharing", values[PAGES_SHARING].filename));
+ ff_pages_sharing = procfile_open(values[PAGES_SHARING].filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ }
+
+ if(unlikely(!ff_pages_unshared)) {
+ snprintfz(values[PAGES_UNSHARED].filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/kernel/mm/ksm/pages_unshared");
+ snprintfz(values[PAGES_UNSHARED].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_unshared", values[PAGES_UNSHARED].filename));
+ ff_pages_unshared = procfile_open(values[PAGES_UNSHARED].filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ }
+
+ if(unlikely(!ff_pages_volatile)) {
+ snprintfz(values[PAGES_VOLATILE].filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/kernel/mm/ksm/pages_volatile");
+ snprintfz(values[PAGES_VOLATILE].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_volatile", values[PAGES_VOLATILE].filename));
+ ff_pages_volatile = procfile_open(values[PAGES_VOLATILE].filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ }
+
+ //if(unlikely(!ff_pages_to_scan)) {
+ // snprintfz(values[PAGES_TO_SCAN].filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/kernel/mm/ksm/pages_to_scan");
+ // snprintfz(values[PAGES_TO_SCAN].filename, FILENAME_MAX, "%s", config_get("plugin:proc:/sys/kernel/mm/ksm", "/sys/kernel/mm/ksm/pages_to_scan", values[PAGES_TO_SCAN].filename));
+ // ff_pages_to_scan = procfile_open(values[PAGES_TO_SCAN].filename, " \t:", PROCFILE_FLAG_DEFAULT);
+ //}
+
+ if(unlikely(!ff_pages_shared || !ff_pages_sharing || !ff_pages_unshared || !ff_pages_volatile /*|| !ff_pages_to_scan */))
+ return 1;
+
+ unsigned long long pages_shared = 0, pages_sharing = 0, pages_unshared = 0, pages_volatile = 0, /*pages_to_scan = 0,*/ offered = 0, saved = 0;
+
+ ff_pages_shared = procfile_readall(ff_pages_shared);
+ if(unlikely(!ff_pages_shared)) return 0; // we return 0, so that we will retry to open it next time
+ pages_shared = str2ull(procfile_lineword(ff_pages_shared, 0, 0));
+
+ ff_pages_sharing = procfile_readall(ff_pages_sharing);
+ if(unlikely(!ff_pages_sharing)) return 0; // we return 0, so that we will retry to open it next time
+ pages_sharing = str2ull(procfile_lineword(ff_pages_sharing, 0, 0));
+
+ ff_pages_unshared = procfile_readall(ff_pages_unshared);
+ if(unlikely(!ff_pages_unshared)) return 0; // we return 0, so that we will retry to open it next time
+ pages_unshared = str2ull(procfile_lineword(ff_pages_unshared, 0, 0));
+
+ ff_pages_volatile = procfile_readall(ff_pages_volatile);
+ if(unlikely(!ff_pages_volatile)) return 0; // we return 0, so that we will retry to open it next time
+ pages_volatile = str2ull(procfile_lineword(ff_pages_volatile, 0, 0));
+
+ //ff_pages_to_scan = procfile_readall(ff_pages_to_scan);
+ //if(unlikely(!ff_pages_to_scan)) return 0; // we return 0, so that we will retry to open it next time
+ //pages_to_scan = str2ull(procfile_lineword(ff_pages_to_scan, 0, 0));
+
+ offered = pages_sharing + pages_shared + pages_unshared + pages_volatile;
+ saved = pages_sharing;
+
+ if(unlikely(!offered /*|| !pages_to_scan*/ && netdata_zero_metrics_enabled == CONFIG_BOOLEAN_NO)) return 0;
+
+ // --------------------------------------------------------------------
+
+ {
+ static RRDSET *st_mem_ksm = NULL;
+ static RRDDIM *rd_shared = NULL, *rd_unshared = NULL, *rd_sharing = NULL, *rd_volatile = NULL/*, *rd_to_scan = NULL*/;
+
+ if (unlikely(!st_mem_ksm)) {
+ st_mem_ksm = rrdset_create_localhost(
+ "mem"
+ , "ksm"
+ , NULL
+ , "ksm"
+ , NULL
+ , "Kernel Same Page Merging"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_KSM_NAME
+ , NETDATA_CHART_PRIO_MEM_KSM
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_shared = rrddim_add(st_mem_ksm, "shared", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_unshared = rrddim_add(st_mem_ksm, "unshared", NULL, -1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_sharing = rrddim_add(st_mem_ksm, "sharing", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_volatile = rrddim_add(st_mem_ksm, "volatile", NULL, -1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ //rd_to_scan = rrddim_add(st_mem_ksm, "to_scan", "to scan", -1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_mem_ksm);
+
+ rrddim_set_by_pointer(st_mem_ksm, rd_shared, pages_shared * page_size);
+ rrddim_set_by_pointer(st_mem_ksm, rd_unshared, pages_unshared * page_size);
+ rrddim_set_by_pointer(st_mem_ksm, rd_sharing, pages_sharing * page_size);
+ rrddim_set_by_pointer(st_mem_ksm, rd_volatile, pages_volatile * page_size);
+ //rrddim_set_by_pointer(st_mem_ksm, rd_to_scan, pages_to_scan * page_size);
+
+ rrdset_done(st_mem_ksm);
+ }
+
+ // --------------------------------------------------------------------
+
+ {
+ static RRDSET *st_mem_ksm_savings = NULL;
+ static RRDDIM *rd_savings = NULL, *rd_offered = NULL;
+
+ if (unlikely(!st_mem_ksm_savings)) {
+ st_mem_ksm_savings = rrdset_create_localhost(
+ "mem"
+ , "ksm_savings"
+ , NULL
+ , "ksm"
+ , NULL
+ , "Kernel Same Page Merging Savings"
+ , "MiB"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_KSM_NAME
+ , NETDATA_CHART_PRIO_MEM_KSM_SAVINGS
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_savings = rrddim_add(st_mem_ksm_savings, "savings", NULL, -1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_offered = rrddim_add(st_mem_ksm_savings, "offered", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_mem_ksm_savings);
+
+ rrddim_set_by_pointer(st_mem_ksm_savings, rd_savings, saved * page_size);
+ rrddim_set_by_pointer(st_mem_ksm_savings, rd_offered, offered * page_size);
+
+ rrdset_done(st_mem_ksm_savings);
+ }
+
+ // --------------------------------------------------------------------
+
+ {
+ static RRDSET *st_mem_ksm_ratios = NULL;
+ static RRDDIM *rd_savings = NULL;
+
+ if (unlikely(!st_mem_ksm_ratios)) {
+ st_mem_ksm_ratios = rrdset_create_localhost(
+ "mem"
+ , "ksm_ratios"
+ , NULL
+ , "ksm"
+ , NULL
+ , "Kernel Same Page Merging Effectiveness"
+ , "percentage"
+ , PLUGIN_PROC_NAME
+ , PLUGIN_PROC_MODULE_KSM_NAME
+ , NETDATA_CHART_PRIO_MEM_KSM_RATIOS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_savings = rrddim_add(st_mem_ksm_ratios, "savings", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_mem_ksm_ratios);
+
+ rrddim_set_by_pointer(st_mem_ksm_ratios, rd_savings, offered ? (saved * 1000000) / offered : 0);
+
+ rrdset_done(st_mem_ksm_ratios);
+ }
+
+ return 0;
+}
diff --git a/collectors/proc.plugin/zfs_common.c b/collectors/proc.plugin/zfs_common.c
new file mode 100644
index 0000000..330bcf1
--- /dev/null
+++ b/collectors/proc.plugin/zfs_common.c
@@ -0,0 +1,772 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "zfs_common.h"
+
+struct arcstats arcstats = { 0 };
+
+void generate_charts_arcstats(const char *plugin, const char *module, int show_zero_charts, int update_every) {
+ static int do_arc_size = -1, do_l2_size = -1, do_reads = -1, do_l2bytes = -1, do_ahits = -1, do_dhits = -1, \
+ do_phits = -1, do_mhits = -1, do_l2hits = -1, do_list_hits = -1;
+
+ if(unlikely(do_arc_size == -1))
+ do_arc_size = do_l2_size = do_reads = do_l2bytes = do_ahits = do_dhits = do_phits = do_mhits \
+ = do_l2hits = do_list_hits = show_zero_charts;
+
+ // ARC reads
+ unsigned long long aread = arcstats.hits + arcstats.misses;
+
+ // Demand reads
+ unsigned long long dhit = arcstats.demand_data_hits + arcstats.demand_metadata_hits;
+ unsigned long long dmiss = arcstats.demand_data_misses + arcstats.demand_metadata_misses;
+ unsigned long long dread = dhit + dmiss;
+
+ // Prefetch reads
+ unsigned long long phit = arcstats.prefetch_data_hits + arcstats.prefetch_metadata_hits;
+ unsigned long long pmiss = arcstats.prefetch_data_misses + arcstats.prefetch_metadata_misses;
+ unsigned long long pread = phit + pmiss;
+
+ // Metadata reads
+ unsigned long long mhit = arcstats.prefetch_metadata_hits + arcstats.demand_metadata_hits;
+ unsigned long long mmiss = arcstats.prefetch_metadata_misses + arcstats.demand_metadata_misses;
+ unsigned long long mread = mhit + mmiss;
+
+ // l2 reads
+ unsigned long long l2hit = arcstats.l2_hits;
+ unsigned long long l2miss = arcstats.l2_misses;
+ unsigned long long l2read = l2hit + l2miss;
+
+ // --------------------------------------------------------------------
+
+ if(do_arc_size == CONFIG_BOOLEAN_YES || arcstats.size || arcstats.c || arcstats.c_min || arcstats.c_max) {
+ do_arc_size = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_arc_size = NULL;
+ static RRDDIM *rd_arc_size = NULL;
+ static RRDDIM *rd_arc_target_size = NULL;
+ static RRDDIM *rd_arc_target_min_size = NULL;
+ static RRDDIM *rd_arc_target_max_size = NULL;
+
+ if (unlikely(!st_arc_size)) {
+ st_arc_size = rrdset_create_localhost(
+ "zfs"
+ , "arc_size"
+ , NULL
+ , ZFS_FAMILY_SIZE
+ , NULL
+ , "ZFS ARC Size"
+ , "MiB"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_ARC_SIZE
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_arc_size = rrddim_add(st_arc_size, "size", "arcsz", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_arc_target_size = rrddim_add(st_arc_size, "target", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_arc_target_min_size = rrddim_add(st_arc_size, "min", "min (hard limit)", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_arc_target_max_size = rrddim_add(st_arc_size, "max", "max (high water)", 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_arc_size);
+
+ rrddim_set_by_pointer(st_arc_size, rd_arc_size, arcstats.size);
+ rrddim_set_by_pointer(st_arc_size, rd_arc_target_size, arcstats.c);
+ rrddim_set_by_pointer(st_arc_size, rd_arc_target_min_size, arcstats.c_min);
+ rrddim_set_by_pointer(st_arc_size, rd_arc_target_max_size, arcstats.c_max);
+ rrdset_done(st_arc_size);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(arcstats.l2exist) && (do_l2_size == CONFIG_BOOLEAN_YES || arcstats.l2_size || arcstats.l2_asize)) {
+ do_l2_size = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_l2_size = NULL;
+ static RRDDIM *rd_l2_size = NULL;
+ static RRDDIM *rd_l2_asize = NULL;
+
+ if (unlikely(!st_l2_size)) {
+ st_l2_size = rrdset_create_localhost(
+ "zfs"
+ , "l2_size"
+ , NULL
+ , ZFS_FAMILY_SIZE
+ , NULL
+ , "ZFS L2 ARC Size"
+ , "MiB"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_L2_SIZE
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_l2_asize = rrddim_add(st_l2_size, "actual", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ rd_l2_size = rrddim_add(st_l2_size, "size", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_l2_size);
+
+ rrddim_set_by_pointer(st_l2_size, rd_l2_size, arcstats.l2_size);
+ rrddim_set_by_pointer(st_l2_size, rd_l2_asize, arcstats.l2_asize);
+ rrdset_done(st_l2_size);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_reads == CONFIG_BOOLEAN_YES || aread || dread || pread || mread || l2read)) {
+ do_reads = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_reads = NULL;
+ static RRDDIM *rd_aread = NULL;
+ static RRDDIM *rd_dread = NULL;
+ static RRDDIM *rd_pread = NULL;
+ static RRDDIM *rd_mread = NULL;
+ static RRDDIM *rd_l2read = NULL;
+
+ if (unlikely(!st_reads)) {
+ st_reads = rrdset_create_localhost(
+ "zfs"
+ , "reads"
+ , NULL
+ , ZFS_FAMILY_ACCESSES
+ , NULL
+ , "ZFS Reads"
+ , "reads/s"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_READS
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_aread = rrddim_add(st_reads, "areads", "arc", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_dread = rrddim_add(st_reads, "dreads", "demand", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_pread = rrddim_add(st_reads, "preads", "prefetch", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mread = rrddim_add(st_reads, "mreads", "metadata", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+
+ if(arcstats.l2exist)
+ rd_l2read = rrddim_add(st_reads, "l2reads", "l2", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_reads);
+
+ rrddim_set_by_pointer(st_reads, rd_aread, aread);
+ rrddim_set_by_pointer(st_reads, rd_dread, dread);
+ rrddim_set_by_pointer(st_reads, rd_pread, pread);
+ rrddim_set_by_pointer(st_reads, rd_mread, mread);
+
+ if(arcstats.l2exist)
+ rrddim_set_by_pointer(st_reads, rd_l2read, l2read);
+
+ rrdset_done(st_reads);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(arcstats.l2exist && (do_l2bytes == CONFIG_BOOLEAN_YES || arcstats.l2_read_bytes || arcstats.l2_write_bytes))) {
+ do_l2bytes = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_l2bytes = NULL;
+ static RRDDIM *rd_l2_read_bytes = NULL;
+ static RRDDIM *rd_l2_write_bytes = NULL;
+
+ if (unlikely(!st_l2bytes)) {
+ st_l2bytes = rrdset_create_localhost(
+ "zfs"
+ , "bytes"
+ , NULL
+ , ZFS_FAMILY_ACCESSES
+ , NULL
+ , "ZFS ARC L2 Read/Write Rate"
+ , "KiB/s"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_IO
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_l2_read_bytes = rrddim_add(st_l2bytes, "read", NULL, 1, 1024, RRD_ALGORITHM_INCREMENTAL);
+ rd_l2_write_bytes = rrddim_add(st_l2bytes, "write", NULL, -1, 1024, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_l2bytes);
+
+ rrddim_set_by_pointer(st_l2bytes, rd_l2_read_bytes, arcstats.l2_read_bytes);
+ rrddim_set_by_pointer(st_l2bytes, rd_l2_write_bytes, arcstats.l2_write_bytes);
+ rrdset_done(st_l2bytes);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_ahits == CONFIG_BOOLEAN_YES || arcstats.hits || arcstats.misses)) {
+ do_ahits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_ahits = NULL;
+ static RRDDIM *rd_ahits = NULL;
+ static RRDDIM *rd_amisses = NULL;
+
+ if (unlikely(!st_ahits)) {
+ st_ahits = rrdset_create_localhost(
+ "zfs"
+ , "hits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS ARC Hits"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_HITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_ahits = rrddim_add(st_ahits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_amisses = rrddim_add(st_ahits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_ahits);
+
+ rrddim_set_by_pointer(st_ahits, rd_ahits, arcstats.hits);
+ rrddim_set_by_pointer(st_ahits, rd_amisses, arcstats.misses);
+ rrdset_done(st_ahits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_dhits == CONFIG_BOOLEAN_YES || dhit || dmiss)) {
+ do_dhits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_dhits = NULL;
+ static RRDDIM *rd_dhits = NULL;
+ static RRDDIM *rd_dmisses = NULL;
+
+ if (unlikely(!st_dhits)) {
+ st_dhits = rrdset_create_localhost(
+ "zfs"
+ , "dhits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS Demand Hits"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_DHITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_dhits = rrddim_add(st_dhits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_dmisses = rrddim_add(st_dhits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_dhits);
+
+ rrddim_set_by_pointer(st_dhits, rd_dhits, dhit);
+ rrddim_set_by_pointer(st_dhits, rd_dmisses, dmiss);
+ rrdset_done(st_dhits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_phits == CONFIG_BOOLEAN_YES || phit || pmiss)) {
+ do_phits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_phits = NULL;
+ static RRDDIM *rd_phits = NULL;
+ static RRDDIM *rd_pmisses = NULL;
+
+ if (unlikely(!st_phits)) {
+ st_phits = rrdset_create_localhost(
+ "zfs"
+ , "phits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS Prefetch Hits"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_PHITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_phits = rrddim_add(st_phits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_pmisses = rrddim_add(st_phits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_phits);
+
+ rrddim_set_by_pointer(st_phits, rd_phits, phit);
+ rrddim_set_by_pointer(st_phits, rd_pmisses, pmiss);
+ rrdset_done(st_phits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_mhits == CONFIG_BOOLEAN_YES || mhit || mmiss)) {
+ do_mhits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_mhits = NULL;
+ static RRDDIM *rd_mhits = NULL;
+ static RRDDIM *rd_mmisses = NULL;
+
+ if (unlikely(!st_mhits)) {
+ st_mhits = rrdset_create_localhost(
+ "zfs"
+ , "mhits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS Metadata Hits"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_MHITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_mhits = rrddim_add(st_mhits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_mmisses = rrddim_add(st_mhits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_mhits);
+
+ rrddim_set_by_pointer(st_mhits, rd_mhits, mhit);
+ rrddim_set_by_pointer(st_mhits, rd_mmisses, mmiss);
+ rrdset_done(st_mhits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(arcstats.l2exist && (do_l2hits == CONFIG_BOOLEAN_YES || l2hit || l2miss))) {
+ do_l2hits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_l2hits = NULL;
+ static RRDDIM *rd_l2hits = NULL;
+ static RRDDIM *rd_l2misses = NULL;
+
+ if (unlikely(!st_l2hits)) {
+ st_l2hits = rrdset_create_localhost(
+ "zfs"
+ , "l2hits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS L2 Hits"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_L2HITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_l2hits = rrddim_add(st_l2hits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_l2misses = rrddim_add(st_l2hits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_l2hits);
+
+ rrddim_set_by_pointer(st_l2hits, rd_l2hits, l2hit);
+ rrddim_set_by_pointer(st_l2hits, rd_l2misses, l2miss);
+ rrdset_done(st_l2hits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_list_hits == CONFIG_BOOLEAN_YES || arcstats.mfu_hits \
+ || arcstats.mru_hits \
+ || arcstats.mfu_ghost_hits \
+ || arcstats.mru_ghost_hits)) {
+ do_list_hits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_list_hits = NULL;
+ static RRDDIM *rd_mfu = NULL;
+ static RRDDIM *rd_mru = NULL;
+ static RRDDIM *rd_mfug = NULL;
+ static RRDDIM *rd_mrug = NULL;
+
+ if (unlikely(!st_list_hits)) {
+ st_list_hits = rrdset_create_localhost(
+ "zfs"
+ , "list_hits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS List Hits"
+ , "hits/s"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_LIST_HITS
+ , update_every
+ , RRDSET_TYPE_AREA
+ );
+
+ rd_mfu = rrddim_add(st_list_hits, "mfu", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mfug = rrddim_add(st_list_hits, "mfug", "mfu ghost", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mru = rrddim_add(st_list_hits, "mru", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mrug = rrddim_add(st_list_hits, "mrug", "mru ghost", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_list_hits);
+
+ rrddim_set_by_pointer(st_list_hits, rd_mfu, arcstats.mfu_hits);
+ rrddim_set_by_pointer(st_list_hits, rd_mru, arcstats.mru_hits);
+ rrddim_set_by_pointer(st_list_hits, rd_mfug, arcstats.mfu_ghost_hits);
+ rrddim_set_by_pointer(st_list_hits, rd_mrug, arcstats.mru_ghost_hits);
+ rrdset_done(st_list_hits);
+ }
+}
+
+void generate_charts_arc_summary(const char *plugin, const char *module, int show_zero_charts, int update_every) {
+ static int do_arc_size_breakdown = -1, do_memory = -1, do_important_ops = -1, do_actual_hits = -1, \
+ do_demand_data_hits = -1, do_prefetch_data_hits = -1, do_hash_elements = -1, do_hash_chains = -1;
+
+ if(unlikely(do_arc_size_breakdown == -1))
+ do_arc_size_breakdown = do_memory = do_important_ops = do_actual_hits = do_demand_data_hits \
+ = do_prefetch_data_hits = do_hash_elements = do_hash_chains = show_zero_charts;
+
+ unsigned long long arc_accesses_total = arcstats.hits + arcstats.misses;
+ unsigned long long real_hits = arcstats.mfu_hits + arcstats.mru_hits;
+ unsigned long long real_misses = arc_accesses_total - real_hits;
+
+ //unsigned long long anon_hits = arcstats.hits - (arcstats.mfu_hits + arcstats.mru_hits + arcstats.mfu_ghost_hits + arcstats.mru_ghost_hits);
+
+ unsigned long long arc_size = arcstats.size;
+ unsigned long long mru_size = arcstats.p;
+ //unsigned long long target_min_size = arcstats.c_min;
+ //unsigned long long target_max_size = arcstats.c_max;
+ unsigned long long target_size = arcstats.c;
+ //unsigned long long target_size_ratio = (target_max_size / target_min_size);
+
+ unsigned long long mfu_size;
+ if(arc_size > target_size)
+ mfu_size = arc_size - mru_size;
+ else
+ mfu_size = target_size - mru_size;
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_arc_size_breakdown == CONFIG_BOOLEAN_YES || mru_size || mfu_size)) {
+ do_arc_size_breakdown = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_arc_size_breakdown = NULL;
+ static RRDDIM *rd_most_recent = NULL;
+ static RRDDIM *rd_most_frequent = NULL;
+
+ if (unlikely(!st_arc_size_breakdown)) {
+ st_arc_size_breakdown = rrdset_create_localhost(
+ "zfs"
+ , "arc_size_breakdown"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS ARC Size Breakdown"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_ARC_SIZE_BREAKDOWN
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_most_recent = rrddim_add(st_arc_size_breakdown, "recent", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_ROW_TOTAL);
+ rd_most_frequent = rrddim_add(st_arc_size_breakdown, "frequent", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_ROW_TOTAL);
+ }
+ else
+ rrdset_next(st_arc_size_breakdown);
+
+ rrddim_set_by_pointer(st_arc_size_breakdown, rd_most_recent, mru_size);
+ rrddim_set_by_pointer(st_arc_size_breakdown, rd_most_frequent, mfu_size);
+ rrdset_done(st_arc_size_breakdown);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_memory == CONFIG_BOOLEAN_YES || arcstats.memory_direct_count \
+ || arcstats.memory_throttle_count \
+ || arcstats.memory_indirect_count)) {
+ do_memory = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_memory = NULL;
+#ifndef __FreeBSD__
+ static RRDDIM *rd_direct = NULL;
+#endif
+ static RRDDIM *rd_throttled = NULL;
+#ifndef __FreeBSD__
+ static RRDDIM *rd_indirect = NULL;
+#endif
+
+ if (unlikely(!st_memory)) {
+ st_memory = rrdset_create_localhost(
+ "zfs"
+ , "memory_ops"
+ , NULL
+ , ZFS_FAMILY_OPERATIONS
+ , NULL
+ , "ZFS Memory Operations"
+ , "operations/s"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_MEMORY_OPS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+#ifndef __FreeBSD__
+ rd_direct = rrddim_add(st_memory, "direct", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+#endif
+ rd_throttled = rrddim_add(st_memory, "throttled", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+#ifndef __FreeBSD__
+ rd_indirect = rrddim_add(st_memory, "indirect", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+#endif
+ }
+ else
+ rrdset_next(st_memory);
+
+#ifndef __FreeBSD__
+ rrddim_set_by_pointer(st_memory, rd_direct, arcstats.memory_direct_count);
+#endif
+ rrddim_set_by_pointer(st_memory, rd_throttled, arcstats.memory_throttle_count);
+#ifndef __FreeBSD__
+ rrddim_set_by_pointer(st_memory, rd_indirect, arcstats.memory_indirect_count);
+#endif
+ rrdset_done(st_memory);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_important_ops == CONFIG_BOOLEAN_YES || arcstats.deleted \
+ || arcstats.evict_skip \
+ || arcstats.mutex_miss \
+ || arcstats.hash_collisions)) {
+ do_important_ops = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_important_ops = NULL;
+ static RRDDIM *rd_deleted = NULL;
+ static RRDDIM *rd_mutex_misses = NULL;
+ static RRDDIM *rd_evict_skips = NULL;
+ static RRDDIM *rd_hash_collisions = NULL;
+
+ if (unlikely(!st_important_ops)) {
+ st_important_ops = rrdset_create_localhost(
+ "zfs"
+ , "important_ops"
+ , NULL
+ , ZFS_FAMILY_OPERATIONS
+ , NULL
+ , "ZFS Important Operations"
+ , "operations/s"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_IMPORTANT_OPS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_evict_skips = rrddim_add(st_important_ops, "eskip", "evict skip", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_deleted = rrddim_add(st_important_ops, "deleted", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_mutex_misses = rrddim_add(st_important_ops, "mtxmis", "mutex miss", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_hash_collisions = rrddim_add(st_important_ops, "hash_collisions", "hash collisions", 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_important_ops);
+
+ rrddim_set_by_pointer(st_important_ops, rd_deleted, arcstats.deleted);
+ rrddim_set_by_pointer(st_important_ops, rd_evict_skips, arcstats.evict_skip);
+ rrddim_set_by_pointer(st_important_ops, rd_mutex_misses, arcstats.mutex_miss);
+ rrddim_set_by_pointer(st_important_ops, rd_hash_collisions, arcstats.hash_collisions);
+ rrdset_done(st_important_ops);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_actual_hits == CONFIG_BOOLEAN_YES || real_hits || real_misses)) {
+ do_actual_hits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_actual_hits = NULL;
+ static RRDDIM *rd_actual_hits = NULL;
+ static RRDDIM *rd_actual_misses = NULL;
+
+ if (unlikely(!st_actual_hits)) {
+ st_actual_hits = rrdset_create_localhost(
+ "zfs"
+ , "actual_hits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS Actual Cache Hits"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_ACTUAL_HITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_actual_hits = rrddim_add(st_actual_hits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_actual_misses = rrddim_add(st_actual_hits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_actual_hits);
+
+ rrddim_set_by_pointer(st_actual_hits, rd_actual_hits, real_hits);
+ rrddim_set_by_pointer(st_actual_hits, rd_actual_misses, real_misses);
+ rrdset_done(st_actual_hits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_demand_data_hits == CONFIG_BOOLEAN_YES || arcstats.demand_data_hits || arcstats.demand_data_misses)) {
+ do_demand_data_hits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_demand_data_hits = NULL;
+ static RRDDIM *rd_demand_data_hits = NULL;
+ static RRDDIM *rd_demand_data_misses = NULL;
+
+ if (unlikely(!st_demand_data_hits)) {
+ st_demand_data_hits = rrdset_create_localhost(
+ "zfs"
+ , "demand_data_hits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS Data Demand Efficiency"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_DEMAND_DATA_HITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_demand_data_hits = rrddim_add(st_demand_data_hits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_demand_data_misses = rrddim_add(st_demand_data_hits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_demand_data_hits);
+
+ rrddim_set_by_pointer(st_demand_data_hits, rd_demand_data_hits, arcstats.demand_data_hits);
+ rrddim_set_by_pointer(st_demand_data_hits, rd_demand_data_misses, arcstats.demand_data_misses);
+ rrdset_done(st_demand_data_hits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_prefetch_data_hits == CONFIG_BOOLEAN_YES || arcstats.prefetch_data_hits \
+ || arcstats.prefetch_data_misses)) {
+ do_prefetch_data_hits = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_prefetch_data_hits = NULL;
+ static RRDDIM *rd_prefetch_data_hits = NULL;
+ static RRDDIM *rd_prefetch_data_misses = NULL;
+
+ if (unlikely(!st_prefetch_data_hits)) {
+ st_prefetch_data_hits = rrdset_create_localhost(
+ "zfs"
+ , "prefetch_data_hits"
+ , NULL
+ , ZFS_FAMILY_EFFICIENCY
+ , NULL
+ , "ZFS Data Prefetch Efficiency"
+ , "percentage"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_PREFETCH_DATA_HITS
+ , update_every
+ , RRDSET_TYPE_STACKED
+ );
+
+ rd_prefetch_data_hits = rrddim_add(st_prefetch_data_hits, "hits", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ rd_prefetch_data_misses = rrddim_add(st_prefetch_data_hits, "misses", NULL, 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL);
+ }
+ else
+ rrdset_next(st_prefetch_data_hits);
+
+ rrddim_set_by_pointer(st_prefetch_data_hits, rd_prefetch_data_hits, arcstats.prefetch_data_hits);
+ rrddim_set_by_pointer(st_prefetch_data_hits, rd_prefetch_data_misses, arcstats.prefetch_data_misses);
+ rrdset_done(st_prefetch_data_hits);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_hash_elements == CONFIG_BOOLEAN_YES || arcstats.hash_elements || arcstats.hash_elements_max)) {
+ do_hash_elements = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_hash_elements = NULL;
+ static RRDDIM *rd_hash_elements_current = NULL;
+ static RRDDIM *rd_hash_elements_max = NULL;
+
+ if (unlikely(!st_hash_elements)) {
+ st_hash_elements = rrdset_create_localhost(
+ "zfs"
+ , "hash_elements"
+ , NULL
+ , ZFS_FAMILY_HASH
+ , NULL
+ , "ZFS ARC Hash Elements"
+ , "elements"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_HASH_ELEMENTS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_hash_elements_current = rrddim_add(st_hash_elements, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_hash_elements_max = rrddim_add(st_hash_elements, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_hash_elements);
+
+ rrddim_set_by_pointer(st_hash_elements, rd_hash_elements_current, arcstats.hash_elements);
+ rrddim_set_by_pointer(st_hash_elements, rd_hash_elements_max, arcstats.hash_elements_max);
+ rrdset_done(st_hash_elements);
+ }
+
+ // --------------------------------------------------------------------
+
+ if(likely(do_hash_chains == CONFIG_BOOLEAN_YES || arcstats.hash_chains || arcstats.hash_chain_max)) {
+ do_hash_chains = CONFIG_BOOLEAN_YES;
+
+ static RRDSET *st_hash_chains = NULL;
+ static RRDDIM *rd_hash_chains_current = NULL;
+ static RRDDIM *rd_hash_chains_max = NULL;
+
+ if (unlikely(!st_hash_chains)) {
+ st_hash_chains = rrdset_create_localhost(
+ "zfs"
+ , "hash_chains"
+ , NULL
+ , ZFS_FAMILY_HASH
+ , NULL
+ , "ZFS ARC Hash Chains"
+ , "chains"
+ , plugin
+ , module
+ , NETDATA_CHART_PRIO_ZFS_HASH_CHAINS
+ , update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_hash_chains_current = rrddim_add(st_hash_chains, "current", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ rd_hash_chains_max = rrddim_add(st_hash_chains, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
+ }
+ else
+ rrdset_next(st_hash_chains);
+
+ rrddim_set_by_pointer(st_hash_chains, rd_hash_chains_current, arcstats.hash_chains);
+ rrddim_set_by_pointer(st_hash_chains, rd_hash_chains_max, arcstats.hash_chain_max);
+ rrdset_done(st_hash_chains);
+ }
+
+ // --------------------------------------------------------------------
+
+} \ No newline at end of file
diff --git a/collectors/proc.plugin/zfs_common.h b/collectors/proc.plugin/zfs_common.h
new file mode 100644
index 0000000..148f9e4
--- /dev/null
+++ b/collectors/proc.plugin/zfs_common.h
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_ZFS_COMMON_H
+#define NETDATA_ZFS_COMMON_H 1
+
+#include "../../daemon/common.h"
+
+#define ZFS_FAMILY_SIZE "size"
+#define ZFS_FAMILY_EFFICIENCY "efficiency"
+#define ZFS_FAMILY_ACCESSES "accesses"
+#define ZFS_FAMILY_OPERATIONS "operations"
+#define ZFS_FAMILY_HASH "hashes"
+
+struct arcstats {
+ // values
+ unsigned long long hits;
+ unsigned long long misses;
+ unsigned long long demand_data_hits;
+ unsigned long long demand_data_misses;
+ unsigned long long demand_metadata_hits;
+ unsigned long long demand_metadata_misses;
+ unsigned long long prefetch_data_hits;
+ unsigned long long prefetch_data_misses;
+ unsigned long long prefetch_metadata_hits;
+ unsigned long long prefetch_metadata_misses;
+ unsigned long long mru_hits;
+ unsigned long long mru_ghost_hits;
+ unsigned long long mfu_hits;
+ unsigned long long mfu_ghost_hits;
+ unsigned long long deleted;
+ unsigned long long mutex_miss;
+ unsigned long long evict_skip;
+ unsigned long long evict_not_enough;
+ unsigned long long evict_l2_cached;
+ unsigned long long evict_l2_eligible;
+ unsigned long long evict_l2_ineligible;
+ unsigned long long evict_l2_skip;
+ unsigned long long hash_elements;
+ unsigned long long hash_elements_max;
+ unsigned long long hash_collisions;
+ unsigned long long hash_chains;
+ unsigned long long hash_chain_max;
+ unsigned long long p;
+ unsigned long long c;
+ unsigned long long c_min;
+ unsigned long long c_max;
+ unsigned long long size;
+ unsigned long long hdr_size;
+ unsigned long long data_size;
+ unsigned long long metadata_size;
+ unsigned long long other_size;
+ unsigned long long anon_size;
+ unsigned long long anon_evictable_data;
+ unsigned long long anon_evictable_metadata;
+ unsigned long long mru_size;
+ unsigned long long mru_evictable_data;
+ unsigned long long mru_evictable_metadata;
+ unsigned long long mru_ghost_size;
+ unsigned long long mru_ghost_evictable_data;
+ unsigned long long mru_ghost_evictable_metadata;
+ unsigned long long mfu_size;
+ unsigned long long mfu_evictable_data;
+ unsigned long long mfu_evictable_metadata;
+ unsigned long long mfu_ghost_size;
+ unsigned long long mfu_ghost_evictable_data;
+ unsigned long long mfu_ghost_evictable_metadata;
+ unsigned long long l2_hits;
+ unsigned long long l2_misses;
+ unsigned long long l2_feeds;
+ unsigned long long l2_rw_clash;
+ unsigned long long l2_read_bytes;
+ unsigned long long l2_write_bytes;
+ unsigned long long l2_writes_sent;
+ unsigned long long l2_writes_done;
+ unsigned long long l2_writes_error;
+ unsigned long long l2_writes_lock_retry;
+ unsigned long long l2_evict_lock_retry;
+ unsigned long long l2_evict_reading;
+ unsigned long long l2_evict_l1cached;
+ unsigned long long l2_free_on_write;
+ unsigned long long l2_cdata_free_on_write;
+ unsigned long long l2_abort_lowmem;
+ unsigned long long l2_cksum_bad;
+ unsigned long long l2_io_error;
+ unsigned long long l2_size;
+ unsigned long long l2_asize;
+ unsigned long long l2_hdr_size;
+ unsigned long long l2_compress_successes;
+ unsigned long long l2_compress_zeros;
+ unsigned long long l2_compress_failures;
+ unsigned long long memory_throttle_count;
+ unsigned long long duplicate_buffers;
+ unsigned long long duplicate_buffers_size;
+ unsigned long long duplicate_reads;
+ unsigned long long memory_direct_count;
+ unsigned long long memory_indirect_count;
+ unsigned long long arc_no_grow;
+ unsigned long long arc_tempreserve;
+ unsigned long long arc_loaned_bytes;
+ unsigned long long arc_prune;
+ unsigned long long arc_meta_used;
+ unsigned long long arc_meta_limit;
+ unsigned long long arc_meta_max;
+ unsigned long long arc_meta_min;
+ unsigned long long arc_need_free;
+ unsigned long long arc_sys_free;
+
+ // flags
+ int l2exist;
+};
+
+void generate_charts_arcstats(const char *plugin, const char *module, int show_zero_charts, int update_every);
+void generate_charts_arc_summary(const char *plugin, const char *module, int show_zero_charts, int update_every);
+
+#endif //NETDATA_ZFS_COMMON_H