Langzeitmessung der Internetgeschwindigkeit mit speedtest-cli und rrdtool
In diesem Beitrag will ich beschreiben, wie ich langfristige Messungen
meiner Internetgeschwindigkeit anstelle und die Ergebnisse in einem
Graphen aufmalen lasse. Dazu nutze ich
speedtest-cli
und rrdtool
.
Wer sich nicht interessiert, wie das alles funktioniert und nur gern das Script nutzen möchte, findet das Script mit Anleitung weiter unten.
Komponenten
speedtest-cli
ist im Grunde ein Programm um speedtest.net zu verwenden. Wie der Name schon vermuten lässt, kann man es auf der Kommandozeile verwenden — aber auch als Teil eines Python-Programms.rrdtool
ist ein Programm zum Befüllen und Auswerten einer Round Robin-Datenbank. Im Grunde hat man da Platz für eine vorher definierte Anzahl von Datensätzen und wenn man weiter rein schreibt, nachdem dieser voll ist, wird der älteste Datensatz dafür fallen gelassen. Ideal also, wenn sowieso immer nur die letzte Woche an Daten braucht und nicht unendlich viel Platz zur Speicherung verwenden will.measure.py
ist mein Python-Script zur Automatisierung der Messung, Speicherung, Erstellung des Graphen und optional des Uploads des Graphen via WebDAV, etwa zu Nextcloud oder einem vergleichbaren Speicher.docker
hilft optional dabei, alle Abhängigkeiten zu installieren und beinander zu behalten.
Von Hand
Geschwindigkeitsmessung
Um automatisiert eine Messung der Internetgeschwindigkeit anzustellen,
bietet sich etwa die Verwendung von speedtest-cli
als Python-Modul an. Die Installation ist einfach:
$ pip3 install speedtest-cli
Collecting speedtest-cli
Installing collected packages: speedtest-cli
Successfully installed speedtest-cli-2.1.2
Die Verwendung ist dann auch nicht schwer:
>>> import speedtest
>>> s = speedtest.SpeedTest()
>>> s.get_best_server()
{'url': 'http://muc.speedtest.contabo.net:8080/speedtest/upload.php', 'lat': '48.1333', 'lon': '11.5667', 'name': 'Munich', 'country': 'Germany', 'cc': 'DE', 'sponsor': 'Contabo GmbH', 'id': '21514', 'host': 'muc.speedtest.contabo.net:8080', 'd': 56.52425148132704, 'latency': 42.665}
>>> s.download() / 1024 / 1024
4.458612648376581
>>> s.upload() / 1024 / 1024
7.970390055040803
>>> print(s.results)
{'download': 4675194.216384122, 'upload': 8357559.722354465, 'ping': 42.665, 'server': {'url': 'http://muc.speedtest.contabo.net:8080/speedtest/upload.php', 'name': 'Munich', 'country': 'Germany', 'cc': 'DE', 'sponsor': 'Contabo GmbH', 'id': '21514', 'host': 'muc.speedtest.contabo.net:8080', 'd': 56.52425148132704, 'latency': 42.665}, 'timestamp': '2020-02-17T19:38:29.685189Z', 'bytes_sent': 10674176, 'bytes_received': 8256848, 'share': None, 'client': {'ip': '1.3.3.7', 'lat': '1337', 'lon': '4223', 'isp': 'Vodafone Kabel Deutschland', 'isprating': '3.7', 'rating': '0', 'ispdlavg': '0', 'ispulavg': '0', 'loggedin': '0', 'country': 'DE'}}
Hier sieht man auch schön, wieso ich das Script und den Blogpost schreibe. 4 mbit/s statt 1000 Mbit/s ist nicht mehr ganz im akzeptablen Rahmen.
Speicherung im rrdtool
Die Bedienung des rrdtool
ist etwas schwer zu
verstehen weil viel der Dokumentation nicht ganz klar ist. Das liegt
nicht zuletzt am großen Funktionsumfang des Programms. Für meinen
eingeschränkten Einstazzweck dokumentiere ich hier also die
verschiedenen genutzten Befehle.
Anlegen der Datenbank
Zuerst muss man die Datenbankdatei anlegen. Das ist eine normale Datei auf der Festplatte. Man muss auch direkt festlegen, was man da für Daten erwartet, also mehrere Datenquellen, wie oft man dort einen Datensatz erwartet, wie das gemessen werden soll und in welchem Bereich die Daten liegen werden.
Darüber hinaus braucht man ein Archiv. Die eigentliche Auwertung findet im Archiv statt. Damit legt man fest, wie viele Datensätze man vorhalten will und wie die Auswertung stattfinden soll.
Die Geschwindigkeit eines Internetanschlusses mit
1000/50
Mbit/s für eine Woche messen zu lassen
benötigt etwa so eine Datenbank:
$ rrdtool create datenbank.rrd \
--step '3600' \ # stündliche Werte
'DS:ping:GAUGE:3600:0:200' \ # DataSet ping, stündliche Werte, min 0, max 200
'DS:upload:GAUGE:3600:0:50' \ # DataSet upload, stündliche Werte, min 0, max 50
'DS:download:GAUGE:3600:0:1000' \ # DataSet download, stündliche Werte, min 0, max 1000
'RRA:MAX:0.5:1:168' # Round Robin Archive, jeweils Maximalwert nehmen, valide Messung bis 50% Vollständigkeit
# 1 Datenpunkt heranziehen um Messpunkt zu erstellen
# 168 Datensätz (=24*7) vorhalten
Werte eintragen
Einen Wert einzutragen ist wesentlich einfacher:
$ rrdtool update datenbank.rrd 1581969684:20:7:4
Die Reihenfolge muss dabei wie beim Erstellen beachtet werden. Hier wird
also ein neuer Datensatz eingefügt mit Timestamp
1581969684
(kurz nach neun an einem Montag im
Februar), Ping von 20
, 7
Mbit/s Upload, 4
Mbit/s Download. Der eine Eintrag
ist natürlich langweilig; den Test sollte man ausreichend oft
wiederholen. Dabei sollte man allerdings nicht zu geringe Abstände
zwischen den Tests wählen. Damit wird zwar die Messung genauer - wenn
man aber alle fünf Minuten die Internetgeschwindkgeit vom Test voll
ausnutzen lässt, fehlt die natürlich beim normalen Internetbetrieb für
andere Dinge. Einmal in der Stunde zu messe sollten ausreichen.
Einen Graphen zeichnen lassen
Einen Graphen mit rrdtool
zeichnen zu lassen ist
auch wieder eine Aufgabe für die Kommandozeile. Dabei können alle
möglichen Schalter gesetzt werden:
$ rrdtool graph internetgeschwindigkeit.png \
-w '600' -h '300' \ # Bild mit Auflösung 600x300
-u '1000' \ # Maximalwert des Graphen
--start=end-1w \ # X-Achse beginnt vor genau einer Woche
'DEF:ping:datenbank.rrd:ping:MAX' \ # Kurve für PING, Maximalwerte nehmen
'DEF:upload:datenbank.rrd:upload:MAX' \
'DEF:download:datenbank.rrd:download:MAX' \
'LINE1:ping#0000FF:Ping (s)' \ # Linie, 1px dick, blau, Legende
'LINE1:upload#FF0000:Upload (Mbit/s)' \
'LINE1:download#99FF00:Download (Mbit/s)' \
'HRULE:600#FF0000' # Rote Linie bei 600
Bei 600
Mbit/s habe ich eine rote horizontale
Linie einzeichnen lassen, weil das die geringste garantierte
Geschwindigkeit meines Internettarifs wäre.
So bekommt man ein
internetgeschwindigkeit.png
-Bild. Das sieht etwas
technisch und nicht so hübsch aus, aber technische Experten werden das
Format wiedererkennen und sofort sehen: „Aha, da hat jemand Ahnung” - so
wie wenn man bei Präsentationen das Warsaw Theme von LaTeX
verwendet. Nicht hübsch, aber strahlt bei Insidern direkt Kompetenz aus.
Hochladen des Graphen
Das Ergebnis des Graphen lässt sich jederzeit betrachten. Lässt man es
allerdings etwa auf einem Server erstellen, kommt man ggf. nicht so
einfach ran. Ich lade das Bild also einfach automatisiert in meine
NextCloud. Das geht über WebDAV, also in dem Fall über
HTTP PUT
. Das mache ich auch mit Python und nutze
dazu das requests
-Modul:
$ pip3 install requests
Collecting requests
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 (from requests)
Collecting idna<2.9,>=2.5 (from requests)
Collecting certifi>=2017.4.17 (from requests)
Collecting chardet<3.1.0,>=3.0.2 (from requests)
Installing collected packages: urllib3, idna, certifi, chardet, requests
Successfully installed certifi-2019.11.28 chardet-3.0.4 idna-2.8 requests-2.22.0 urllib3-1.25.8
Damit geht das Hochladen sehr einfach:
>>> import requests
>>> r = requests.put(
... 'https://url-zum-endpunkt.de/webdav/blabla/graph.png',
... auth=('username', 'password'),
... data=open('graph.png', 'rb').read()
... )
>>> print(r.status_code)
204
Returncode 204
bedeutet
No Content
, das ist in Ordnung.
Mit Script
Das ist alles sehr aufwändig und manuell. Nützlich wird es erst automatisiert und reglemäßig. Deswegen habe ich ein Script geschrieben, was den Aufwand abnimmt.
Konfiguration
Das Script hat ein paar Konfigurationmöglichkeiten, die alle mit Umgebungsvariablen übergeben werden können:
Variable | Default | Beschreibung |
---|---|---|
DOWNLOAD_MAX |
1000 |
Maximale Download-Geschwindigkeit |
GRAPH_FNAME |
/data/graph.png |
Speicherort des Graphen |
GRAPH_HEIGHT |
300 |
Höhe des Graphen |
GRAPH_WIDTH |
600 |
Breite des Graphen |
LINE_POS |
600 |
Wo die rote Linie kommen soll |
LOGLEVEL |
INFO |
Ausführlichkeit der Ausgaben |
RRD_FNAME |
/data/speed.rrd |
Speicherort der Datenbanke |
TARGET_PASS |
- | Passwort für HTTP Authentication |
TARGET_URL |
- | Wohin der Graph hochgeladen werden soll |
TARGET_USER |
- | Username für HTTP Authentication |
UPLOAD_GRAPH |
True | Alles außer false lässt Graphen hochladen |
UPLOAD_MAX |
50 |
Maximale Upload-Geschwindigkeit |
Die Variablen kann man entweder alle auf der Kommandozeile setzen:
$ DOWNLOAD_MAX=1000 GRAPH_FNAME=./graph.png …
…oder, viel praktischer, in eine Datei schreiben, von da laden lassen und dann das Script ausführen.
$ cat settings.env
export DOWNLOAD_MAX=1000
export GRAPH_FNAME=./graph.png
…
$ source settings.env
$ ./measure.py
2020-02-17 21:10:28,155 Download: 4.89 Upload: 7.82 Ping: 20
Keine Sorge— das mit dem Umgebungsvariablen ist viel praktischer,
wenn man docker
verwendet.
Mit Docker
Damit ich auf meinem Server nicht alle Abhängigkeiten wie
rrdtool
und die notwendigen Python-Module auf das
Host-System installieren muss und eine VM oder auch nur ein
Linux-Container aufzusetzen stark übertrieben wären, habe ich einen
Container mit Docker erstellt.
Das Dockerfile ist ziemlich einfach; es nimmt
python3.8-buster
als Grundlage. Das ist Python 3.8
mit Debian Buster unten drin, beides jeweils aktuell das aktuellste.
Noch kleiner wäre der Container, hätte man darunter Alpine Linux
- damit hatte ich aber seltsame Font-Probleme mit
rrdtool
und man konnte die Legenden im Graph nicht lesen. Obendrauf werden noch die notwendigen Pakete installiert und das Script wird reinkopiert.
Das notwendige Dockerfile gibt’s mit dem Script in einem GitHub-Repo
Container bauen
Um den Container zu bauen muss das Repo ausgecheckt weren:
$ git clone https://github.com/very-emmazing/speedtest-rrdtool-docker
$ git checkout tags/blogpost2 # Sonst geht diese Anleitung nicht!
Cloning into 'speedtest-rrdtool-docker'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 5 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
$ cd speedtest-rrdtool-docker
$ docker build -t speedchart .
Sending build context to Docker daemon 65.02kB
Step 1/4 : FROM python:3.8-buster
---> efdecc2e377a
Step 2/4 : RUN mkdir /data && apt-get update -q && apt-get install -y rrdtool && pip3 install requests speedtest-cli python-dateutil && fc-cache && apt-get clean
---> Using cache
---> 0deeef7212a0
Step 3/4 : COPY measure.py /measure.py
---> a257eb95c6e8
Step 4/4 : CMD python3 ./measure.py
---> Running in 9e77ed79915f
Removing intermediate container 9e77ed79915f
---> d07e84fc7793
Successfully built d07e84fc7793
Successfully tagged speedchart:latest
Nun liegt das Container-Image zur Nutzung bereit. Nun sollte man noch die Datei mit den Einstellungen kopieren und anpassen:
$ cp settings.env.sample settings.env && vi settings.env
Achtung: diesmal kein export
vor den jeweiligen
Variablen!
Damit kann man dann den Container erstellen, den man dann immer wieder aufruft:
$ mkdir data
$ docker create --name speedchart -v $(pwd)/data:/data --env-file=settings.env speedchart
Damit wird ein Container angelegt, der auf den Namen
speedchart
hört. Das Verzeichnis
data
wird innerhalb des Containers auf
/data
bereitgestellt. Das ist praktisch, denn da
sucht das Script auch standardmäßig nach dem Verzeichnis. Die
Einstellungen werden aus der settings.env
als
Umgebungsvariablen eingelesen. Der Container verwendet als Image das
vorhin gebaute speedchart
.
Container nutzen
Hat man den Container angelegt, kann man ihn
start
en:
$ docker start speedchart
Die Konfigurationsparameter wurden bereits bei der Erstellung des Containers in die Umgebungsvariablen eingelesen.
Der Container wird nicht jedes Mal neu angelegt, nur neu gestartet. Ändert man Einstellungen, muss man den Container erst löschen und dann neu bauen:
$ docker container rm speedchart
$ docker create --name speedchart -v $(pwd)/data:/data --env-file=settings.env speedchart
Cronjob
Um den Container jede Stunde laufen zu lassen, kann man den Aufruf auch in einen Cronjob verpacken:
$ crontab -e
# Eintragen:
0 * * * * docker start speedchart
Kontrolle
Möchte man sehen, ob der Container erfolgreich die Messung vorgenommen
hat und was er gemessen hat, kann man in den Logs von
docker
nachsehen:
$ docker logs speedchart
2020-02-17 21:10:28,155 Download: 4.89 Upload: 7.82 Ping: 20
Will man mehr Ausgabe, falls unterwegs etwas schiefgegangen ist, kann
man die Ausgabe erweitern, indem man die Umgebungsvariable
LOGLEVEL
auf DEBUG
setzt.
Danach nicht vergessen, wie oben beschrieben den Container neu zu bauen.