ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/external/subpack/utils/prometheus-node-exporter-ucode/files/base/conntrack.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/conntrack.uc
new file mode 100644
index 0000000..b3ee3f3
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/cpu.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/cpu.uc
new file mode 100644
index 0000000..574655d
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/entropy.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/entropy.uc
new file mode 100644
index 0000000..2df4426
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/filefd.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/filefd.uc
new file mode 100644
index 0000000..359cffd
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/loadavg.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/loadavg.uc
new file mode 100644
index 0000000..ba67daf
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/meminfo.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/meminfo.uc
new file mode 100644
index 0000000..3cecb12
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/netclass.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/netclass.uc
new file mode 100644
index 0000000..10b3cfd
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/netdev.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/netdev.uc
new file mode 100644
index 0000000..f8fc68d
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/selinux.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/selinux.uc
new file mode 100644
index 0000000..11840a8
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/base/time.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/time.uc
new file mode 100644
index 0000000..7d13ea8
--- /dev/null
+++ b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/time.uc
@@ -0,0 +1 @@
+gauge("node_time_seconds")(null, time());
diff --git a/external/subpack/utils/prometheus-node-exporter-ucode/files/base/uname.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/base/uname.uc
new file mode 100644
index 0000000..50cb352
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/config b/external/subpack/utils/prometheus-node-exporter-ucode/files/config
new file mode 100644
index 0000000..8741f4a
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/dnsmasq.uc
new file mode 100644
index 0000000..3644b20
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/ltq-dsl.uc
new file mode 100644
index 0000000..1644497
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/netstat.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/netstat.uc
new file mode 100644
index 0000000..7449305
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/openwrt.uc
new file mode 100644
index 0000000..10c15a1
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/snmp6.uc
new file mode 100644
index 0000000..d440a88
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/uci_dhcp_host.uc
new file mode 100644
index 0000000..0d55724
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc
new file mode 100644
index 0000000..fb46b55
--- /dev/null
+++ b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/wifi.uc
@@ -0,0 +1,121 @@
+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"]);
+			if (info["rx_bitrate"] && info["rx_bitrate"]["bitrate32"])
+				m_station_rx_bitrate(labels, info["rx_bitrate"]["bitrate32"] * 100);
+			if (info["tx_bitrate"] && info["tx_bitrate"]["bitrate32"])
+				m_station_tx_bitrate(labels, info["tx_bitrate"]["bitrate32"] * 100);
+			if (info["expected_throughput"])
+				m_station_exp_tp(labels, info["expected_throughput"]);
+		}
+	}
+}
diff --git a/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/extra/wireguard.uc
new file mode 100644
index 0000000..12ae56a
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/init b/external/subpack/utils/prometheus-node-exporter-ucode/files/init
new file mode 100644
index 0000000..1735236
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/metrics.uc b/external/subpack/utils/prometheus-node-exporter-ucode/files/metrics.uc
new file mode 100644
index 0000000..3dce77a
--- /dev/null
+++ b/external/subpack/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/external/subpack/utils/prometheus-node-exporter-ucode/files/run.sh b/external/subpack/utils/prometheus-node-exporter-ucode/files/run.sh
new file mode 100755
index 0000000..a7cefef
--- /dev/null
+++ b/external/subpack/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 $*