mirror of
https://github.com/StefBuwalda/dashboard_test.git
synced 2025-10-29 10:49: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 typing import Any, Optional, cast
|
||||
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]]]:
|
||||
if len(logs) <= 0:
|
||||
# Prepares log data for chart.js chart
|
||||
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 ([], [])
|
||||
|
||||
x = [logs[0].dateCreated.isoformat()]
|
||||
x = [logs[0].dateCreatedUTC().isoformat()]
|
||||
y = [logs[0].ping]
|
||||
|
||||
for i in range(1, len(logs)):
|
||||
log1 = logs[i]
|
||||
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)
|
||||
|
||||
x.append(log1.dateCreated.isoformat())
|
||||
x.append(log1.dateCreatedUTC().isoformat())
|
||||
y.append(log1.ping)
|
||||
return (x, y)
|
||||
|
||||
@@ -74,12 +81,17 @@ def chart(id: int):
|
||||
)
|
||||
else:
|
||||
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(
|
||||
"chart.html",
|
||||
dates=x,
|
||||
values=json.dumps(y),
|
||||
min=min_.isoformat(),
|
||||
max=max_.isoformat(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -22,12 +22,19 @@
|
||||
const data = {
|
||||
labels: chartDates,
|
||||
datasets: [{
|
||||
label: 'Example Data',
|
||||
label: 'Ping',
|
||||
data: {{ values }},
|
||||
}]
|
||||
};
|
||||
|
||||
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, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
@@ -36,8 +43,14 @@
|
||||
x: {
|
||||
type: 'time', // Important for datetime axis
|
||||
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,
|
||||
"service_id": self.service_id,
|
||||
"ping": self.ping,
|
||||
"dateCreated": self.dateCreated,
|
||||
"dateCreated": self.dateCreatedUTC(),
|
||||
"timeout": self.timeout,
|
||||
}
|
||||
|
||||
def dateCreatedUTC(self) -> datetime:
|
||||
return self.dateCreated.replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
class service(db.Model):
|
||||
id: int = db.Column(db.Integer, primary_key=True) # TODO: Switch to UUID
|
||||
|
||||
@@ -4,21 +4,31 @@ import asyncio
|
||||
import time
|
||||
from models import log, service
|
||||
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:
|
||||
case 0:
|
||||
r = await client.head(
|
||||
url=s.url,
|
||||
ssl=True if s.public_access else False,
|
||||
allow_redirects=True,
|
||||
trace_request_ctx=ctx, # type: ignore
|
||||
)
|
||||
case 1:
|
||||
r = await client.get(
|
||||
url=s.url,
|
||||
ssl=True if s.public_access else False,
|
||||
allow_redirects=True,
|
||||
trace_request_ctx=ctx, # type: ignore
|
||||
)
|
||||
case _:
|
||||
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:
|
||||
try:
|
||||
# TODO: Use aiohttp latency timing rather than timing it manually
|
||||
before = time.perf_counter()
|
||||
status = await ping(client=client, s=s)
|
||||
after = time.perf_counter()
|
||||
ctx = SimpleNamespace()
|
||||
status = await ping(client=client, s=s, ctx=ctx)
|
||||
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:
|
||||
return log(service_id=s.id, ping=None)
|
||||
except aiohttp.ConnectionTimeoutError:
|
||||
@@ -48,16 +56,48 @@ def start_async_loop():
|
||||
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):
|
||||
print("Starting service updates...")
|
||||
# Create new session
|
||||
with app.app_context():
|
||||
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:
|
||||
session = WorkerSession()
|
||||
sleeptask = asyncio.create_task(asyncio.sleep(5))
|
||||
sleeptask = asyncio.create_task(asyncio.sleep(timeout_ / 1000 + 1))
|
||||
tasks = [
|
||||
check_service(client=client, s=s)
|
||||
for s in session.query(service).all()
|
||||
|
||||
Reference in New Issue
Block a user