Commit 74d8341c authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: elphel-perfd - CPU+GPU performance daemon, no sudo in the control path

Design ruled by Andrey 07/17/2026 (lives in imagej-elphel: the capability
follows the hardware the project runs on - 224/137; other programs use the
same localhost socket/CLI):
- perfd/elphel_perfd.py: root systemd service; line protocol on
  127.0.0.1:48890 (PING/STATUS/HOLD measure|max/RELEASE); a hold is TIED TO
  THE CONNECTION (client crash = auto-release), refcounted, strongest
  profile wins. 'measure' = CPU performance (power-profiles-daemon hold,
  same call as the old cpu_perf_hold.py it supersedes) + GPU persistence +
  clocks LOCKED (default 2550 MHz; 5060 Ti burst boost measured ~2595 @
  23 W/49 C - the lock is about run-to-run +-1% reproducibility for
  per-item margin evaluation, not speed). 'max' = performance, unlocked.
  ExecStopPost=nvidia-smi -rgc so a dead daemon never leaves clocks locked.
- perfd/install.sh (one-time sudo, idempotent) + perfctl (unprivileged CLI:
  'perfctl hold measure -- <cmd>') + README.
- Java: com.elphel.imagej.common.PerfDaemon (plain TCP, Java-8, zero deps;
  Hold implements AutoCloseable); Eyesis_Correction startup probe (one
  console line - uses the daemon when installed, prints the sudo install
  command when not); CuasPoseRT preloaded RT harness holds 'measure' for
  the timed run, releases after the RT summary.
