Commit f7b1b7c5 by PLN (Algolia)

perf-tray: publish tray state on change, not on the 2s timer

The xpswatch tray lane measured ~1.9 tray signals/s at idle: refresh()
re-pushed icon (sparkline sample made every pixmap 'new'), tooltip and
menu rows every 2s, each push a NewIcon/PropertiesChanged the shell turns
into StTextureCache growth (the gnome-shell heap). Icon now publishes
only on (temp, gear, trim, badge) change, tooltip on text change with a
10s floor, rows/headers only on change.
parent 954b5d65
...@@ -60,6 +60,9 @@ import rig_health # noqa: E402 (the one health.json gig-log publishes; this on ...@@ -60,6 +60,9 @@ import rig_health # noqa: E402 (the one health.json gig-log publishes; this on
SERVICE = "perf-tray" # systemd --user unit controlling autostart SERVICE = "perf-tray" # systemd --user unit controlling autostart
END_SESSION = Path(__file__).resolve().parent / "tools" / "end_session.py" END_SESSION = Path(__file__).resolve().parent / "tools" / "end_session.py"
REFRESH_MS = 2000 REFRESH_MS = 2000
TIP_MIN_S = 10 # floor between tooltip publishes: the freq readout flaps
# every tick, and each flap is a D-Bus PropertiesChanged
# the appindicator extension makes the shell chew
TRAY_WAIT_S = 120 # how long to wait for the tray host (a GNOME extension) at login TRAY_WAIT_S = 120 # how long to wait for the tray host (a GNOME extension) at login
BRIDGE_URL = "http://127.0.0.1:8773/" BRIDGE_URL = "http://127.0.0.1:8773/"
...@@ -290,6 +293,21 @@ class PerfTray: ...@@ -290,6 +293,21 @@ class PerfTray:
self.timer.start(REFRESH_MS) self.timer.start(REFRESH_MS)
self.refresh() self.refresh()
# Publish-gating state (2026-11-01 tray-churn fix). The xpswatch tray lane
# measured this process at ~1.9 tray signals/s while idling, all of it from
# refresh() re-pushing the icon, tooltip and menu rows every 2 s: the
# sparkline gets a new sample per tick, so the pixmap is always "new", and
# each push is a NewIcon/PropertiesChanged the shell turns into
# StTextureCache growth (the gnome-shell heap, runbooks/
# tray-churn-and-gnome-shell-heap.md). A tray icon must change on a state
# change, not on a timer; the sparkline catches up on the next real one.
self._icon_sig = None # (temp, gear, trim, badge) last published
self._last_tip = None
self._last_tip_t = 0.0
self._stat_txt = [None] * 4
self._health_txt = None
self._headers_txt = (None, None)
# ------------------------------------------------------------- ending a session # ------------------------------------------------------------- ending a session
def _rig_is_up(self): def _rig_is_up(self):
active, _ = sc_state() active, _ = sc_state()
...@@ -939,7 +957,10 @@ class PerfTray: ...@@ -939,7 +957,10 @@ class PerfTray:
gb = GB.state() gb = GB.state()
hs = rig_health.tray_summary(rig_health.read_health()) hs = rig_health.tray_summary(rig_health.read_health())
self.tray.setIcon(make_icon(pkg, self.hist, gb["gear"], gb["trim"], hs["badge"])) sig = (pkg, gb["gear"], gb["trim"], hs["badge"])
if sig != self._icon_sig:
self._icon_sig = sig
self.tray.setIcon(make_icon(pkg, self.hist, sig[1], sig[2], sig[3]))
# Check the gear the FILE says, never one inferred from sysfs. The old # Check the gear the FILE says, never one inferred from sysfs. The old
# menu inferred it (perf.detect_mode reads governor/EPP/max_perf_pct), and # menu inferred it (perf.detect_mode reads governor/EPP/max_perf_pct), and
...@@ -955,12 +976,16 @@ class PerfTray: ...@@ -955,12 +976,16 @@ class PerfTray:
act.setChecked(key == (gb["trim"] or GB.DEFAULT_TRIM)) act.setChecked(key == (gb["trim"] or GB.DEFAULT_TRIM))
act.blockSignals(False) act.blockSignals(False)
if GB.installed(): if GB.installed():
self.gear_header.setText( hdr = (
"⚙ gear: " + (GB.label("gear", gb["gear"]) if gb["gear"] "⚙ gear: " + (GB.label("gear", gb["gear"]) if gb["gear"]
else "none engaged (ppd owns the knobs)")) else "none engaged (ppd owns the knobs)"),
self.trim_header.setText(
"🌡 trim: " + GB.label("trim", gb["trim"] or GB.DEFAULT_TRIM) "🌡 trim: " + GB.label("trim", gb["trim"] or GB.DEFAULT_TRIM)
+ ("" if gb["trim"] else " (default)")) + ("" if gb["trim"] else " (default)"),
)
if hdr != self._headers_txt:
self._headers_txt = hdr
self.gear_header.setText(hdr[0])
self.trim_header.setText(hdr[1])
# Reflect autostart state without re-triggering the toggle handler. # Reflect autostart state without re-triggering the toggle handler.
self.autostart_action.blockSignals(True) self.autostart_action.blockSignals(True)
...@@ -999,7 +1024,13 @@ class PerfTray: ...@@ -999,7 +1024,13 @@ class PerfTray:
) )
if hs["tip"]: if hs["tip"]:
tip += "\n" + "\n".join(hs["tip"]) tip += "\n" + "\n".join(hs["tip"])
self.tray.setToolTip(tip) # The freq readout alone flaps every tick; push the tooltip only when the
# text changed AND the floor elapsed, so a live tick never publishes more
# than one PropertiesChanged per TIP_MIN_S.
if tip != self._last_tip and time.monotonic() - self._last_tip_t >= TIP_MIN_S:
self._last_tip = tip
self._last_tip_t = time.monotonic()
self.tray.setToolTip(tip)
rows = [ rows = [
f"gear: {label} · cap {cap}", f"gear: {label} · cap {cap}",
...@@ -1007,9 +1038,13 @@ class PerfTray: ...@@ -1007,9 +1038,13 @@ class PerfTray:
f"freq {favg}/{fmax} MHz · fans {'/'.join(str(f) for f in fans) or 'n/a'} RPM", f"freq {favg}/{fmax} MHz · fans {'/'.join(str(f) for f in fans) or 'n/a'} RPM",
f"gpu {gpu} · thermald {td} · throttle Δ {t.throttle_delta()}", f"gpu {gpu} · thermald {td} · throttle Δ {t.throttle_delta()}",
] ]
for a, txt in zip(self.stat_actions, rows): for a, txt, old in zip(self.stat_actions, rows, self._stat_txt):
a.setText(txt) if txt != old:
self.health_action.setText(hs["row"]) a.setText(txt)
self._stat_txt = rows
if hs["row"] != self._health_txt:
self._health_txt = hs["row"]
self.health_action.setText(hs["row"])
self.fix_driver_action.setVisible("start_driver" in hs["fixes"]) self.fix_driver_action.setVisible("start_driver" in hs["fixes"])
self.fix_recorder_action.setVisible("start_recorder" in hs["fixes"]) self.fix_recorder_action.setVisible("start_recorder" in hs["fixes"])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment