Backend (nvcurve/): - hal/fans.py, hal/limits.py, hal/gpu.py: replace conditional pynvml imports with the established 'pynvml: Any = _pynvml_import' pattern (fixes ~50 'possibly unbound' errors); type the result dicts; guard query_interface() results; explicit uuid/pci-bus parsing (int, hex convention documented); modernize Optional[T] -> T | None - cli.py: fix 'curve_state' possibly-unbound and snap_path None handling in cmd_setup; wrap unchecked int()/open()/makedirs() calls in try/except with clean CLI errors; add module logger for silent except-pass blocks; raise ... from exc; fix unused loop vars and set-comprehension - hal/snapshot.py: filepath: str | None; wrap all file ops; sorted imports; remove unused CT_POINTS import - daemon.py: extract 0o666 to _SOCKET_MODE constant (intentional for /run sockets) with nosemgrep - server.py: nosemgrep for Python 3.7-compat false positive (project requires >= 3.12); log previously-swallowed exception - profiles/native.py, profiles/apply.py: wrap file ops and int(k) profile-key parsing; sorted imports; modernize typing Frontend (frontend/src): - Add .js extensions to all relative imports (standard TS-ESM; Vite resolves .js -> .ts) - React.FormEvent (deprecated in React 19 types) -> React.SubmitEvent - catch (e: any) -> catch (e: unknown) + instanceof Error narrowing - React-hooks: move ref writes from render into effects; convert viewport reset to render-phase state adjustment; split selectPoint(index, multi) into selectPoint + togglePoint (no flag argument); remove non-null assertion - Static inline styles -> Tailwind classes (dynamic positioning/cursor styles kept) - Remove non-standard 'container' option from scrollIntoView (browsers ignore unknown options) which had orphaned a @ts-expect-error - Object.fromEntries for Map -> Record conversion Tooling: - .gitignore: ignore .codegraph/ local tool data Verified: tsc --noEmit, vite production build, python imports, and full LSP scan (0 errors/warnings in both projects).
78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
import type { VFPoint } from '../types.js';
|
||
|
||
/**
|
||
* Approximate reference frequency (MHz) for a point: effective − delta.
|
||
*
|
||
* `p.freq_mhz` is the current effective frequency as reported by GetVFPCurve
|
||
* (already including any applied boost delta). Subtracting the delta gives an
|
||
* approximation of the hardware base.
|
||
*
|
||
* Note: NVIDIA enforces monotonicity across the V/F curve — a large delta on
|
||
* a lower-voltage point pushes up neighbouring points' effective frequencies
|
||
* even when those points have zero delta. So for monotonicity-affected points,
|
||
* `effective - delta` doesn't recover the true unmodified base. It is still
|
||
* useful as a faint reference line ("where this point would sit with no boost").
|
||
*/
|
||
export function refBaseMhz(p: VFPoint): number {
|
||
return p.freq_mhz - p.delta_mhz;
|
||
}
|
||
|
||
/** Find which VF point the GPU is currently near based on voltage reading */
|
||
export function findCurrentPoint(
|
||
points: VFPoint[],
|
||
voltage_mv: number | null,
|
||
): VFPoint | null {
|
||
if (voltage_mv == null || points.length === 0) return null;
|
||
return points.reduce((best, p) =>
|
||
Math.abs(p.volt_mv - voltage_mv) < Math.abs(best.volt_mv - voltage_mv) ? p : best,
|
||
);
|
||
}
|
||
|
||
/** Voltage domain extent, with padding */
|
||
export function voltExtent(points: VFPoint[], padMv = 20): [number, number] {
|
||
if (points.length === 0) return [600, 1100];
|
||
const min = Math.min(...points.map((p) => p.volt_mv));
|
||
const max = Math.max(...points.map((p) => p.volt_mv));
|
||
return [min - padMv, max + padMv];
|
||
}
|
||
|
||
/** Frequency domain extent for the effective (boosted) curve, with padding */
|
||
export function freqExtent(points: VFPoint[], padMhz = 50): [number, number] {
|
||
if (points.length === 0) return [1000, 3000];
|
||
const allFreqs = points.flatMap((p) => [p.freq_mhz, refBaseMhz(p)]);
|
||
const min = Math.min(...allFreqs);
|
||
const max = Math.max(...points.map((p) => p.freq_mhz));
|
||
return [min - padMhz, max + padMhz];
|
||
}
|
||
|
||
/**
|
||
* Detect points whose effective frequency is being held up by NVIDIA's
|
||
* monotonicity enforcement rather than their own offset.
|
||
*
|
||
* Walk points in voltage order, tracking the "ceiling" — the highest
|
||
* effective frequency seen so far and the offset that produced it. A point is
|
||
* clamped when:
|
||
* 1. Its effective freq is at or below the ceiling (hasn't moved past it)
|
||
* 2. Its own offset is lower than the offset that set the ceiling
|
||
*
|
||
* This catches cases like: point 103 has +950 MHz → effective 3907 MHz,
|
||
* point 104 has +315 MHz → effective also 3907 MHz (clamped).
|
||
*/
|
||
export function detectClampedPoints(points: VFPoint[]): Set<number> {
|
||
const clamped = new Set<number>();
|
||
let ceiling = -Infinity;
|
||
let ceilingOffset = -Infinity;
|
||
|
||
for (const p of points) {
|
||
if (p.freq_mhz <= ceiling && p.delta_khz < ceilingOffset) {
|
||
clamped.add(p.index);
|
||
}
|
||
if (p.freq_mhz > ceiling) {
|
||
ceiling = p.freq_mhz;
|
||
ceilingOffset = p.delta_khz;
|
||
}
|
||
}
|
||
|
||
return clamped;
|
||
}
|