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 Type=simple
User=dsutanto User=dsutanto
WorkingDirectory=/media/jetson/DATA/.Codes/chicken-counting-sukawarna-det 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 Restart=always
RestartSec=5 RestartSec=5
Environment=PYTHONUNBUFFERED=1 Environment=PYTHONUNBUFFERED=1
+7 -4
View File
@@ -14,7 +14,8 @@ import sqlite3
import threading import threading
from pathlib import Path 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 # Suppress Flask request logs
log = logging.getLogger("werkzeug") 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" frame_path = Path(app.config["shm_dir"]) / f"chicken_counter_{camera_id}" / "frame.jpg"
if not frame_path.exists(): if not frame_path.exists():
return jsonify({"error": "not found"}), 404 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(): def _get_db():
@@ -205,7 +209,6 @@ def main():
parser.add_argument("--poll-ms", type=int, default=500) parser.add_argument("--poll-ms", type=int, default=500)
parser.add_argument("--date", default="", help="Processing date") parser.add_argument("--date", default="", help="Processing date")
parser.add_argument("--db", default="chicken_counts.db", help="SQLite database path") parser.add_argument("--db", default="chicken_counts.db", help="SQLite database path")
parser.add_argument("--debug", action="store_true")
args = parser.parse_args() args = parser.parse_args()
app.config["shm_dir"] = args.shm_dir app.config["shm_dir"] = args.shm_dir
@@ -217,7 +220,7 @@ def main():
_init_db(app.config["db_path"]) _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 "")) 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__": if __name__ == "__main__":
+5 -2
View File
@@ -121,11 +121,10 @@ function selectCam(id) {
function poll() { function poll() {
if (!activeCam) return; if (!activeCam) return;
var t = Date.now(); 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 => { fetch("/shm/" + activeCam + "/stats.json?t=" + t).then(r => r.ok ? r.json() : null).then(s => {
if (!s) { setOffline(); return; } if (!s) { setOffline(); return; }
lastUpdate = Date.now(); lastUpdate = Date.now();
var oldFrame = lastFramePerCam[activeCam] || 0;
lastFramePerCam[activeCam] = s.frame_index; 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 }; 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; document.getElementById("s-total").textContent = s.total_entered_count;
@@ -138,6 +137,10 @@ function poll() {
tag.textContent = activeCam + " \u2022 " + status; tag.textContent = activeCam + " \u2022 " + status;
tag.className = "tag " + (s.backward_active ? "off" : "live"); tag.className = "tag " + (s.backward_active ? "off" : "live");
document.getElementById("cam-count").textContent = s.total_entered_count; 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 #!/bin/bash
set -euo pipefail 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=( dates=(
"2026-06-10" "2026-06-11" "2026-06-12" "2026-06-13" "2026-06-14" "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 for date in "${dates[@]}"; do
echo "=== Processing $date ===" 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 done