Files
LEDMatrix/systemd/ledmatrix.service
T
ChuckandClaude Opus 5 8927a1b6b1 perf(memory): size the cache to the board and stop reinstalling deps
On a 1GB Pi 3B+ the display process settles around 600MB RSS of 905MB
total. When the remaining headroom runs out the failure is not a clean
crash: fork() starts returning ENOMEM, so sshd accepts connections and
closes them before its banner, timer jobs stop running, and the panel
goes dark, while already-resident processes keep serving normally. The
board looks healthy from outside and cannot be logged into. Only a power
cycle clears it.

Three contributing causes:

- MemoryCache had a fixed 1000-entry ceiling. Entries are parsed API
  payloads of tens of KB, so one ceiling cannot serve both a 512MB Zero
  2 W and an 8GB Pi 5. Now scaled from MemTotal (150 entries at <=1GB,
  1500 at >=8GB), overridable with LEDMATRIX_CACHE_MAX_ENTRIES.

- requirements_are_satisfied() returned False for any requirement with
  extras, so a plugin depending on python-socketio[client] re-ran pip on
  every single start: ~8s, a network dependency, and a 100-200MB spike
  at the least convenient moment. During a restart loop it repeats for
  each restart. Extras are now resolved one level deep against installed
  metadata, keeping the conservative "anything unverifiable falls
  through to pip" contract.

- ledmatrix.service had no memory ceiling. MemoryMax=85% expressed as a
  percentage so one unit file suits every board. Note this needs the
  memory cgroup controller, which Pi firmware disables by default;
  first_time_install.sh now adds cgroup_enable=memory to cmdline.txt,
  and the unit file documents how to verify it took effect.

first_time_install.sh also enables persistent journald storage (capped
at 64M). Default storage is volatile, so every reboot destroys the logs
that would explain why the board rebooted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:13:30 -04:00

41 lines
1.7 KiB
Desktop File

[Unit]
Description=LED Matrix Display Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=__PROJECT_ROOT_DIR__
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 __PROJECT_ROOT_DIR__/run.py
# Restart=always, not on-failure: run.py exiting 0 (a clean shutdown path taken
# for a reason that no longer applies, e.g. a config reload) would otherwise leave
# the service stopped and the panel dark indefinitely, with systemd considering
# that a successful outcome and never bringing it back.
Restart=always
RestartSec=10
# Memory ceiling as a share of physical RAM, so one unit file suits a 512 MB
# Pi Zero 2 W and an 8 GB Pi 5 alike. This is a backstop, not a tuning knob: it
# turns "the board runs out of memory, stops being able to fork, and takes sshd
# and the panel down together until someone pulls the plug" into "this one
# service restarts".
#
# NOTE: Raspberry Pi firmware boots the kernel with cgroup_disable=memory, and
# systemd accepts this setting and then silently ignores it. Verify with:
# grep memory /sys/fs/cgroup/cgroup.controllers
# If that prints nothing, add "cgroup_enable=memory cgroup_memory=1" to
# /boot/firmware/cmdline.txt (all on line 1) and reboot. first_time_install.sh
# does this for you.
MemoryMax=85%
StandardOutput=journal
StandardError=journal
SyslogIdentifier=ledmatrix
# Support for on-demand plugin filtering via environment variable
# The environment variable LEDMATRIX_ON_DEMAND_PLUGIN can be set via:
# sudo systemctl set-environment LEDMATRIX_ON_DEMAND_PLUGIN=<plugin_id>
# Or by using an EnvironmentFile (see below)
# EnvironmentFile=__PROJECT_ROOT_DIR__/config/on_demand_env.conf
[Install]
WantedBy=multi-user.target