diff --git a/net/mwan3/test-version.sh b/net/mwan3/test-version.sh new file mode 100755 index 0000000000000..c52d3c26b35fd --- /dev/null +++ b/net/mwan3/test-version.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 diff --git a/utils/prometheus-node-exporter-lua/Makefile b/utils/prometheus-node-exporter-lua/Makefile index 37c6f7d1bee37..8eaa9a79ff3fb 100644 --- a/utils/prometheus-node-exporter-lua/Makefile +++ b/utils/prometheus-node-exporter-lua/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=prometheus-node-exporter-lua -PKG_VERSION:=2025.11.22 +PKG_VERSION:=2026.06.05 PKG_RELEASE:=1 PKG_MAINTAINER:=Etienne CHAMPETIER @@ -54,7 +54,7 @@ endef define Package/prometheus-node-exporter-lua/description Provides node metrics as Prometheus scraping endpoint. - This service is a lightweight rewrite in LUA of the offical Prometheus node_exporter. + This service is a lightweight rewrite in LUA of the official Prometheus node_exporter. endef # Additional optional exporters: @@ -81,6 +81,17 @@ define Package/prometheus-node-exporter-lua-dawn/install $(INSTALL_DATA) ./files/usr/lib/lua/prometheus-collectors/dawn.lua $(1)/usr/lib/lua/prometheus-collectors/ endef +define Package/prometheus-node-exporter-lua-filesystem + $(call Package/prometheus-node-exporter-lua/Default) + TITLE+= (filesystem collector) + DEPENDS:=prometheus-node-exporter-lua +luci-lib-nixio +endef + +define Package/prometheus-node-exporter-lua-filesystem/install + $(INSTALL_DIR) $(1)/usr/lib/lua/prometheus-collectors + $(INSTALL_BIN) ./files/usr/lib/lua/prometheus-collectors/filesystem.lua $(1)/usr/lib/lua/prometheus-collectors/ +endef + define Package/prometheus-node-exporter-lua-hostapd_stations $(call Package/prometheus-node-exporter-lua/Default) TITLE+= (hostapd_stations collector) - Requires a full hostapd / wpad build @@ -279,9 +290,37 @@ define Package/prometheus-node-exporter-lua-nft-counters/install $(INSTALL_DATA) ./files/usr/lib/lua/prometheus-collectors/nft_counters.lua $(1)/usr/lib/lua/prometheus-collectors/ endef +define Package/prometheus-node-exporter-lua-modemmanager + $(call Package/prometheus-node-exporter-lua/Default) + TITLE+= (modemmanager collector) + DEPENDS:=prometheus-node-exporter-lua +modemmanager +lua-cjson +endef + +define Package/prometheus-node-exporter-lua-modemmanager/install + $(INSTALL_DIR) $(1)/usr/lib/lua/prometheus-collectors + $(INSTALL_DATA) ./files/usr/lib/lua/prometheus-collectors/modemmanager.lua $(1)/usr/lib/lua/prometheus-collectors/ +endef + +define Package/prometheus-node-exporter-lua-unbound + $(call Package/prometheus-node-exporter-lua/Default) + TITLE+= (unbound stats collector) + DEPENDS:=prometheus-node-exporter-lua +unbound-daemon +endef + +define Package/prometheus-node-exporter-lua-unbound/description + Adds a Unbound stats collector to the Prometheus node exporter. + Refer to https://github.com/marinierb/prometheus-node-exporter-lua-unbound for configuration details. +endef + +define Package/prometheus-node-exporter-lua-unbound/install + $(INSTALL_DIR) $(1)/usr/lib/lua/prometheus-collectors + $(INSTALL_DATA) ./files/usr/lib/lua/prometheus-collectors/unbound.lua $(1)/usr/lib/lua/prometheus-collectors/ +endef + $(eval $(call BuildPackage,prometheus-node-exporter-lua)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-bmx7)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-dawn)) +$(eval $(call BuildPackage,prometheus-node-exporter-lua-filesystem)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-hostapd_stations)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-hostapd_ubus_stations)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-hwmon)) @@ -300,3 +339,5 @@ $(eval $(call BuildPackage,prometheus-node-exporter-lua-realtek-poe)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-mwan3)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-ethtool)) $(eval $(call BuildPackage,prometheus-node-exporter-lua-nft-counters)) +$(eval $(call BuildPackage,prometheus-node-exporter-lua-modemmanager)) +$(eval $(call BuildPackage,prometheus-node-exporter-lua-unbound)) diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/bmx6.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/bmx6.lua deleted file mode 100644 index be26072d165be..0000000000000 --- a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/bmx6.lua +++ /dev/null @@ -1,37 +0,0 @@ -local json = require "cjson" - -local function interpret_suffix(rate) - local value = string.sub(rate, 1, -2) - local suffix = string.sub(rate, -1) - if suffix == "K" then return tonumber(value) * 10^3 end - if suffix == "M" then return tonumber(value) * 10^6 end - if suffix == "G" then return tonumber(value) * 10^9 end - return rate -end - -local function scrape() - local status = json.decode(get_contents("/var/run/bmx6/json/status")).status - local labels = { - version = status.version, - id = status.name, - address = status.primaryIp - } - - metric("bmx6_status", "gauge", labels, 1) - - local links = json.decode(get_contents("/var/run/bmx6/json/links")).links - local metric_bmx6_rxRate = metric("bmx6_link_rxRate","gauge") - local metric_bmx6_txRate = metric("bmx6_link_txRate","gauge") - - for _, link in pairs(links) do - local labels = { - source = status.name, - target = link.name, - dev = link.viaDev - } - metric_bmx6_rxRate(labels, interpret_suffix(link.rxRate)) - metric_bmx6_txRate(labels, interpret_suffix(link.txRate)) - end -end - -return { scrape = scrape } diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/filesystem.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/filesystem.lua new file mode 100644 index 0000000000000..25a1084e7816a --- /dev/null +++ b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/filesystem.lua @@ -0,0 +1,85 @@ +-- depends on luci-lib-nixio +local nix = require "nixio" + +local function scrape() + -- node exporter description - Filesystem size in bytes + local metric_size_bytes = metric("node_filesystem_size_bytes", "gauge") + -- node exporter description - Filesystem free space in bytes + local metric_free_bytes = metric("node_filesystem_free_bytes", "gauge") + -- node exporter description - Filesystem space available to non-root users in bytes + local metric_avail_bytes = metric("node_filesystem_avail_bytes", "gauge") + -- node exporter description - Filesystem total file nodes + local metric_files = metric("node_filesystem_files", "gauge") + -- node exporter description - Filesystem total free file nodes + local metric_files_free = metric("node_filesystem_files_free", "gauge") + -- node exporter description - Filesystem read-only status + local metric_readonly = metric("node_filesystem_readonly", "gauge") + + for e in io.lines("/proc/self/mounts") do + local fields = space_split(e) + + local device = fields[1] + local mount_point = fields[2] + local fs_type = fields[3] + + -- Filter list from node exporter: + -- https://github.com/prometheus/node_exporter/blob/b9d0932179a0c5b3a8863f3d6cdafe8584cedc8e/collector/filesystem_linux.go#L36-L37 + if mount_point:find("/dev/?", 1) ~= 1 + and mount_point:find("/proc/?", 1) ~= 1 + and mount_point:find("/run/credentials/?", 1) ~= 1 + and mount_point:find("/sys/?", 1) ~= 1 + and mount_point:find("/var/lib/docker/?", 1) ~= 1 + and mount_point:find("/var/lib/containers/storage/?", 1) ~= 1 + and fs_type ~= "autofs" + and fs_type ~= "binfmt_misc" + and fs_type ~= "bpf" + and fs_type ~= "cgroup" + and fs_type ~= "cgroup2" + and fs_type ~= "configfs" + and fs_type ~= "debugfs" + and fs_type ~= "devpts" + and fs_type ~= "devtmpfs" + and fs_type ~= "fusectl" + and fs_type ~= "hugetlbfs" + and fs_type ~= "iso9660" + and fs_type ~= "mqueue" + and fs_type ~= "nsfs" + and fs_type ~= "overlay" + and fs_type ~= "proc" + and fs_type ~= "procfs" + and fs_type ~= "pstore" + and fs_type ~= "rpc_pipefs" + and fs_type ~= "securityfs" + and fs_type ~= "selinuxfs" + and fs_type ~= "squashfs" + and fs_type ~= "sysfs" + and fs_type ~= "tracefs" then + -- note that this excludes / as it's an overlay filesystem + + local stat = nix.fs.statvfs(mount_point) + + -- https://github.com/torvalds/linux/blob/e5fa841af679cb830da6c609c740a37bdc0b8b35/include/linux/statfs.h#L31 + local ST_RDONLY = 0x001 + + local labels = { + device = device, + fstype = fs_type, + mountpoint = mount_point, + } + + local ro = 0 + if (nix.bit.band(stat.flag, ST_RDONLY)) == 1 then + ro = 1 + end + + metric_size_bytes(labels, stat.blocks * stat.bsize) + metric_free_bytes(labels, stat.bfree * stat.bsize) + metric_avail_bytes(labels, stat.bavail * stat.bsize) + metric_files(labels, stat.files) + metric_files_free(labels, stat.ffree) + metric_readonly(labels, ro) + end + end +end + +return { scrape = scrape } diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/modemmanager.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/modemmanager.lua new file mode 100644 index 0000000000000..8f9a83cfa5153 --- /dev/null +++ b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/modemmanager.lua @@ -0,0 +1,48 @@ +local cjson = require "cjson" + +local function scrape() + local file, output, data + + file = io.popen("mmcli -L -J", 'r') + if not file then return end + output = file:read('*all') + file:close() + + local ok, modem_list = pcall(cjson.decode, output) + if not ok or not modem_list["modem-list"] then return end + + for _, modem in ipairs(modem_list["modem-list"]) do + + file = io.popen("mmcli -m " .. modem .. " -J", 'r') + if file then + output = file:read('*all') + file:close() + + ok, data = pcall(cjson.decode, output) + if ok and data["modem"] then + local quality = data["modem"]["generic"]["signal-quality"]["value"] + metric("modemmanager_signal_quality", "gauge", {modem=modem}, tonumber(quality)) + end + end + + file = io.popen("mmcli -m " .. modem .. " --signal-get -J", 'r') + if file then + output = file:read('*all') + file:close() + + ok, data = pcall(cjson.decode, output) + if ok and data["modem"] and data["modem"]["signal"] then + for tech, values in pairs(data["modem"]["signal"]) do + for metric_name, value in pairs(values) do + local num = tonumber(value) + if num then + metric("modemmanager_signal_" .. tech .. "_" .. metric_name, "gauge", {modem=modem}, num) + end + end + end + end + end + end +end + +return { scrape = scrape } diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/openwrt.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/openwrt.lua index a92b7ddc16f87..3493453931d3e 100644 --- a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/openwrt.lua +++ b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/openwrt.lua @@ -12,13 +12,22 @@ local labels = { target = b.release.target } +local os_info = { + id = string.lower(b.release.distribution), + name = b.release.distribution, + pretty_name = b.release.distribution .. " " .. b.release.version, + version = b.release.version, + version_id = b.release.version, + build_id = b.release.revision, +} + local b = nil local u = nil local ubus = nil local function scrape() metric("node_openwrt_info", "gauge", labels, 1) + metric("node_os_info", "gauge", os_info, 1) end return { scrape = scrape } - diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/uci_dhcp_host.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/uci_dhcp_host.lua index 95096259999c9..2c16fc3c83254 100644 --- a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/uci_dhcp_host.lua +++ b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/uci_dhcp_host.lua @@ -5,8 +5,16 @@ local function scrape() local metric_uci_host = metric("uci_dhcp_host", "gauge") curs:foreach("dhcp", "host", function(s) - if s[".type"] == "host" then - labels = {name=s["name"], mac=string.upper(s["mac"]), dns=s["dns"], ip=s["ip"]} + local labels = {name=s["name"], dns=s["dns"], ip=s["ip"], duid=s["duid"]} + + if s["mac"] == nil then + metric_uci_host(labels, 1) + return + end + + local macs = type(s["mac"]) == "table" and s["mac"] or {s["mac"]} + for _, mac in ipairs(macs) do + labels["mac"] = string.upper(mac) metric_uci_host(labels, 1) end end) diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/unbound.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/unbound.lua new file mode 100644 index 0000000000000..0555e8decc910 --- /dev/null +++ b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/unbound.lua @@ -0,0 +1,56 @@ +-- Unbound stats exporter + +local socket = require("socket") +local unix = require("socket.unix") + +local function scrape() + local metrics = { + ["total.num.queries" ] = metric("unbound_num_queries_total", "counter"), + ["total.num.queries_ip_ratelimited" ] = metric("unbound_num_queries_ip_ratelimited_total", "counter"), + ["total.num.queries_cookie_valid" ] = metric("unbound_num_queries_cookie_valid_total", "counter"), + ["total.num.queries_cookie_client" ] = metric("unbound_num_queries_cookie_client_total", "counter"), + ["total.num.queries_cookie_invalid" ] = metric("unbound_num_queries_cookie_invalid_total", "counter"), + ["total.num.queries_discard_timeout"] = metric("unbound_num_queries_discard_timeout_total","counter"), + ["total.num.queries_wait_limit" ] = metric("unbound_num_queries_wait_limit_total", "counter"), + ["total.num.cachehits" ] = metric("unbound_cachehits_total", "counter"), + ["total.num.cachemiss" ] = metric("unbound_cachemiss_total", "counter"), + ["total.num.prefetch" ] = metric("unbound_prefetch_total", "counter"), + ["total.num.queries_timed_out" ] = metric("unbound_num_queries_timed_out_total", "counter"), + ["total.query.queue_time_us.max" ] = metric("unbound_query_queue_time_us_max", "gauge"), + ["total.num.expired" ] = metric("unbound_num_expired_total", "counter"), + ["total.num.recursivereplies" ] = metric("unbound_num_recursivereplies_total", "counter"), + ["total.num.dns_error_reports" ] = metric("unbound_num_dns_error_reports_total", "counter"), + ["total.requestlist.avg" ] = metric("unbound_requestlist_avg", "gauge"), + ["total.requestlist.max" ] = metric("unbound_requestlist_max", "gauge"), + ["total.requestlist.overwritten" ] = metric("unbound_requestlist_overwritten_total", "counter"), + ["total.requestlist.exceeded" ] = metric("unbound_requestlist_exceeded_total", "counter"), + ["total.requestlist.current.all" ] = metric("unbound_requestlist_current_all", "gauge"), + ["total.requestlist.current.user" ] = metric("unbound_requestlist_current_user", "gauge"), + ["total.recursion.time.avg" ] = metric("unbound_recursion_time_avg", "gauge"), + ["total.recursion.time.median" ] = metric("unbound_recursion_time_median", "gauge"), + } + + local sock = unix() + local ok, err = sock:connect("/run/unbound.ctl") + if not ok then + return nil, "failed to connect to unbound socket: " .. (err or "unknown") + end + + sock:settimeout(1) + sock:send("UBCT1 stats_noreset\n") + local out = sock:receive("*a") + + sock:close() + + for line in out:gmatch("[^\r\n]+") do + local key, val = line:match("^([^=]+)=(.+)$") + if key and val then + local n = tonumber(val) + if metrics[key] then + metrics[key]({}, n) + end + end + end +end + +return { scrape = scrape } diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi.lua index 4b1e8f40208ac..054ae86a762de 100644 --- a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi.lua +++ b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi.lua @@ -10,6 +10,10 @@ local function scrape() local u = ubus.connect() local status = u:call("network.wireless", "status", {}) + if not status then + return + end + for dev, dev_table in pairs(status) do for _, intf in ipairs(dev_table['interfaces']) do local ifname = intf['ifname'] diff --git a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi_stations.lua b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi_stations.lua index c632f94d11826..db95c52f68266 100644 --- a/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi_stations.lua +++ b/utils/prometheus-node-exporter-lua/files/usr/lib/lua/prometheus-collectors/wifi_stations.lua @@ -23,6 +23,10 @@ local function scrape() local u = ubus.connect() local status = u:call("network.wireless", "status", {}) + if not status then + return + end + for dev, dev_table in pairs(status) do for _, intf in ipairs(dev_table['interfaces']) do local ifname = intf['ifname'] diff --git a/utils/prometheus-node-exporter-lua/test-version.sh b/utils/prometheus-node-exporter-lua/test-version.sh new file mode 100755 index 0000000000000..c52d3c26b35fd --- /dev/null +++ b/utils/prometheus-node-exporter-lua/test-version.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 diff --git a/utils/prometheus-node-exporter-lua/test.sh b/utils/prometheus-node-exporter-lua/test.sh new file mode 100755 index 0000000000000..913d863eb7d25 --- /dev/null +++ b/utils/prometheus-node-exporter-lua/test.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# shellcheck shell=busybox + +case "$PKG_NAME" in +prometheus-node-exporter-lua-openwrt) + # call ubus outside scape, so skip the check + exit 0 + ;; + +*) + prometheus-node-exporter-lua | grep node_scrape_collector_success + ;; +esac diff --git a/utils/realtek-poe/test-version.sh b/utils/realtek-poe/test-version.sh new file mode 100755 index 0000000000000..c52d3c26b35fd --- /dev/null +++ b/utils/realtek-poe/test-version.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 diff --git a/utils/ubnt-manager/test-version.sh b/utils/ubnt-manager/test-version.sh new file mode 100755 index 0000000000000..c52d3c26b35fd --- /dev/null +++ b/utils/ubnt-manager/test-version.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0