Fix dashboard freeze: waitress, reduce frame refresh, atomic reads

- Switch Flask dev server -> waitress (8 threads, production WSGI)
- Only refresh frame.jpg when frame_index changes, not every second
- Safety try/except on send_file for edge-case file conflicts
- Fix test_run.sh: export PYTHONPATH, use venv python
- Update service file to use venv python
This commit is contained in:
proitlab committed 2026-07-22 16:14:03 +07:00
1 parent b9f4ba2ef8
commit 6cddf2c9d3
4 files changed
+16 -9

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ After=network.target
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
ExecStart=/media/jetson/DATA/karung-sukawarna/venv/bin/python dashboard.py --port 8080 --db chicken_counts.db
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
+7 -4
View File
@@ -14,7 +14,8 @@ import sqlite3
import threading
from pathlib import Path
from flask import Flask, Response, jsonify, render_template, send_file
from flask import Flask, jsonify, render_template, send_file
from waitress import serve
# Suppress Flask request logs
log = logging.getLogger("werkzeug")
@@ -103,7 +104,10 @@ def shm_frame(camera_id):
frame_path = Path(app.config["shm_dir"]) / f"chicken_counter_{camera_id}" / "frame.jpg"
if not frame_path.exists():
return jsonify({"error": "not found"}), 404
return send_file(frame_path, mimetype="image/jpeg", max_age=0, download_name=None)
try:
return send_file(frame_path, mimetype="image/jpeg", max_age=2, download_name=None)
except (FileNotFoundError, PermissionError):
return jsonify({"error": "not ready"}), 503
def _get_db():
@@ -205,7 +209,6 @@ def main():
parser.add_argument("--poll-ms", type=int, default=500)
parser.add_argument("--date", default="", help="Processing date")
parser.add_argument("--db", default="chicken_counts.db", help="SQLite database path")
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
app.config["shm_dir"] = args.shm_dir
@@ -217,7 +220,7 @@ def main():
_init_db(app.config["db_path"])
print(f"[dashboard] http://0.0.0.0:{args.port} shm={args.shm_dir} db={args.db}" + (f" date={args.date}" if args.date else ""))
app.run(host="0.0.0.0", port=args.port, debug=args.debug, threaded=True)
serve(app, host="0.0.0.0", port=args.port, threads=8)
if __name__ == "__main__":
+5 -2
View File
@@ -121,11 +121,10 @@ function selectCam(id) {
function poll() {
if (!activeCam) return;
var t = Date.now();
var img = document.getElementById("frame-img");
img.src = "/shm/" + activeCam + "/frame.jpg?t=" + t;
fetch("/shm/" + activeCam + "/stats.json?t=" + t).then(r => r.ok ? r.json() : null).then(s => {
if (!s) { setOffline(); return; }
lastUpdate = Date.now();
var oldFrame = lastFramePerCam[activeCam] || 0;
lastFramePerCam[activeCam] = s.frame_index;
perCam[activeCam] = { total: s.total_entered_count, inside: s.inside_box_count, frame: s.frame_index, tracks: s.track_count, speed: s.smoothed_speed };
document.getElementById("s-total").textContent = s.total_entered_count;
@@ -138,6 +137,10 @@ function poll() {
tag.textContent = activeCam + " \u2022 " + status;
tag.className = "tag " + (s.backward_active ? "off" : "live");
document.getElementById("cam-count").textContent = s.total_entered_count;
// only refresh frame when index changes (every 5 frames with interval_frames=5)
if (s.frame_index != oldFrame) {
document.getElementById("frame-img").src = "/shm/" + activeCam + "/frame.jpg?t=" + t;
}
});
}
+3 -2
View File
@@ -1,7 +1,8 @@
#!/bin/bash
set -euo pipefail
CMD="PYTHONPATH=/media/jetson/DATA/.Codes/chicken-counting-sukawarna-det/src /media/jetson/DATA/karung-sukawarna/venv/bin/python -m chicken_counter.cli"
export PYTHONPATH=/media/jetson/DATA/.Codes/chicken-counting-sukawarna-det/src
PYTHON=/media/jetson/DATA/karung-sukawarna/venv/bin/python
dates=(
"2026-06-10" "2026-06-11" "2026-06-12" "2026-06-13" "2026-06-14"
@@ -10,5 +11,5 @@ dates=(
for date in "${dates[@]}"; do
echo "=== Processing $date ==="
$CMD batch --config configs/cycle7_batch.yaml --date "$date" --no-video
$PYTHON -m chicken_counter.cli batch --config configs/cycle7_batch.yaml --date "$date" --no-video
done