From ae3825387bd9b47813e0b862db38959e7000cb02 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sun, 28 Nov 2010 06:41:45 +0000 Subject: [PATCH] modules/admin-full: add SVG based realtime bandwidth status --- modules/admin-full/Makefile | 24 +- .../htdocs/luci-static/resources/bandwidth.svg | 16 ++ modules/admin-full/ipkg/postinst | 6 + .../admin-full/luasrc/controller/admin/status.lua | 31 ++ .../luasrc/view/admin_status/bandwidth.htm | 310 ++++++++++++++++++++ modules/admin-full/root/etc/init.d/luci_bwc | 14 + modules/admin-full/src/luci-bwc.c | 315 +++++++++++++++++++++ 7 files changed, 715 insertions(+), 1 deletion(-) create mode 100644 modules/admin-full/htdocs/luci-static/resources/bandwidth.svg create mode 100755 modules/admin-full/ipkg/postinst create mode 100644 modules/admin-full/luasrc/view/admin_status/bandwidth.htm create mode 100755 modules/admin-full/root/etc/init.d/luci_bwc create mode 100644 modules/admin-full/src/luci-bwc.c diff --git a/modules/admin-full/Makefile b/modules/admin-full/Makefile index 81a96f6a8..347a34286 100644 --- a/modules/admin-full/Makefile +++ b/modules/admin-full/Makefile @@ -1,2 +1,24 @@ include ../../build/config.mk -include ../../build/module.mk \ No newline at end of file +include ../../build/module.mk +include ../../build/gccconfig.mk + +BWC_LDFLAGS = +BWC_CFLAGS = +BWC_BIN = luci-bwc +BWC_OBJ = src/luci-bwc.o + +%.o: %.c + $(COMPILE) $(BWC_CFLAGS) $(LUA_CFLAGS) $(FPIC) -c -o $@ $< + +compile: build-clean $(BWC_OBJ) + $(LINK) $(BWC_LDFLAGS) -o src/$(BWC_BIN) $(BWC_OBJ) + mkdir -p dist/usr/bin + cp src/$(BWC_BIN) dist/usr/bin/$(BWC_BIN) + +install: build + cp -pR dist/usr/bin/$(BWC_BIN) /usr/bin/$(BWC_BIN) + +clean: build-clean + +build-clean: + rm -f src/*.o src/$(BWC_BIN) diff --git a/modules/admin-full/htdocs/luci-static/resources/bandwidth.svg b/modules/admin-full/htdocs/luci-static/resources/bandwidth.svg new file mode 100644 index 000000000..4f9148833 --- /dev/null +++ b/modules/admin-full/htdocs/luci-static/resources/bandwidth.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/modules/admin-full/ipkg/postinst b/modules/admin-full/ipkg/postinst new file mode 100755 index 000000000..a36479ba7 --- /dev/null +++ b/modules/admin-full/ipkg/postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +[ -n "${IPKG_INSTROOT}" ] || { + /etc/init.d/luci_bwc enabled || /etc/init.d/luci_bwc enable + exit 0 +} diff --git a/modules/admin-full/luasrc/controller/admin/status.lua b/modules/admin-full/luasrc/controller/admin/status.lua index 55bba2f38..d283627eb 100644 --- a/modules/admin-full/luasrc/controller/admin/status.lua +++ b/modules/admin-full/luasrc/controller/admin/status.lua @@ -25,6 +25,9 @@ function index() entry({"admin", "status", "syslog"}, call("action_syslog"), i18n("System Log"), 5) entry({"admin", "status", "dmesg"}, call("action_dmesg"), i18n("Kernel Log"), 6) + entry({"admin", "status", "bandwidth"}, template("admin_status/bandwidth"), i18n("Realtime Traffic"), 7).leaf = true + entry({"admin", "status", "bandwidth_status"}, call("action_bandwidth")).leaf = true + end function action_syslog() @@ -52,3 +55,31 @@ function action_iptables() luci.template.render("admin_status/iptables") end end + +function action_bandwidth() + local path = luci.dispatcher.context.requestpath + local iface = path[#path] + + local fs = require "luci.fs" + if fs.access("/var/lib/luci-bwc/%s" % iface) then + luci.http.prepare_content("application/json") + + local bwc = io.popen("luci-bwc -p %q 2>/dev/null" % iface) + if bwc then + luci.http.write("[") + + while true do + local ln = bwc:read("*l") + if not ln then break end + luci.http.write(ln) + end + + luci.http.write("]") + bwc:close() + end + + return + end + + luci.http.status(404, "No such interface") +end diff --git a/modules/admin-full/luasrc/view/admin_status/bandwidth.htm b/modules/admin-full/luasrc/view/admin_status/bandwidth.htm new file mode 100644 index 000000000..f12a22251 --- /dev/null +++ b/modules/admin-full/luasrc/view/admin_status/bandwidth.htm @@ -0,0 +1,310 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2010 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ + +-%> + +<%- + local dev + local devices = { } + for _, dev in luci.util.kspairs(luci.sys.net.devices()) do + if dev ~= "lo" then + devices[#devices+1] = dev + end + end + + local curdev = luci.dispatcher.context.requestpath + curdev = curdev[#curdev] ~= "bandwidth" and curdev[#curdev] or devices[1] +-%> + +<%+header%> + + + + +

<%:Realtime Traffic%>

+ + + + +
-
+
+ + + + + + + + + + + + + + + + + + + + + + +
<%:Inbound:%>0 kbit/s
(0 KB/s)
<%:Average:%>0 kbit/s
(0 KB/s)
<%:Peak:%>0 kbit/s
(0 KB/s)
<%:Outbound:%>0 kbit/s
(0 KB/s)
<%:Average:%>0 kbit/s
(0 KB/s)
<%:Peak:%>0 kbit/s
(0 KB/s)
+ + +

+
+
+<%+footer%>
diff --git a/modules/admin-full/root/etc/init.d/luci_bwc b/modules/admin-full/root/etc/init.d/luci_bwc
new file mode 100755
index 000000000..fe246073f
--- /dev/null
+++ b/modules/admin-full/root/etc/init.d/luci_bwc
@@ -0,0 +1,14 @@
+#!/bin/sh /etc/rc.common
+
+START=95
+STOP=95
+
+BWC=/usr/bin/luci-bwc
+
+start() {
+	$BWC -d
+}
+
+stop() {
+	killall ${BWC##*/}
+}
diff --git a/modules/admin-full/src/luci-bwc.c b/modules/admin-full/src/luci-bwc.c
new file mode 100644
index 000000000..6b6c0c710
--- /dev/null
+++ b/modules/admin-full/src/luci-bwc.c
@@ -0,0 +1,315 @@
+/*
+ * luci-bwc - Very simple bandwidth collector cache for LuCI realtime graphs
+ *
+ *   Copyright (C) 2010 Jo-Philipp Wich 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+
+#define STEP_COUNT	60
+#define STEP_TIME	1
+
+#define DB_PATH		"/var/lib/luci-bwc"
+#define DB_FILE		DB_PATH "/%s"
+
+#define SCAN_PATTERN \
+	" %[^ :]:%" SCNu64 " %" SCNu64 \
+	" %*d %*d %*d %*d %*d %*d" \
+	" %" SCNu64 " %" SCNu64
+
+
+struct traffic_entry {
+	uint64_t time;
+	uint64_t rxb;
+	uint64_t rxp;
+	uint64_t txb;
+	uint64_t txp;
+};
+
+static uint64_t htonll(uint64_t value)
+{
+	int num = 1;
+
+	if (*(char *)&num == 1)
+		return htonl((uint32_t)(value & 0xFFFFFFFF)) |
+		       htonl((uint32_t)(value >> 32));
+
+	return value;
+}
+
+#define ntohll htonll
+
+
+static int init_minutely(const char *ifname)
+{
+	int i, file;
+	char path[1024];
+	char *p;
+	struct traffic_entry e = { 0 };
+
+	snprintf(path, sizeof(path), DB_FILE, ifname);
+
+	for (p = &path[1]; *p; p++)
+	{
+		if (*p == '/')
+		{
+			*p = 0;
+
+			if (mkdir(path, 0700) && (errno != EEXIST))
+				return -1;
+
+			*p = '/';
+		}
+	}
+
+	if ((file = open(path, O_WRONLY | O_CREAT, 0600)) >= 0)
+	{
+		for (i = 0; i < STEP_COUNT; i++)
+		{
+			if (write(file, &e, sizeof(struct traffic_entry)) < 0)
+				break;
+		}
+
+		close(file);
+
+		return 0;
+	}
+
+	return -1;
+}
+
+static int update_minutely(
+	const char *ifname, uint64_t rxb, uint64_t rxp, uint64_t txb, uint64_t txp
+) {
+	int rv = -1;
+
+	int file;
+	int entrysize = sizeof(struct traffic_entry);
+	int mapsize = STEP_COUNT * entrysize;
+
+	char path[1024];
+	char *map;
+
+	struct stat s;
+	struct traffic_entry e;
+
+	snprintf(path, sizeof(path), DB_FILE, ifname);
+
+	if (stat(path, &s))
+	{
+		if (init_minutely(ifname))
+		{
+			fprintf(stderr, "Failed to init %s: %s\n",
+					path, strerror(errno));
+
+			return rv;
+		}
+	}
+
+	if ((file = open(path, O_RDWR)) >= 0)
+	{
+		map = mmap(NULL, mapsize, PROT_READ | PROT_WRITE,
+				   MAP_SHARED | MAP_LOCKED, file, 0);
+
+		if ((map != NULL) && (map != MAP_FAILED))
+		{
+			e.time = htonll(time(NULL));
+			e.rxb  = htonll(rxb);
+			e.rxp  = htonll(rxp);
+			e.txb  = htonll(txb);
+			e.txp  = htonll(txp);
+
+			memmove(map, map + entrysize, mapsize - entrysize);
+			memcpy(map + mapsize - entrysize, &e, entrysize);
+
+			munmap(map, mapsize);
+
+			rv = 0;
+		}
+
+		close(file);
+	}
+
+	return rv;
+}
+
+static int run_daemon(int nofork)
+{
+	FILE *info;
+	uint64_t rxb, txb, rxp, txp;
+	char line[1024];
+	char ifname[16];
+
+
+	if (!nofork)
+	{
+		switch (fork())
+		{
+			case -1:
+				perror("fork()");
+				return -1;
+
+			case 0:
+				if (chdir("/") < 0)
+				{
+					perror("chdir()");
+					exit(1);
+				}
+
+				close(0);
+				close(1);
+				close(2);
+				break;
+
+			default:
+				exit(0);
+		}
+	}
+
+
+	/* go */
+	while (1)
+	{
+		if ((info = fopen("/proc/net/dev", "r")) != NULL)
+		{
+			while (fgets(line, sizeof(line), info))
+			{
+				if (strchr(line, '|'))
+					continue;
+
+				if (sscanf(line, SCAN_PATTERN, ifname, &rxb, &rxp, &txb, &txp))
+				{
+					if (strncmp(ifname, "lo", sizeof(ifname)))
+						update_minutely(ifname, rxb, rxp, txb, txp);
+				}
+			}
+
+			fclose(info);
+		}
+
+		sleep(STEP_TIME);
+	}
+}
+
+static int run_dump(const char *ifname)
+{
+	int rv = 1;
+
+	int i, file;
+	int entrysize = sizeof(struct traffic_entry);
+	int mapsize = STEP_COUNT * entrysize;
+
+	char path[1024];
+	char *map;
+
+	struct traffic_entry *e;
+
+	snprintf(path, sizeof(path), DB_FILE, ifname);
+
+	if ((file = open(path, O_RDONLY)) >= 0)
+	{
+		map = mmap(NULL, mapsize, PROT_READ, MAP_SHARED | MAP_LOCKED, file, 0);
+
+		if ((map != NULL) && (map != MAP_FAILED))
+		{
+			for (i = 0; i < mapsize; i += entrysize)
+			{
+				e = (struct traffic_entry *) &map[i];
+
+				if (!e->time)
+					continue;
+
+				printf("[ %" PRIu64 ", %" PRIu64 ", %" PRIu64
+					   ", %" PRIu64 ", %" PRIu64 " ]%s\n",
+					ntohll(e->time),
+					ntohll(e->rxb), ntohll(e->rxp),
+					ntohll(e->txb), ntohll(e->txp),
+					((i + entrysize) < mapsize) ? "," : "");
+			}
+
+			munmap(map, mapsize);
+			rv = 0;
+		}
+
+		close(file);
+	}
+	else
+	{
+		fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
+	}
+
+	return rv;
+}
+
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	int daemon = 0;
+	int nofork = 0;
+	int dprint = 0;
+	char *ifname = NULL;
+
+	while ((opt = getopt(argc, argv, "dfp:")) > -1)
+	{
+		switch (opt)
+		{
+			case 'd':
+				daemon = 1;
+				break;
+
+			case 'f':
+				nofork = 1;
+				break;
+
+			case 'p':
+				dprint = 1;
+				ifname = optarg;
+				break;
+
+			default:
+				break;
+		}
+	}
+
+	if (daemon)
+		return run_daemon(nofork);
+
+	else if (dprint && ifname)
+		return run_dump(ifname);
+
+	else
+		fprintf(stderr,
+			"Usage:\n"
+			"	%s -d [-f]\n"
+			"	%s -p ifname\n",
+				argv[0], argv[0]
+		);
+
+	return 1;
+}
-- 
2.11.0