Internet-Speed- / Bandbreiten-Messung mit Python
Wie schnell ist mein Internet? Viele machen ab und an mal einen Speedtest mit speedtest.net oder anderen Tools. Meist bekommt so zwar Probleme mit, aber erst dann, wenn sie schon da da sind.
Genau aus dem Grund habe ich auf bestehenden Tools aufgebaut (speedtest-cli) [Danke an Christoph Langer für deinen ausführlichen Artikel zur speedtest-cli und ein Danke an Matthias Storck aus einem Kommentar dem selben Artikel für die Idee], um eine kontinuierliche Messung meiner Leitung durchführen zu können. Die Daten werden dann in einer MySQL-Datenbank gespeichert und können dann mit beliebigen Tools grafisch visualisiert werden.
Einrichtung der speedtest-cli
Die Einrichtung der CLI (Command Line Interface) ist recht einfach und schnell gemacht, hängt aber auch stark von Eurem eingesetztem System ab. Ob nun via Powershell oder mit der Ubuntu-Bash unter Windows 10, beides geht ohne Probleme. Selbstverständlich auch nativ unter Linux. Das einzige was die CLI benötgt, ist ein installiertes Python.
Die CLI selbst fragt die Daten von speedtest.net ab und sind daher recht aussagekräftig.
In meinem Beispiel habe ich das Skript unter Windows entwickelt, aber führe es über einen CRON-Job auf meinem (Raspbian) aus.
Herunterladen der CLI
Das Herunterladen geht mittels wget
schnell und zuverlässig:
wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py chmod +x speedtest-cli
Das Tool liegt dann in Eurem aktuellen Verzeichnis (ausgehend vom RaspberryPi). Damit könntet Ihr auch schon direkt Messungen durchführen und z.B. in einer CSV-Datei lagern. Ist dann aber immer wieder blöd, wenn man erst die CSV-Datei mit tail
via SSH öffnen muss um die Daten zu prüfen.
Die speedtest-cli bietet auch noch einige andere sehr nützliche Features an:
$ speedtest-cli -h usage: speedtest-cli [-h] [--no-download] [--no-upload] [--bytes] [--share] [--simple] [--csv] [--csv-delimiter CSV_DELIMITER] [--csv-header] [--json] [--list] [--server SERVER] [--exclude EXCLUDE] [--mini MINI] [--source SOURCE] [--timeout TIMEOUT] [--secure] [--no-pre-allocate] [--version] Command line interface for testing internet bandwidth using speedtest.net. -------------------------------------------------------------------------- https://github.com/sivel/speedtest-cli optional arguments: -h, --help show this help message and exit --no-download Do not perform download test --no-upload Do not perform upload test --bytes Display values in bytes instead of bits. Does not affect the image generated by --share, nor output from --json or --csv --share Generate and provide a URL to the speedtest.net share results image, not displayed with --csv --simple Suppress verbose output, only show basic information --csv Suppress verbose output, only show basic information in CSV format. Speeds listed in bit/s and not affected by --bytes --csv-delimiter CSV_DELIMITER Single character delimiter to use in CSV output. Default "," --csv-header Print CSV headers --json Suppress verbose output, only show basic information in JSON format. Speeds listed in bit/s and not affected by --bytes --list Display a list of speedtest.net servers sorted by distance --server SERVER Specify a server ID to test against. Can be supplied multiple times --exclude EXCLUDE Exclude a server from selection. Can be supplied multiple times --mini MINI URL of the Speedtest Mini server --source SOURCE Source IP address to bind to --timeout TIMEOUT HTTP timeout in seconds. Default 10 --secure Use HTTPS instead of HTTP when communicating with speedtest.net operated servers --no-pre-allocate Do not pre allocate upload data. Pre allocation is enabled by default to improve upload performance. To support systems with insufficient memory, use this option to avoid a MemoryError --version Show the version number and exit
Eine Messung könnte dann wie folgt aussehen:
Speichern der Werte in einer Datenbank
Erstellung einer Tabelle
Damit die Daten nun auch gespeichert werden können, müsst Ihr eine entsprechende Tabelle in einer beliebigen Datenbank anlegen, in meinem Falle ist das eine MySQL-Datenbank auf meinem Raspberry.
Mit nachfolgendem Statement könnt ihr die Tabelle entsprechend erzeugen:
CREATE TABLE `Bandbreite` ( `ID` INT(11) NOT NULL AUTO_INCREMENT, `Server_ID` INT(11) NOT NULL, `Sponsor` VARCHAR(50) NOT NULL, `Server` VARCHAR(50) NOT NULL, `Distance` INT(11) NULL DEFAULT NULL, `Ping` FLOAT NOT NULL, `Down` FLOAT NOT NULL, `Up` FLOAT NOT NULL, `Device_ID` INT(11) NULL DEFAULT NULL, `Date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`ID`), INDEX `FK_Bandbreite_Devices` (`Device_ID`), CONSTRAINT `FK_Bandbreite_Devices` FOREIGN KEY (`Device_ID`) REFERENCES `Devices` (`ID`) ON UPDATE NO ACTION ON DELETE NO ACTION ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB AUTO_INCREMENT=132 ;
Die entspringt aus meiner Tabelle aus dem Beitrag Automatische Firmware-Updates für Microcontroller mit Gitlab und PlatformIO und muss nicht zwingend von Euch übernommen werden. Ich finde es aber ganz nett, wenn man unterschiedliche Messungen an mehreren Geräten fahren kann und so einen Mittelwert bildet.
Das Wrapper-Skript
Damit die Daten nun auch in die Tabelle gespeichert werden können, muss das Skript wiederum durch ein anderes Skript ausgeführt werden. Die Ausgabe wird entsprechend durch mein Skript geparsed. Dazu verwende ich den Parameter --csv --csv-delimiter ';'
. Dadurch wird die Rückgabe in einem CSV-Format zurückgegeben.
Das Skript:
#!/usr/bin/python ##################################################### import os import MySQLdb import sys import datetime now = datetime.datetime.now() now = str(now) def adddata(): print "["+now+"] Checking bandwidth" try: db = MySQLdb.connect(host="localhost",user="test",passwd="test",db="DATENBANKNAME") curs=db.cursor() print "["+now+"] Opening speedtest-cli" f = os.popen("/usr/local/bin/speedtest-cli --csv --csv-delimiter ';'", 'r') d1 = f.read() d2=d1.rstrip("\n") d3=d2.split(";") for s in d3: print "["+now+"] Got item: "+ s dbstring="""INSERT INTO Bandbreite(Server_ID,Sponsor,Server,Distance,Ping,Down,Up,Device_ID) VALUES ( '"""+d3[0]+"""', '"""+d3[1]+"""' , '"""+d3[2]+"""', '"""+d3[4]+"""', '"""+d3[5]+"""', '"""+d3[6]+"""', '"""+d3[7]+"""', 58)""" #.format( d3[0], d3[1], d3[2], d3[4], d3[5], d3[6]) print "["+now+"] DB-String: "+ dbstring curs.execute( dbstring ) db.commit() db.close() print "["+now+"] Data stored in database" except MySQLdb.Error,e: print "["+now+"] Error:%d:%s" % (e.args[0], e.args[1]) db.rollback() except: print "["+now+"] Unexpected error:", sys.exc_info()[0] return adddata adddata() ############################################################### #
Wichtig ist, dass Ihr die Verbindungsdaten zu Eurer Datenbank anpasst. Solltet ihr die mysqldb-Library noch nicht installiert haben, könnt Ihr sie ganz einfach mit sudo apt-get install python-mysqldb
installieren.
Technisch gesehen macht das Skript folgendes:
- Holen der Rückgabe der speedtest-cli
- Splitten der Zeilen mit Hilfe von
d2=d1.rstrip("\n")
und Rückgabe in ein Arrayd2
- Splitten der einzelnen Spalten/Werte mit Hilfe von
d3=d2.split(";")
und Rückgabe in ein Arrayd3
Die Werte stehen dann im Array d3
zur Verfügung und können in das SQL-Statement eingefügt werden. Es geht sicherlich schöner mit .format()
, aber da ich doch recht neu in Python bin, klappte das bei mir nicht auf Anhieb und ich habe mich dann für die „Quick & Dirty“-Variante entschieden.
Kontinuierlich die Daten abfragen
Das Skript könnt Ihr nun auf Euren RaspberryPi via SSH übertragen. Oder direkt mit Hilfe von Nano (Was ist Nano?) in /usr/local/bin
schreiben. Vorteil ist, dass Skripte unter diesem Pfad von überall ausführbar sind. Zudem solltet Ihr die heruntergeladene Datei speedtest-cli
auch in diesen Pfad hinterlegen.
Nun kann unser „Wrapper-Skript“ als Cronjob angelegt werden. Dazu einfach die Cronjobs mittels sudo crontab -e
bearbeiten. Ich lasse das Skeipt im 15-Minuten-Takt laufen, das könnt ihr aber ändern wie Ihr möchtet. Da ich mir den Aufbau der Cronjobs nicht merken kann, empfehle ich Euch daher die Seite Crontab Generator. Damit könnt ihr Eure Konfiguration ganz einfach erstellen lassen:
Danach einfach die Datei schließen und schon sollte Eure Messung in Eurem Intervall laufen.
Mein Cronjob sieht so aus:
*/15 * * * * /usr/bin/python /usr/local/bin/storebandwithdata >> /var/log/bandwithtest.log 2>&1
Der Parameter */15
heißt, dass das Skript (ab Speichern der crontab-Datei) alle 15 Minuten ausgeführt wird. Mit >> /var/log/bandwithtest.log 2>&1
speichere ich die Rückgabe des Skriptes in einer Log-Datei, damit man die Ausführung auch noch debuggen kann, wenn etwas schief läuft.
Wenn du nun tail -f /var/log/bandwithtest.log
ausführst, solltest du nun die Rückgaben des Skriptes sehen. Möchtest du keine Log-Datei nutzen, kannst du auch mit sudo tail /var/log/syslog -f | grep CRON
die Systemlogs einsehen. Tail ist ein recht nützlicher Befehl um Log-Files zu prüfen.
Et voilà, die Daten sind in unserer Datenbank angekommen:
Nachtrag
Wie mir jedoch aufgefallen ist, scheint der RaspberryPi 3B recht schwach auf der Brust zu sein, was der Durchsatz des Netzwerkcontrollers angeht. Daher sind die Werte bei mir recht schwach, obwohl ich eine Leitung mit 120MBit/s Download und 6MBit/s Upload habe.
Daran erkennt man aber recht gut, dass die Messung keinerlei fundierte Grundlage sind, um z. B. dem Provider diese vorhalten zu können. Zumal vermutlich jeder Provider diese Werte ablehnen würde, sofern es zu einem Streifall käme. Daher sollten diese Werte nur als grobe Richtung verstanden werden.
Mit meiner NAS bekomme ich ganz andere Werte die auch höher sind, obwohl die NAS in meinem Büro unter dem Dach steht, das aktuell nur via DLAN von angebunden ist und das auch nicht so die besten Durchsätze hat bei uns im Haus.
Mir persönlich ist das aber egal, da ich auch wirklich nur grobe Werte der Leitung haben möchte und damit meinem Provider nicht nerven will.