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 pandas as pd
|
||||
from config import DB_PATH
|
||||
from datetime import datetime
|
||||
import math
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def load_data():
|
||||
# Connect to SQLite database
|
||||
# 1) grab raw
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
query = "SELECT * FROM speed_tests"
|
||||
df = pd.read_sql(query, conn)
|
||||
df = pd.read_sql("SELECT * FROM speed_tests", conn)
|
||||
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
|
||||
|
||||
|
||||
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('/')
|
||||
def index():
|
||||
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
|
||||
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"))
|
||||
#if math.isnan(chart_data['down_90th'][-1]) or math.isnan(chart_data['up_90th'][-1]):
|
||||
#return render_template('local.html', data=df.to_dict(orient='records'), chart_data=chart_data)
|
||||
|
||||
# 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()
|
||||
}
|
||||
|
||||
return render_template('index.html', data=df.to_dict(orient='records'), chart_data=chart_data)
|
||||
return render_template('index.html', aggregation=agg, data=df.to_dict(orient='records'), chart_data=chart_data)
|
||||
|
||||
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",
|
||||
"getmac>=0.9.5",
|
||||
"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 %}
|
||||
<h2>Graph: Download vs. Upload Speeds</h2>
|
||||
<canvas id="speedChart"></canvas>
|
||||
<!-- DataTables CSS -->
|
||||
<link
|
||||
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">
|
||||
<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 %}
|
||||
<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>
|
||||
<!-- Chart.js core (umd build) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.umd.min.js"></script>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<script>
|
||||
Chart.register(window['chartjs-plugin-annotation']);
|
||||
<!-- 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>
|
||||
var chartData = {{ chart_data | tojson }};
|
||||
|
||||
const acceptableDownloadRange = {
|
||||
@@ -181,5 +183,36 @@
|
||||
}
|
||||
});
|
||||
</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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
@@ -267,6 +276,7 @@ dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "getmac" },
|
||||
{ name = "pandas" },
|
||||
{ name = "prometheus-client" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -275,6 +285,7 @@ requires-dist = [
|
||||
{ name = "flask", specifier = ">=3.1.0" },
|
||||
{ name = "getmac", specifier = ">=0.9.5" },
|
||||
{ name = "pandas", specifier = ">=2.2.3" },
|
||||
{ name = "prometheus-client", specifier = ">=0.21.1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user