Statystyki routera dostępne via www

Jako, że routery domowe chodzą zwykle 24h/d i ich moc obliczeniowa się czasem marnuje, możemy zaprzęgnąć nasz sprzęcik do zbierania statystyk, które mogą pochodzić z różnych źródeł i nie koniecznie muszą to być dane systemowe czy też sprzętowe samego routera. W tym howto spróbujemy zaprogramować router by zbierał pewne dane i rysował na ich podstawie wykresy, tak by to wyglądało jak na fotce poniżej:

Jest wiele różnych narzędzi realizujących przedsięwzięcie, za które się zabieramy i my skupimy się na collecd -- to przy jego pomocy będziemy tworzyć bazę danych. Drugim potrzebnym nam programem będzie rrdtool, przy pomocy którego to będzie wydobywać dane z bazy i na ich podstawie generować zrozumiałe dla człowieka wykresy. Dodatkowo, by mieć łatwy dostęp do tak stworzonych obrazków, postawimy serwer www w oparciu o uhttpd , przy pomocy którego udostępnimy je w sieci lokalnej.

Instalujemy zatem potrzebne pakiety:

# opkg update
# opkg install collectd collectd-mod-rrdtool rrdtool uhttpd

Poza powyższymi pakietami, potrzebne nam są również dodatkowe moduły dla collectd. Jest ich dość sporo:

# opkg list | grep collectd-mod
collectd-mod-apache - 4.10.8-3 - apache status input plugin
collectd-mod-apcups - 4.10.8-3 - apcups status input plugin
collectd-mod-ascent - 4.10.8-3 - ascent status input plugin
collectd-mod-bind - 4.10.8-3 - BIND server/zone input plugin
collectd-mod-conntrack - 4.10.8-3 - connection tracking table size input plugin
collectd-mod-contextswitch - 4.10.8-3 - context switch input plugin
collectd-mod-cpu - 4.10.8-3 - CPU input plugin
collectd-mod-csv - 4.10.8-3 - CSV output plugin
collectd-mod-curl - 4.10.8-3 - cURL input plugin
collectd-mod-df - 4.10.8-3 - disk space input plugin
collectd-mod-disk - 4.10.8-3 - disk usage/timing input plugin
collectd-mod-dns - 4.10.8-3 - DNS traffic input plugin
collectd-mod-email - 4.10.8-3 - email output plugin
collectd-mod-exec - 4.10.8-3 - process exec input plugin
collectd-mod-filecount - 4.10.8-3 - file count input plugin
collectd-mod-fscache - 4.10.8-3 - file-system based caching framework input plugin
collectd-mod-interface - 4.10.8-3 - network interfaces input plugin
collectd-mod-iptables - 4.10.8-3 - iptables status input plugin
collectd-mod-irq - 4.10.8-3 - interrupt usage input plugin
collectd-mod-iwinfo - 4.10.8-3 - libiwinfo wireless statistics plugin
collectd-mod-load - 4.10.8-3 - system load input plugin
collectd-mod-logfile - 4.10.8-3 - log files output plugin
collectd-mod-madwifi - 4.10.8-3 - MadWifi status input plugin
collectd-mod-memory - 4.10.8-3 - physical memory usage input plugin
collectd-mod-network - 4.10.8-3 - network input/output plugin
collectd-mod-nginx - 4.10.8-3 - nginx status input plugin
collectd-mod-ntpd - 4.10.8-3 - NTP daemon status input plugin
collectd-mod-olsrd - 4.10.8-3 - OLSRd status input plugin
collectd-mod-openvpn - 4.10.8-3 - OpenVPN traffic/compression input plugin
collectd-mod-ping - 4.10.8-3 - ping status input plugin
collectd-mod-postgresql - 4.10.8-3 - PostgreSQL status input plugin
collectd-mod-powerdns - 4.10.8-3 - PowerDNS server status input plugin
collectd-mod-processes - 4.10.8-3 - process status input plugin
collectd-mod-protocols - 4.10.8-3 - network protocols input plugin
collectd-mod-rrdtool - 4.10.8-3 - RRDtool output plugin
collectd-mod-snmp - 4.10.8-3 - SNMP input plugin
collectd-mod-syslog - 4.10.8-3 - syslog output plugin
collectd-mod-table - 4.10.8-3 - table-like structured file input plugin
collectd-mod-tail - 4.10.8-3 - tail input plugin
collectd-mod-tcpconns - 4.10.8-3 - TCP connection tracking input plugin
collectd-mod-teamspeak2 - 4.10.8-3 - TeamSpeak2 input plugin
collectd-mod-ted - 4.10.8-3 - The Energy Detective input plugin
collectd-mod-thermal - 4.10.8-3 - system temperatures input plugin
collectd-mod-unixsock - 4.10.8-3 - unix socket output plugin
collectd-mod-uptime - 4.10.8-3 - uptime status input plugin
collectd-mod-users - 4.10.8-3 - user logged in status input plugin
collectd-mod-vmem - 4.10.8-3 - virtual memory usage input plugin
collectd-mod-wireless - 4.10.8-3 - wireless status input plugin
collectd-mod-write-http - 4.10.8-3 - HTTP POST output plugin

