mirror of
https://github.com/StefBuwalda/dashboard_test.git
synced 2025-10-30 03:09:59 +00:00
Compare commits
4 Commits
05d9ea1104
...
3b87da9292
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b87da9292 | |||
| 2ab46f1994 | |||
| afd6383daa | |||
| 8a5ff68f23 |
28
app.py
28
app.py
@@ -8,25 +8,32 @@ from pathlib import Path
|
|||||||
from models import service, log
|
from models import service, log
|
||||||
from typing import Any, Optional, cast
|
from typing import Any, Optional, cast
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from config import timeout
|
||||||
|
|
||||||
|
|
||||||
def split_graph(logs: list[log]) -> tuple[list[str], list[Optional[int]]]:
|
# Prepares log data for chart.js chart
|
||||||
if len(logs) <= 0:
|
def prepare_chart_data(
|
||||||
|
logs: list[log],
|
||||||
|
) -> tuple[list[str], list[Optional[int]]]:
|
||||||
|
if len(logs) <= 0: # Return empty if there are no logs
|
||||||
return ([], [])
|
return ([], [])
|
||||||
|
|
||||||
x = [logs[0].dateCreated.isoformat()]
|
x = [logs[0].dateCreatedUTC().isoformat()]
|
||||||
y = [logs[0].ping]
|
y = [logs[0].ping]
|
||||||
|
|
||||||
for i in range(1, len(logs)):
|
for i in range(1, len(logs)):
|
||||||
log1 = logs[i]
|
log1 = logs[i]
|
||||||
log2 = logs[i - 1]
|
log2 = logs[i - 1]
|
||||||
if (abs(log1.dateCreated - log2.dateCreated)) > timedelta(seconds=6):
|
|
||||||
|
|
||||||
x.append(log2.dateCreated.isoformat())
|
# Check if the gap in points exceeds a threshold
|
||||||
|
if (abs(log1.dateCreatedUTC() - log2.dateCreatedUTC())) > timedelta(
|
||||||
|
milliseconds=1.5 * (timeout + 1000)
|
||||||
|
):
|
||||||
|
x.append(log2.dateCreatedUTC().isoformat())
|
||||||
y.append(None)
|
y.append(None)
|
||||||
|
|
||||||
x.append(log1.dateCreated.isoformat())
|
x.append(log1.dateCreatedUTC().isoformat())
|
||||||
y.append(log1.ping)
|
y.append(log1.ping)
|
||||||
return (x, y)
|
return (x, y)
|
||||||
|
|
||||||
@@ -74,12 +81,17 @@ def chart(id: int):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return abort(code=403)
|
return abort(code=403)
|
||||||
x, y = split_graph(logs=logs)
|
x, y = prepare_chart_data(logs=logs)
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
max_ = now
|
||||||
|
min_ = now - timedelta(hours=1)
|
||||||
return render_template(
|
return render_template(
|
||||||
"chart.html",
|
"chart.html",
|
||||||
dates=x,
|
dates=x,
|
||||||
values=json.dumps(y),
|
values=json.dumps(y),
|
||||||
|
min=min_.isoformat(),
|
||||||
|
max=max_.isoformat(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,19 @@
|
|||||||
const data = {
|
const data = {
|
||||||
labels: chartDates,
|
labels: chartDates,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Example Data',
|
label: 'Ping',
|
||||||
data: {{ values }},
|
data: {{ values }},
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
const ctx = document.getElementById('myChart').getContext('2d');
|
const ctx = document.getElementById('myChart').getContext('2d');
|
||||||
|
// Current time in UTC
|
||||||
|
const nowUTC = new Date();
|
||||||
|
|
||||||
|
// One hour ago in UTC
|
||||||
|
const oneDayAgoUTC = new Date(nowUTC.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
const min = "{{ min }}"
|
||||||
|
const max = "{{ max }}"
|
||||||
new Chart(ctx, {
|
new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: data,
|
data: data,
|
||||||
@@ -36,8 +43,14 @@
|
|||||||
x: {
|
x: {
|
||||||
type: 'time', // Important for datetime axis
|
type: 'time', // Important for datetime axis
|
||||||
time: {
|
time: {
|
||||||
unit: 'day'
|
unit: 'hour',
|
||||||
}
|
tooltipFormat: 'HH:mm:ss',
|
||||||
|
displayFormats: {
|
||||||
|
hour: 'HH:mm'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: min,
|
||||||
|
max: max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,13 @@ class log(db.Model):
|
|||||||
"log_id": self.id,
|
"log_id": self.id,
|
||||||
"service_id": self.service_id,
|
"service_id": self.service_id,
|
||||||
"ping": self.ping,
|
"ping": self.ping,
|
||||||
"dateCreated": self.dateCreated,
|
"dateCreated": self.dateCreatedUTC(),
|
||||||
"timeout": self.timeout,
|
"timeout": self.timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def dateCreatedUTC(self) -> datetime:
|
||||||
|
return self.dateCreated.replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
class service(db.Model):
|
class service(db.Model):
|
||||||
id: int = db.Column(db.Integer, primary_key=True) # TODO: Switch to UUID
|
id: int = db.Column(db.Integer, primary_key=True) # TODO: Switch to UUID
|
||||||
|
|||||||
@@ -4,21 +4,31 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
from models import log, service
|
from models import log, service
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from config import timeout as timeout_
|
||||||
|
from typing import Optional
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
|
||||||
async def ping(client: aiohttp.ClientSession, s: service) -> int:
|
async def ping(
|
||||||
|
client: aiohttp.ClientSession,
|
||||||
|
s: service,
|
||||||
|
ctx: Optional[SimpleNamespace] = None,
|
||||||
|
) -> int:
|
||||||
|
ctx = ctx or SimpleNamespace()
|
||||||
match s.ping_method:
|
match s.ping_method:
|
||||||
case 0:
|
case 0:
|
||||||
r = await client.head(
|
r = await client.head(
|
||||||
url=s.url,
|
url=s.url,
|
||||||
ssl=True if s.public_access else False,
|
ssl=True if s.public_access else False,
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
|
trace_request_ctx=ctx, # type: ignore
|
||||||
)
|
)
|
||||||
case 1:
|
case 1:
|
||||||
r = await client.get(
|
r = await client.get(
|
||||||
url=s.url,
|
url=s.url,
|
||||||
ssl=True if s.public_access else False,
|
ssl=True if s.public_access else False,
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
|
trace_request_ctx=ctx, # type: ignore
|
||||||
)
|
)
|
||||||
case _:
|
case _:
|
||||||
raise Exception("UNKNOWN PING METHOD")
|
raise Exception("UNKNOWN PING METHOD")
|
||||||
@@ -27,12 +37,10 @@ async def ping(client: aiohttp.ClientSession, s: service) -> int:
|
|||||||
|
|
||||||
async def check_service(client: aiohttp.ClientSession, s: service) -> log:
|
async def check_service(client: aiohttp.ClientSession, s: service) -> log:
|
||||||
try:
|
try:
|
||||||
# TODO: Use aiohttp latency timing rather than timing it manually
|
ctx = SimpleNamespace()
|
||||||
before = time.perf_counter()
|
status = await ping(client=client, s=s, ctx=ctx)
|
||||||
status = await ping(client=client, s=s)
|
|
||||||
after = time.perf_counter()
|
|
||||||
if status == 200:
|
if status == 200:
|
||||||
return log(service_id=s.id, ping=int((after - before) * 1000))
|
return log(service_id=s.id, ping=int(ctx.duration_ms))
|
||||||
else:
|
else:
|
||||||
return log(service_id=s.id, ping=None)
|
return log(service_id=s.id, ping=None)
|
||||||
except aiohttp.ConnectionTimeoutError:
|
except aiohttp.ConnectionTimeoutError:
|
||||||
@@ -48,16 +56,48 @@ def start_async_loop():
|
|||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
||||||
|
|
||||||
|
def setup_client() -> aiohttp.ClientSession:
|
||||||
|
timeout = aiohttp.client.ClientTimeout(total=timeout_ / 1000)
|
||||||
|
# Each request will get its own context
|
||||||
|
trace_config = aiohttp.TraceConfig()
|
||||||
|
|
||||||
|
async def on_start(
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
context: SimpleNamespace,
|
||||||
|
params: aiohttp.TraceRequestStartParams,
|
||||||
|
):
|
||||||
|
ctx = context.trace_request_ctx
|
||||||
|
ctx.start = time.perf_counter() # store per-request
|
||||||
|
|
||||||
|
async def on_end(
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
context: SimpleNamespace,
|
||||||
|
params: aiohttp.TraceRequestEndParams,
|
||||||
|
):
|
||||||
|
ctx = context.trace_request_ctx
|
||||||
|
ctx.end = time.perf_counter()
|
||||||
|
ctx.duration_ms = int((ctx.end - ctx.start) * 1000)
|
||||||
|
|
||||||
|
trace_config.on_request_start.append(on_start)
|
||||||
|
trace_config.on_request_end.append(on_end)
|
||||||
|
client = aiohttp.ClientSession(
|
||||||
|
timeout=timeout, auto_decompress=False, trace_configs=[trace_config]
|
||||||
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
async def update_services(loop: asyncio.AbstractEventLoop):
|
async def update_services(loop: asyncio.AbstractEventLoop):
|
||||||
print("Starting service updates...")
|
print("Starting service updates...")
|
||||||
# Create new session
|
# Create new session
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
WorkerSession = sessionmaker(bind=db.engine)
|
WorkerSession = sessionmaker(bind=db.engine)
|
||||||
timeout = aiohttp.client.ClientTimeout(total=4)
|
|
||||||
client = aiohttp.ClientSession(timeout=timeout, auto_decompress=False)
|
client = setup_client()
|
||||||
|
|
||||||
|
# Actual update loop
|
||||||
while True:
|
while True:
|
||||||
session = WorkerSession()
|
session = WorkerSession()
|
||||||
sleeptask = asyncio.create_task(asyncio.sleep(5))
|
sleeptask = asyncio.create_task(asyncio.sleep(timeout_ / 1000 + 1))
|
||||||
tasks = [
|
tasks = [
|
||||||
check_service(client=client, s=s)
|
check_service(client=client, s=s)
|
||||||
for s in session.query(service).all()
|
for s in session.query(service).all()
|
||||||
|
|||||||
Reference in New Issue
Block a user