From 895e58ff251e36fb52935c7a754c0299b8046c76 Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Mon, 2 Sep 2024 16:40:47 +0200 Subject: [PATCH] Reporting / rrd - refactor existing code (#7836) Add RRD package with a simple factory class and a basic construct to define different rrd output types we support. This package contains the following: * RRD/Types -- Output definitions, responsible for generating RRD structures and feeding data * RRD/Stats -- Statistics gathering classes * Factory -- binds types and statistics together. On my end on a simple test this is roughly 40% faster than running /var/db/rrd/updaterrd.sh, which makes caching of metadata (config access) less relevant. The new script should be able to replace all existing rrd cruft and supports a debug mode to find discrepanties between defined outputs in types and collected data in stats. ``` Usage: updaterrd.php [-h] [-d] -d debug mode, output errors to stdout ``` --- plist | 24 + src/etc/inc/plugins.inc.d/core.inc | 12 +- src/etc/inc/rrd.inc | 532 ------------------ src/etc/rc.bootup | 1 - src/etc/rc.newwanip | 1 - src/etc/rc.newwanipv6 | 1 - src/etc/rc.reload_all | 1 - src/etc/rc.restart_webgui | 1 - .../health/library/OPNsense/RRD/Factory.php | 154 +++++ .../library/OPNsense/RRD/Stats/Base.php | 105 ++++ .../OPNsense/RRD/Stats/GatewayQuality.php | 57 ++ .../library/OPNsense/RRD/Stats/Interfaces.php | 47 ++ .../library/OPNsense/RRD/Stats/Mbuf.php | 54 ++ .../library/OPNsense/RRD/Stats/Memory.php | 63 +++ .../health/library/OPNsense/RRD/Stats/Ntp.php | 63 +++ .../library/OPNsense/RRD/Stats/OpenVPN.php | 56 ++ .../library/OPNsense/RRD/Stats/Processor.php | 52 ++ .../library/OPNsense/RRD/Stats/States.php | 44 ++ .../OPNsense/RRD/Stats/Temperature.php | 49 ++ .../library/OPNsense/RRD/Types/Base.php | 208 +++++++ .../OPNsense/RRD/Types/GatewayQuality.php | 59 ++ .../library/OPNsense/RRD/Types/Mbuf.php | 63 +++ .../library/OPNsense/RRD/Types/Memory.php | 63 +++ .../health/library/OPNsense/RRD/Types/Ntp.php | 64 +++ .../library/OPNsense/RRD/Types/OpenVPN.php | 60 ++ .../library/OPNsense/RRD/Types/Packets.php | 80 +++ .../library/OPNsense/RRD/Types/Processor.php | 49 ++ .../library/OPNsense/RRD/Types/States.php | 49 ++ .../OPNsense/RRD/Types/Temperature.php | 66 +++ .../library/OPNsense/RRD/Types/Traffic.php | 80 +++ .../library/OPNsense/RRD/Types/Wireless.php | 48 ++ src/opnsense/scripts/health/updaterrd.php | 73 +++ src/opnsense/scripts/openvpn/ovpn_status.py | 2 +- src/opnsense/scripts/shell/setports.php | 3 +- src/www/interfaces.php | 1 - src/www/interfaces_assign.php | 1 - src/www/reporting_settings.php | 3 +- src/www/services_ntpd.php | 1 - 38 files changed, 1741 insertions(+), 549 deletions(-) create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Factory.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Base.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/GatewayQuality.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Interfaces.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Mbuf.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Memory.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Ntp.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/OpenVPN.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Processor.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/States.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Temperature.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Base.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/GatewayQuality.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Mbuf.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Memory.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Ntp.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/OpenVPN.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Packets.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Processor.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/States.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Temperature.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Traffic.php create mode 100644 src/opnsense/scripts/health/library/OPNsense/RRD/Types/Wireless.php create mode 100755 src/opnsense/scripts/health/updaterrd.php diff --git a/plist b/plist index d7409ac5f..41b2c2568 100644 --- a/plist +++ b/plist @@ -1080,7 +1080,31 @@ /usr/local/opnsense/scripts/health/definitions/system-processor.xml /usr/local/opnsense/scripts/health/fetchData.py /usr/local/opnsense/scripts/health/flush_rrd.py +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Factory.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/Base.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/GatewayQuality.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/Interfaces.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/Mbuf.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/Memory.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/Ntp.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/OpenVPN.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/Processor.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/States.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Stats/Temperature.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Base.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/GatewayQuality.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Mbuf.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Memory.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Ntp.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/OpenVPN.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Packets.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Processor.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/States.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Temperature.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Traffic.php +/usr/local/opnsense/scripts/health/library/OPNsense/RRD/Types/Wireless.php /usr/local/opnsense/scripts/health/listReports.py +/usr/local/opnsense/scripts/health/updaterrd.php /usr/local/opnsense/scripts/interfaces/capture.py /usr/local/opnsense/scripts/interfaces/carp_global_status.php /usr/local/opnsense/scripts/interfaces/carp_set_status.php diff --git a/src/etc/inc/plugins.inc.d/core.inc b/src/etc/inc/plugins.inc.d/core.inc index d3f0d94c7..350b6e9d5 100644 --- a/src/etc/inc/plugins.inc.d/core.inc +++ b/src/etc/inc/plugins.inc.d/core.inc @@ -296,12 +296,14 @@ function core_cron() $jobs[]['autocron'] = array('/usr/local/sbin/configctl -d firmware changelog cron', '0', '22'); /** - * rrd graph collector, only execute when script is present and not locked by rrd_configure() + * rrd graph collector, only schedule execution when enabled */ - $jobs[]['autocron'] = [ - 'test -e /var/db/rrd/updaterrd.sh && /usr/local/bin/flock -n -E 1 /var/db/rrd/updaterrd.sh /var/db/rrd/updaterrd.sh', - '*' - ]; + if (!empty($config['rrd']['enable'])) { + $jobs[]['autocron'] = [ + '/usr/local/bin/flock -n -E 0 -o /tmp/updaterrd.lock /usr/local/opnsense/scripts/health/updaterrd.php', + '*' + ]; + } if (!empty($config['system']['rrdbackup']) && $config['system']['rrdbackup'] > 0) { $jobs[]['autocron'] = array( diff --git a/src/etc/inc/rrd.inc b/src/etc/inc/rrd.inc index 6f5c4bf45..8a1b9cdda 100644 --- a/src/etc/inc/rrd.inc +++ b/src/etc/inc/rrd.inc @@ -26,538 +26,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -function rrd_create($rrdcreatecmd) -{ - @mkdir('/var/db/rrd', 0775); - @chown('/var/db/rrd', 'nobody'); - - $rrdcreateoutput = array(); - $rrdcreatereturn = 0; - exec("$rrdcreatecmd 2>&1", $rrdcreateoutput, $rrdcreatereturn); - if ($rrdcreatereturn != 0) { - $rrdcreateoutput = implode(" ", $rrdcreateoutput); - log_msg(sprintf('RRD create failed exited with %s, the error is: %s', $rrdcreatereturn, $rrdcreateoutput), LOG_ERR); - } - unset($rrdcreateoutput); - return $rrdcreatereturn; -} - -function rrd_configure($verbose = false, $bootup = false) -{ - global $config; - - service_log('Generating RRD graphs...', $verbose); - - $rrddbpath = "/var/db/rrd/"; - - $traffic = "-traffic.rrd"; - $packets = "-packets.rrd"; - $states = "-states.rrd"; - $wireless = "-wireless.rrd"; - $proc = "-processor.rrd"; - $mem = "-memory.rrd"; - $mbuf = "-mbuf.rrd"; - $cellular = "-cellular.rrd"; - $vpnusers = "-vpnusers.rrd"; - $ntpd = "ntpd.rrd"; - $cputemp = "-cputemp.rrd"; - - $rrdtool = "/usr/local/bin/rrdtool"; - $netstat = "/usr/bin/netstat"; - $awk = "/usr/bin/awk"; - $pfctl = "/sbin/pfctl"; - $sysctl = "/sbin/sysctl"; - $cpustats = "/usr/local/sbin/cpustats"; - $ifconfig = "/sbin/ifconfig"; - $ntpq = "/usr/local/sbin/ntpq"; - - $rrdtrafficinterval = 60; - $rrdwirelessinterval = 60; - $rrdpacketsinterval = 60; - $rrdstatesinterval = 60; - $rrdlbpoolinterval = 60; - $rrdprocinterval = 60; - $rrdmeminterval = 60; - $rrdmbufinterval = 60; - $rrdcellularinterval = 60; - $rrdvpninterval = 60; - $rrdntpdinterval = 60; - $rrdcputempinterval = 60; - - $trafficvalid = $rrdtrafficinterval * 2; - $wirelessvalid = $rrdwirelessinterval * 2; - $packetsvalid = $rrdpacketsinterval * 2; - $statesvalid = $rrdstatesinterval * 2; - $procvalid = $rrdlbpoolinterval * 2; - $memvalid = $rrdmeminterval * 2; - $mbufvalid = $rrdmbufinterval * 2; - $cellularvalid = $rrdcellularinterval * 2; - $vpnvalid = $rrdvpninterval * 2; - $ntpdvalid = $rrdntpdinterval * 2; - $cputempvalid = $rrdcputempinterval * 2; - - /* Assume 2*10GigE for now */ - $downstream = 2500000000; - $upstream = 2500000000; - - /* XXX: kill off traffic collectors, remove when we know the update required a reboot */ - killbypid('/var/run/updaterrd.pid'); - - if (isset($config['rrd']['enable'])) { - /* create directory if needed */ - if (!is_dir($rrddbpath)) { - mkdir($rrddbpath, 0775); - } - chown($rrddbpath, "nobody"); - /* open file handle to update script and lock it in exclusive mode */ - @touch('/var/db/rrd/updaterrd.sh'); - $fobj = new \OPNsense\Core\FileObject('/var/db/rrd/updaterrd.sh', 'r+', 0755, LOCK_EX); - - /* db update script */ - $rrdupdatesh = "#!/bin/sh\n"; - $rrdupdatesh .= "\n"; - $rrdupdatesh .= "export TERM=dumb\n"; - $rrdupdatesh .= "\n"; - - $ifdescrs = get_configured_interface_with_descr(); - /* IPsec counters */ - $ifdescrs['ipsec'] = "IPsec"; - $ovpn_servers = (new OPNsense\OpenVPN\OpenVPN())->serverDevices(); - foreach ($ovpn_servers as $ifname => $data) { - $ifdescrs[$ifname] = $data['descr']; - } - - /* process all real and pseudo interfaces */ - foreach ($ifdescrs as $ifname => $ifdescr) { - $temp = get_real_interface($ifname); - if ($temp != '') { - $realif = $temp; - } - - /* TRAFFIC, set up the rrd file */ - if (!file_exists("$rrddbpath$ifname$traffic")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$traffic --step $rrdtrafficinterval "; - $rrdcreate .= "DS:inpass:COUNTER:$trafficvalid:0:$downstream "; - $rrdcreate .= "DS:outpass:COUNTER:$trafficvalid:0:$upstream "; - $rrdcreate .= "DS:inblock:COUNTER:$trafficvalid:0:$downstream "; - $rrdcreate .= "DS:outblock:COUNTER:$trafficvalid:0:$upstream "; - $rrdcreate .= "DS:inpass6:COUNTER:$trafficvalid:0:$downstream "; - $rrdcreate .= "DS:outpass6:COUNTER:$trafficvalid:0:$upstream "; - $rrdcreate .= "DS:inblock6:COUNTER:$trafficvalid:0:$downstream "; - $rrdcreate .= "DS:outblock6:COUNTER:$trafficvalid:0:$upstream "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$traffic N:U:U:U:U:U:U:U:U"); - } - - $rrdupdatesh .= "\n"; - $rrdupdatesh .= "# polling traffic for interface $ifname $realif IPv4/IPv6 counters \n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$traffic N:"; - $rrdupdatesh .= "`$pfctl -vvsI -i {$realif} | awk '\\\n"; - $rrdupdatesh .= "/In4\/Pass/ { b4pi = \$6 };/Out4\/Pass/ { b4po = \$6 };/In4\/Block/ { b4bi = \$6 };/Out4\/Block/ { b4bo = \$6 };\\\n"; - $rrdupdatesh .= "/In6\/Pass/ { b6pi = \$6 };/Out6\/Pass/ { b6po = \$6 };/In6\/Block/ { b6bi = \$6 };/Out6\/Block/ { b6bo = \$6 };\\\n"; - $rrdupdatesh .= "END {print b4pi \":\" b4po \":\" b4bi \":\" b4bo \":\" b6pi \":\" b6po \":\" b6bi \":\" b6bo};'`\n"; - - /* PACKETS, set up the rrd file */ - if (!file_exists("$rrddbpath$ifname$packets")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$packets --step $rrdpacketsinterval "; - $rrdcreate .= "DS:inpass:COUNTER:$packetsvalid:0:$downstream "; - $rrdcreate .= "DS:outpass:COUNTER:$packetsvalid:0:$upstream "; - $rrdcreate .= "DS:inblock:COUNTER:$packetsvalid:0:$downstream "; - $rrdcreate .= "DS:outblock:COUNTER:$packetsvalid:0:$upstream "; - $rrdcreate .= "DS:inpass6:COUNTER:$packetsvalid:0:$downstream "; - $rrdcreate .= "DS:outpass6:COUNTER:$packetsvalid:0:$upstream "; - $rrdcreate .= "DS:inblock6:COUNTER:$packetsvalid:0:$downstream "; - $rrdcreate .= "DS:outblock6:COUNTER:$packetsvalid:0:$upstream "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$packets N:U:U:U:U:U:U:U:U"); - } - - $rrdupdatesh .= "\n"; - $rrdupdatesh .= "# polling packets for interface $ifname $realif \n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$packets N:"; - $rrdupdatesh .= "`$pfctl -vvsI -i {$realif} | awk '\\\n"; - $rrdupdatesh .= "/In4\/Pass/ { b4pi = \$4 };/Out4\/Pass/ { b4po = \$4 };/In4\/Block/ { b4bi = \$4 };/Out4\/Block/ { b4bo = \$4 };\\\n"; - $rrdupdatesh .= "/In6\/Pass/ { b6pi = \$4 };/Out6\/Pass/ { b6po = \$4 };/In6\/Block/ { b6bi = \$4 };/Out6\/Block/ { b6bo = \$4 };\\\n"; - $rrdupdatesh .= "END {print b4pi \":\" b4po \":\" b4bi \":\" b4bo \":\" b6pi \":\" b6po \":\" b6bi \":\" b6bo};'`\n"; - - /* WIRELESS, set up the rrd file */ - if (isset($config['interfaces'][$ifname]['wireless']['mode']) && $config['interfaces'][$ifname]['wireless']['mode'] == "bss") { - if (!file_exists("$rrddbpath$ifname$wireless")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$wireless --step $rrdwirelessinterval "; - $rrdcreate .= "DS:snr:GAUGE:$wirelessvalid:0:1000 "; - $rrdcreate .= "DS:rate:GAUGE:$wirelessvalid:0:1000 "; - $rrdcreate .= "DS:channel:GAUGE:$wirelessvalid:0:1000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$wireless N:U:U:U"); - } - - $rrdupdatesh .= "\n"; - $rrdupdatesh .= "# polling wireless for interface $ifname $realif \n"; - $rrdupdatesh .= "WIFI=`$ifconfig {$realif} list sta| $awk 'gsub(\"M\", \"\") {getline 2;print substr(\$5, 0, length(\$5)-2) \":\" $4 \":\" $3}'`\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$wireless N:\${WIFI}\n"; - } - - /* OpenVPN, set up the rrd file */ - if (isset($ovpn_servers[$ifname])) { - if (!file_exists("$rrddbpath$ifname$vpnusers")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$vpnusers --step $rrdvpninterval "; - $rrdcreate .= "DS:users:GAUGE:$vpnvalid:0:10000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$vpnusers N:U"); - } - $rrdupdatesh .= "\n"; - $rrdupdatesh .= "# polling vpn users for interface $ifname\n"; - $rrdupdatesh .= "list_current_users() {\n"; - $rrdupdatesh .= " sleep 0.2\n"; - $rrdupdatesh .= " echo \"status 2\"\n"; - $rrdupdatesh .= " sleep 0.2\n"; - $rrdupdatesh .= " echo \"quit\"\n"; - $rrdupdatesh .= "}\n"; - $rrdupdatesh .= "OVPN=`list_current_users | nc -U {$ovpn_servers[$ifname]['sockFilename']} | awk -F\",\" '/^CLIENT_LIST/ {print \$2}' | wc -l | awk '{print $1}'`\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$vpnusers N:\${OVPN}\n"; - } - } - - /* System only statistics */ - $ifname = "system"; - - /* STATES, create pf states database */ - if (! file_exists("$rrddbpath$ifname$states")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$states --step $rrdstatesinterval "; - $rrdcreate .= "DS:pfrate:GAUGE:$statesvalid:0:10000000 "; - $rrdcreate .= "DS:pfstates:GAUGE:$statesvalid:0:10000000 "; - $rrdcreate .= "DS:pfnat:GAUGE:$statesvalid:0:10000000 "; - $rrdcreate .= "DS:srcip:GAUGE:$statesvalid:0:10000000 "; - $rrdcreate .= "DS:dstip:GAUGE:$statesvalid:0:10000000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$states N:U:U:U:U:U"); - } - - /* the pf states gathering function. */ - $rrdupdatesh .= "\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$states N:`/usr/local/opnsense/scripts/system/rrd_pfstate_info.py`\n\n"; - /* End pf states statistics */ - - /* CPU, create CPU statistics database */ - if (!file_exists("$rrddbpath$ifname$proc")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$proc --step $rrdprocinterval "; - $rrdcreate .= "DS:user:GAUGE:$procvalid:0:10000000 "; - $rrdcreate .= "DS:nice:GAUGE:$procvalid:0:10000000 "; - $rrdcreate .= "DS:system:GAUGE:$procvalid:0:10000000 "; - $rrdcreate .= "DS:interrupt:GAUGE:$procvalid:0:10000000 "; - $rrdcreate .= "DS:processes:GAUGE:$procvalid:0:10000000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$proc N:U:U:U:U:U"); - } - - /* the CPU stats gathering function. */ - $rrdupdatesh .= "CPU=`$cpustats | cut -f1-4 -d':'`\n"; - /* Using ps uxaH will count all processes including system threads. Top was undercounting. */ - $rrdupdatesh .= "PROCS=`ps uxaH | wc -l | awk '{print \$1;}'`\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$proc N:\${CPU}:\${PROCS}\n"; - /* End CPU statistics */ - - /* Memory, create Memory statistics database */ - if (!file_exists("$rrddbpath$ifname$mem")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$mem --step $rrdmeminterval "; - $rrdcreate .= "DS:active:GAUGE:$memvalid:0:10000000 "; - $rrdcreate .= "DS:inactive:GAUGE:$memvalid:0:10000000 "; - $rrdcreate .= "DS:free:GAUGE:$memvalid:0:10000000 "; - $rrdcreate .= "DS:cache:GAUGE:$memvalid:0:10000000 "; - $rrdcreate .= "DS:wire:GAUGE:$memvalid:0:10000000 "; - $rrdcreate .= "RRA:MIN:0.5:1:1200 "; - $rrdcreate .= "RRA:MIN:0.5:5:720 "; - $rrdcreate .= "RRA:MIN:0.5:60:1860 "; - $rrdcreate .= "RRA:MIN:0.5:1440:2284 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - $rrdcreate .= "RRA:MAX:0.5:1:1200 "; - $rrdcreate .= "RRA:MAX:0.5:5:720 "; - $rrdcreate .= "RRA:MAX:0.5:60:1860 "; - $rrdcreate .= "RRA:MAX:0.5:1440:2284"; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$mem N:U:U:U:U:U"); - } - - /* the Memory stats gathering function. */ - $rrdupdatesh .= "MEM=`$sysctl -n vm.stats.vm.v_page_count vm.stats.vm.v_active_count vm.stats.vm.v_inactive_count vm.stats.vm.v_free_count vm.stats.vm.v_cache_count vm.stats.vm.v_wire_count | "; - $rrdupdatesh .= " $awk '{getline active;getline inactive;getline free;getline cache;getline wire;printf "; - $rrdupdatesh .= "((active/$0) * 100)\":\"((inactive/$0) * 100)\":\"((free/$0) * 100)\":\"((cache/$0) * 100)\":\"(wire/$0 * 100)}'`\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$mem N:\${MEM}\n"; - /* End Memory statistics */ - - /* mbuf, create mbuf statistics database */ - if (!file_exists("$rrddbpath$ifname$mbuf")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$mbuf --step $rrdmbufinterval "; - $rrdcreate .= "DS:current:GAUGE:$mbufvalid:0:10000000 "; - $rrdcreate .= "DS:cache:GAUGE:$mbufvalid:0:10000000 "; - $rrdcreate .= "DS:total:GAUGE:$mbufvalid:0:10000000 "; - $rrdcreate .= "DS:max:GAUGE:$mbufvalid:0:10000000 "; - $rrdcreate .= "RRA:MIN:0.5:1:1200 "; - $rrdcreate .= "RRA:MIN:0.5:5:720 "; - $rrdcreate .= "RRA:MIN:0.5:60:1860 "; - $rrdcreate .= "RRA:MIN:0.5:1440:2284 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - $rrdcreate .= "RRA:MAX:0.5:1:1200 "; - $rrdcreate .= "RRA:MAX:0.5:5:720 "; - $rrdcreate .= "RRA:MAX:0.5:60:1860 "; - $rrdcreate .= "RRA:MAX:0.5:1440:2284"; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$mbuf N:U:U:U:U"); - } - - /* the mbuf stats gathering function. */ - $rrdupdatesh .= "MBUF=`$netstat -m | "; - $rrdupdatesh .= " $awk '/mbuf clusters in use/ { gsub(/\//, \":\", $1); print $1; }'`\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$mbuf N:\${MBUF}\n"; - /* End mbuf statistics */ - - /* End System statistics */ - - /* NTP, set up the ntpd rrd file */ - if (isset($config['ntpd']['statsgraph'])) { - /* set up the ntpd rrd file */ - if (!file_exists("$rrddbpath$ntpd")) { - $rrdcreate = "$rrdtool create $rrddbpath$ntpd --step $rrdntpdinterval "; - $rrdcreate .= "DS:offset:GAUGE:$ntpdvalid:-1000:1000 "; - $rrdcreate .= "DS:sjit:GAUGE:$ntpdvalid:0:1000 "; - $rrdcreate .= "DS:cjit:GAUGE:$ntpdvalid:0:1000 "; - $rrdcreate .= "DS:wander:GAUGE:$ntpdvalid:0:1000 "; - $rrdcreate .= "DS:freq:GAUGE:$ntpdvalid:0:1000 "; - $rrdcreate .= "DS:disp:GAUGE:$ntpdvalid:0:1000 "; - $rrdcreate .= "RRA:MIN:0.5:1:1200 "; - $rrdcreate .= "RRA:MIN:0.5:5:720 "; - $rrdcreate .= "RRA:MIN:0.5:60:1860 "; - $rrdcreate .= "RRA:MIN:0.5:1440:2284 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - $rrdcreate .= "RRA:MAX:0.5:1:1200 "; - $rrdcreate .= "RRA:MAX:0.5:5:720 "; - $rrdcreate .= "RRA:MAX:0.5:60:1860 "; - $rrdcreate .= "RRA:MAX:0.5:1440:2284 "; - - rrd_create($rrdcreate); - unset($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ntpd N:U:U:U:U:U:U"); - } - /* the ntp stats gathering function. */ - $rrdupdatesh .= "\n"; - $rrdupdatesh .= "$ntpq -c rv | $awk 'BEGIN{ RS=\",\"}{ print }' >> /tmp/ntp-rrdstats.$$\n"; - $rrdupdatesh .= "NOFFSET=`grep offset /tmp/ntp-rrdstats.$$ | awk 'BEGIN{FS=\"=\"}{print $2}'`\n"; - $rrdupdatesh .= "NFREQ=`grep frequency /tmp/ntp-rrdstats.$$ | awk 'BEGIN{FS=\"=\"}{print $2}'`\n"; - $rrdupdatesh .= "NSJIT=`grep sys_jitter /tmp/ntp-rrdstats.$$ | awk 'BEGIN{FS=\"=\"}{print $2}'`\n"; - $rrdupdatesh .= "NCJIT=`grep clk_jitter /tmp/ntp-rrdstats.$$ | awk 'BEGIN{FS=\"=\"}{print $2}'`\n"; - $rrdupdatesh .= "NWANDER=`grep clk_wander /tmp/ntp-rrdstats.$$ | awk 'BEGIN{FS=\"=\"}{print $2}'`\n"; - $rrdupdatesh .= "NDISPER=`grep rootdisp /tmp/ntp-rrdstats.$$ | awk 'BEGIN{FS=\"=\"}{print $2}'`\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ntpd \N:\${NOFFSET}:\${NSJIT}:\${NCJIT}:\${NWANDER}:\${NFREQ}:\${NDISPER}\n"; - $rrdupdatesh .= "rm /tmp/ntp-rrdstats.$$\n"; - $rrdupdatesh .= "\n"; - } - /* End NTP statistics */ - - /* CPU Temperature */ - /* the CPU Temperature gathering Function. */ - /* Cpu Temp, create CPU Temperature database */ - if (!file_exists("$rrddbpath$ifname$cputemp")) { - $rrdcreate = "$rrdtool create $rrddbpath$ifname$cputemp --step $rrdcputempinterval "; - $rrdcreate .= "DS:cpu0temp:GAUGE:$cputempvalid:-273:5000 "; - $rrdcreate .= "RRA:MIN:0.5:1:1000 "; - $rrdcreate .= "RRA:MIN:0.5:5:1000 "; - $rrdcreate .= "RRA:MIN:0.5:60:1000 "; - $rrdcreate .= "RRA:MIN:0.5:720:3000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:1000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:720:3000 "; - $rrdcreate .= "RRA:MAX:0.5:1:1000 "; - $rrdcreate .= "RRA:MAX:0.5:5:1000 "; - $rrdcreate .= "RRA:MAX:0.5:60:1000 "; - $rrdcreate .= "RRA:MAX:0.5:720:3000 "; - $rrdcreate .= "RRA:LAST:0.5:1:1000 "; - $rrdcreate .= "RRA:LAST:0.5:5:1000 "; - $rrdcreate .= "RRA:LAST:0.5:60:1000 "; - $rrdcreate .= "RRA:LAST:0.5:720:3000 "; - - rrd_create($rrdcreate); - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($bootup) { - mwexec("$rrdtool update $rrddbpath$ifname$cputemp N:U"); - } - - /* the CPU Temperature gathering function */ - $rrdupdatesh .= "CPUTEMP=\$(/usr/local/opnsense/scripts/system/temperature.sh rrd)\n"; - $rrdupdatesh .= "$rrdtool update $rrddbpath$ifname$cputemp N:\${CPUTEMP}\n"; - /* end CPU Temp gathering */ - - /* Start gateway quality */ - $rrdupdatesh .= <<seek(0)->truncate(0)->write($rrdupdatesh); - unset($fobj); - } elseif (file_exists('/var/db/rrd/updaterrd.sh')) { - /* remove script, stop collecting. cron checks for existence if this file */ - unlink('/var/db/rrd/updaterrd.sh'); - } - - $databases = glob("{$rrddbpath}/*.rrd"); - foreach ($databases as $database) { - chown($database, "nobody"); - } - - service_log("done.\n", $verbose); -} - -function rrd_create_gateway_quality($rrd_file, $unknown = false) -{ - $rrdtool = '/usr/local/bin/rrdtool'; - - $rrdinterval = 60; - $valid = $rrdinterval * 2; - - /* GATEWAY QUALITY, set up the rrd file */ - if (!file_exists("$rrd_file")) { - $rrdcreate = "$rrdtool create $rrd_file --step $rrdinterval "; - $rrdcreate .= "DS:loss:GAUGE:$valid:0:100 "; - $rrdcreate .= "DS:delay:GAUGE:$valid:0:100000 "; - $rrdcreate .= "DS:stddev:GAUGE:$valid:0:100000 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1:1200 "; - $rrdcreate .= "RRA:AVERAGE:0.5:5:720 "; - $rrdcreate .= "RRA:AVERAGE:0.5:60:1860 "; - $rrdcreate .= "RRA:AVERAGE:0.5:1440:2284 "; - - rrd_create($rrdcreate); - $unknown = true; - } - - /* enter UNKNOWN values in the RRD so it knows we rebooted. */ - if ($unknown) { - mwexec("$rrdtool update $rrd_file N:U:U:U"); - } -} - function rrd_export() { $rrddbpath = '/var/db/rrd'; diff --git a/src/etc/rc.bootup b/src/etc/rc.bootup index 313fdb31f..f9693d4e7 100755 --- a/src/etc/rc.bootup +++ b/src/etc/rc.bootup @@ -101,7 +101,6 @@ filter_configure_sync(true); plugins_configure('monitor', true, [null, true]); plugins_configure('vpn_map', true); plugins_configure('bootup', true); -rrd_configure(true, true); system_powerd_configure(true); /* XXX this seems misplaced */ diff --git a/src/etc/rc.newwanip b/src/etc/rc.newwanip index d4f44a06e..70db64075 100755 --- a/src/etc/rc.newwanip +++ b/src/etc/rc.newwanip @@ -117,4 +117,3 @@ if (is_ipaddr($cacheip) && $ip != $cacheip) { plugins_configure('vpn_map', false, [$interface, 'inet']); plugins_configure('newwanip_map', false, [$interface, 'inet']); -rrd_configure(); diff --git a/src/etc/rc.newwanipv6 b/src/etc/rc.newwanipv6 index 2a536ccf4..98cb8d2b5 100755 --- a/src/etc/rc.newwanipv6 +++ b/src/etc/rc.newwanipv6 @@ -128,4 +128,3 @@ foreach ($interfaces as $interface) { filter_configure_sync(); plugins_configure('vpn_map', false, [join(',', $interfaces), 'inet6']); plugins_configure('newwanip_map', false, [join(',', $interfaces), 'inet6']); -rrd_configure(); diff --git a/src/etc/rc.reload_all b/src/etc/rc.reload_all index 6bfc1ef3c..4c009025d 100755 --- a/src/etc/rc.reload_all +++ b/src/etc/rc.reload_all @@ -56,7 +56,6 @@ system_routing_configure(true); filter_configure_sync(true); plugins_configure('local', true); plugins_configure('vpn_map', true); -rrd_configure(true); /* plugins service reload */ passthru('/usr/local/etc/rc.freebsd stop'); diff --git a/src/etc/rc.restart_webgui b/src/etc/rc.restart_webgui index 4c626dcab..5fdbbc03a 100755 --- a/src/etc/rc.restart_webgui +++ b/src/etc/rc.restart_webgui @@ -18,4 +18,3 @@ if (count($argv) > 1) { } webgui_configure_do(true); -rrd_configure(true); diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Factory.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Factory.php new file mode 100644 index 000000000..69d71a77e --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Factory.php @@ -0,0 +1,154 @@ +isInstantiable() || !$cls->isSubclassOf('OPNsense\\RRD\\Types\\Base')) { + throw new TypeNotFound(sprintf("%s not found", $type)); + } + } catch (ReflectionException) { + throw new TypeNotFound(sprintf("%s not found", $type)); + } + return $cls->newInstance($target); + } + + /** + * collect statistics to feed to rrd, generates an array containing classnames including runtimes and payload + * for example: ['Mbuf'] => ['data' => [], 'runtime' => 0.1555] + * @return $this + */ + public function collect() + { + $this->stats = []; + foreach (glob(sprintf("%s/Stats/*.php", __DIR__)) as $filename) { + $classname = substr(basename($filename),0, -4); + $cls = new ReflectionClass('\\OPNsense\\RRD\\Stats\\'. $classname); + if ($cls->isInstantiable() && $cls->isSubclassOf('OPNsense\\RRD\\Stats\\Base')) { + try { + $start_time = microtime(true); + $obj = $cls->newInstance(); + $tmp = $obj->run(); + $this->stats[$classname] = [ + 'data' => $tmp, + 'runtime' => microtime(true) - $start_time + ]; + } catch (\Error | \Exception $e) { + echo $e; + syslog(LOG_ERR, sprintf("Error collecting %s [%s]", $classname, $e)); + } + } + } + return $this; + } + + /** + * get gathered statistics for a specific collector + * @param string $name name of the collector, e.g. Mbuf + * @return array + */ + public function getData(string $name) + { + return isset($this->stats[$name]) ? $this->stats[$name]['data'] : []; + } + + /** + * @return array collected statistics + */ + public function getRawStats() + { + return $this->stats; + } + + /** + * update all registered RRD graphs + * @return $this + */ + public function updateAll($debug=false) + { + foreach (glob(sprintf("%s/Types/*.php", __DIR__)) as $filename) { + $classname = substr(basename($filename),0, -4); + $fclassname = '\\OPNsense\\RRD\\Types\\'. $classname; + try { + $cls = new ReflectionClass($fclassname); + if ($cls->isInstantiable() && $cls->isSubclassOf('OPNsense\\RRD\\Types\\Base')) { + $wants = call_user_func([$fclassname, 'wantsStats']); + $the_data = $this->getData($wants); + if (empty($the_data)) { + continue; + } + /** + * Because some data sets should be split into multiple targets, we need a facility to map + * (a part) of the dataset to the target. + * + * In most cases an RRD type results in a single target, in which case the 'type' knows where + * it should render the content to by default. + * + * If the type+stats combination results in multiple targets we need to leave this mapping + * somewhere, in which case the 'Type' is the most logical place + * as being responsible for the actual output. + */ + foreach (call_user_func([$fclassname, 'payloadSplitter'], $the_data) as $tfilename => $data) { + $obj = $cls->newInstance($tfilename); + $obj->create(); /* only creates when no target exists yet */ + $obj->update($data, $debug); + } + + } + } catch (\Error | \Exception $e) { + echo $e; + syslog(LOG_ERR, sprintf("Error instantiating %s [%s]", $classname, $e)); + } + } + return $this; + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Base.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Base.php new file mode 100644 index 000000000..8953d2dbc --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Base.php @@ -0,0 +1,105 @@ +&1', $payload, $returncode); + if ($returncode == 0 && !empty($payload[0])) { + return json_decode($payload[0], true) ?? []; + } + return null; + } + + /** + * run simple shell command + * @param string $cmd command to execute + * @return array output lines when returnvalue equals 0 + */ + protected function shellCmd(string $cmd) + { + exec($cmd . ' 2>&1', $payload, $returncode); + if ($returncode == 0 && !empty($payload[0])) { + return $payload; + } + return []; + } + + /** + * collect static generic metadata on init + */ + public function __construct() + { + if (empty(self::$metadata)) { + self::$metadata['interfaces'] = []; + + self::$metadata['interfaces']['ipsec'] = ['name' => 'IPsec', 'if' => 'enc0']; + foreach (Config::getInstance()->object()->interfaces->children() as $ifname => $node) { + if (isset($node->enable)) { + self::$metadata['interfaces'][$ifname] = [ + 'name' => !empty((string)$node->descr) ? (string)$node->descr : $ifname, + 'if' => (string)$node->if + ]; + /* relevant parts from get_real_interface() using in old rrd.inc */ + if (isset($node->wireless) && !strstr((string)$node->if, '_wlan')) { + self::$metadata['interfaces'][$ifname]['if'] .= '_wlan0'; + } + } + } + foreach ((new \OPNsense\OpenVPN\OpenVPN())->serverDevices() as $ifname => $data) { + self::$metadata['interfaces'][$ifname] = [ + 'name' => $data['descr'], + 'if' => $ifname, + 'openvpn_socket' => $data['sockFilename'] + ]; + } + self::$metadata['ntp_statsgraph'] = empty((string)Config::getInstance()->object()->ntp->statsgraph); + } + } + + /** + * run + */ + public function run() + { + throw new \Exception("Need to implement run()"); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/GatewayQuality.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/GatewayQuality.php new file mode 100644 index 000000000..dd566d5be --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/GatewayQuality.php @@ -0,0 +1,57 @@ + $record[0], + 'delay' => sprintf('%.07f', $record[1] / 1000.0 / 1000.0), + 'stddev' => sprintf('%.07f', $record[2] / 1000.0 / 1000.0), + 'loss' => $record[3] + ]; + } + return $result; + } +} diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Interfaces.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Interfaces.php new file mode 100644 index 000000000..3074f665a --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Interfaces.php @@ -0,0 +1,47 @@ +jsonShellCmd('/usr/local/opnsense/scripts/filter/pfstatistics.py interfaces'); + if (!empty($data) && !empty($data['interfaces'])) { + $data = $data['interfaces']; + foreach (self::$metadata['interfaces'] as $ifname => $ifdata) { + if (isset($ifdata['if']) && !empty($data[$ifdata['if']])) { + $result[$ifname] = $data[$ifdata['if']]; + } + } + } + return $result; + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Mbuf.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Mbuf.php new file mode 100644 index 000000000..2fd468105 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Mbuf.php @@ -0,0 +1,54 @@ +shellCmd('/usr/bin/netstat -m'); + if (!empty($netstat)) { + foreach ($netstat as $line) { + if (strpos($line, 'mbuf clusters in use') !== false) { + $tmp = explode('/', explode(' ', $line)[0]); + return [ + 'current' => $tmp[0], + 'cache' => $tmp[1], + 'total' => $tmp[2], + 'max' => $tmp[3] + ]; + } + } + } + return []; + } +} + + + diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Memory.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Memory.php new file mode 100644 index 000000000..baf6a7065 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Memory.php @@ -0,0 +1,63 @@ +shellCmd('/sbin/sysctl ' . implode(' ', $sysctls)); + if (!empty($memory)) { + $percentages = []; + $data = []; + foreach ($memory as $idx => $item) { + // strip vm.stats.vm.v_ and collect into $result + $tmp = explode(':', substr($item, 14)); + $data[$tmp[0]] = trim($tmp[1]); + if ($idx > 0) { + $percentages[explode('_', $tmp[0])[0]] = ($data[$tmp[0]] / $data['page_count']) * 100.0; + } + } + return $percentages; + } + return []; + } +} + + + diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Ntp.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Ntp.php new file mode 100644 index 000000000..5cc831426 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Ntp.php @@ -0,0 +1,63 @@ +shellCmd('/usr/local/sbin/ntpq -c rv'); + if (!empty($ntpq)) { + $fieldmap = [ + 'offset' => 'offset', + 'frequency' => 'freq', + 'sys_jitter' => 'sjit', + 'clk_jitter' => 'cjit', + 'clk_wander' => 'wander', + 'rootdisp' => 'disp' + ]; + foreach ($ntpq as $idx => $item) { + foreach (explode(',', $item) as $part) { + $tmp = explode('=', trim($part)); + if (isset($fieldmap[$tmp[0]])) { + $data[$fieldmap[$tmp[0]]] = $tmp[1]; + } + } + } + } + return $data; + } +} + + + diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/OpenVPN.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/OpenVPN.php new file mode 100644 index 000000000..2e6ff68c7 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/OpenVPN.php @@ -0,0 +1,56 @@ +jsonShellCmd('/usr/local/opnsense/scripts/openvpn/ovpn_status.py --options server'); + if (!empty($data) && !empty($data['server'])) { + $data = $data['server']; + foreach (self::$metadata['interfaces'] as $ifname => $ifdata) { + if (isset($ifdata['openvpn_socket'])) { + foreach ($data as $ovpn) { + if ($ovpn['socket'] == $ifdata['openvpn_socket']) { + $result[$ifname] = [ + 'users' => !empty($ovpn['client_list']) ? count($ovpn['client_list']) : 0 + ]; + } + } + } + } + } + return $result; + } +} + + + diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Processor.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Processor.php new file mode 100644 index 000000000..f8b683e49 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Processor.php @@ -0,0 +1,52 @@ +shellCmd('/usr/local/sbin/cpustats'); + $ps = $this->shellCmd('/bin/ps uxaH'); + if (!empty($cpustats) && !empty($ps)) { + $tmp = explode(':', $cpustats[0]); + return [ + 'user' => $tmp[0], + 'nice' => $tmp[1], + 'system' => $tmp[2], + 'interrupt' => $tmp[3], + 'processes' => count($ps)-1 + ]; + } + return []; + } +} + + + diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/States.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/States.php new file mode 100644 index 000000000..add1e27ee --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/States.php @@ -0,0 +1,44 @@ +shellCmd('/usr/local/opnsense/scripts/system/rrd_pfstate_info.py'); + if (!empty($data)) { + return explode(':', $data[0]); + } + return []; + } +} + + + diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Temperature.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Temperature.php new file mode 100644 index 000000000..30736aeb7 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Stats/Temperature.php @@ -0,0 +1,49 @@ +shellCmd( + '/sbin/sysctl -n dev.cpu.0.temperature hw.acpi.thermal.tz0.temperature hw.temperature.CPU' + ); + if (!empty($data)) { + return [$data[0]]; + } + return []; + } +} + + + + + + diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Base.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Base.php new file mode 100644 index 000000000..f9388f61d --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Base.php @@ -0,0 +1,208 @@ +datasets = $ds; + } + + /** + * @param array $rra datasets + */ + protected function setRRA($rra) + { + $this->round_robin_archives = $rra; + } + + /** + * @param string $filename filename to link to this collection + */ + public function __construct(string $filename) + { + $this->filename = $filename; + } + + /** + * Maps datasets (stats) to input a new type object requires + * + * @param array collected stats for the type + * @return \Iterator + */ + public static function payloadSplitter(array $payload) + { + if (!empty(static::$stdfilename)) { + yield static::$basedir . static::$stdfilename => $payload; + } + } + + /** + * @param string $name dataset name + * @param string $opp operation to use + * @param int $heartbeat heartbeat to use, default when null + * @param int $min min value to use, default when null + * @param int $max max value to use, default when null + * @return $this + */ + public function addDataset( + string $name, + string $opp, + ?int $heartbeat=null, + ?int $min=null, + ?int $max=null + ){ + $this->datasets[] = [ + $name, + $opp, + $heartbeat != null ? $heartbeat : $this->ds_heartbeat, + $min != null ? $min : $this->ds_min, + $max != null ? $max : $this->ds_max + ]; + return $this; + } + + /** + * @param array $names list of names to add + * @param string $opp operation to use + * @param int $heartbeat heartbeat to use, default when null + * @param int $min min value to use, default when null + * @param int $max max value to use, default when null + * @return $this + */ + public function addDatasets( + array $names, + string $opp, + ?int $heartbeat=null, + ?int $min=null, + ?int $max=null + ){ + foreach ($names as $name) { + $this->addDataset($name, $opp, $heartbeat, $min, $max); + } + return $this; + } + + /** + * create or replace RRD database + * @param bool $overwrite overwrite when file exists + * @return $this + */ + public function create($overwrite=false) + { + if (!$overwrite && file_exists($this->filename)) { + return $this; + } + $cmd_text = sprintf('/usr/local/bin/rrdtool create %s --step %d ', $this->filename, 60); + foreach ($this->datasets as $dataset) { + $cmd_text .= 'DS:' . implode(':', $dataset) . ' '; + } + foreach ($this->round_robin_archives as $rra) + { + $cmd_text .= 'RRA:' . implode(':', $rra) . ' '; + } + $cmd_text .= ' 2>&1'; + exec($cmd_text, $rrdcreateoutput, $rrdcreatereturn); + if ($rrdcreatereturn != 0) { + $rrdcreateoutput = implode(" ", $rrdcreateoutput); + syslog( + LOG_ERR, + sprintf('RRD create failed exited with %s, the error is: %s', $rrdcreatereturn, $rrdcreateoutput) + ); + } + return $this; + } + + /** + * update the dataset + * @param array $dataset [named] dataset to use + * @param bool $debug throw debug messages to stdout + * @return $this + */ + public function update(array $dataset = [], bool $debug=false) + { + $values = []; + $map_by_name = count($dataset) > 0 && !isset($dataset[0]); + foreach ($this->datasets as $idx => $ds) { + if ($map_by_name) { + $value = isset($dataset[$ds[0]]) ? $dataset[$ds[0]] : 'U'; + } else { + $value = !empty($dataset) && isset($dataset[$idx]) ? $dataset[$idx] : 'U'; + } + $values[] = $value; + if ($value == 'U' && $debug) { + echo sprintf("[%s] '%s' missing in datafeed\n", get_class($this), $ds[0]); + } + } + $cmd_text = sprintf('/usr/local/bin/rrdtool update %s N:%s 2>&1', $this->filename, implode(':', $values)); + exec($cmd_text, $rrdcreateoutput, $rrdcreatereturn); + if ($rrdcreatereturn != 0 && $debug) { + echo sprintf("[cmd failed] %s\n", $cmd_text); + } + return $this; + } + + /** + * @return string name of the Stats class this type requires data from. + */ + public static function wantsStats() + { + $tmp = explode('\\', static::class); + return array_pop($tmp); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/GatewayQuality.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/GatewayQuality.php new file mode 100644 index 000000000..c877af5b7 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/GatewayQuality.php @@ -0,0 +1,59 @@ +addDatasets(['loss','delay','stddev'], 'GAUGE'); + } + + /** + * @inheritdoc + */ + public static function payloadSplitter(array $payload) + { + foreach ($payload as $gw => $data) { + yield static::$basedir . sprintf(static::$stdfilename, $gw) => $data; + } + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Mbuf.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Mbuf.php new file mode 100644 index 000000000..51b3552cf --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Mbuf.php @@ -0,0 +1,63 @@ +addDatasets(['current', 'cache', 'total', 'max'], 'GAUGE'); + $this->setRRA([ + ['MIN', 0.5, 1, 1200], + ['MIN', 0.5, 5, 720], + ['MIN', 0.5, 60, 1860], + ['MIN', 0.5, 1440, 2284], + ['AVERAGE', 0.5, 1, 1200], + ['AVERAGE', 0.5, 5, 720], + ['AVERAGE', 0.5, 60, 1860], + ['AVERAGE', 0.5, 1440, 2284], + ['MAX', 0.5, 1, 1200], + ['MAX', 0.5, 5, 720], + ['MAX', 0.5, 60, 1860], + ['MAX', 0.5, 1440, 2284], + ]); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Memory.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Memory.php new file mode 100644 index 000000000..eca877205 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Memory.php @@ -0,0 +1,63 @@ +addDatasets(['active', 'inactive', 'free', 'cache', 'wire'], 'GAUGE'); + $this->setRRA([ + ['MIN', 0.5, 1, 1200], + ['MIN', 0.5, 5, 720], + ['MIN', 0.5, 60, 1860], + ['MIN', 0.5, 1440, 2284], + ['AVERAGE', 0.5, 1, 1200], + ['AVERAGE', 0.5, 5, 720], + ['AVERAGE', 0.5, 60, 1860], + ['AVERAGE', 0.5, 1440, 2284], + ['MAX', 0.5, 1, 1200], + ['MAX', 0.5, 5, 720], + ['MAX', 0.5, 60, 1860], + ['MAX', 0.5, 1440, 2284], + ]); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Ntp.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Ntp.php new file mode 100644 index 000000000..2fd9bf4af --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Ntp.php @@ -0,0 +1,64 @@ +addDataset('offset', 'GAUGE', 120, -1000, 1000); + $this->addDatasets(['sjit', 'cjit', 'wander', 'freq', 'disp'], 'GAUGE'); + $this->setRRA([ + ['MIN', 0.5, 1, 1200], + ['MIN', 0.5, 5, 720], + ['MIN', 0.5, 60, 1860], + ['MIN', 0.5, 1440, 2284], + ['AVERAGE', 0.5, 1, 1200], + ['AVERAGE', 0.5, 5, 720], + ['AVERAGE', 0.5, 60, 1860], + ['AVERAGE', 0.5, 1440, 2284], + ['MAX', 0.5, 1, 1200], + ['MAX', 0.5, 5, 720], + ['MAX', 0.5, 60, 1860], + ['MAX', 0.5, 1440, 2284], + ]); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/OpenVPN.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/OpenVPN.php new file mode 100644 index 000000000..7d1e77e1d --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/OpenVPN.php @@ -0,0 +1,60 @@ +addDatasets(['users'], 'GAUGE'); + } + + /** + * @inheritdoc + */ + public static function payloadSplitter(array $payload) + { + foreach ($payload as $intf => $data) { + yield static::$basedir . sprintf(static::$stdfilename, $intf) => $data; + } + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Packets.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Packets.php new file mode 100644 index 000000000..67b78430a --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Packets.php @@ -0,0 +1,80 @@ +addDatasets( + ['inpass','outpass','inblock','outblock','inpass6','outpass6','inblock6','outblock6'], + 'COUNTER' + ); + } + + /** + * Packets is a subcollection of Interfaces + */ + public static function wantsStats() + { + return 'Interfaces'; + } + + /** + * @inheritdoc + */ + public static function payloadSplitter(array $payload) + { + foreach ($payload as $intf => $data) { + $tmp = [ + 'inpass' => $data['in4_pass_packets'], + 'outpass' => $data['out4_pass_packets'], + 'inblock' => $data['in4_block_packets'], + 'outblock' => $data['out4_block_packets'], + 'inpass6' => $data['in6_pass_packets'], + 'outpass6' => $data['out6_pass_packets'], + 'inblock6' => $data['in6_block_packets'], + 'outblock6' => $data['out6_block_packets'] + ]; + yield static::$basedir . sprintf(static::$stdfilename, $intf) => $tmp; + } + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Processor.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Processor.php new file mode 100644 index 000000000..38165d8ed --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Processor.php @@ -0,0 +1,49 @@ +addDatasets(['user', 'nice', 'system', 'interrupt', 'processes'], 'GAUGE'); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/States.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/States.php new file mode 100644 index 000000000..05c5c563e --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/States.php @@ -0,0 +1,49 @@ +addDatasets(['pfrate', 'pfstates', 'pfnat', 'srcip', 'dstip'], 'GAUGE'); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Temperature.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Temperature.php new file mode 100644 index 000000000..46ae836ca --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Temperature.php @@ -0,0 +1,66 @@ +addDatasets(['cpu0temp'], 'GAUGE'); + $this->setRRA([ + ['MIN', 0.5, 1, 1200], + ['MIN', 0.5, 5, 720], + ['MIN', 0.5, 60, 1860], + ['MIN', 0.5, 1440, 2284], + ['AVERAGE', 0.5, 1, 1200], + ['AVERAGE', 0.5, 5, 720], + ['AVERAGE', 0.5, 60, 1860], + ['AVERAGE', 0.5, 1440, 2284], + ['MAX', 0.5, 1, 1200], + ['MAX', 0.5, 5, 720], + ['MAX', 0.5, 60, 1860], + ['MAX', 0.5, 1440, 2284], + ['LAST', 0.5, 1, 1200], + ['LAST', 0.5, 5, 720], + ['LAST', 0.5, 60, 1860], + ['LAST', 0.5, 1440, 2284], + ]); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Traffic.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Traffic.php new file mode 100644 index 000000000..da1a9a77f --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Traffic.php @@ -0,0 +1,80 @@ +addDatasets( + ['inpass','outpass','inblock','outblock','inpass6','outpass6','inblock6','outblock6'], + 'COUNTER' + ); + } + + /** + * Traffic is a subcollection of Interfaces + */ + public static function wantsStats() + { + return 'Interfaces'; + } + + /** + * @inheritdoc + */ + public static function payloadSplitter(array $payload) + { + foreach ($payload as $intf => $data) { + $tmp = [ + 'inpass' => $data['in4_pass_bytes'], + 'outpass' => $data['out4_pass_bytes'], + 'inblock' => $data['in4_block_bytes'], + 'outblock' => $data['out4_block_bytes'], + 'inpass6' => $data['in6_pass_bytes'], + 'outpass6' => $data['out6_pass_bytes'], + 'inblock6' => $data['in6_block_bytes'], + 'outblock6' => $data['out6_block_bytes'] + ]; + yield static::$basedir . sprintf(static::$stdfilename, $intf) => $tmp; + } + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Wireless.php b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Wireless.php new file mode 100644 index 000000000..030c961f5 --- /dev/null +++ b/src/opnsense/scripts/health/library/OPNsense/RRD/Types/Wireless.php @@ -0,0 +1,48 @@ +addDatasets(['snr','rate','channel'], 'GAUGE'); + } +} \ No newline at end of file diff --git a/src/opnsense/scripts/health/updaterrd.php b/src/opnsense/scripts/health/updaterrd.php new file mode 100755 index 000000000..e1022e310 --- /dev/null +++ b/src/opnsense/scripts/health/updaterrd.php @@ -0,0 +1,73 @@ +#!/usr/local/bin/php +application->modelsDir, + $phalcon_config->application->libraryDir, + __DIR__ . '/library/']) +)->register(); +/* end loader */ + +$opts = getopt('hd', [], $optind); +$args = array_slice($argv, $optind); + +if (isset($opts['h'])) { + echo "Usage: updaterrd.php [-h] [-d]\n\n"; + echo "\t-d debug mode, output errors to stdout\n"; + exit(0); +} + +$rrdcnf = OPNsense\Core\Config::getInstance()->object()->rrd; +if ($rrdcnf === null || !isset($rrdcnf->enable)) { + echo "RRD statistics disabled... exit\n"; + exit(0); +} + +$start_time = microtime(True); + +if (!is_dir('/var/db/rrd')) { + @mkdir('/var/db/rrd', 0775); + @chown('/var/db/rrd', 'nobody'); +} + +$rrd_factory = new \OPNsense\RRD\Factory(); +$rrd_factory->collect()->updateAll(isset($opts['d'])); + +if (isset($opts['d'])) { + $collect_time = 0.0; + echo sprintf("total runtime [seconds] \t: %0.2f\n", microtime(True) - $start_time); + foreach ($rrd_factory->getRawStats() as $name => $payload) { + $collect_time += $payload['runtime']; + } + echo sprintf("total collection [seconds] \t: %0.2f\n", $collect_time); +} diff --git a/src/opnsense/scripts/openvpn/ovpn_status.py b/src/opnsense/scripts/openvpn/ovpn_status.py index 99dc574ec..7cbc29aed 100755 --- a/src/opnsense/scripts/openvpn/ovpn_status.py +++ b/src/opnsense/scripts/openvpn/ovpn_status.py @@ -57,7 +57,7 @@ def ovpn_cmd(filename, cmd): def ovpn_status(filename): - response = {} + response = {'socket': filename} buffer = ovpn_cmd(filename, 'status 3') if buffer is None: return {'status': 'failed'} diff --git a/src/opnsense/scripts/shell/setports.php b/src/opnsense/scripts/shell/setports.php index f9c92a539..baed2ae57 100755 --- a/src/opnsense/scripts/shell/setports.php +++ b/src/opnsense/scripts/shell/setports.php @@ -46,5 +46,6 @@ if (set_networking_interfaces_ports()) { filter_configure_sync(true); plugins_configure('local', true); plugins_configure('vpn_map', true); - rrd_configure(true); + /* rrd graphs depend on a cronjob */ + system_cron_configure(); } diff --git a/src/www/interfaces.php b/src/www/interfaces.php index b45d4cc23..0666298e7 100644 --- a/src/www/interfaces.php +++ b/src/www/interfaces.php @@ -577,7 +577,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { system_routing_configure(); filter_configure(); plugins_configure('newwanip_map', false, [join(',', array_keys($toapplylist))]); - rrd_configure(); } clear_subsystem_dirty('interfaces'); diff --git a/src/www/interfaces_assign.php b/src/www/interfaces_assign.php index 1f4bbe660..e5c62e808 100644 --- a/src/www/interfaces_assign.php +++ b/src/www/interfaces_assign.php @@ -302,7 +302,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($changes > 0) { // reload filter, rrd when interfaces have changed (original from apply action) filter_configure(); - rrd_configure(); } header(url_safe('Location: /interfaces_assign.php')); exit; diff --git a/src/www/reporting_settings.php b/src/www/reporting_settings.php index 632deec4a..b2f63c877 100644 --- a/src/www/reporting_settings.php +++ b/src/www/reporting_settings.php @@ -76,8 +76,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { unbound_configure_do(); } else { plugins_configure('monitor'); - rrd_configure(); - /* XXX: rrd graphs depend on a cronjob, we can probably remove this in a future version when the job is unconditional */ + /* rrd graphs depend on a cronjob */ system_cron_configure(); } } diff --git a/src/www/services_ntpd.php b/src/www/services_ntpd.php index 1e8c17d65..089f28862 100644 --- a/src/www/services_ntpd.php +++ b/src/www/services_ntpd.php @@ -149,7 +149,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { write_config("Updated NTP Server Settings"); - rrd_configure(); ntpd_configure_do(); system_cron_configure();