Adds an experimental power-cap mode using the undocumented RM ioctl interface (based on panchovix's LACT PR #1205) to set power limits below the VBIOS minimum (down to 30 W). - hal/rm_power.py: RM ioctl power-cap read/write/reset + runtime probe - limits.py: power_cap_mode (nvml/ioctl) with support detection - config.py: persist power_cap_mode per GPU - profiles: record/apply power_cap_mode - server.py: POST /api/limits validates ioctl support (409 on failure) - cli.py: profile save falls back to persisted mode - client.py: power_cap_mode in Limits - frontend: toggle + warning with panchovix attribution (LACT #1205) - tests: test_rm_power.py (unit) + integration coverage - Makefile: add test_rm_power.py to make test Also includes automated linter reformatting (prettier, ruff, shellcheck, isort, markdownlint) that the linter would apply anyway.
84 lines
3.4 KiB
TypeScript
84 lines
3.4 KiB
TypeScript
import { fmt } from "../../utils/units.js";
|
|
import type { VFPoint } from "../../types.js";
|
|
|
|
interface Props {
|
|
point: VFPoint;
|
|
/** Pending delta in kHz (from store), if any */
|
|
pendingDeltaKhz?: number;
|
|
/** True if this point is being held up by monotonicity enforcement */
|
|
isClamped?: boolean;
|
|
}
|
|
|
|
/**
|
|
* HTML tooltip rendered as an absolutely-positioned overlay on top of the
|
|
* SVG so it's never clipped by the SVG viewport.
|
|
*/
|
|
export function CurveTooltip({ point, pendingDeltaKhz, isClamped }: Props) {
|
|
const hasPending = pendingDeltaKhz !== undefined;
|
|
const pendingMhz = hasPending ? pendingDeltaKhz! / 1000 : 0;
|
|
const deltaChange = hasPending ? pendingDeltaKhz! - point.delta_khz : 0;
|
|
const pendingEffMhz = hasPending
|
|
? point.freq_mhz + deltaChange / 1000
|
|
: null;
|
|
|
|
return (
|
|
<div className="absolute right-4 bottom-4 pointer-events-none z-50 w-[172px] bg-zinc-800 border border-zinc-700 rounded-md p-2 text-xs shadow-xl">
|
|
<div className="text-zinc-400 mb-1">Point {point.index}</div>
|
|
<div className="text-zinc-200">
|
|
<span className="text-zinc-400">Volt: </span>
|
|
{fmt.mv(point.volt_mv, 1)}
|
|
</div>
|
|
<div className="text-zinc-200">
|
|
<span className="text-zinc-400">Offset: </span>
|
|
<span
|
|
className={
|
|
point.delta_khz > 0
|
|
? "text-emerald-400"
|
|
: point.delta_khz < 0
|
|
? "text-red-400"
|
|
: "text-zinc-400"
|
|
}
|
|
>
|
|
{point.delta_khz > 0 ? "+" : ""}
|
|
{fmt.mhz(point.delta_mhz, 1)}
|
|
</span>
|
|
</div>
|
|
<div className="text-emerald-300 font-semibold">
|
|
<span className="text-zinc-400">Eff.: </span>
|
|
{fmt.mhz(point.freq_mhz, 0)}
|
|
{isClamped && <span className="text-amber-500 ml-1">⇡</span>}
|
|
</div>
|
|
{isClamped && (
|
|
<div className="text-amber-500/80 text-[10px] mt-0.5">
|
|
Clamped by lower-voltage point
|
|
</div>
|
|
)}
|
|
{hasPending && (
|
|
<>
|
|
<div className="border-t border-zinc-700 mt-1.5 pt-1.5">
|
|
<div className="text-zinc-200">
|
|
<span className="text-zinc-400">Pending: </span>
|
|
<span
|
|
className={
|
|
pendingMhz > 0
|
|
? "text-cyan-400"
|
|
: pendingMhz < 0
|
|
? "text-orange-400"
|
|
: "text-zinc-400"
|
|
}
|
|
>
|
|
{pendingMhz > 0 ? "+" : ""}
|
|
{pendingMhz.toFixed(1)} MHz
|
|
</span>
|
|
</div>
|
|
<div className="text-cyan-300 font-semibold">
|
|
<span className="text-zinc-400">→ Eff.: </span>
|
|
{fmt.mhz(pendingEffMhz, 0)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|