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:
proitlab committed 2026-07-22 14:56:10 +07:00
1 parent 9512fc7f4d
commit dc447264b7
3 files changed
+58 -11

No files matched your search

+15
View File
@@ -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
View File
@@ -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
View File
@@ -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 &#x2022; 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";