Internet-Speed- / Bandbreiten-Messung mit Python

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:

Ausführung der speedtest-cli innerhalb der Powershell von Windows

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 Array d2
  • Splitten der einzelnen Spalten/Werte mit Hilfe von d3=d2.split(";") und Rückgabe in ein Array d3

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.