Wybieramy te, które nam są potrzebne i instalujemy. W moim przypadku są to:

# opkg install \
	collectd-mod-conntrack \
	collectd-mod-cpu \
	collectd-mod-dns \
	collectd-mod-interface \
	collectd-mod-iwinfo \
	collectd-mod-load \
	collectd-mod-memory \
	collectd-mod-ping \
	collectd-mod-tcpconns \
	collectd-mod-vmem

Na początek skonfigurujmy serwer www -- interesuje nas plik /etc/config/uhttpd :

# HTTP listen addresses, multiple allowed
#      list listen_http        0.0.0.0:80
		list listen_http        192.168.1.1:80
#      list listen_http        [::]:80
# HTTPS listen addresses, multiple allowed
#       list listen_https       0.0.0.0:443
#       list listen_https       192.168.1.1:443
#       list listen_https       [::]:443
# Server document root
		option home             /tmp/router/www

Nie potrzebujemy SSL, dlatego też możemy zakomentować sekcję od HTTPS. Z kolei w przypadku HTTP ustawiamy adres interfejsu LAN. Musimy także dostosować odpowiednio katalog dla stron www.

Odpalamy serwer i dodajemy go do autostartu:

# /etc/init.d/uhttpd start
# /etc/init.d/uhttpd enable

Następnie konfigurujemy collectd w pliku /etc/collectd.conf . Na początku tego pliku definiujemy zachowanie samego daemona:

Hostname   "the-mountain"
#FQDNLookup  true
BaseDir     "/var/lib/collectd"
PIDFile     "/var/run/collectd.pid"
#PluginDir  "/usr/lib/collectd"
#TypesDB    "/usr/share/collectd/types.db"
Interval    10
ReadThreads 2

Chodzi głównie o dostosowanie interwału, bo będzie on nam potrzeby w późniejszej fazie konfiguracji. Kolejne linijki w tym pliku dotyczą ładowania odpowiednich modułów, te które zainstalowaliśmy, przykładowo, by włączyć moduł ping, musimy dodać poniższy blok:

<Plugin ping>
		Host "8.8.8.8"
		Host "208.67.220.220"
		Host "212.77.100.101"
		Interval 10.0
		Timeout 2.0
		TTL 64
#       SourceAddress "1.2.3.4"
		Device "eth0"
		MaxMissed -1
</Plugin>

W powyższym przypadku, moduł ping będzie odpytywał trzy hosty o określonych adresach w 10 sekundowych odstępach czasu, czyli tyle ile wynosi interwał przy zbieraniu statystyk. Częściej nie ma potrzeby odpytywać serwerów, bo i tak tylko jeden ping na 10s zostanie zanotowany. Pingowany host ma 2s czasu na odpowiedź, jeśli nie wyrobi się w tym przedziale, próba zostanie oznaczona jako nieudana. Ważne jest też by określić interfejs, przez który będzie wychodził ping. W tym przypadku jest to interfejs WAN.
Wszystkie pożądane moduły ładujemy w ten sam sposób -- prze ujęcie ich w oraz , zmieniając ping na nazwę innego modułu. Każdy moduł ma też inne opcje. Wszystkie dostępne moduły jak i ich opcje zostały szczegółowo opisane pod tym linkiem.

Jeśli chcemy generować statystyki przy pomocy rddtool, a chcemy, to musimy także skonfigurować ten moduł:

<Plugin rrdtool>
		DataDir "/tmp/router/stats"
		RRARows 720
		RRASingle 1
		RRATimespan 1800
		RRATimespan 3600
		RRATimespan 43200
		RRATimespan 86400
		RRATimespan 172800
		RRATimespan 604800
</Plugin>