Tested end-to-end user-mode (daemon on a test port): protocol, Java client,
CPU hold engaged, GPU lock correctly permission-refused as non-root,
auto-release on disconnect. GPU lock path activates once installed under
root. mvn package + test PASS.
Co-authored-by: 's avatarClaude Opus 4.8 <claude-opus-4-8@anthropic.com>
Co-authored-by: 's avatarClaude Fable 5 <claude-fable-5@anthropic.com>
parent a30784a5
# elphel-perfd — CPU+GPU performance management without sudo
Lets Eyesis/imagej-elphel (and any other local program) request deterministic
or maximum-performance clock states with **no sudo in the control path**.
Design ruled by Andrey 07/17/2026: the capability ships with imagej-elphel
because it follows the hardware the project runs on (224/137), while staying
usable by any program through the same local socket.
## Install (once per machine, the only sudo ever needed)
sudo <imagej-elphel>/perfd/install.sh
Installs `/usr/local/lib/elphel-perfd/elphel_perfd.py`, the systemd unit
`elphel-perfd.service` (enabled, running as root), and the unprivileged
client `/usr/local/bin/perfctl`. Idempotent — re-run to update.
Eyesis checks for the daemon at startup: uses it when present, prints the
install command above when absent (and continues normally).
## Use
perfctl status # clocks/holds one-liner
perfctl hold measure -- <command> # deterministic clocks for the command
perfctl hold max # production profile until Ctrl-C
Profiles:
- `measure` — CPU `performance` + GPU persistence + clocks **locked**
(default 2550 MHz, `PERFD_GPU_LOCK_MHZ` in the unit): run-to-run timing
reproducibility (±1% instead of ±10% with free-running boost) for per-item
performance evaluation.
- `max` — CPU `performance` + GPU persistence, clocks unlocked: throughput.
A hold is tied to the client's connection — process exit or crash releases
it automatically (refcounted across concurrent holders; `measure` outranks
`max`). Daemon stop resets GPU clocks (`ExecStopPost=nvidia-smi -rgc`).
Protocol (127.0.0.1:48890, line-based): `PING`, `STATUS`, `HOLD measure|max`,
`RELEASE`, `QUIT`. Java client: `com.elphel.imagej.common.PerfDaemon`.
Supersedes `attic/elphel-agent-tools/bin/cpu_perf_hold.py` (CPU-only,
process-watch based) once installed.
# elphel-perfd - CPU+GPU performance-management daemon (see elphel_perfd.py).
# Installed by perfd/install.sh from the imagej-elphel repository.
# By Claude on 07/17/2026, design ruled by Andrey same day.
[Unit]
Description=Elphel performance-management daemon (CPU + GPU clock holds without sudo)
After=network.target nvidia-persistenced.service
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/lib/elphel-perfd/elphel_perfd.py
# Config overrides (defaults live in the script):
#Environment=PERFD_PORT=48890
#Environment=PERFD_GPU_LOCK_MHZ=2550
#Environment=PERFD_GPU_INDEX=0
Restart=on-failure
RestartSec=2
# Safety: never leave GPU clocks locked after the daemon is gone
ExecStopPost=/usr/bin/nvidia-smi -rgc
# Hardening (root is needed for nvidia-smi clock control; contain the rest)
NoNewPrivileges=yes
ProtectHome=yes
PrivateTmp=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
[Install]
WantedBy=multi-user.target
This diff is collapsed.
#!/usr/bin/env bash
# elphel-perfd installer - one-time, requires sudo; after this NO program
# ever needs sudo for clock control (they talk to 127.0.0.1:48890).
# Idempotent: re-running updates the script/unit and restarts the service.
# Usage: sudo ./install.sh (from the imagej-elphel/perfd directory)
# By Claude on 07/17/2026.
set -eu
cd "$(dirname "$0")"
if [ "$(id -u)" != 0 ]; then
echo "elphel-perfd install needs root: sudo $0" >&2
exit 1
fi
echo "== installing /usr/local/lib/elphel-perfd/elphel_perfd.py"
install -D -m 0755 elphel_perfd.py /usr/local/lib/elphel-perfd/elphel_perfd.py
echo "== installing /etc/systemd/system/elphel-perfd.service"
install -m 0644 elphel-perfd.service /etc/systemd/system/elphel-perfd.service
echo "== installing /usr/local/bin/perfctl (unprivileged client)"
install -m 0755 perfctl /usr/local/bin/perfctl
echo "== systemctl daemon-reload + enable --now"
systemctl daemon-reload
systemctl enable --now elphel-perfd.service
systemctl restart elphel-perfd.service
sleep 1
echo "== status probe (unprivileged from now on):"
perfctl status || { echo "PROBE FAILED - check: journalctl -u elphel-perfd" >&2; exit 1; }
echo "== elphel-perfd installed. Try: perfctl hold measure -- <command>"
#!/usr/bin/env python3
"""
perfctl - unprivileged client for elphel-perfd (127.0.0.1:48890).
perfctl status one-line daemon/clock status
perfctl ping probe (exit 0 = daemon installed)
perfctl hold <measure|max> hold until Ctrl-C
perfctl hold <measure|max> -- CMD [ARGS...]
hold for the duration of CMD
The hold is tied to this process's connection: if perfctl dies, the daemon
releases automatically. By Claude on 07/17/2026.
"""
import os
import signal
import socket
import subprocess
import sys
PORT = int(os.environ.get("PERFD_PORT", "48890"))
def connect():
s = socket.create_connection(("127.0.0.1", PORT), timeout=2.0)
return s, s.makefile("rw", encoding="utf-8")
def ask(f, line):
f.write(line + "\n")
f.flush()
return f.readline().strip()
def main():
args = sys.argv[1:]
if not args or args[0] not in ("status", "ping", "hold"):
print(__doc__.strip())
return 2
try:
s, f = connect()
except OSError as e:
print("perfctl: elphel-perfd not reachable on 127.0.0.1:%d (%s)" % (PORT, e))
print("perfctl: install once with: sudo <imagej-elphel>/perfd/install.sh")
return 3
if args[0] == "ping":
print(ask(f, "PING"))
return 0
if args[0] == "status":
print(ask(f, "STATUS"))
return 0
# hold
if len(args) < 2 or args[1] not in ("measure", "max"):
print("perfctl: hold needs a profile: measure | max")
return 2
reply = ask(f, "HOLD " + args[1])
print("perfctl: " + reply)
if not reply.startswith("OK"):
return 1
if len(args) > 2 and args[2] == "--":
rc = subprocess.call(args[3:])
ask(f, "RELEASE")
return rc
print("perfctl: holding '%s' until Ctrl-C ..." % args[1])
try:
signal.pause()
except KeyboardInterrupt:
pass
ask(f, "RELEASE")
print("perfctl: released")
return 0
if __name__ == "__main__":
sys.exit(main())
/**
** PerfDaemon.java - unprivileged client for the elphel-perfd performance-
** management daemon (CPU + GPU clock holds without sudo; see perfd/README.md).
**
** Design ruled by Andrey 07/17/2026: the daemon ships with the imagej-elphel
** codebase (the capability follows the hardware the project runs on), Eyesis
** checks for it at startup - uses it when installed, suggests the one-time
** "sudo .../perfd/install.sh" when not.
**
** Copyright (C) 2026 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** PerfDaemon.java is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
** -----------------------------------------------------------------------------**
*/
package com.elphel.imagej.common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* Plain-TCP (127.0.0.1:48890, Java-8 compatible, zero dependencies) client
* for elphel-perfd. A hold is tied to the open socket: closing it (or the
* JVM exiting for any reason) releases the hold in the daemon - crash-safe
* by construction. By Claude on 07/17/2026.
*/
public class PerfDaemon {
public static final String HOST = "127.0.0.1";
public static final int PORT = Integer.parseInt(
System.getProperty("elphel.perfd.port", "48890"));
private static final int CONNECT_TIMEOUT_MS = 300;
private static boolean startup_reported = false;
/** A live profile hold; close() (or JVM exit) releases it. */
public static class Hold implements AutoCloseable {
private final Socket socket;
private final Writer out;
private final String profile;
private Hold(Socket socket, Writer out, String profile) {
this.socket = socket;
this.out = out;
this.profile = profile;
}
public String getProfile() { return profile; }
@Override public void close() {
try {
out.write("RELEASE\n");
out.flush();
} catch (IOException e) { /* daemon releases on disconnect anyway */ }
try { socket.close(); } catch (IOException e) { }
}
}
private static Socket connect() throws IOException {
final Socket s = new Socket();
s.connect(new InetSocketAddress(HOST, PORT), CONNECT_TIMEOUT_MS);
s.setSoTimeout(2000);
return s;
}
/** One-shot command on a throwaway connection; null if the daemon is absent. */
private static String query(String command) {
try (Socket s = connect()) {
final Writer w = new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8);
final BufferedReader r = new BufferedReader(
new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));
w.write(command + "\n");
w.flush();
return r.readLine();
} catch (IOException e) {
return null;
}
}
/** true when elphel-perfd is installed and answering. */
public static boolean isAvailable() {
final String reply = query("PING");
return (reply != null) && reply.startsWith("OK");
}
/** Daemon status line (JSON after "OK "), or null when absent. */
public static String status() {
return query("STATUS");
}
/**
* Acquire a profile hold ("measure" = deterministic locked clocks,
* "max" = production performance). Returns null (with one console line,
* never an exception) when the daemon is not installed/reachable -
* callers proceed normally without it.
*/
public static Hold hold(String profile) {
try {
final Socket s = connect();
final Writer w = new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8);
final BufferedReader r = new BufferedReader(
new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));
w.write("HOLD " + profile + "\n");
w.flush();
final String reply = r.readLine();
if ((reply != null) && reply.startsWith("OK")) {
System.out.println("PerfDaemon: " + reply);
return new Hold(s, w, profile);
}
System.out.println("PerfDaemon: hold '" + profile + "' refused: " + reply);
s.close();
return null;
} catch (IOException e) {
System.out.println("PerfDaemon: not available (" + e.getMessage() +
") - continuing with default power management");
return null;
}
}
/**
* Startup probe (once per program): one console line either way; when the
* daemon is absent, prints the one-time install command and continues.
*/
public static void startupCheck() {
if (startup_reported) return;
startup_reported = true;
if (isAvailable()) {
System.out.println("PerfDaemon: elphel-perfd available on " + HOST + ":" + PORT +
" (CPU+GPU clock holds without sudo; profiles: measure/max)");
} else {
System.out.println("PerfDaemon: elphel-perfd NOT installed - runs use default power "+
"management (timing varies with clock boost). Install once with:");
System.out.println("PerfDaemon: sudo <imagej-elphel>/perfd/install.sh");
}
}
}
...@@ -522,6 +522,10 @@ public class Eyesis_Correction implements PlugIn, ActionListener { ...@@ -522,6 +522,10 @@ public class Eyesis_Correction implements PlugIn, ActionListener {
} }
if (IJ.versionLessThan("1.43q")) if (IJ.versionLessThan("1.43q"))
return; return;
// elphel-perfd startup probe (one console line): use the CPU+GPU clock
// daemon when installed, suggest the one-time sudo install when not.
// Design ruled by Andrey 07/17/2026. By Claude on 07/17/2026.
com.elphel.imagej.common.PerfDaemon.startupCheck();
/* /*
if (instance != null) { if (instance != null) {
instance.toFront(); instance.toFront();
......
...@@ -2911,6 +2911,7 @@ public class CuasPoseRT { ...@@ -2911,6 +2911,7 @@ public class CuasPoseRT {
} }
RtPoseTiming rtTiming = null; RtPoseTiming rtTiming = null;
RtPoseProfile rtProfile = null; RtPoseProfile rtProfile = null;
com.elphel.imagej.common.PerfDaemon.Hold rtPerfHold = null; // 'measure' clock hold for the timed run // By Claude on 07/17/2026
RT_POSE_PROFILE.remove(); RT_POSE_PROFILE.remove();
if (activePreload != null) { if (activePreload != null) {
int rtInputs = 0; int rtInputs = 0;
...@@ -2924,6 +2925,12 @@ public class CuasPoseRT { ...@@ -2924,6 +2925,12 @@ public class CuasPoseRT {
clt_parameters.curt.pose_cycles : clt_parameters.imp.max_cycles; clt_parameters.curt.pose_cycles : clt_parameters.imp.max_cycles;
rtProfile = new RtPoseProfile(rtInputs, profileCycles); rtProfile = new RtPoseProfile(rtInputs, profileCycles);
RT_POSE_PROFILE.set(rtProfile); RT_POSE_PROFILE.set(rtProfile);
// elphel-perfd 'measure' hold for the timed run: deterministic CPU/GPU
// clocks (per-item A/B evaluation needs +-1%, free-running boost gives
// +-10%). Null when the daemon is not installed (one console line, run
// proceeds normally); released after the RT summary; an abnormal exit
// releases via the JVM's socket close. By Claude on 07/17/2026.
rtPerfHold = com.elphel.imagej.common.PerfDaemon.hold("measure");
} }
for (int nscene = earliest; nscene < quadCLTs.length; nscene++) { for (int nscene = earliest; nscene < quadCLTs.length; nscene++) {
if (quadCLTs[nscene] == null) continue; if (quadCLTs[nscene] == null) continue;
...@@ -3333,6 +3340,10 @@ public class CuasPoseRT { ...@@ -3333,6 +3340,10 @@ public class CuasPoseRT {
if (rtTiming != null) rtTiming.printSummary(num_fail); if (rtTiming != null) rtTiming.printSummary(num_fail);
if (rtProfile != null) rtProfile.printSummary(); if (rtProfile != null) rtProfile.printSummary();
RT_POSE_PROFILE.remove(); RT_POSE_PROFILE.remove();
if (rtPerfHold != null) { // release the 'measure' clock hold (perfd) // By Claude on 07/17/2026
rtPerfHold.close();
rtPerfHold = null;
}
// ---- Output ---- // ---- Output ----
final String csv_path = center_CLT.getX3dDirectory(true) + Prefs.getFileSeparator() + final String csv_path = center_CLT.getX3dDirectory(true) + Prefs.getFileSeparator() +
......
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