터치 패널 정리와 재부팅 원인 기록 보강
This commit is contained in:
1 parent
64510e8fe5
commit
11f27e5347
9 files changed
+488
-358
No files matched your search
+118
-341
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
@@ -27,103 +29,22 @@ NET_GROUPS = {
|
||||
"wifi5": ["wlan1"],
|
||||
}
|
||||
|
||||
PC_IP = "192.168.50.112"
|
||||
PC_IP = "192.168.50.34"
|
||||
PC_MAC = "00:00:00:00:00:d7"
|
||||
PC_DHCP_LEASE_FILE = "/var/lib/misc/dnsmasq.leases"
|
||||
SYNOLOGY_PING_HOST = "chaegeon.com"
|
||||
SYNOLOGY_SSH = [
|
||||
"/usr/bin/ssh",
|
||||
"-i",
|
||||
"/home/seo/.ssh/id_ed25519",
|
||||
"-o",
|
||||
"BatchMode=yes",
|
||||
"-o",
|
||||
"IdentitiesOnly=yes",
|
||||
"-o",
|
||||
"ConnectTimeout=5",
|
||||
"-p",
|
||||
"1026",
|
||||
"seo@chaegeon.com",
|
||||
]
|
||||
SYNOLOGY_STATUS_SCRIPT = r"""
|
||||
health_tick=49
|
||||
while :; do
|
||||
wan="$(awk '$2=="00000000"{print $1; exit}' /proc/net/route)"
|
||||
[ -n "$wan" ] || wan=ovs_eth0
|
||||
echo "WAN_IFACE=$wan"
|
||||
awk -v wan="$wan" '
|
||||
NR > 2 {
|
||||
split($0, row, ":")
|
||||
iface = row[1]
|
||||
gsub(/^[ \t]+|[ \t]+$/, "", iface)
|
||||
data = row[2]
|
||||
gsub(/^[ \t]+/, "", data)
|
||||
split(data, f, /[ \t]+/)
|
||||
}
|
||||
iface == wan {printf "WAN_RX=%s\nWAN_TX=%s\nWAN_RX_ERR=%s\nWAN_TX_ERR=%s\nWAN_RX_DROP=%s\nWAN_TX_DROP=%s\n", f[1], f[9], f[3], f[11], f[4], f[12]}
|
||||
iface == "lo" {printf "LOCAL_RX=%s\nLOCAL_TX=%s\nLOCAL_RX_ERR=%s\nLOCAL_TX_ERR=%s\nLOCAL_RX_DROP=%s\nLOCAL_TX_DROP=%s\n", f[1], f[9], f[3], f[11], f[4], f[12]}
|
||||
' /proc/net/dev
|
||||
awk '
|
||||
$3 == "sata1" {printf "SATA1_READS=%s\nSATA1_WRITES=%s\n", $6, $10}
|
||||
$3 == "sata2" {printf "SATA2_READS=%s\nSATA2_WRITES=%s\n", $6, $10}
|
||||
$3 == "sata4" {printf "SATA4_READS=%s\nSATA4_WRITES=%s\n", $6, $10}
|
||||
' /proc/diskstats
|
||||
awk '
|
||||
/^cpu / {
|
||||
total=0
|
||||
for (i=2; i<=NF; i++) total += $i
|
||||
printf "CPU_TOTAL=%s\nCPU_IDLE=%s\nCPU_USER=%s\nCPU_NICE=%s\nCPU_SYSTEM=%s\nCPU_IOWAIT=%s\nCPU_IRQ=%s\nCPU_SOFTIRQ=%s\n", total, $5 + $6, $2, $3, $4, $6, $7, $8
|
||||
}
|
||||
/^intr / {printf "INTR=%s\n", $2}
|
||||
/^ctxt / {printf "CTXT=%s\n", $2}
|
||||
/^processes / {printf "PROCESSES=%s\n", $2}
|
||||
/^procs_running / {printf "PROCS_RUNNING=%s\n", $2}
|
||||
/^procs_blocked / {printf "PROCS_BLOCKED=%s\n", $2}
|
||||
/^softirq / {printf "SOFTIRQ=%s\n", $2}
|
||||
' /proc/stat
|
||||
awk '
|
||||
$1 == "Tcp:" && tcp_header == 0 {for (i=2; i<=NF; i++) tcp_key[i]=$i; tcp_header=1; next}
|
||||
$1 == "Tcp:" && tcp_header == 1 {for (i=2; i<=NF; i++) tcp[tcp_key[i]]=$i; tcp_header=0; next}
|
||||
$1 == "Udp:" && udp_header == 0 {for (i=2; i<=NF; i++) udp_key[i]=$i; udp_header=1; next}
|
||||
$1 == "Udp:" && udp_header == 1 {for (i=2; i<=NF; i++) udp[udp_key[i]]=$i; udp_header=0; next}
|
||||
END {
|
||||
printf "TCP_IN=%s\nTCP_OUT=%s\nTCP_RETRANS=%s\n", tcp["InSegs"] + 0, tcp["OutSegs"] + 0, tcp["RetransSegs"] + 0
|
||||
printf "UDP_IN=%s\nUDP_OUT=%s\nUDP_ERRORS=%s\n", udp["InDatagrams"] + 0, udp["OutDatagrams"] + 0, udp["InErrors"] + udp["RcvbufErrors"] + udp["SndbufErrors"] + 0
|
||||
}
|
||||
' /proc/net/snmp
|
||||
awk '/^(pgfault|pgmajfault|pgpgin|pgpgout) / {key=toupper($1); printf "%s=%s\n", key, $2}' /proc/vmstat
|
||||
if [ "$health_tick" -ge 49 ]; then
|
||||
cpu="$(cat /sys/class/hwmon/hwmon0/temp1_input 2>/dev/null)"
|
||||
fan="$(cat /sys/class/hwmon/hwmon1/fan2_input 2>/dev/null)"
|
||||
pwm="$(cat /sys/class/hwmon/hwmon1/pwm2 2>/dev/null)"
|
||||
enable="$(cat /sys/class/hwmon/hwmon1/pwm2_enable 2>/dev/null)"
|
||||
guard="$(systemctl is-active synology-fan-guard.service 2>/dev/null || printf unknown)"
|
||||
case "$cpu" in ''|*[!0-9]*) cpu=0 ;; esac
|
||||
case "$fan" in ''|*[!0-9]*) fan=0 ;; esac
|
||||
case "$pwm" in ''|*[!0-9]*) pwm=0 ;; esac
|
||||
case "$enable" in ''|*[!0-9]*) enable=-1 ;; esac
|
||||
awk -v cpu="$cpu" -v fan="$fan" -v pwm="$pwm" -v enable="$enable" -v guard="$guard" 'BEGIN {printf "SYNO_CPU=%.1f\nSYNO_FAN=%d\nSYNO_GUARD=%s\nSYNO_PWM=%d\nSYNO_PWM_ENABLE=%d\n", cpu / 1000, fan, guard, pwm, enable}'
|
||||
health_tick=0
|
||||
else
|
||||
health_tick=$((health_tick + 1))
|
||||
fi
|
||||
echo END=1
|
||||
sleep 0.1
|
||||
done
|
||||
"""
|
||||
SAMPLE_INTERVAL_MS = 100
|
||||
DASHBOARD_INTERVAL_MS = 500
|
||||
FAN_INTERVAL_MS = 500
|
||||
WAN_GRAPH_SECONDS = 10
|
||||
TEMP_GRAPH_SECONDS = 60
|
||||
WAN_GRAPH_POINT_DENSITY = 2
|
||||
WAN_GRAPH_POINTS = WAN_GRAPH_SECONDS * 1000 // SAMPLE_INTERVAL_MS * WAN_GRAPH_POINT_DENSITY
|
||||
TEMP_GRAPH_POINTS = TEMP_GRAPH_SECONDS * 1000 // DASHBOARD_INTERVAL_MS
|
||||
SYNOLOGY_RESTART_INTERVAL_MS = 3000
|
||||
WAN_GRAPH_SECONDS = 20
|
||||
TEMP_GRAPH_SECONDS = 20
|
||||
WAN_GRAPH_POINTS = WAN_GRAPH_SECONDS * 1000 // SAMPLE_INTERVAL_MS
|
||||
TEMP_GRAPH_POINTS = TEMP_GRAPH_SECONDS * 1000 // SAMPLE_INTERVAL_MS
|
||||
TEMP_INTERVAL_MS = 500
|
||||
DISK_TEMP_INTERVAL_MS = TEMP_INTERVAL_MS
|
||||
DISK_TEMP_CACHE_FILE = "/run/control-rpi-disk-temp"
|
||||
DISK_TEMP_CACHE_MAX_AGE_SECONDS = 30
|
||||
RPI_THROTTLE_INTERVAL_MS = SAMPLE_INTERVAL_MS
|
||||
SYSTEM_INTERVAL_MS = 1000
|
||||
|
||||
|
||||
def byte_rate_unit(values):
|
||||
@@ -180,6 +101,35 @@ def cpu_temp():
|
||||
return round(raw / 1000, 1) if raw > 0 else 0.0
|
||||
|
||||
|
||||
def load_average():
|
||||
try:
|
||||
return float(read_text("/proc/loadavg").split()[0])
|
||||
except (IndexError, ValueError):
|
||||
return 0.0
|
||||
|
||||
|
||||
def memory_used_percent():
|
||||
values = {}
|
||||
for line in read_text("/proc/meminfo").splitlines():
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, rest = line.split(":", 1)
|
||||
parts = rest.strip().split()
|
||||
if parts and parts[0].isdigit():
|
||||
values[key] = int(parts[0])
|
||||
total = values.get("MemTotal", 0)
|
||||
available = values.get("MemAvailable", 0)
|
||||
return None if total <= 0 else (total - available) * 100 / total
|
||||
|
||||
|
||||
def disk_used_percent(path):
|
||||
try:
|
||||
usage = shutil.disk_usage(path)
|
||||
except OSError:
|
||||
return None
|
||||
return usage.used * 100 / usage.total if usage.total > 0 else None
|
||||
|
||||
|
||||
def cached_disk_temp():
|
||||
values = {}
|
||||
for line in read_text(DISK_TEMP_CACHE_FILE).splitlines():
|
||||
@@ -353,13 +303,17 @@ def normalize_mac(mac):
|
||||
return ":".join(clean[i:i + 2] for i in range(0, 12, 2)).lower()
|
||||
|
||||
|
||||
def dhcp_mac_for_ip(ip):
|
||||
def dhcp_ip_for_mac(mac):
|
||||
mac = normalize_mac(mac)
|
||||
if not mac:
|
||||
return ""
|
||||
for line in read_text(PC_DHCP_LEASE_FILE).splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 3 and parts[2] == ip:
|
||||
mac = normalize_mac(parts[1])
|
||||
if mac:
|
||||
return mac
|
||||
if len(parts) >= 3 and normalize_mac(parts[1]) == mac:
|
||||
ip = parts[2]
|
||||
octets = ip.split(".")
|
||||
if len(octets) == 4 and all(part.isdigit() and 0 <= int(part) <= 255 for part in octets):
|
||||
return ip
|
||||
return ""
|
||||
|
||||
|
||||
@@ -566,13 +520,13 @@ class PcWakeButton(StatusTile):
|
||||
self.error_until = 0.0
|
||||
self.error_dot.set_steady(False)
|
||||
self.detail.setText("깨우는 중")
|
||||
mac = dhcp_mac_for_ip(PC_IP)
|
||||
if not mac or not send_magic_packet(mac, PC_IP):
|
||||
pc_ip = dhcp_ip_for_mac(PC_MAC) or PC_IP
|
||||
if not send_magic_packet(PC_MAC, pc_ip):
|
||||
self.wait_until = 0.0
|
||||
self.error_until = now + 5
|
||||
self.error_dot.set_steady(True)
|
||||
self.write_dot.set_steady(False)
|
||||
self.detail.setText("MAC 없음" if not mac else "실패")
|
||||
self.detail.setText("실패")
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def tick(self):
|
||||
@@ -687,30 +641,18 @@ class TouchPanel(QMainWindow):
|
||||
self.wan_iface = default_iface()
|
||||
NET_GROUPS["wan"] = [self.wan_iface]
|
||||
self.last_net = {}
|
||||
self.last_synology = {}
|
||||
self.last_synology_net_rate = {}
|
||||
self.last_rpi_activity = {}
|
||||
self.last_synology_activity = {}
|
||||
self.synology_buffer = ""
|
||||
self.synology_values = {}
|
||||
self.synology_cpu_temp = 0.0
|
||||
self.synology_fan_rpm = 0
|
||||
self.synology_guard_values = {}
|
||||
self.last_io = None
|
||||
self.last_sample = None
|
||||
self.last_graph = None
|
||||
self.current_rates = {"wan": {"rx": 0.0, "tx": 0.0}}
|
||||
self.current_synology_rates = {"wan": {"rx": 0.0, "tx": 0.0}}
|
||||
self.disk_temp = None
|
||||
self.latest_temp = 0.0
|
||||
self.latest_rpm = 0
|
||||
self.rpi_temp_history = deque(maxlen=TEMP_GRAPH_POINTS)
|
||||
self.rpi_rpm_history = deque(maxlen=TEMP_GRAPH_POINTS)
|
||||
self.synology_temp_history = deque(maxlen=TEMP_GRAPH_POINTS)
|
||||
self.synology_rpm_history = deque(maxlen=TEMP_GRAPH_POINTS)
|
||||
self.net_history = {
|
||||
"wan": {"rx": deque(maxlen=WAN_GRAPH_POINTS), "tx": deque(maxlen=WAN_GRAPH_POINTS)},
|
||||
"synology_wan": {"rx": deque(maxlen=WAN_GRAPH_POINTS), "tx": deque(maxlen=WAN_GRAPH_POINTS)},
|
||||
}
|
||||
|
||||
self.setWindowTitle("Standalone Touch Records")
|
||||
@@ -736,12 +678,9 @@ class TouchPanel(QMainWindow):
|
||||
self.disk_temp_timer.timeout.connect(self.update_disk_temp)
|
||||
self.disk_temp_timer.start(DISK_TEMP_INTERVAL_MS)
|
||||
|
||||
self.synology_proc = QProcess(self)
|
||||
self.synology_proc.readyReadStandardOutput.connect(self.synology_status_ready)
|
||||
self.synology_proc.finished.connect(self.synology_status_finished)
|
||||
self.synology_timer = QTimer(self)
|
||||
self.synology_timer.timeout.connect(self.update_synology_status)
|
||||
self.synology_timer.start(SYNOLOGY_RESTART_INTERVAL_MS)
|
||||
self.system_timer = QTimer(self)
|
||||
self.system_timer.timeout.connect(self.update_system_status)
|
||||
self.system_timer.start(SYSTEM_INTERVAL_MS)
|
||||
|
||||
self.synology_ping_proc = QProcess(self)
|
||||
self.synology_ping_buffer = ""
|
||||
@@ -765,50 +704,65 @@ class TouchPanel(QMainWindow):
|
||||
self.update_fan_status()
|
||||
self.ping_pc()
|
||||
self.update_disk_temp()
|
||||
self.update_synology_status()
|
||||
self.update_system_status()
|
||||
self.ping_synology()
|
||||
self.update_rpi_throttle()
|
||||
|
||||
def _build_ui(self):
|
||||
root = QWidget()
|
||||
layout = QVBoxLayout(root)
|
||||
layout = QHBoxLayout(root)
|
||||
layout.setContentsMargins(4, 8, 10, 8)
|
||||
layout.setSpacing(6)
|
||||
layout.setSpacing(8)
|
||||
|
||||
panel_layout = QHBoxLayout()
|
||||
panel_layout.setContentsMargins(0, 0, 0, 0)
|
||||
panel_layout.setSpacing(8)
|
||||
|
||||
rpi_rows = [
|
||||
(("io", "SSD I/O"), ("rpi_temp", "TEMP"), ("rpi_fan", "FAN")),
|
||||
status_rows = [
|
||||
(("io", "SSD I/O"), ("synology_ping", "PING"), ("rpi_fan", "FAN")),
|
||||
(("rpi_throttle", "THROT"), ("rpi_cpu_activity", "CPU"), ("rpi_irq", "IRQ")),
|
||||
(("rpi_ctx", "CTX"), ("rpi_fork", "FORK"), ("local", "LOCAL")),
|
||||
(("wan", "UPLINK"), ("lan", "ETH1"), ("wifi5", "5G")),
|
||||
(("wifi24", "2.4G"), ("rpi_softirq", "SOFTIRQ"), ("rpi_tcp", "TCP")),
|
||||
(("rpi_udp", "UDP"), ("rpi_vm", "VM"), ("rpi_runq", "RUNQ")),
|
||||
(("rpi_load", "LOAD"), ("rpi_mem", "MEM"), ("rpi_root", "ROOT")),
|
||||
]
|
||||
synology_rows = [
|
||||
(("synology_ping", "PING"), ("synology_temp", "TEMP"), ("synology_fan", "FAN")),
|
||||
(("synology_guard", "GUARD"), ("synology_cpu_activity", "CPU"), ("synology_irq", "IRQ")),
|
||||
(("synology_ctx", "CTX"), ("synology_fork", "FORK"), ("synology_softirq", "SOFTIRQ")),
|
||||
(("synology_tcp", "TCP"), ("synology_udp", "UDP"), ("synology_vm", "VM")),
|
||||
(("synology_local", "LOCAL"), ("synology_wan", "UPLINK"), ("synology_runq", "RUNQ")),
|
||||
(("synology_sata1", "SATA1 I/O"), ("synology_sata2", "SATA2 I/O"), ("synology_sata4", "SATA4 I/O")),
|
||||
]
|
||||
|
||||
left = self.build_status_panel("RASPBERRY", rpi_rows, fixed_width=398)
|
||||
right = self.build_status_panel("SYNOLOGY", synology_rows)
|
||||
|
||||
panel_layout.addWidget(left)
|
||||
panel_layout.addWidget(right, 1)
|
||||
layout.addLayout(panel_layout, 1)
|
||||
|
||||
self.pc_button = PcWakeButton()
|
||||
self.pc_button.setFixedHeight(48)
|
||||
layout.addWidget(self.pc_button)
|
||||
left = self.build_status_panel(None, status_rows, fixed_width=398, footer_widget=self.pc_button)
|
||||
|
||||
right = QWidget()
|
||||
right_layout = QVBoxLayout(right)
|
||||
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||
right_layout.setSpacing(8)
|
||||
for key, chart in [
|
||||
(
|
||||
"rpi_temp",
|
||||
Sparkline(
|
||||
"온도 / RPM",
|
||||
"#f97316",
|
||||
"#38bdf8",
|
||||
"C",
|
||||
"rpm",
|
||||
max_points=TEMP_GRAPH_POINTS,
|
||||
),
|
||||
),
|
||||
(
|
||||
"rpi_wan",
|
||||
Sparkline(
|
||||
"WAN 트래픽",
|
||||
"#f97316",
|
||||
"#22c55e",
|
||||
auto_byte_unit=True,
|
||||
max_points=WAN_GRAPH_POINTS,
|
||||
),
|
||||
),
|
||||
]:
|
||||
self.charts[key] = chart
|
||||
right_layout.addWidget(chart, 1)
|
||||
|
||||
layout.addWidget(left)
|
||||
layout.addWidget(right, 1)
|
||||
self.setCentralWidget(root)
|
||||
|
||||
def build_status_panel(self, title_text, rows, fixed_width=None, bottom_blank=False):
|
||||
def build_status_panel(self, title_text, rows, fixed_width=None, footer_widget=None):
|
||||
panel = QFrame()
|
||||
panel.setObjectName("statusPanel")
|
||||
if fixed_width:
|
||||
@@ -817,9 +771,10 @@ class TouchPanel(QMainWindow):
|
||||
panel_layout.setContentsMargins(6, 8, 6, 8)
|
||||
panel_layout.setSpacing(6)
|
||||
|
||||
title = QLabel(title_text)
|
||||
title.setObjectName("sectionTitle")
|
||||
panel_layout.addWidget(title)
|
||||
if title_text:
|
||||
title = QLabel(title_text)
|
||||
title.setObjectName("sectionTitle")
|
||||
panel_layout.addWidget(title)
|
||||
|
||||
grid = QGridLayout()
|
||||
grid.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -831,11 +786,8 @@ class TouchPanel(QMainWindow):
|
||||
panel_layout.addLayout(grid)
|
||||
panel_layout.addStretch(1)
|
||||
|
||||
if bottom_blank:
|
||||
blank = QWidget()
|
||||
blank.setFixedHeight(48)
|
||||
blank.setObjectName("statusEmpty")
|
||||
panel_layout.addWidget(blank)
|
||||
if footer_widget is not None:
|
||||
panel_layout.addWidget(footer_widget)
|
||||
|
||||
return panel
|
||||
|
||||
@@ -891,7 +843,7 @@ class TouchPanel(QMainWindow):
|
||||
self.last_net[key] = current
|
||||
|
||||
self.current_rates = rates
|
||||
self.update_wan_graph()
|
||||
self.update_graphs()
|
||||
self.render_status(groups, rates, activity, io_read_activity, io_write_activity, disk_error)
|
||||
self.render_rpi_activity()
|
||||
|
||||
@@ -1007,7 +959,8 @@ class TouchPanel(QMainWindow):
|
||||
def ping_pc(self):
|
||||
if self.pc_ping.state() != QProcess.NotRunning:
|
||||
return
|
||||
self.pc_ping.start("/usr/bin/ping", ["-c", "1", "-W", "1", PC_IP])
|
||||
pc_ip = dhcp_ip_for_mac(PC_MAC) or PC_IP
|
||||
self.pc_ping.start("/usr/bin/ping", ["-c", "1", "-W", "1", pc_ip])
|
||||
|
||||
def pc_ping_finished(self, exit_code, exit_status):
|
||||
del exit_status
|
||||
@@ -1093,153 +1046,22 @@ class TouchPanel(QMainWindow):
|
||||
def update_disk_temp(self):
|
||||
self.latest_temp = cpu_temp()
|
||||
self.disk_temp = cached_disk_temp()
|
||||
self.render_rpi_temp(self.latest_temp)
|
||||
|
||||
def update_synology_status(self):
|
||||
if self.synology_proc.state() != QProcess.NotRunning:
|
||||
return
|
||||
self.synology_buffer = ""
|
||||
self.synology_values = {}
|
||||
self.synology_proc.start(SYNOLOGY_SSH[0], SYNOLOGY_SSH[1:] + [SYNOLOGY_STATUS_SCRIPT])
|
||||
|
||||
def apply_synology_health(self, values):
|
||||
health_keys = ["SYNO_CPU", "SYNO_FAN", "SYNO_GUARD", "SYNO_PWM", "SYNO_PWM_ENABLE"]
|
||||
if not any(key in values for key in health_keys):
|
||||
return
|
||||
if "SYNO_CPU" in values:
|
||||
try:
|
||||
self.synology_cpu_temp = float(values.get("SYNO_CPU", "0") or 0)
|
||||
except ValueError:
|
||||
self.synology_cpu_temp = 0.0
|
||||
if "SYNO_FAN" in values:
|
||||
try:
|
||||
self.synology_fan_rpm = int(values.get("SYNO_FAN", "0") or 0)
|
||||
except ValueError:
|
||||
self.synology_fan_rpm = 0
|
||||
self.synology_guard_values.update({key: values[key] for key in health_keys if key in values})
|
||||
self.render_synology_temp()
|
||||
self.render_synology_fan()
|
||||
self.render_synology_guard(self.synology_guard_values)
|
||||
|
||||
def synology_status_ready(self):
|
||||
output = bytes(self.synology_proc.readAllStandardOutput()).decode("utf-8", "ignore")
|
||||
self.synology_buffer += output
|
||||
while "\n" in self.synology_buffer:
|
||||
line, self.synology_buffer = self.synology_buffer.split("\n", 1)
|
||||
if line == "END=1":
|
||||
self.render_synology_status(self.synology_values)
|
||||
self.synology_values = {}
|
||||
continue
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
self.synology_values[key.strip()] = value.strip()
|
||||
|
||||
def synology_status_finished(self, exit_code, exit_status):
|
||||
del exit_code, exit_status
|
||||
self.set_synology_offline()
|
||||
|
||||
def set_synology_offline(self):
|
||||
for key in [
|
||||
"synology_sata1",
|
||||
"synology_sata2",
|
||||
"synology_sata4",
|
||||
"synology_wan",
|
||||
"synology_local",
|
||||
"synology_guard",
|
||||
"synology_cpu_activity",
|
||||
"synology_irq",
|
||||
"synology_ctx",
|
||||
"synology_fork",
|
||||
"synology_softirq",
|
||||
"synology_tcp",
|
||||
"synology_udp",
|
||||
"synology_vm",
|
||||
"synology_runq",
|
||||
]:
|
||||
self.rows[key].set_state("-", read_steady=False, write_steady=False, error_steady=True)
|
||||
|
||||
def render_synology_status(self, values):
|
||||
if not values:
|
||||
self.set_synology_offline()
|
||||
return
|
||||
self.apply_synology_health(values)
|
||||
for disk in ["SATA1", "SATA2", "SATA4"]:
|
||||
self.set_synology_io_tile(
|
||||
f"synology_{disk.lower()}",
|
||||
int(values.get(f"{disk}_READS", "0") or 0),
|
||||
int(values.get(f"{disk}_WRITES", "0") or 0),
|
||||
self.synology_disk_temp_detail(disk),
|
||||
)
|
||||
self.set_synology_net_tile(
|
||||
"synology_wan",
|
||||
int(values.get("WAN_RX", "0") or 0),
|
||||
int(values.get("WAN_TX", "0") or 0),
|
||||
int(values.get("WAN_RX_ERR", "0") or 0) + int(values.get("WAN_TX_ERR", "0") or 0),
|
||||
int(values.get("WAN_RX_DROP", "0") or 0) + int(values.get("WAN_TX_DROP", "0") or 0),
|
||||
values.get("WAN_IFACE", "wan"),
|
||||
)
|
||||
self.set_synology_net_tile(
|
||||
"synology_local",
|
||||
int(values.get("LOCAL_RX", "0") or 0),
|
||||
int(values.get("LOCAL_TX", "0") or 0),
|
||||
int(values.get("LOCAL_RX_ERR", "0") or 0) + int(values.get("LOCAL_TX_ERR", "0") or 0),
|
||||
int(values.get("LOCAL_RX_DROP", "0") or 0) + int(values.get("LOCAL_TX_DROP", "0") or 0),
|
||||
"lo",
|
||||
)
|
||||
self.render_activity_tiles("synology", values, "last_synology_activity")
|
||||
|
||||
def set_synology_io_tile(self, key, reads, writes, detail):
|
||||
previous = self.last_synology.get(key)
|
||||
self.last_synology[key] = (reads, writes, 0, 0)
|
||||
read_activity = previous is not None and reads > previous[0]
|
||||
write_activity = previous is not None and writes > previous[1]
|
||||
self.rows[key].set_state(detail, read_activity=read_activity, write_activity=write_activity, error_steady=False)
|
||||
|
||||
def render_rpi_temp(self, pi_temp):
|
||||
pi_disk = self.disk_temp or 0.0
|
||||
hottest = max(float(pi_temp), pi_disk)
|
||||
detail = f"{hottest:.0f} C" if hottest > 0 else "- C"
|
||||
self.rows["rpi_temp"].set_state(
|
||||
detail,
|
||||
read_steady=0 < hottest < 60,
|
||||
write_steady=60 <= hottest < 80,
|
||||
error_steady=False,
|
||||
error_blink=hottest >= 80,
|
||||
def update_system_status(self):
|
||||
cores = max(1, os.cpu_count() or 1)
|
||||
load = load_average()
|
||||
self.rows["rpi_load"].set_state(
|
||||
f"{load:.2f}",
|
||||
read_steady=load < cores * 0.7,
|
||||
write_steady=cores * 0.7 <= load < cores * 1.2,
|
||||
error_steady=load >= cores * 1.2,
|
||||
)
|
||||
self.set_percent_tile("rpi_mem", memory_used_percent(), warn=80, critical=92)
|
||||
self.set_percent_tile("rpi_root", disk_used_percent("/"), warn=80, critical=92)
|
||||
|
||||
def render_rpi_fan(self, rpm):
|
||||
self.render_fan_tile("rpi_fan", rpm)
|
||||
|
||||
def render_synology_temp(self):
|
||||
temp = self.synology_cpu_temp
|
||||
detail = f"{temp:.0f} C" if temp > 0 else "- C"
|
||||
self.rows["synology_temp"].set_state(
|
||||
detail,
|
||||
read_steady=0 < temp < 60,
|
||||
write_steady=60 <= temp < 80,
|
||||
error_steady=False,
|
||||
error_blink=temp >= 80,
|
||||
)
|
||||
|
||||
def render_synology_fan(self):
|
||||
self.render_fan_tile("synology_fan", self.synology_fan_rpm)
|
||||
|
||||
def render_synology_guard(self, values):
|
||||
guard = values.get("SYNO_GUARD", "unknown")
|
||||
try:
|
||||
pwm = int(values.get("SYNO_PWM", "0") or 0)
|
||||
enable = int(values.get("SYNO_PWM_ENABLE", "-1") or -1)
|
||||
except ValueError:
|
||||
pwm = 0
|
||||
enable = -1
|
||||
if guard != "active" or self.synology_fan_rpm <= 0:
|
||||
self.rows["synology_guard"].set_state("FAIL", read_steady=False, write_steady=False, error_steady=True)
|
||||
elif pwm >= 255 or enable != 5:
|
||||
self.rows["synology_guard"].set_state("FULL", read_steady=False, write_steady=True, error_steady=False)
|
||||
else:
|
||||
self.rows["synology_guard"].set_state("AUTO", read_steady=True, write_steady=False, error_steady=False)
|
||||
|
||||
def render_fan_tile(self, key, rpm):
|
||||
detail = f"{int(rpm):,} rpm" if rpm > 0 else "0 rpm"
|
||||
self.rows[key].set_state(
|
||||
@@ -1249,39 +1071,9 @@ class TouchPanel(QMainWindow):
|
||||
error_steady=rpm == 0 or rpm > 7000,
|
||||
)
|
||||
|
||||
def synology_disk_temp_detail(self, disk):
|
||||
del disk
|
||||
return "조회 중지"
|
||||
|
||||
def set_synology_net_tile(self, key, rx, tx, errors, drops, detail):
|
||||
previous = self.last_synology.get(key)
|
||||
self.last_synology[key] = (rx, tx, errors, drops)
|
||||
read_activity = previous is not None and tx > previous[1]
|
||||
write_activity = previous is not None and rx > previous[0]
|
||||
error_activity = previous is not None and (errors > previous[2] or drops > previous[3])
|
||||
self.rows[key].set_state(self.drop_detail(drops, detail), error_activity, read_activity, write_activity)
|
||||
if key == "synology_wan":
|
||||
self.update_synology_wan_rate(rx, tx)
|
||||
|
||||
def update_synology_wan_rate(self, rx, tx):
|
||||
now = time.monotonic()
|
||||
previous = self.last_synology_net_rate.get("wan")
|
||||
if previous is not None:
|
||||
prev_rx, prev_tx, prev_time = previous
|
||||
dt = max(0.001, now - prev_time)
|
||||
self.current_synology_rates["wan"] = {
|
||||
"rx": max(0.0, (rx - prev_rx) / dt),
|
||||
"tx": max(0.0, (tx - prev_tx) / dt),
|
||||
}
|
||||
self.last_synology_net_rate["wan"] = (rx, tx, now)
|
||||
self.update_synology_wan_graph()
|
||||
|
||||
def update_temp_graphs(self, rpi_temp, rpi_rpm):
|
||||
if "rpi_temp" not in self.charts or "synology_temp" not in self.charts:
|
||||
if "rpi_temp" not in self.charts:
|
||||
return
|
||||
synology_temp = getattr(self, "synology_cpu_temp", 0.0)
|
||||
synology_rpm = getattr(self, "synology_fan_rpm", 0)
|
||||
|
||||
self.rpi_temp_history.append(rpi_temp)
|
||||
self.rpi_rpm_history.append(rpi_rpm)
|
||||
self.charts["rpi_temp"].set_values(
|
||||
@@ -1290,13 +1082,10 @@ class TouchPanel(QMainWindow):
|
||||
self.rpi_rpm_history,
|
||||
)
|
||||
|
||||
self.synology_temp_history.append(synology_temp)
|
||||
self.synology_rpm_history.append(synology_rpm)
|
||||
self.charts["synology_temp"].set_values(
|
||||
self.synology_temp_history,
|
||||
format_temp_rpm_caption(synology_temp, synology_rpm),
|
||||
self.synology_rpm_history,
|
||||
)
|
||||
def update_graphs(self):
|
||||
hottest = max(float(self.latest_temp), self.disk_temp or 0.0)
|
||||
self.update_temp_graphs(hottest, self.latest_rpm)
|
||||
self.update_wan_graph()
|
||||
|
||||
def update_wan_graph(self):
|
||||
if "rpi_wan" not in self.charts:
|
||||
@@ -1310,18 +1099,6 @@ class TouchPanel(QMainWindow):
|
||||
self.net_history["wan"]["tx"],
|
||||
)
|
||||
|
||||
def update_synology_wan_graph(self):
|
||||
if "synology_wan" not in self.charts:
|
||||
return
|
||||
wan = self.current_synology_rates.get("wan", {"rx": 0.0, "tx": 0.0})
|
||||
self.net_history["synology_wan"]["rx"].append(wan["rx"])
|
||||
self.net_history["synology_wan"]["tx"].append(wan["tx"])
|
||||
self.charts["synology_wan"].set_values(
|
||||
self.net_history["synology_wan"]["rx"],
|
||||
format_wan_caption(wan["rx"], wan["tx"]),
|
||||
self.net_history["synology_wan"]["tx"],
|
||||
)
|
||||
|
||||
def tick_pulses(self):
|
||||
for row in self.rows.values():
|
||||
row.tick()
|
||||
|
||||
Reference in new issue
Block a user