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.

Das Script gibt es bei GitHub

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/fheinle/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 starten:

$ 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.