yay
This commit is contained in:
14
measurement/Dockerfile
Normal file
14
measurement/Dockerfile
Normal 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
3
measurement/config.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import os
|
||||
|
||||
DB_PATH = os.get_env("DB_PATH")
|
||||
1
measurement/crontab
Normal file
1
measurement/crontab
Normal 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
89
measurement/db.py
Normal 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()
|
||||
|
||||
45
measurement/requirements.txt
Normal file
45
measurement/requirements.txt
Normal 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
16
measurement/run_speedtest.py
Executable 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 ====")
|
||||
Reference in New Issue
Block a user