From 06c2ef2ce4c048b262ba17a31edd00d48f79b303 Mon Sep 17 00:00:00 2001 From: Andre Heider Date: Fri, 2 Dec 2022 14:45:01 +0100 Subject: [PATCH] prometheus-node-exporter-ucode: add new package This is a port of prometheus-node-exporter-lua to ucode. Signed-off-by: Andre Heider --- utils/prometheus-node-exporter-ucode/Makefile | 74 ++++++ .../files/base/conntrack.uc | 4 + .../files/base/cpu.uc | 44 ++++ .../files/base/entropy.uc | 4 + .../files/base/filefd.uc | 7 + .../files/base/loadavg.uc | 8 + .../files/base/meminfo.uc | 24 ++ .../files/base/netclass.uc | 48 ++++ .../files/base/netdev.uc | 40 +++ .../files/base/selinux.uc | 10 + .../files/base/time.uc | 1 + .../files/base/uname.uc | 8 + .../files/config | 7 + .../files/extra/dnsmasq.uc | 6 + .../files/extra/ltq-dsl.uc | 72 ++++++ .../files/extra/netstat.uc | 30 +++ .../files/extra/openwrt.uc | 14 ++ .../files/extra/snmp6.uc | 23 ++ .../files/extra/uci_dhcp_host.uc | 14 ++ .../files/extra/wifi.uc | 118 +++++++++ .../files/extra/wireguard.uc | 49 ++++ .../prometheus-node-exporter-ucode/files/init | 73 ++++++ .../files/metrics.uc | 227 ++++++++++++++++++ .../files/run.sh | 2 + utils/prometheus-node-exporter-ucode/test.sh | 3 + 25 files changed, 910 insertions(+) create mode 100644 utils/prometheus-node-exporter-ucode/Makefile create mode 100644 utils/prometheus-node-exporter-ucode/files/base/conntrack.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/cpu.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/entropy.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/filefd.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/loadavg.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/meminfo.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/netclass.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/netdev.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/selinux.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/time.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/base/uname.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/config create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/netstat.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/wifi.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc create mode 100644 utils/prometheus-node-exporter-ucode/files/init create mode 100644 utils/prometheus-node-exporter-ucode/files/metrics.uc create mode 100755 utils/prometheus-node-exporter-ucode/files/run.sh create mode 100755 utils/prometheus-node-exporter-ucode/test.sh diff --git a/utils/prometheus-node-exporter-ucode/Makefile b/utils/prometheus-node-exporter-ucode/Makefile new file mode 100644 index 0000000000..3d712c10a3 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/Makefile @@ -0,0 +1,74 @@ +# Copyright (C) 2013-2017 OpenWrt.org + +include $(TOPDIR)/rules.mk + +PKG_NAME:=prometheus-node-exporter-ucode +PKG_VERSION:=2022.12.02 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Andre Heider +PKG_LICENSE:=Apache-2.0 + +include $(INCLUDE_DIR)/package.mk + +Build/Compile= + +define Package/$(PKG_NAME)/Default + SECTION:=utils + CATEGORY:=Utilities + TITLE:=Prometheus node exporter + PKGARCH:=all +endef + +define Package/$(PKG_NAME) + $(call Package/$(PKG_NAME)/Default) + DEPENDS:=+uhttpd +uhttpd-mod-ucode +rpcd +ucode-mod-fs +ucode-mod-ubus +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/config $(1)/etc/config/$(PKG_NAME) + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/init $(1)/etc/init.d/$(PKG_NAME) + $(INSTALL_DIR) $(1)/usr/share/ucode/node-exporter/lib + $(INSTALL_DATA) ./files/metrics.uc $(1)/usr/share/ucode/node-exporter/ + $(INSTALL_DATA) ./files/base/*.uc $(1)/usr/share/ucode/node-exporter/lib/ + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./files/run.sh $(1)/usr/bin/$(PKG_NAME) +endef + +define Package/$(PKG_NAME)/conffiles +/etc/config/$(PKG_NAME) +endef + +define Package/$(PKG_NAME)/description + Provides node metrics as Prometheus scraping endpoint. + + This service is a lightweight rewrite in ucode of the offical Prometheus node_exporter. +endef + +$(eval $(call BuildPackage,prometheus-node-exporter-ucode)) + +define Collector + define Package/$(PKG_NAME)-$(1) + $$(call Package/$(PKG_NAME)/Default) + TITLE+= ($(2)) + DEPENDS:=$(PKG_NAME) $(3) + endef + + define Package/$(PKG_NAME)-$(1)/install + $$(INSTALL_DIR) $$(1)/usr/share/ucode/node-exporter/lib + $$(INSTALL_DATA) ./files/extra/$(1).uc $$(1)/usr/share/ucode/node-exporter/lib/ + endef + + $$(eval $$(call BuildPackage,$(PKG_NAME)-$(1))) +endef + +$(eval $(call Collector,dnsmasq,Dnsmasq collector,@dnsmasq)) +$(eval $(call Collector,ltq-dsl,Lantiq/Intel/MaxLinear DSL collector,@ltq-dsl-app)) +$(eval $(call Collector,netstat,netstat collector,)) +$(eval $(call Collector,openwrt,OpenWrt collector,)) +$(eval $(call Collector,snmp6,snmp6 collector,)) +$(eval $(call Collector,uci_dhcp_host,UCI DHCP host collector,)) +$(eval $(call Collector,wifi,Wi-Fi collector,+ucode-mod-nl80211)) +$(eval $(call Collector,wireguard,Wireguard collector,+rpcd-mod-wireguard)) diff --git a/utils/prometheus-node-exporter-ucode/files/base/conntrack.uc b/utils/prometheus-node-exporter-ucode/files/base/conntrack.uc new file mode 100644 index 0000000000..b3ee3f3bc9 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/conntrack.uc @@ -0,0 +1,4 @@ +gauge("node_nf_conntrack_entries") + (null, oneline("/proc/sys/net/netfilter/nf_conntrack_count")); +gauge("node_nf_conntrack_entries_limit") + (null, oneline("/proc/sys/net/netfilter/nf_conntrack_max")); diff --git a/utils/prometheus-node-exporter-ucode/files/base/cpu.uc b/utils/prometheus-node-exporter-ucode/files/base/cpu.uc new file mode 100644 index 0000000000..574655df8e --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/cpu.uc @@ -0,0 +1,44 @@ +let f = fs.open("/proc/stat"); + +if (!f) + return false; + +const desc = [ + null, + "user", + "nice", + "system", + "idle", + "iowait", + "irq", + "softirq", + "steal", + "guest", + "guest_nice", +]; +const m_cpu = counter("node_cpu_seconds_total"); + +let line; +while (line = nextline(f)) { + const x = wsplit(line); + + if (length(x) < 2) + continue; + + if (match(x[0], /^cpu\d+/)) { + const count = min(length(x), length(desc)); + for (let i = 1; i < count; i++) + m_cpu({ cpu: x[0], mode: desc[i] }, x[i] / 100.0); + } else if (x[0] == "intr") + counter("node_intr_total")(null, x[1]); + else if (x[0] == "ctxt") + counter("node_context_switches_total")(null, x[1]); + else if (x[0] == "btime") + gauge("node_boot_time_seconds")(null, x[1]); + else if (x[0] == "processes") + counter("node_forks_total")(null, x[1]); + else if (x[0] == "procs_running") + gauge("node_procs_running_total")(null, x[1]); + else if (x[0] == "procs_blocked") + gauge("node_procs_blocked_total")(null, x[1]); +} diff --git a/utils/prometheus-node-exporter-ucode/files/base/entropy.uc b/utils/prometheus-node-exporter-ucode/files/base/entropy.uc new file mode 100644 index 0000000000..2df44261a3 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/entropy.uc @@ -0,0 +1,4 @@ +gauge("node_entropy_available_bits") + (null, oneline("/proc/sys/kernel/random/entropy_avail")); +gauge("node_entropy_pool_size_bits") + (null, oneline("/proc/sys/kernel/random/poolsize")); diff --git a/utils/prometheus-node-exporter-ucode/files/base/filefd.uc b/utils/prometheus-node-exporter-ucode/files/base/filefd.uc new file mode 100644 index 0000000000..359cffd4eb --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/filefd.uc @@ -0,0 +1,7 @@ +const x = wsplit(oneline("/proc/sys/fs/file-nr")); + +if (length(x) < 3) + return false; + +gauge("node_filefd_allocated")(null, x[0]); +gauge("node_filefd_maximum")(null, x[2]); diff --git a/utils/prometheus-node-exporter-ucode/files/base/loadavg.uc b/utils/prometheus-node-exporter-ucode/files/base/loadavg.uc new file mode 100644 index 0000000000..ba67dafe34 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/loadavg.uc @@ -0,0 +1,8 @@ +const x = wsplit(oneline("/proc/loadavg")); + +if (length(x) < 3) + return false; + +gauge("node_load1")(null, x[0]); +gauge("node_load5")(null, x[1]); +gauge("node_load15")(null, x[2]); diff --git a/utils/prometheus-node-exporter-ucode/files/base/meminfo.uc b/utils/prometheus-node-exporter-ucode/files/base/meminfo.uc new file mode 100644 index 0000000000..3cecb124dc --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/meminfo.uc @@ -0,0 +1,24 @@ +let f = fs.open("/proc/meminfo"); + +if (!f) + return false; + +let line; +while (line = nextline(f)) { + const x = wsplit(line); + + if (length(x) < 2) + continue; + + if (substr(x[0], -1) != ":") + continue; + + let name; + if (substr(x[0], -2) == "):") + name = replace(substr(x[0], 0, -2), "(", "_"); + else + name = substr(x[0], 0, -1); + + gauge(`node_memory_${name}_bytes`) + (null, x[2] == "kB" ? x[1] * 1024 : x[1]); +} diff --git a/utils/prometheus-node-exporter-ucode/files/base/netclass.uc b/utils/prometheus-node-exporter-ucode/files/base/netclass.uc new file mode 100644 index 0000000000..10b3cfd343 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/netclass.uc @@ -0,0 +1,48 @@ +const root = "/sys/class/net/"; +const devices = fs.lsdir(root); + +if (length(devices) < 1) + return false; + +const m_info = gauge("node_network_info"); +const m_speed = gauge("node_network_speed_bytes"); +const metrics = { + addr_assign_type: gauge("node_network_address_assign_type"), + carrier: gauge("node_network_carrier"), + carrier_changes: counter("node_network_carrier_changes_total"), + carrier_down_count: counter("node_network_carrier_down_changes_total"), + carrier_up_count: counter("node_network_carrier_up_changes_total"), + dev_id: gauge("node_network_device_id"), + dormant: gauge("node_network_dormant"), + flags: gauge("node_network_flags"), + ifindex: gauge("node_network_iface_id"), + iflink: gauge("node_network_iface_link"), + link_mode: gauge("node_network_iface_link_mode"), + mtu: gauge("node_network_mtu_bytes"), + name_assign_type: gauge("node_network_name_assign_type"), + netdev_group: gauge("node_network_net_dev_group"), + type: gauge("node_network_protocol_type"), + tx_queue_len: gauge("node_network_transmit_queue_length"), +}; + +for (let device in devices) { + const devroot = root + device + "/"; + + m_info({ + device, + address: oneline(devroot + "address"), + broadcast: oneline(devroot + "broadcast"), + duplex: oneline(devroot + "duplex"), + operstate: oneline(devroot + "operstate"), + ifalias: oneline(devroot + "ifalias"), + }, 1); + + for (let m in metrics) { + let line = oneline(devroot + m); + metrics[m]({ device }, line); + } + + const speed = int(oneline(devroot + "speed")); + if (speed > 0) + m_speed({ device }, speed * 1000 * 1000 / 8); +} diff --git a/utils/prometheus-node-exporter-ucode/files/base/netdev.uc b/utils/prometheus-node-exporter-ucode/files/base/netdev.uc new file mode 100644 index 0000000000..f8fc68de44 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/netdev.uc @@ -0,0 +1,40 @@ +let f = fs.open("/proc/net/dev"); + +if (!f) + return false; + +const m = [ + null, + counter("node_network_receive_bytes_total"), + counter("node_network_receive_packets_total"), + counter("node_network_receive_errs_total"), + counter("node_network_receive_drop_total"), + counter("node_network_receive_fifo_total"), + counter("node_network_receive_frame_total"), + counter("node_network_receive_compressed_total"), + counter("node_network_receive_multicast_total"), + counter("node_network_transmit_bytes_total"), + counter("node_network_transmit_packets_total"), + counter("node_network_transmit_errs_total"), + counter("node_network_transmit_drop_total"), + counter("node_network_transmit_fifo_total"), + counter("node_network_transmit_colls_total"), + counter("node_network_transmit_carrier_total"), + counter("node_network_transmit_compressed_total"), +]; + +let line; +while (line = nextline(f)) { + const x = wsplit(ltrim(line), " "); + + if (length(x) < 2) + continue; + + if (substr(x[0], -1) != ":") + continue; + + const count = min(length(x), length(m)); + const labels = { device: substr(x[0], 0, -1) }; + for (let i = 1; i < count; i++) + m[i](labels, x[i]); +} diff --git a/utils/prometheus-node-exporter-ucode/files/base/selinux.uc b/utils/prometheus-node-exporter-ucode/files/base/selinux.uc new file mode 100644 index 0000000000..11840a8305 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/selinux.uc @@ -0,0 +1,10 @@ +const mode = oneline("/sys/fs/selinux/enforce"); +const enabled = gauge("node_selinux_enabled"); + +if (mode == null) { + enabled(null, 0); + return; +} + +enabled(null, 1); +gauge("node_selinux_current_mode")(null, mode); diff --git a/utils/prometheus-node-exporter-ucode/files/base/time.uc b/utils/prometheus-node-exporter-ucode/files/base/time.uc new file mode 100644 index 0000000000..7d13ea841a --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/time.uc @@ -0,0 +1 @@ +gauge("node_time_seconds")(null, time()); diff --git a/utils/prometheus-node-exporter-ucode/files/base/uname.uc b/utils/prometheus-node-exporter-ucode/files/base/uname.uc new file mode 100644 index 0000000000..50cb352ac1 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/base/uname.uc @@ -0,0 +1,8 @@ +gauge("node_uname_info")({ + sysname: oneline("/proc/sys/kernel/ostype"), + nodename: oneline("/proc/sys/kernel/hostname"), + release: oneline("/proc/sys/kernel/osrelease"), + version: oneline("/proc/sys/kernel/version"), + machine: poneline("uname -m"), // TODO lame + domainname: oneline("/proc/sys/kernel/domainname"), +}, 1); diff --git a/utils/prometheus-node-exporter-ucode/files/config b/utils/prometheus-node-exporter-ucode/files/config new file mode 100644 index 0000000000..8741f4a255 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/config @@ -0,0 +1,7 @@ +config prometheus-node-exporter-ucode 'main' + option listen_interface 'loopback' + option listen_port '9101' + option http_keepalive '70' + +config collector 'wifi' + option stations '1' diff --git a/utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc b/utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc new file mode 100644 index 0000000000..3644b20f29 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc @@ -0,0 +1,6 @@ +const x = ubus.call("dnsmasq", "metrics"); +if (!x) + return false; + +for (let i in x) + gauge(`dnsmasq_${i}_total`)(null, x[i]); diff --git a/utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc b/utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc new file mode 100644 index 0000000000..164449722e --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc @@ -0,0 +1,72 @@ +const x = ubus.call("dsl", "metrics"); + +if (!x) + return false; + +gauge("dsl_info")({ + atuc_vendor: x.atu_c.vendor, + atuc_system_vendor: x.atu_c.system_vendor, + chipset: x.chipset, + firmware_version: x.firmware_version, + api_version: x.api_version, + driver_version: x.driver_version, +}, 1); + +gauge("dsl_line_info")({ + annex: x.annex, + standard: x.standard, + mode: x.mode, + profile: x.profile, +}, 1); + +gauge("dsl_up")({ detail: x.state }, x.up); +gauge("dsl_uptime_seconds")(null, x.uptime); + +gauge("dsl_line_attenuation_db") + ({ direction: "down" }, x.downstream.latn) + ({ direction: "up" }, x.upstream.latn); +gauge("dsl_signal_attenuation_db") + ({ direction: "down" }, x.downstream.satn) + ({ direction: "up" }, x.upstream.satn); +gauge("dsl_signal_to_noise_margin_db") + ({ direction: "down" }, x.downstream.snr) + ({ direction: "up" }, x.upstream.snr); +gauge("dsl_aggregated_transmit_power_db") + ({ direction: "down" }, x.downstream.actatp) + ({ direction: "up" }, x.upstream.actatp); + +if (x.downstream.interleave_delay) + gauge("dsl_latency_seconds") + ({ direction: "down" }, x.downstream.interleave_delay / 1000000.0) + ({ direction: "up" }, x.upstream.interleave_delay / 1000000.0); +gauge("dsl_datarate") + ({ direction: "down" }, x.downstream.data_rate) + ({ direction: "up" }, x.upstream.data_rate); +gauge("dsl_max_datarate") + ({ direction: "down" }, x.downstream.attndr) + ({ direction: "up" }, x.upstream.attndr); + +counter("dsl_error_seconds_total") + ({ err: "forward error correction", loc: "near" }, x.errors.near.fecs) + ({ err: "forward error correction", loc: "far" }, x.errors.far.fecs) + ({ err: "errored", loc: "near" }, x.errors.near.es) + ({ err: "errored", loc: "far" }, x.errors.far.es) + ({ err: "severely errored", loc: "near" }, x.errors.near.ses) + ({ err: "severely errored", loc: "far" }, x.errors.far.ses) + ({ err: "loss of signal", loc: "near" }, x.errors.near.loss) + ({ err: "loss of signal", loc: "far" }, x.errors.far.loss) + ({ err: "unavailable", loc: "near" }, x.errors.near.uas) + ({ err: "unavailable", loc: "far" }, x.errors.far.uas); + +counter("dsl_errors_total") + ({ err: "header error code error", loc: "near" }, x.errors.near.hec) + ({ err: "header error code error", loc: "far" }, x.errors.far.hec) + ({ err: "non pre-emptive crc error", loc: "near" }, x.errors.near.crc_p) + ({ err: "non pre-emptive crc error", loc: "far" }, x.errors.far.crc_p) + ({ err: "pre-emptive crc error", loc: "near" }, x.errors.near.crcp_p) + ({ err: "pre-emptive crc error", loc: "far" }, x.errors.far.crcp_p); + +if (x.erb) + counter("dsl_erb_total") + ({ counter: "sent" }, x.erb.sent) + ({ counter: "discarded" }, x.erb.discarded); diff --git a/utils/prometheus-node-exporter-ucode/files/extra/netstat.uc b/utils/prometheus-node-exporter-ucode/files/extra/netstat.uc new file mode 100644 index 0000000000..7449305145 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/netstat.uc @@ -0,0 +1,30 @@ +function parse(fn) { + let f = fs.open(fn); + + if (!f) + return false; + + let names, values; + while (names = nextline(f), values = nextline(f)) { + const name = wsplit(names); + const value = wsplit(values); + + if (name[0] != value[0]) + continue; + + if (length(name) != length(value)) + continue; + + let prefix = substr(name[0], 0, -1); + for (let i = 1; i < length(name); i++) + gauge(`node_netstat_${prefix}_${name[i]}`)(null, value[i]); + } + + return true; +} + +let n = parse("/proc/net/netstat"); +let s = parse("/proc/net/snmp"); + +if (!n && !s) + return false; diff --git a/utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc b/utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc new file mode 100644 index 0000000000..10c15a10ca --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc @@ -0,0 +1,14 @@ +const x = ubus.call("system", "board"); + +if (!x) + return false; + +gauge("node_openwrt_info")({ + board_name: x.board_name, + id: x.release.distribution, + model: x.model, + release: x.release.version, + revision: x.release.revision, + system: x.system, + target: x.release.target, +}, 1); diff --git a/utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc b/utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc new file mode 100644 index 0000000000..d440a889e2 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc @@ -0,0 +1,23 @@ +function parse(fn, device, skipdecl) { + let f = fs.open(fn); + + if (!f) + return false; + + const labels = { device }; + let line; + while (line = nextline(f)) { + const x = wsplit(line); + + if (length(x) < 2) + continue; + + counter(`snmp6_${x[0]}`, null, skipdecl)(labels, x[1]); + } +} + +parse("/proc/net/snmp6", "all"); + +const root = "/proc/net/dev_snmp6/"; +for (let device in fs.lsdir(root)) + parse(root + device, device, true); diff --git a/utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc b/utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc new file mode 100644 index 0000000000..0d55724f97 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc @@ -0,0 +1,14 @@ +import { cursor } from "uci"; + +const uci = cursor(); +uci.load("dhcp"); + +let m = gauge("dhcp_host_info"); + +uci.foreach('dhcp', `host`, (s) => { + m({ + name: s.name, + mac: s.mac, + ip: s.ip, + }, 1); +}); diff --git a/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc b/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc new file mode 100644 index 0000000000..f5f58ef687 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc @@ -0,0 +1,118 @@ +import { request, 'const' as wlconst } from 'nl80211'; + +const x = ubus.call("network.wireless", "status"); + +if (!x) + return false; + +const iftypes = [ + "Unknown", + "Ad-Hoc", + "Client", + "Master", + "Master (VLAN)", + "WDS", + "Monitor", + "Mesh Point", + "P2P Client", + "P2P Go", + "P2P Device", + "OCB", +]; + +let m_radio_info = gauge("wifi_radio_info"); +let m_network_info = gauge("wifi_network_info"); +let m_network_quality = gauge("wifi_network_quality"); +let m_network_bitrate = gauge("wifi_network_bitrate"); +let m_network_noise = gauge("wifi_network_noise_dbm"); +let m_network_signal = gauge("wifi_network_signal_dbm"); +let m_stations_total = counter("wifi_stations_total"); +let m_station_inactive = gauge("wifi_station_inactive_milliseconds"); +let m_station_rx_bytes = counter("wifi_station_receive_bytes_total"); +let m_station_tx_bytes = counter("wifi_station_transmit_bytes_total"); +let m_station_rx_packets = counter("wifi_station_receive_packets_total"); +let m_station_tx_packets = counter("wifi_station_transmit_packets_total"); +let m_station_signal = gauge("wifi_station_signal_dbm"); +let m_station_rx_bitrate = gauge("wifi_station_receive_kilobits_per_second"); +let m_station_tx_bitrate = gauge("wifi_station_transmit_kilobits_per_second"); +let m_station_exp_tp = gauge("wifi_station_expected_throughput_kilobits_per_second"); + +for (let radio in x) { + const rc = x[radio]["config"]; + + m_radio_info({ + radio, + htmode: rc["htmode"], + channel: rc["channel"], + country: rc["country"], + } ,1); + + for (let iface in x[radio]["interfaces"]) { + const ifname = iface["ifname"]; + const nc = iface["config"]; + const wif = request(wlconst.NL80211_CMD_GET_INTERFACE, 0, { dev: ifname }); + + if (!wif) + continue; + + m_network_info({ + radio, + ifname, + ssid: nc["ssid"] || nc["mesh_id"], + bssid: wif["mac"], + mode: iftypes[wif["iftype"]], + }, 1); + + const wsta = request(wlconst.NL80211_CMD_GET_STATION, wlconst.NLM_F_DUMP, { dev: ifname }); + let signal = 0; + let bitrate = 0; + const stations = length(wsta) || 0; + if (stations) { + for (let sta in wsta) { + signal += sta["sta_info"].signal; + bitrate += sta["sta_info"]["tx_bitrate"].bitrate32; + } + bitrate /= stations * 0.01; + signal /= stations; + } + + let labels = { radio, ifname }; + m_network_bitrate(labels, bitrate || NaN); + m_network_signal(labels, signal || NaN); + m_network_quality(labels, signal ? 100.0 / 70 * (signal + 110) : NaN); + + const wsur = request(wlconst.NL80211_CMD_GET_SURVEY, wlconst.NLM_F_DUMP, { dev: ifname }); + let noise = 0; + for (let i in wsur) { + if (i["survey_info"]["frequency"] != wif["wiphy_freq"]) + continue; + + noise = i["survey_info"]["noise"]; + break; + } + + m_network_noise(labels, noise || NaN); + + if (config["stations"] != "1") + continue; + + m_stations_total(labels, stations); + if (!stations) + continue; + + for (let sta in wsta) { + labels["mac"] = sta["mac"]; + const info = sta["sta_info"]; + + m_station_inactive(labels, info["inactive_time"]); + m_station_rx_bytes(labels, info["rx_bytes64"]); + m_station_tx_bytes(labels, info["tx_bytes64"]); + m_station_rx_packets(labels, info["rx_packets"]); + m_station_tx_packets(labels, info["tx_packets"]); + m_station_signal(labels, info["signal"]); + m_station_rx_bitrate(labels, info["rx_bitrate"]["bitrate32"] * 100); + m_station_tx_bitrate(labels, info["tx_bitrate"]["bitrate32"] * 100); + m_station_exp_tp(labels, info["expected_throughput"]); + } + } +} diff --git a/utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc b/utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc new file mode 100644 index 0000000000..12ae56af91 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc @@ -0,0 +1,49 @@ +import { cursor } from "uci"; + +const x = ubus.call("wireguard", "status"); +if (!x) + return false; + +const uci = cursor(); +uci.load("network"); + +let m_wg_iface_info = gauge("wireguard_interface_info"); +let m_wg_peer_info = gauge("wireguard_peer_info"); +let m_wg_handshake = gauge ("wireguard_latest_handshake_seconds"); +let m_wg_rx = gauge ("wireguard_received_bytes_total"); +let m_wg_tx = gauge ("wireguard_sent_bytes_total"); + +for (let iface in x) { + const wc = x[iface]; + + m_wg_iface_info({ + name: iface, + public_key: wc["public_key"], + listen_port: wc["listen_port"], + fwmark: wc["fwmark"] || NaN, + }, 1); + + for (let peer in wc["peers"]) { + let description; + uci.foreach('network', `wireguard_${iface}`, (s) => { + if (s.public_key == peer) + description = s.description; + }); + + const pc = wc["peers"][peer]; + + m_wg_peer_info({ + interface: iface, + public_key: peer, + description, + endpoint: pc["endpoint"], + persistent_keepalive_interval: pc["persistent_keepalive_interval"] || NaN, + }, 1); + + const labels = { public_key: peer }; + + m_wg_handshake(labels, pc["last_handshake"]); + m_wg_rx(labels, pc["rx_bytes"]); + m_wg_tx(labels, pc["tx_bytes"]); + } +} diff --git a/utils/prometheus-node-exporter-ucode/files/init b/utils/prometheus-node-exporter-ucode/files/init new file mode 100644 index 0000000000..1735236f72 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/init @@ -0,0 +1,73 @@ +#!/bin/sh /etc/rc.common +# Copyright (C) 2013-2017 OpenWrt.org + +START=60 +USE_PROCD=1 + +_log() { + logger -p daemon.info -t prometheus-node-exporter-ucode "$@" +} + +start_service() { + . /lib/functions/network.sh + + local interface port bind4 bind6 + + config_load prometheus-node-exporter-ucode.main + config_get interface "main" listen_interface "loopback" + config_get port "main" listen_port 9101 + config_get keepalive "main" http_keepalive 70 + + [ "$interface" = "*" ] || { + network_get_ipaddr bind4 "$interface" + network_get_ipaddr6 bind6 "$interface" + [ -n "$bind4$bind6" ] || { + _log "defering start until listen interface $interface becomes ready" + return 0 + } + } + + procd_open_instance + + procd_set_param command /usr/sbin/uhttpd -f -c /dev/null -h /dev/null -S -D -o /metrics -O /usr/share/ucode/node-exporter/metrics.uc + + if [ "$interface" = "*" ]; then + procd_append_param command -p $port + else + [ -n "$bind4" ] && procd_append_param command -p $bind4:$port + [ -n "$bind6" ] && procd_append_param command -p [$bind6]:$port + fi + [ $keepalive -gt 0 ] && procd_append_param command -k $keepalive + + procd_add_jail prometheus-node-exporter-ucode log procfs sysfs ubus + procd_add_jail_mount "/usr/lib/uhttpd_ucode.so" + procd_add_jail_mount "/lib/libubus.so*" + procd_add_jail_mount "/lib/libuci.so" + procd_add_jail_mount "/usr/lib/ucode" + procd_add_jail_mount "/usr/lib/libnl*.so*" + procd_add_jail_mount "/usr/share/ucode/node-exporter" + procd_add_jail_mount "/etc/config" + + # TODO breaks the dsl collector? + #procd_set_param user nobody + #procd_set_param group nogroup + procd_set_param no_new_privs 1 + + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_set_param respawn + + procd_close_instance +} + +service_triggers() +{ + local interface + + procd_add_reload_trigger "prometheus-node-exporter-ucode" + + config_load prometheus-node-exporter-ucode.main + config_get interface "main" listen_interface "loopback" + + [ "$interface" = "*" ] || procd_add_reload_interface_trigger "$interface" +} diff --git a/utils/prometheus-node-exporter-ucode/files/metrics.uc b/utils/prometheus-node-exporter-ucode/files/metrics.uc new file mode 100644 index 0000000000..3dce77accb --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/metrics.uc @@ -0,0 +1,227 @@ +{% +'use strict'; + +import * as fs from "fs"; +import { connect } from "ubus"; +import { cursor } from "uci"; + +function debug(...s) { + if (global.debug) + warn("DEBUG: ", ...s, "\n"); +} + +function puts(...s) { + return uhttpd.send(...s, "\n"); +} + +function govalue(value) { + if (value == Infinity) + return "+Inf"; + else if (value == -Infinity) + return "-Inf"; + else if (value != value) + return "NaN"; + else if (type(value) in [ "int", "double" ]) + return value; + else if (type(value) in [ "bool", "string" ]) + return +value; + + return null; +} + +function metric(name, mtype, help, skipdecl) { + let func; + let decl = skipdecl == true ? false : true; + + let yield = function(labels, value) { + let v = govalue(value); + + if (v == null) { + debug(`skipping metric: unsupported value '${value}' (${name})`); + return func; + } + + let labels_str = ""; + if (length(labels)) { + let sep = ""; + let s; + labels_str = "{"; + for (let l in labels) { + if (labels[l] == null) + s = ""; + else if (type(labels[l]) == "string") { + s = labels[l]; + s = replace(labels[l], "\\", "\\\\"); + s = replace(s, "\"", "\\\""); + s = replace(s, "\n", "\\n"); + } else { + s = govalue(labels[l]); + + if (!s) + continue; + } + + labels_str += sep + l + "=\"" + s + "\""; + sep = ","; + } + labels_str += "}"; + } + + if (decl) { + if (help) + puts("# HELP ", name, " ", help); + puts("# TYPE ", name, " ", mtype); + decl = false; + } + + puts(name, labels_str, " ", v); + return func; + }; + + func = yield; + return func; +} + +function counter(name, help, skipdecl) { + return metric(name, "counter", help, skipdecl); +} + +function gauge(name, help, skipdecl) { + return metric(name, "gauge", help, skipdecl); +} + +function httpstatus(status) { + puts("Status: ", status, "\nContent-Type: text/plain; version=0.0.4; charset=utf-8\n"); +} + +function clockdiff(t1, t2) { + return (t2[0] - t1[0]) * 1000000000 + t2[1] - t1[1]; +} + +let collectors = {}; + +global.handle_request = function(env) { + let scope = { + config: null, + fs, + ubus: connect(), + counter, + gauge, + wsplit: function(line) { + return split(line, /\s+/); + }, + nextline: function(f) { + return rtrim(f.read("line"), "\n"); + }, + oneline: function(fn) { + let f = fs.open(fn); + + if (!f) + return null; + + return nextline(f); + }, + poneline: function(cmd) { + let f = fs.popen(cmd); + + if (!f) + return null; + + return nextline(f); + }, + }; + + if (length(collectors) < 1) { + httpstatus("404 No Collectors found"); + return; + } + + let cols = []; + for (let q in split(env.QUERY_STRING, "&")) { + let s = split(q, "=", 2); + if (length(s) == 2 && s[0] == "collect") { + if (!(s[1] in collectors)) { + httpstatus(`404 Collector ${s[1]} not found`); + return; + } + + push(cols, s[1]); + } + } + + if (length(cols) > 0) + cols = uniq(cols); + else + cols = keys(collectors); + + httpstatus("200 OK"); + + let duration = gauge("node_scrape_collector_duration_seconds"); + let success = gauge("node_scrape_collector_success"); + + for (let col in cols) { + let ok = false; + let t1, t2; + + scope["config"] = collectors[col].config; + t1 = clock(true); + try { + ok = call(collectors[col].func, null, scope) != false; + } catch(e) { + warn(`error running collector '${col}':\n${e.message}\n`); + } + t2 = clock(true); + + duration({ collector: col }, clockdiff(t1, t2) / 1000000000.0); + success({ collector: col }, ok); + } +}; + +const lib = "/usr/share/ucode/node-exporter/lib"; +const opts = { + strict_declarations: true, + raw_mode: true, +}; + +let cols = fs.lsdir(lib, "*.uc"); +for (let col in cols) { + let func; + let uci = cursor(); + + try { + func = loadfile(lib + "/" + col, opts); + } catch(e) { + warn(`error compiling collector '${col}':\n${e.message}\n`); + continue; + } + + let name = substr(col, 0, -3); + let config = uci.get_all("prometheus-node-exporter-ucode", name); + if (!config || config[".type"] != "collector") + config = {}; + else { + delete config[".anonymous"]; + delete config[".type"]; + delete config[".name"]; + } + + collectors[name] = { + func, + config, + }; +} + +warn(`prometheus-node-exporter-ucode now serving requests with ${length(collectors)} collectors\n`); + +if (!("uhttpd" in global)) { + global.debug = true; + + puts = function(...s) { + return print(...s, "\n"); + }; + + handle_request({ + QUERY_STRING: join("&", map(ARGV, v => "collect=" + v)), + }); +} +%} diff --git a/utils/prometheus-node-exporter-ucode/files/run.sh b/utils/prometheus-node-exporter-ucode/files/run.sh new file mode 100755 index 0000000000..a7cefeff38 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/bin/ucode -T /usr/share/ucode/node-exporter/metrics.uc $* diff --git a/utils/prometheus-node-exporter-ucode/test.sh b/utils/prometheus-node-exporter-ucode/test.sh new file mode 100755 index 0000000000..e72942b8e0 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +prometheus-node-exporter-ucode time