This commit is contained in:
2026-02-23 02:29:50 +01:00
parent 8a0e48b3cb
commit 77c1615675
16 changed files with 1671 additions and 838 deletions

14
measurement/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM python:3.12-slim
RUN apt-get update && apt-get install -y cron && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
COPY crontab /etc/cron.d/speedtest
RUN chmod 0644 /etc/cron.d/speedtest && crontab /etc/cron.d/speedtest
CMD ["cron", "-f"]

3
measurement/config.py Normal file
View File

@@ -0,0 +1,3 @@
import os
DB_PATH = os.get_env("DB_PATH")

1
measurement/crontab Normal file
View File

@@ -0,0 +1 @@
*/15 * * * * cd /app && python run_speedtest.py >> /proc/1/fd/1 2>> /proc/1/fd/2

89
measurement/db.py Normal file
View File

@@ -0,0 +1,89 @@
import sqlite3
from config import DB_PATH
def init_db(db_path=DB_PATH):
conn = sqlite3.connect(db_path)
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS speed_tests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
failed BOOLEAN NOT NULL,
isp TEXT,
ip TEXT,
location_code TEXT,
location_city TEXT,
location_region TEXT,
latency REAL,
jitter REAL,
down_100kB REAL,
down_1MB REAL,
down_10MB REAL,
down_25MB REAL,
down_90th REAL,
up_100kB REAL,
up_1MB REAL,
up_10MB REAL,
up_90th REAL
)
''')
conn.commit()
conn.close()
def insert_result(results: dict|None, db_path=DB_PATH):
conn = sqlite3.connect(db_path)
c = conn.cursor()
# If the test failed entirely, store it as a failure with timestamp now
if results is None or "tests" not in results:
from time import time
c.execute("INSERT INTO speed_tests (timestamp, failed) VALUES (?, ?)", (time(), True))
conn.commit()
conn.close()
return
tests = results.get("tests", {})
meta = results.get("meta", {})
# Get a consistent timestamp from any TestResult (or fallback to now)
from time import time as now
sample_test = next(iter(tests.values()), None)
timestamp = sample_test.time if sample_test else now()
print(tests)
print(meta)
def get(tests, key):
return tests[key].value if key in tests else None
c.execute('''
INSERT INTO speed_tests (
timestamp, failed, isp, ip, location_code, location_city, location_region,
latency, jitter,
down_100kB, down_1MB, down_10MB, down_25MB, down_90th,
up_100kB, up_1MB, up_10MB, up_90th
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
timestamp, False,
get(tests, "isp"),
get(meta, "ip"),
get(meta, "location_code"),
get(meta, "location_city"),
get(meta, "location_region"),
get(tests, "latency"),
get(tests, "jitter"),
get(tests, "100kB_down_mbps"),
get(tests, "1MB_down_mbps"),
get(tests, "10MB_down_mbps"),
get(tests, "25MB_down_mbps"),
get(tests, "90th_percentile_down_mbps"),
get(tests, "100kB_up_mbps"),
get(tests, "1MB_up_mbps"),
get(tests, "10MB_up_mbps"),
get(tests, "90th_percentile_up_mbps")
))
conn.commit()
conn.close()

View File

@@ -0,0 +1,45 @@
# This file was autogenerated by uv via the following command:
# uv export --format requirements-txt -o requirements.txt --no-hashes
blinker==1.9.0
# via flask
click==8.3.1
# via flask
colorama==0.4.6 ; sys_platform == 'win32'
# via click
flask==3.1.2
# via speed-logger
getmac==0.9.5
# via speed-logger
gunicorn==23.0.0
# via speed-logger
itsdangerous==2.2.0
# via flask
jinja2==3.1.6
# via flask
markupsafe==3.0.3
# via
# flask
# jinja2
# werkzeug
numpy==2.4.0
# via pandas
packaging==25.0
# via gunicorn
pandas==2.3.3
# via speed-logger
python-dateutil==2.9.0.post0
# via pandas
pytz==2025.2
# via
# pandas
# speed-logger
six==1.17.0
# via python-dateutil
tzdata==2025.3
# via pandas
werkzeug==3.1.4
# via flask
sqlalchemy==2.0.46
# via flask-sqlalchemy
flask-sqlalchemy==3.1.1
# via speed-logger

16
measurement/run_speedtest.py Executable file
View File

@@ -0,0 +1,16 @@
from cfspeedtest import CloudflareSpeedtest
from db import init_db, insert_result
def run_test_and_save():
try:
tester = CloudflareSpeedtest()
results = tester.run_all(megabits=True) # returns SuiteResults
except Exception:
results = None # Trigger a failed test record
init_db()
insert_result(results)
print("==== Running Speedtest ====")
run_test_and_save()
print("==== Speedtest ended ====")