updated speed logger
This commit is contained in:
65
app.py
65
app.py
@@ -1,38 +1,67 @@
|
|||||||
from flask import Flask, render_template
|
from flask import Flask, render_template, request
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from config import DB_PATH
|
from config import DB_PATH
|
||||||
from datetime import datetime
|
import math
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
def load_data():
|
def load_data():
|
||||||
# Connect to SQLite database
|
# 1) grab raw
|
||||||
conn = sqlite3.connect(DB_PATH)
|
conn = sqlite3.connect(DB_PATH)
|
||||||
query = "SELECT * FROM speed_tests"
|
df = pd.read_sql("SELECT * FROM speed_tests", conn)
|
||||||
df = pd.read_sql(query, conn)
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
# 2) parse epoch→UTC datetimes, then shift to Europe/Berlin
|
||||||
|
df['datetime'] = (
|
||||||
|
pd.to_datetime(df['timestamp'], unit='s', utc=True)
|
||||||
|
.dt.tz_convert('Europe/Berlin')
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3) for your table display, format as naive strings
|
||||||
|
df['recorded_at'] = df['datetime'].dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def get_aggregated_data(df: pd.DataFrame, interval: str = '5min'):
|
||||||
|
# ensure 'datetime' is the index
|
||||||
|
df = df.set_index('datetime')
|
||||||
|
|
||||||
|
# 1) resample into N-minute bins, mean
|
||||||
|
agg = (
|
||||||
|
df
|
||||||
|
.resample(interval)
|
||||||
|
.agg({
|
||||||
|
'down_90th': 'mean',
|
||||||
|
'up_90th': 'mean'
|
||||||
|
})
|
||||||
|
.reset_index()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3) output for Chart.js
|
||||||
|
return {
|
||||||
|
"times": agg['recorded_at'].tolist(),
|
||||||
|
"down_90th": agg['down_90th'].round(2).tolist(),
|
||||||
|
"up_90th": agg['up_90th'].round(2).tolist()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
df = load_data()
|
df = load_data()
|
||||||
|
agg = request.args.get("agg", "5min")
|
||||||
|
chart_data = get_aggregated_data(df, agg)
|
||||||
|
|
||||||
|
print(f"down last 10: {chart_data['down_90th'][-10:]}")
|
||||||
|
print(f"down last 10: {chart_data['up_90th'][-10:]}")
|
||||||
|
|
||||||
|
print(math.isnan(chart_data['down_90th'][-1]) or math.isnan(chart_data['up_90th'][-1]))
|
||||||
|
|
||||||
# Convert timestamps to human-readable format
|
#if math.isnan(chart_data['down_90th'][-1]) or math.isnan(chart_data['up_90th'][-1]):
|
||||||
df['datetime'] = pd.to_datetime(df['timestamp'], unit='s')
|
#return render_template('local.html', data=df.to_dict(orient='records'), chart_data=chart_data)
|
||||||
# Suppose your DataFrame is called `df`
|
|
||||||
df["timestamp"] = df["timestamp"].apply(lambda ts: datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S"))
|
|
||||||
|
|
||||||
# Collect the data for charts
|
return render_template('index.html', aggregation=agg, data=df.to_dict(orient='records'), chart_data=chart_data)
|
||||||
chart_data = {
|
|
||||||
"times": df['datetime'].dt.strftime('%Y-%m-%d %H:%M:%S').tolist(),
|
|
||||||
"down_90th": df['down_90th'].tolist(),
|
|
||||||
"up_90th": df['up_90th'].tolist()
|
|
||||||
}
|
|
||||||
|
|
||||||
return render_template('index.html', data=df.to_dict(orient='records'), chart_data=chart_data)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(port=5001)
|
app.run(host='0.0.0.0', port=5001)
|
||||||
|
|||||||
17
prometheus_test.py
Normal file
17
prometheus_test.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
|
||||||
|
|
||||||
|
# Config
|
||||||
|
PUSHGATEWAY_URL = "https://pushgateway.cato447.de"
|
||||||
|
JOB_NAME = "test_push"
|
||||||
|
|
||||||
|
# Create a new registry for the job
|
||||||
|
registry = CollectorRegistry()
|
||||||
|
test_metric = Gauge('test_metric_value', 'Just a test metric', registry=registry)
|
||||||
|
|
||||||
|
# Set the metric value (e.g., random or fixed)
|
||||||
|
test_metric.set(42)
|
||||||
|
|
||||||
|
# Push to the gateway
|
||||||
|
push_to_gateway(PUSHGATEWAY_URL, job=JOB_NAME, registry=registry)
|
||||||
|
print("Pushed test metric to Pushgateway.")
|
||||||
|
|
||||||
@@ -9,4 +9,5 @@ dependencies = [
|
|||||||
"flask>=3.1.0",
|
"flask>=3.1.0",
|
||||||
"getmac>=0.9.5",
|
"getmac>=0.9.5",
|
||||||
"pandas>=2.2.3",
|
"pandas>=2.2.3",
|
||||||
|
"prometheus-client>=0.21.1",
|
||||||
]
|
]
|
||||||
|
|||||||
30
show_data.py
Normal file
30
show_data.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import sqlite3
|
||||||
|
import pandas as pd
|
||||||
|
from config import DB_PATH
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def load_data():
|
||||||
|
# Connect to SQLite database
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
query = "SELECT * FROM speed_tests"
|
||||||
|
df = pd.read_sql(query, conn)
|
||||||
|
conn.close()
|
||||||
|
return df
|
||||||
|
|
||||||
|
df = load_data()
|
||||||
|
|
||||||
|
# Convert timestamps to human-readable format
|
||||||
|
df['datetime'] = pd.to_datetime(df['timestamp'], unit='s')
|
||||||
|
# Suppose your DataFrame is called `df`
|
||||||
|
df["timestamp"] = df["timestamp"].apply(lambda ts: datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
|
||||||
|
# Collect the data for charts
|
||||||
|
chart_data = {
|
||||||
|
"times": df['datetime'].dt.strftime('%Y-%m-%d %H:%M:%S').tolist(),
|
||||||
|
"down_90th": df['down_90th'].tolist(),
|
||||||
|
"up_90th": df['up_90th'].tolist()
|
||||||
|
}
|
||||||
|
|
||||||
|
print(df[-60:])
|
||||||
|
|
||||||
|
|
||||||
20
static/js/chart.js
Normal file
20
static/js/chart.js
Normal file
File diff suppressed because one or more lines are too long
7
static/js/chartjs-plugin-annotation
Normal file
7
static/js/chartjs-plugin-annotation
Normal file
File diff suppressed because one or more lines are too long
@@ -1,40 +1,42 @@
|
|||||||
{% extends 'layout.html' %}
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Internet Speed Dashboard</title>
|
||||||
|
|
||||||
{% block content %}
|
<!-- DataTables CSS -->
|
||||||
<h2>Graph: Download vs. Upload Speeds</h2>
|
<link
|
||||||
<canvas id="speedChart"></canvas>
|
rel="stylesheet"
|
||||||
|
href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css"
|
||||||
|
/>
|
||||||
|
|
||||||
<h2>Test Results</h2>
|
<!-- jQuery + DataTables JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||||
|
|
||||||
<table border="1">
|
<!-- Chart.js core (umd build) -->
|
||||||
<thead>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.umd.min.js"></script>
|
||||||
<tr>
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>ISP</th>
|
|
||||||
<th>Latency</th>
|
|
||||||
<th>Jitter</th>
|
|
||||||
<th>Download (90th Percentile)</th>
|
|
||||||
<th>Upload (90th Percentile)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for row in data %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ row.timestamp }}</td>
|
|
||||||
<td>{{ row.isp }}</td>
|
|
||||||
<td>{{ row.latency }}</td>
|
|
||||||
<td>{{ row.jitter }}</td>
|
|
||||||
<td>{{ row.down_90th }}</td>
|
|
||||||
<td>{{ row.up_90th }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
<!-- date adapter (Moment.js + Chart.js adapter) -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/moment/min/moment.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment/dist/chartjs-adapter-moment.min.js"></script>
|
||||||
|
|
||||||
|
<!-- zoom & pan plugin -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom/dist/chartjs-plugin-zoom.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; margin: 2rem; }
|
||||||
|
#speedChart { max-width: 100%; margin-bottom: 2rem; }
|
||||||
|
table.dataTable { width: 100% !important; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>90th-Percentile Speeds (Aggregated every {{ aggregation }})</h1>
|
||||||
|
|
||||||
|
<!-- Chart -->
|
||||||
|
<canvas id="speedChart" width="800" height="300"></canvas>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
Chart.register(window['chartjs-plugin-annotation']);
|
|
||||||
|
|
||||||
var chartData = {{ chart_data | tojson }};
|
var chartData = {{ chart_data | tojson }};
|
||||||
|
|
||||||
const acceptableDownloadRange = {
|
const acceptableDownloadRange = {
|
||||||
@@ -181,5 +183,36 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
|
||||||
|
<!-- Data table -->
|
||||||
|
<h2>Raw Data</h2>
|
||||||
|
<table id="speedTable" class="display">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Recorded At</th>
|
||||||
|
<th>Download (90th)</th>
|
||||||
|
<th>Upload (90th)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in data %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.datetime }}</td>
|
||||||
|
<td>{{ row.down_90th }}</td>
|
||||||
|
<td>{{ row.up_90th }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#speedTable').DataTable({
|
||||||
|
pageLength: 50,
|
||||||
|
order: [[0, 'desc']]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Speed Test Results</title>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>Network Speed Test Results</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
34
templates/local.html
Normal file
34
templates/local.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Graph: Download vs. Upload Speeds</h2>
|
||||||
|
<canvas id="speedChart"></canvas>
|
||||||
|
|
||||||
|
<h2>Test Results</h2>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>ISP</th>
|
||||||
|
<th>Latency</th>
|
||||||
|
<th>Jitter</th>
|
||||||
|
<th>Download (90th Percentile)</th>
|
||||||
|
<th>Upload (90th Percentile)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in data|reverse %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.timestamp }}</td>
|
||||||
|
<td>{{ row.isp }}</td>
|
||||||
|
<td>{{ row.latency }}</td>
|
||||||
|
<td>{{ row.jitter }}</td>
|
||||||
|
<td>{{ row.down_90th }}</td>
|
||||||
|
<td>{{ row.up_90th }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
11
uv.lock
generated
11
uv.lock
generated
@@ -213,6 +213,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" },
|
{ url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prometheus-client"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551, upload-time = "2024-12-03T14:59:12.164Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682, upload-time = "2024-12-03T14:59:10.935Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.9.0.post0"
|
version = "2.9.0.post0"
|
||||||
@@ -267,6 +276,7 @@ dependencies = [
|
|||||||
{ name = "flask" },
|
{ name = "flask" },
|
||||||
{ name = "getmac" },
|
{ name = "getmac" },
|
||||||
{ name = "pandas" },
|
{ name = "pandas" },
|
||||||
|
{ name = "prometheus-client" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
@@ -275,6 +285,7 @@ requires-dist = [
|
|||||||
{ name = "flask", specifier = ">=3.1.0" },
|
{ name = "flask", specifier = ">=3.1.0" },
|
||||||
{ name = "getmac", specifier = ">=0.9.5" },
|
{ name = "getmac", specifier = ">=0.9.5" },
|
||||||
{ name = "pandas", specifier = ">=2.2.3" },
|
{ name = "pandas", specifier = ">=2.2.3" },
|
||||||
|
{ name = "prometheus-client", specifier = ">=0.21.1" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user