Opcja DataDir określa gdzie zapisywać zbierane dane. Kluczowe jest wyliczenie ile PDP (Primary Data Point) przypada na jeden CDP (Consolidated Data Point) i robi się to ze wzoru: liczba PDP = timespan/(stepsize * rrarows). W oparciu o powyższą konfigurację otrzymujemy: liczba PDP = 1800/(10*720), czyli 0.25. Podobnie z pozostałymi przedziałami czasu określonymi w RRATimespan -- 0.5 , 6 , 12 , 84 . Chodzi o to by wyszły w miarę okrągłe liczby. Co one nam dają? Tam gdzie CDP jest mniejsze lub równe 1, nie ma większego problemu z określenie tego co zostanie pokazane na wykresie. Natomiast jeśli wartość ta jest większa od 1, np. dla przedziału czasu 2 dni CDP przyjmuje wartość 12, wtedy rzecz jasna nie można umieścić wszystkich 12 wartości na wykresie, zostanie wybrana tylko jedna z nich i możemy określić ją na podstawie parametrów MAX, MIN, AVERAGE albo też LAST. Jeśli określimy MAX, wtedy najwyższa z tych 12 wartości zostanie uwzględniona na grafie, jeśli MIN, to najmniejsza, itd ale o tym później.
Odpalamy daemona collectd i również dodajemy go do autostartu routera:

# /etc/init.d/collectd enable
# /etc/init.d/collectd start

Potrzebny nam będzie również skrypt, który wygeneruje odpowiednie wykresy. Poniżej kawałek mojego skryptu:

#!/bin/sh
HOST="the-mountain"
DATADIR="/tmp/router/stats"
IMAGEDIR="/tmp/router/www/stats/$HOST/"
PERIOD="1hour 12hours 24hours 48hours 1week"
INTERFACES="eth0 br-lan wlan0 wlan1"
PLUGINS="cpu-0 interface iwinfo-wlan0 load memory ping conntrack dns"
for PLUGIN in $PLUGINS
do
	mkdir -p $IMAGEDIR/$PLUGIN
done

Powyżej mamy zdefiniowanych kilka zmiennych. Pliki statystyk (DATADIR) oraz obrazki z grafiami (IMAGEDIR) będą przechowywane w pamięci operacyjnej router. Można oczywiście określić ścieżki do plików na routerze albo nawet na zewnętrznym nośniku, z tym, że niesie to za sobą komplikację. Po pierwsze, statystyki są zbierane w czasie rzeczywistym, zatem nośnik jest ciągle w fazie zapisu i odłączenie go, zwykle doprowadza do utraty całej bazy i nierzadko też potrafi się popsuć system plików. Druga kwestia dotyczy performance -- statystyki są generowane w pamięci RAM o wiele szybciej i loadavg jest przy tym 2-3x mniejszy niż przy zapisie tych plików bezpośrednio na dysk. Jeśli dysponujemy routerem z RAM > 64MiB, bez większego problemu możemy te obrazki zapisywać w pamięci operacyjnej.

Zmienna PERIOD określa przedziały czasu jakie będą uwzględniane przy generowaniu obrazków i w tym przypadku jest to 1 godzina, 12 godzin, 24 godziny, 48 godzin i 1 tydzień pracy urządzenia. Pliki ze statystykami nie rozrastają się i zajmują zawsze tyle samo miejsca, z tym, że im większy przedział czasu, tym oczywiście baza zajmuje więcej. U mnie wszystkie potrzebne statystyki i wykresy zajmują łącznie prawie 10MiB, także nie jest to znowu tak mało. Zmienna INTERFACES odnosi się tylko do modułów zbierających statystyki sieciowe i definiuje interfejsy routera, które będą poddane analizie. Powyżej są zdefiniowane są 4 interfejsy -- jeden WAN, jeden LAN oraz dwa interfejsy bezprzewodowe, po jednym dla pasma 2,4GHz i 5GHz. Zmienna PLUGINS definiuje włączone pluginy, na podstawie których są tworzone odpowiednie katalogi -- jakby nie patrzeć statystyki będą przechowane w RAM i po zresetowaniu routera, szlag je trafi, dlatego też za każdym razem jak router będzie się budził do życia, trzeba będzie tworzyć te katalogi na nowo.

Dalej w skrypcie potrzebne będą nam sekcje generujące wykresy z danych zbieranych przez odpowiednie moduły i dla przykładu opiszę jak wygenerować np. taki wykres:

Niżej znajduje się kawałek skryptu, który generuje ten obrazek powyżej :

##############################
#        load section        #
##############################
load_load(){
	for TIME in $PERIOD
	do
		rrdtool graph $IMAGEDIR/load/load-$TIME.png --title "$HOST/load/load $TIME" --imgformat PNG --width 600 --height 100 --start now-$TIME --end now --interlaced \
		DEF:longterm_min=$DATADIR/$HOST/load/load.rrd:longterm:MIN \
		DEF:longterm_avg=$DATADIR/$HOST/load/load.rrd:longterm:AVERAGE \
		DEF:longterm_max=$DATADIR/$HOST/load/load.rrd:longterm:MAX \
		DEF:midterm_min=$DATADIR/$HOST/load/load.rrd:midterm:MIN \
		DEF:midterm_avg=$DATADIR/$HOST/load/load.rrd:midterm:AVERAGE \
		DEF:midterm_max=$DATADIR/$HOST/load/load.rrd:midterm:MAX \
		DEF:shortterm_min=$DATADIR/$HOST/load/load.rrd:shortterm:MIN \
		DEF:shortterm_avg=$DATADIR/$HOST/load/load.rrd:shortterm:AVERAGE \
		DEF:shortterm_max=$DATADIR/$HOST/load/load.rrd:shortterm:MAX \
		AREA:longterm_max#ffe8e8 \
		AREA:midterm_max#e8e8ff \
		AREA:shortterm_max#e2ffe2 \
		LINE2:shortterm_max#55ff55:1min \
		GPRINT:shortterm_avg:MIN:" Minimum\:%8.2lf %s" \
		GPRINT:shortterm_avg:AVERAGE:"Average\:%8.2lf %s" \
		GPRINT:shortterm_max:MAX:"Maximum\:%8.2lf %s\n" \
		LINE2:midterm_max#7777ff:5min \
		GPRINT:midterm_avg:MIN:" Minimum\:%8.2lf %s" \
		GPRINT:midterm_avg:AVERAGE:"Average\:%8.2lf %s" \
		GPRINT:midterm_max:MAX:"Maximum\:%8.2lf %s\n" \
		LINE2:longterm_max#ff7777:15min \
		GPRINT:longterm_avg:MIN:"Minimum\:%8.2lf %s" \
		GPRINT:longterm_avg:AVERAGE:"Average\:%8.2lf %s" \
		GPRINT:longterm_max:MAX:"Maximum\:%8.2lf %s\n" \
		>/dev/null 2>&1
	done
}
##############################

Generalnie rzecz biorąc, to co widzimy powyżej zawiera się w jednej linijce ale dla większej czytelności skorzystałem ze znaku łamania lini ([b][/b]) i w ten sposób mogłem przenieść kolejne parametry rrdtool do nowych linijek. Powyższy blok jest ujęty w funkcję określoną przez load_load(){} , w której mamy pętle for , która to z kolei wygeneruje obrazki w oparciu o zdefiniowane wcześniej okresy czasu.
Pierwsza linijka (ta z rrdtool) określa gdzie zapisać obrazek (graph) po wygenerowaniu grafu, tytuł grafu (--title), format w jakim obrazek zostanie zapisany (--imgformat), jego wymiary (--width oraz --height), czas uwzględniony na obrazku (--start i --end now), z tym, że now-$TIME oznacza czas cofnięcia się wstecz, przykładowo, jeśli określimy now-1hour oznaczać to będzie jedną godzinę wcześniej w stosunku do godziny aktualnej. Opcja --interlaced ma zapewnić szybsze ładowanie się obrazków w przeglądarkach. Możemy także dopisać parametr --vertical-label="" w celu opisania pionowej osi wykresu.
Dalej mamy linijki z DEF . Przypisują one zmienne longterm_avg , longterm_max , etc. do danych zebranych przez collecd w odpowiednich plikach. By sprawdzić jakie dane możemy wyciągnąć z pliku, trzeba je odczytać przy pomocy poniższego polecenia:

# rrdtool info /tmp/router/stats/the-mountain/load/load.rrd
...
ds[shortterm]
...
ds[midterm]
...
ds[longterm]
...

Dane na wykresach są określane przez CF (consolidation function) i ten parametr również możemy odczytać z powyższego pliku:

...
rra[0].cf = "AVERAGE"
...
rra[1].cf = "MIN"
...
rra[2].cf = "MAX"
...

