forked from zakaria/chicken-counting-sukawarna-det
Reduce dashboard CPU: cache DB, suppress logs, add systemd service
- Suppress Flask request logs (werkzeug ERROR only) - Cache SQLite connection with WAL + 8MB cache - Increase poll_ms 500->1000, history refresh 30s->60s - Show 'waiting for pipeline...' when /dev/shm empty - Add chicken-dashboard.service for systemd auto-start
This commit is contained in:
1 parent
9512fc7f4d
commit
dc447264b7
3 files changed
+58
-11
No files matched your search
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Chicken Counter Dashboard
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=dsutanto
|
||||||
|
WorkingDirectory=/media/jetson/DATA/.Codes/chicken-counting-sukawarna-det
|
||||||
|
ExecStart=/usr/bin/python3 dashboard.py --port 8080 --db chicken_counts.db
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
Environment=PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
+24
-6
@@ -9,20 +9,43 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from flask import Flask, Response, jsonify, render_template, send_file
|
from flask import Flask, Response, jsonify, render_template, send_file
|
||||||
|
|
||||||
|
# Suppress Flask request logs
|
||||||
|
log = logging.getLogger("werkzeug")
|
||||||
|
log.setLevel(logging.ERROR)
|
||||||
|
|
||||||
DEFAULT_SHM_DIR = "/dev/shm"
|
DEFAULT_SHM_DIR = "/dev/shm"
|
||||||
DEFAULT_PORT = 8080
|
DEFAULT_PORT = 8080
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config["shm_dir"] = DEFAULT_SHM_DIR
|
app.config["shm_dir"] = DEFAULT_SHM_DIR
|
||||||
app.config["poll_ms"] = 500
|
app.config["poll_ms"] = 1000
|
||||||
app.config["run_date"] = ""
|
app.config["run_date"] = ""
|
||||||
app.config["db_path"] = ""
|
app.config["db_path"] = ""
|
||||||
|
|
||||||
|
_db_conn = None
|
||||||
|
_db_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_db():
|
||||||
|
global _db_conn
|
||||||
|
db = app.config["db_path"]
|
||||||
|
if not db or not Path(db).exists():
|
||||||
|
return None
|
||||||
|
with _db_lock:
|
||||||
|
if _db_conn is None:
|
||||||
|
_db_conn = sqlite3.connect(db, check_same_thread=False)
|
||||||
|
_db_conn.row_factory = sqlite3.Row
|
||||||
|
_db_conn.execute("PRAGMA journal_mode=WAL")
|
||||||
|
_db_conn.execute("PRAGMA cache_size=-8000")
|
||||||
|
return _db_conn
|
||||||
|
|
||||||
|
|
||||||
def _discover_cameras():
|
def _discover_cameras():
|
||||||
shm = Path(app.config["shm_dir"])
|
shm = Path(app.config["shm_dir"])
|
||||||
@@ -88,7 +111,6 @@ def db_summary():
|
|||||||
ROUND(SUM(elapsed_seconds)/3600.0, 1) AS total_hours
|
ROUND(SUM(elapsed_seconds)/3600.0, 1) AS total_hours
|
||||||
FROM batch_runs
|
FROM batch_runs
|
||||||
""").fetchone()
|
""").fetchone()
|
||||||
conn.close()
|
|
||||||
return jsonify(dict(row))
|
return jsonify(dict(row))
|
||||||
|
|
||||||
|
|
||||||
@@ -107,7 +129,6 @@ def db_history():
|
|||||||
ORDER BY date DESC, location
|
ORDER BY date DESC, location
|
||||||
LIMIT 50
|
LIMIT 50
|
||||||
""").fetchall()
|
""").fetchall()
|
||||||
conn.close()
|
|
||||||
return jsonify([dict(r) for r in rows])
|
return jsonify([dict(r) for r in rows])
|
||||||
|
|
||||||
|
|
||||||
@@ -125,7 +146,6 @@ def db_date(date):
|
|||||||
total = conn.execute(
|
total = conn.execute(
|
||||||
"SELECT SUM(total_entered) AS total, ROUND(SUM(elapsed_seconds)/60.0,1) AS minutes "
|
"SELECT SUM(total_entered) AS total, ROUND(SUM(elapsed_seconds)/60.0,1) AS minutes "
|
||||||
"FROM batch_runs WHERE date=?", (date,)).fetchone()
|
"FROM batch_runs WHERE date=?", (date,)).fetchone()
|
||||||
conn.close()
|
|
||||||
return jsonify({"date": date, "total": dict(total), "cameras": [dict(r) for r in cameras]})
|
return jsonify({"date": date, "total": dict(total), "cameras": [dict(r) for r in cameras]})
|
||||||
|
|
||||||
|
|
||||||
@@ -139,7 +159,6 @@ def db_camera(camera_id):
|
|||||||
ROUND(elapsed_seconds,1) AS elapsed_seconds, stopped_reason
|
ROUND(elapsed_seconds,1) AS elapsed_seconds, stopped_reason
|
||||||
FROM batch_runs WHERE camera_id=? ORDER BY date DESC LIMIT 50
|
FROM batch_runs WHERE camera_id=? ORDER BY date DESC LIMIT 50
|
||||||
""", (camera_id,)).fetchall()
|
""", (camera_id,)).fetchall()
|
||||||
conn.close()
|
|
||||||
return jsonify([dict(r) for r in rows])
|
return jsonify([dict(r) for r in rows])
|
||||||
|
|
||||||
|
|
||||||
@@ -159,7 +178,6 @@ def db_location(location):
|
|||||||
ROUND(SUM(elapsed_seconds)/3600.0,1) AS hours
|
ROUND(SUM(elapsed_seconds)/3600.0,1) AS hours
|
||||||
FROM batch_runs WHERE location=?
|
FROM batch_runs WHERE location=?
|
||||||
""", (location,)).fetchone()
|
""", (location,)).fetchone()
|
||||||
conn.close()
|
|
||||||
return jsonify({"location": location, "summary": dict(summary), "history": [dict(r) for r in history]})
|
return jsonify({"location": location, "summary": dict(summary), "history": [dict(r) for r in history]})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+19
-5
@@ -45,10 +45,12 @@ body{font-family:system-ui,monospace;background:#0f0f14;color:#e0e0e0;overflow:h
|
|||||||
</div>
|
</div>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="frame-area">
|
<div id="frame-area">
|
||||||
<img id="frame-img" src="" alt="live stream">
|
<canvas id="frame-canvas" style="display:none"></canvas>
|
||||||
<div class="overlay">
|
<img id="frame-img" src="" alt="live stream" style="display:none">
|
||||||
<div class="tag live" id="cam-tag">CC1 • RUNNING</div>
|
<div id="waiting-msg" style="color:#555;font-size:16px;text-align:center">waiting for pipeline...</div>
|
||||||
<div class="count" id="cam-count">--</div>
|
<div class="overlay" id="frame-overlay" style="display:none">
|
||||||
|
<div class="tag live" id="cam-tag"></div>
|
||||||
|
<div class="count" id="cam-count"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
@@ -76,7 +78,7 @@ function boot() {
|
|||||||
setInterval(poll, POLL_MS);
|
setInterval(poll, POLL_MS);
|
||||||
setInterval(updateClock, 1000);
|
setInterval(updateClock, 1000);
|
||||||
loadHistory();
|
loadHistory();
|
||||||
setInterval(loadHistory, 30000);
|
setInterval(loadHistory, 60000);
|
||||||
updateClock();
|
updateClock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +88,9 @@ function loadCameras() {
|
|||||||
renderCamList();
|
renderCamList();
|
||||||
if (cameras.length && !activeCam) selectCam(cameras[0]);
|
if (cameras.length && !activeCam) selectCam(cameras[0]);
|
||||||
detectActive();
|
detectActive();
|
||||||
|
if (!cameras.length) showWaiting("no cameras detected");
|
||||||
});
|
});
|
||||||
|
updateSidebar();
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectActive() {
|
function detectActive() {
|
||||||
@@ -107,6 +111,9 @@ function detectActive() {
|
|||||||
|
|
||||||
function selectCam(id) {
|
function selectCam(id) {
|
||||||
activeCam = id;
|
activeCam = id;
|
||||||
|
document.getElementById("waiting-msg").style.display = "none";
|
||||||
|
document.getElementById("frame-overlay").style.display = "block";
|
||||||
|
document.getElementById("frame-img").style.display = "block";
|
||||||
renderCamList();
|
renderCamList();
|
||||||
refreshNow();
|
refreshNow();
|
||||||
}
|
}
|
||||||
@@ -141,6 +148,13 @@ function refreshNow() {
|
|||||||
document.getElementById("frame-img").src = "/shm/" + activeCam + "/frame.jpg?t=" + t;
|
document.getElementById("frame-img").src = "/shm/" + activeCam + "/frame.jpg?t=" + t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showWaiting(msg) {
|
||||||
|
document.getElementById("waiting-msg").textContent = msg;
|
||||||
|
document.getElementById("waiting-msg").style.display = "block";
|
||||||
|
document.getElementById("frame-overlay").style.display = "none";
|
||||||
|
document.getElementById("frame-img").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
function setOffline() {
|
function setOffline() {
|
||||||
document.getElementById("cam-tag").textContent = activeCam + " \u2022 OFFLINE";
|
document.getElementById("cam-tag").textContent = activeCam + " \u2022 OFFLINE";
|
||||||
document.getElementById("cam-tag").className = "tag off";
|
document.getElementById("cam-tag").className = "tag off";
|
||||||
|
|||||||
Reference in new issue
Block a user