Kolejne linijki w powyższym bloku to te zaczynające się od AREA . Rysują one słupki i oznaczają je odpowiednimi kolorami, tj. #ffe8e8 , #e8e8ff oraz #e2ffe2 (RGB, zapis hexalny). Wartości, w oparciu o które zostaną narysowane te wykresy, są brane ze zdefiniowanych przez nas zmiennych.

Z kolei wpisy zaczynające się od LINE, rysują wykres liniowy i odnoszą się także do legendy pod wykresem. Cyfra 2 zaraz po LINE oznacza grubość kreski, którą rysowany jest wykres. Dalej mamy określone zmienne, które mają być brane pod uwagę. Mamy także określone kolory, odpowiednio #55ff55 , #7777ff i #ff7777 . Ostatnia wartość to opis, ten który pojawi się pod grafem.

Linijki zawierające GPRINT określają wartość maksymalną (MAX) i średnią (AVERAGE) i minimalną (MIN) dla danego wykresu, te informacje również są umieszczane w legendzie.

Każdy moduł ma mniej więcej taki sam zapis, różni się tylko ilością zdefiniowanych parametrów i po części też ich nazwami.

Po skonfigurowaniu wszystkich pluginów, na końcu skryptu wywołujemy odpowiednie funkcje, w tym przypadku jest póki co tylko jeden:

###
load_load

Tak stworzony skrypt zapisujemy sobie na routerze, np. w katalogu /etc/skrypty/mkgraph.sh .

Statystyki może i są zbierane w czasie rzeczywistym i to przez cały czas pracy routera ale obrazki z wykresami trzeba wygenerować ręcznie. Proces zbierania danych nie jest zbytnio zasobożerny, natomiast generowanie wykresów już tak. Naturalnie, nie będziemy ciągle wbijać na router by wygenerować sobie kilka grafów, no chyba, że będziemy potrzebować aktualnych danych, zamiast tego, zaprogramuje narzędzie cron, by cyklicznie te wykresy generował za nas. Dobrze jest ustawić sobie interwał na 30 minut, tak by nie zarżnąć routera ciągłym generowaniem obrazków. Mając powyższy skrypt, dodajemy go to tablicy crona . Wpisujemy zatem w terminalu crontab -e i dodajemy tam poniższy wpis:

# min   hour    day     mon     dow     command
*/30    *       *       *       *       /etc/skrypty/mkgraph.sh

Musimy jeszcze stworzyć prostą stronę www, która będzie nam te obrazku pokazywać. Poniżej przykładowy plik dla modułu loadavg:

<html>
	<body>
		<img src="/stats/the-mountain/load/load-1hour.png">
		<img src="/stats/the-mountain/load/load-12hours.png">
		<img src="/stats/the-mountain/load/load-24hours.png">
		<img src="/stats/the-mountain/load/load-48hours.png">
		<img src="/stats/the-mountain/load/load-1week.png">
	</body>
</html>

Możemy umieszczać wszystkie moduły w jednym pliku, albo każdy w osobnym, lub też wybrać odpowiednie obrazki i stworzyć sobie jedną stronę tak by wszystkie potrzebne nam wykresy były w jednym miejscu. Szkielet stron zapisujemy w /etc/collectd_www/ .
By to wszystko teraz puścić w ruch, potrzebny nam jest jeszcze mały skrypt startowy, który przekopiuje odpowiednie pliki w odpowiednie miejsce. Tworzymy zatem plik /etc/init.d/statistics o poniższej zawartości :

#!/bin/sh /etc/rc.common
START=81
start() {                            
	if [ ! -d /tmp/router/www ]
	then
			mkdir -p /tmp/router/www
	fi
	
	cp -a /etc/collectd_www/* /tmp/router/www/
	/etc/skrypty/mkgraph.sh
}

Dodajemy go do autostartu:

# /etc/init.d/statistics enable

Kolejność powinna być taka jak poniżej:

# ls -al /etc/rc.d/ | egrep "stat|http|collect"
lrwxrwxrwx    1 root     root            16 Oct 29 18:04 S50uhttpd -> ../init.d/uhttpd
lrwxrwxrwx    1 root     root            18 Oct 29 18:05 S80collectd -> ../init.d/collectd
lrwxrwxrwx    1 root     root            20 Oct 30 17:03 S81statistics -> ../init.d/statistics

Teraz możemy zresetować router. Trzeba jednak pamiętać, że generowanie statystyk zajmuje czas i trochę spowolni start routera. W moim przypadku jest to około 6 sekund.