initial commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# Cockpit 360 JS API Sandboxing Notes
|
||||
|
||||
Server: rayserver, Cockpit version 360 (dpkg).
|
||||
|
||||
## What Works
|
||||
|
||||
- Basic page rendering (HTML, CSS, JS)
|
||||
- Cockpit CSS theme (../base1/cockpit.css)
|
||||
- Cockpit JS library (../base1/cockpit.js)
|
||||
|
||||
## What Fails
|
||||
|
||||
### cockpit.script() — ALWAYS FAILS for system commands
|
||||
Even with full paths like `/usr/bin/nvidia-smi`, the call never resolves.
|
||||
The `.done()` callback never fires, `.fail()` may not fire either — it just hangs.
|
||||
Does NOT produce an error in browser console.
|
||||
|
||||
Tested: `cockpit.script("/usr/bin/nvidia-smi --query-gpu=name ...")` — hangs indefinitely.
|
||||
|
||||
### cockpit.file().read() — FAILS for absolute paths
|
||||
Trying to read `/run/nvidia-gpu.txt`, `/tmp/anything` — `.fail()` fires.
|
||||
The error object is empty or useless.
|
||||
Likely Cockpit 360 restricts file reads to package-relative paths or whitelisted dirs.
|
||||
|
||||
### cockpit.spawn() — UNCONFIRMED
|
||||
Currently testing: `cockpit.spawn(["cat", "/run/nvidia-gpu.txt"])`.
|
||||
Theory: simpler commands (cat vs nvidia-smi) may work since they don't need GPU driver access.
|
||||
|
||||
## What's Working (infrastructure)
|
||||
|
||||
- `nvidia-gpu-monitor.service` — writes every 5s to:
|
||||
- `/run/nvidia-gpu.txt` — GPU name, driver, temp, util, memory, power, pstate, fan
|
||||
- `/run/nvidia-processes.txt` — PID, process_name, used_memory
|
||||
- `/run/nvidia-cuda.txt` — CUDA version string
|
||||
- Files are world-readable (644), owned by root
|
||||
- `nvidia-smi` works perfectly from terminal and from the service
|
||||
|
||||
## Current Page Location
|
||||
`/usr/share/cockpit/nvidia-gpu/index.html`
|
||||
Manifest: `/usr/share/cockpit/nvidia-gpu/manifest.json` (CSP allows unsafe-inline, menu section)
|
||||
@@ -0,0 +1,28 @@
|
||||
# Cockpit Connectivity Notes
|
||||
|
||||
## Socket Activation (normal behavior)
|
||||
|
||||
Cockpit is socket-activated — `cockpit.socket` listens on `*:9090` and spawns `cockpit.service` on demand. Seeing `cockpit.service` as `inactive (dead)` is **normal** when no one is connected.
|
||||
|
||||
```
|
||||
cockpit.socket → active (listening) on *:9090
|
||||
cockpit.service → inactive (dead) — wakes on connection
|
||||
```
|
||||
|
||||
## HTTPS required
|
||||
|
||||
Cockpit **refuses plain HTTP**. Always use `https://`:
|
||||
- `https://<hostname>:9090` ✓
|
||||
- `http://<hostname>:9090` ✗ (connection refused or hangs)
|
||||
|
||||
## Self-signed certificate
|
||||
|
||||
Cockpit uses a self-signed cert by default. Browsers show a warning that must be bypassed (click "Advanced" → "Proceed").
|
||||
|
||||
## Tailscale access
|
||||
|
||||
With Tailscale installed on the server, access Cockpit via the Tailscale IP:
|
||||
```
|
||||
https://<tailscale-ip>:9090
|
||||
```
|
||||
No firewall changes needed — Cockpit listens on `*:9090` which includes the `tailscale0` interface. Same HTTPS + self-signed cert caveats apply.
|
||||
@@ -0,0 +1,89 @@
|
||||
# CPU Temperature Data Format (from nvidia-gpu-monitor)
|
||||
|
||||
The monitor script has been extended to write CPU temperatures alongside GPU data.
|
||||
|
||||
## cpu.txt (added by cockpit-custom-packages SKILL.md user session)
|
||||
|
||||
Written by the same systemd service (`nvidia-gpu-monitor`) that writes GPU data, every 5 seconds.
|
||||
|
||||
```
|
||||
48,43,46
|
||||
```
|
||||
|
||||
Comma-separated integers (no spaces):
|
||||
|
||||
| Index | Sensor | Source | Notes |
|
||||
|-------|--------|--------|-------|
|
||||
| 0 | CPU Package | `/sys/class/thermal/thermal_zone3/temp` | Main CPU temp, integer °C |
|
||||
| 1 | PCH | `/sys/class/thermal/thermal_zone2/temp` | Platform Controller Hub temp |
|
||||
| 2 | ACPI | `/sys/class/thermal/thermal_zone0/temp` | ACPI zone temp |
|
||||
|
||||
**Parsing**: `data.trim().split(',')` gives a 3-element array. Check `s.length >= 3` before accessing.
|
||||
|
||||
**Reading from shell**: `cat /sys/class/thermal/thermal_zone3/temp` returns millidegrees (e.g. `48000` = 48°C). Divide by 1000 for °C.
|
||||
|
||||
## Adding to the monitor script
|
||||
|
||||
Insert this block before the `sleep 5` in the monitor loop:
|
||||
|
||||
```bash
|
||||
# CPU temps
|
||||
pkg=$(cat /sys/class/thermal/thermal_zone3/temp 2>/dev/null || echo 0)
|
||||
pch=$(cat /sys/class/thermal/thermal_zone2/temp 2>/dev/null || echo 0)
|
||||
acpi=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0)
|
||||
echo "$((pkg/1000)),$((pch/1000)),$((acpi/1000))" > /usr/share/cockpit/nvidia-gpu/cpu.txt
|
||||
```
|
||||
|
||||
## Adding a CPU Card to the Dashboard
|
||||
|
||||
Use the `accent-cyan` color class to distinguish from GPU cards:
|
||||
|
||||
```css
|
||||
.card.accent-cyan{border-top:3px solid #06b6d4}
|
||||
```
|
||||
|
||||
HTML pattern (compact, big-temp + mini stats):
|
||||
|
||||
```html
|
||||
<div class="card accent-cyan"><h2>🔥 CPU</h2>
|
||||
<div class="temp-group">
|
||||
<div class="temp-big"><span class="temp-cold" id="cpu-temp">--</span><span class="unit">°C</span></div>
|
||||
<div class="temp-mini">
|
||||
<div class="temp-mini-item"><span>PCH</span><span class="val" id="pch-temp">-</span></div>
|
||||
<div class="temp-mini-item"><span>ACPI</span><span class="val" id="acpi-temp">-</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar-track"><div class="bar-fill temp" id="cpu-bar" style="width:0%"></div></div>
|
||||
<div class="bar-labels"><span>0°</span><span>50°</span><span>75°</span><span>100°</span></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
JS to populate (inserted before the CUDA read):
|
||||
|
||||
```javascript
|
||||
cockpit.spawn(["cat",PKG+"/cpu.txt"],{err:"message"}).done(function(d){
|
||||
if(d&&d.trim()){var s=d.trim().split(',');if(s.length>=3){
|
||||
var cp=parseInt(s[0]);$('cpu-temp').textContent=isNaN(cp)?'--':cp;
|
||||
$('pch-temp').textContent=s[1]+'°';$('acpi-temp').textContent=s[2]+'°';
|
||||
var tp=Math.min(100,Math.max(0,cp));$('cpu-bar').style.width=tp+'%';
|
||||
var tc='temp-cold';if(cp>=50)tc='temp-mild';if(cp>=65)tc='temp-warm';if(cp>=80)tc='temp-hot';
|
||||
$('cpu-temp').className=tc;
|
||||
}}
|
||||
});
|
||||
```
|
||||
|
||||
Also add the 7th animation delay entry:
|
||||
```css
|
||||
.grid>*:nth-child(7){animation-delay:0.24s}
|
||||
```
|
||||
|
||||
## Sensor Availability
|
||||
|
||||
Not all systems have all three thermal zones. The script uses `|| echo 0` fallback. Common zone mappings:
|
||||
|
||||
| Zone index | Typical sensor | Common on |
|
||||
|------------|---------------|-----------|
|
||||
| 0 | ACPI | All x86_64 |
|
||||
| 1 | ACPI (secondary) | Laptops, multi-zone |
|
||||
| 2 | PCH | Intel Comet Lake+ |
|
||||
| 3 | x86_pkg_temp | All modern CPUs |
|
||||
@@ -0,0 +1,39 @@
|
||||
# NVIDIA Fan Curves (GTX 1050 Ti Reference)
|
||||
|
||||
The user's GTX 1050 Ti runs a passive/quiet fan curve baked into the GPU BIOS.
|
||||
|
||||
## Typical Stock Fan Curve
|
||||
|
||||
| Temp Range | Fan Speed | Behavior |
|
||||
|-------------|------------|----------------------------------------------|
|
||||
| < 50°C | 20-31% | Idle floor — stays at minimum for silence |
|
||||
| 50-55°C | 30-40% | Start of ramp |
|
||||
| 55-65°C | 40-60% | Moderate load |
|
||||
| 65-75°C | 60-80% | Sustained load (gaming, rendering) |
|
||||
| 75-80°C | 80-90% | Heavy load |
|
||||
| 80°C+ | 90-100% | Thermal protection |
|
||||
|
||||
## Key Observations
|
||||
|
||||
- **31% at 44°C is normal**: The card was in P0 (active perf state) with 7% util, but 44°C is well below the 50°C ramp threshold. The fan stays at its idle floor.
|
||||
- **Perf state matters**: P8 (idle) → P0 (active) transition happens when the card has work. Temp can climb to ~50°C before the fan responds meaningfully.
|
||||
- **Fan at 0% or `[N/A]`**: Means the card relies on passive cooling or the fan hasn't spun up yet. Some Pascal cards have a zero-RPM mode below 50°C.
|
||||
- **No user-side fan curve tool**: Custom fan curves require `nvidia-settings` (X11) or third-party tools — not available in headless/server setups.
|
||||
|
||||
## Power Draw Reporting
|
||||
|
||||
The GTX 1050 Ti (and other budget Pascal GPUs) **cannot report power draw** via `nvidia-smi`:
|
||||
|
||||
- `nvidia-smi --query-gpu=power.draw` returns `[N/A]`
|
||||
- The card has a 75W TDP, runs off PCIe slot power only (no 6/8-pin connector), and lacks the power monitoring hardware/BIOS support
|
||||
- This is not a driver issue or misconfiguration — the sensor simply doesn't exist on this hardware
|
||||
- Cards that do report power: GTX 1060+, RTX series, and higher-end models with dedicated power sensors
|
||||
- **What to tell the user**: "The GTX 1050 Ti doesn't have a power sensor. It's normal — the card literally can't tell us. Nothing to fix."
|
||||
|
||||
## What to Tell the User
|
||||
|
||||
If asked "why is the fan at X% when temp is Y°C":
|
||||
1. Check perf state (P0 = active, P8/P12 = idle)
|
||||
2. Compare temp against the curve above
|
||||
3. Under 50°C at 20-31% = perfectly normal, BIOS prioritizes quiet
|
||||
4. If temp exceeds 55°C and fan stays below 40%, something may be wrong (check for dust, thermal paste, or fan failure via `nvidia-smi -q -d TEMPERATURE`)
|
||||
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head><meta charset="utf-8"><title>NVIDIA GPU</title>
|
||||
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">
|
||||
<link rel="stylesheet" href="../base1/cockpit.css">
|
||||
<script src="../base1/cockpit.js"></script>
|
||||
<style>
|
||||
body{padding:0;font-family:var(--pf-t--global--font--family--body,Inter,RedHatText,Overpass,sans-serif);background:var(--pf-t--global--background--color--primary--default,#f0f2f5)}
|
||||
.ct-page-fill{padding:1.5rem;max-width:1100px;margin:0 auto}
|
||||
.page-header{display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;padding:1rem 1.25rem;background:var(--pf-t--global--background--color--secondary--default,linear-gradient(135deg,#0f172a,#1e293b));border-radius:14px;border:1px solid var(--pf-t--global--border--color--default,rgba(255,255,255,0.08));box-shadow:0 2px 12px rgba(0,0,0,0.06)}
|
||||
.page-header h1{font-size:1.15rem;font-weight:700;margin:0;background:linear-gradient(135deg,#38bdf8,#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
||||
.page-header .subtitle{font-size:0.7rem;color:var(--pf-t--global--text--color--subtle,#94a3b8);margin-left:auto;font-family:"SF Mono","JetBrains Mono","Fira Code",monospace;background:var(--pf-t--global--background--color--secondary--default,rgba(255,255,255,0.06));padding:0.2rem 0.6rem;border-radius:6px}
|
||||
.status-dot{width:10px;height:10px;border-radius:50%;display:inline-block;position:relative}
|
||||
.status-dot::after{content:'';position:absolute;inset:-4px;border-radius:50%;opacity:0.25}
|
||||
.status-dot.ok{background:#22c55e;box-shadow:0 0 10px #22c55e;animation:pulse-dot 2s ease-in-out infinite}
|
||||
.status-dot.warn{background:#f59e0b} .status-dot.err{background:#ef4444}
|
||||
@keyframes pulse-dot{0%,100%{opacity:0.25}50%{opacity:0.6}}
|
||||
.grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem}
|
||||
@media(max-width:900px){.grid{grid-template-columns:1fr 1fr}}
|
||||
@media(max-width:600px){.grid{grid-template-columns:1fr}}
|
||||
.card{background:var(--pf-t--global--background--color--primary--default,#fff);border:1px solid var(--pf-t--global--border--color--default,#e2e8f0);border-radius:12px;padding:1.25rem;transition:box-shadow 0.2s}
|
||||
.card:hover{box-shadow:0 4px 20px rgba(0,0,0,0.06)}
|
||||
.card.full{grid-column:1/-1}
|
||||
.card.accent-blue{border-top:3px solid #3b82f6}.card.accent-green{border-top:3px solid #22c55e}
|
||||
.card.accent-orange{border-top:3px solid #f59e0b}.card.accent-purple{border-top:3px solid #a855f7}
|
||||
.card.accent-red{border-top:3px solid #ef4444}.card.accent-cyan{border-top:3px solid #06b6d4}
|
||||
.card h2{font-size:0.65rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--pf-t--global--text--color--subtle,#94a3b8);margin:0 0 0.85rem 0;font-weight:700}
|
||||
.stat{display:flex;justify-content:space-between;align-items:center;padding:0.45rem 0}
|
||||
.stat+.stat{border-top:1px solid var(--pf-t--global--border--color--default,rgba(0,0,0,0.04))}
|
||||
.stat-label{color:var(--pf-t--global--text--color--subtle,#64748b);font-size:0.82rem;font-weight:500}
|
||||
.stat-value{font-weight:600;font-family:"SF Mono","JetBrains Mono","Fira Code",monospace;font-size:0.85rem;color:var(--pf-t--global--text--color--default,#1e293b);white-space:nowrap}
|
||||
.temp-group{display:flex;align-items:center;gap:1.5rem;justify-content:center;flex-wrap:wrap;margin:0.3rem 0 0.5rem}
|
||||
.temp-big{font-size:2.5rem;font-weight:300;font-family:"SF Mono";line-height:1;display:flex;align-items:baseline;gap:2px}
|
||||
.temp-big .unit{font-size:1rem;color:#94a3b8}
|
||||
.temp-cold{color:#3b82f6}.temp-mild{color:#22c55e}.temp-warm{color:#f59e0b}.temp-hot{color:#ef4444}
|
||||
.temp-mini{display:flex;flex-direction:column;gap:0.2rem;min-width:70px}
|
||||
.temp-mini-item{display:flex;justify-content:space-between;font-size:0.78rem;color:#64748b}
|
||||
.temp-mini-item .val{font-family:"SF Mono";font-weight:600}
|
||||
.bar-track{background:#e2e8f0;border-radius:6px;height:6px;overflow:hidden;margin:0.4rem 0}
|
||||
.bar-fill{height:100%;border-radius:6px;transition:width 0.6s}
|
||||
.bar-fill.green{background:linear-gradient(90deg,#22c55e,#16a34a)}
|
||||
.bar-fill.amber{background:linear-gradient(90deg,#f59e0b,#d97706)}
|
||||
.bar-fill.red{background:linear-gradient(90deg,#ef4444,#dc2626)}
|
||||
.bar-fill.temp{background:linear-gradient(90deg,#3b82f6,#22c55e,#f59e0b,#ef4444)}
|
||||
.bar-labels{display:flex;justify-content:space-between;font-size:0.6rem;color:#94a3b8;margin-top:2px}
|
||||
.mem-row{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:0.3rem}
|
||||
.mem-used{font-size:1.3rem;font-weight:600;font-family:"SF Mono";color:#1e293b}
|
||||
.mem-of{font-size:0.78rem;color:#94a3b8}
|
||||
.mem-pct{text-align:right;font-size:0.78rem;color:#94a3b8;font-family:"SF Mono";margin-top:2px}
|
||||
.power-block{text-align:center;padding:0.4rem 0}
|
||||
.power-num{font-size:1.6rem;font-weight:300;font-family:"SF Mono";color:#1e293b}
|
||||
.power-label{font-size:0.68rem;color:#94a3b8;margin-top:0.1rem}
|
||||
.model-card{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.4rem;text-align:center}
|
||||
.model-card .name{font-weight:700;font-size:0.9rem;color:#1e293b}
|
||||
.model-card .sub{font-size:0.72rem;color:#94a3b8}
|
||||
table{width:100%;border-collapse:collapse;font-size:0.82rem}
|
||||
th{text-align:left;padding:0.45rem 0.5rem;color:#94a3b8;font-weight:600;font-size:0.65rem;text-transform:uppercase;border-bottom:2px solid #e2e8f0}
|
||||
td{padding:0.45rem 0.5rem;border-bottom:1px solid rgba(0,0,0,0.03)}
|
||||
td:last-child{text-align:right;font-family:"SF Mono";font-weight:600}
|
||||
tr:hover td{background:rgba(0,0,0,0.015)}
|
||||
.footer-row{display:flex;gap:1rem;flex-wrap:wrap;margin-top:1rem;padding:0.5rem 1rem;background:rgba(0,0,0,0.015);border:1px solid rgba(0,0,0,0.04);border-radius:8px;font-size:0.72rem;color:#94a3b8}
|
||||
.alert-box{display:none;background:linear-gradient(135deg,#fef2f2,#fff5f5);border:1px solid #fecaca;color:#991b1b;padding:0.9rem 1.25rem;border-radius:10px;margin-bottom:1rem;font-size:0.85rem;align-items:center;gap:0.6rem}
|
||||
@keyframes fadeUp{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
||||
.grid>*{animation:fadeUp 0.3s ease-out both}
|
||||
.grid>*:nth-child(1){animation-delay:0s}
|
||||
.grid>*:nth-child(2){animation-delay:0.04s}
|
||||
.grid>*:nth-child(3){animation-delay:0.08s}
|
||||
.grid>*:nth-child(4){animation-delay:0.12s}
|
||||
.grid>*:nth-child(5){animation-delay:0.16s}
|
||||
.grid>*:nth-child(6){animation-delay:0.20s}
|
||||
.grid>*:nth-child(7){animation-delay:0.24s}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="ct-page-fill">
|
||||
<div id="nogpu" class="alert-box"><span class="icon">⚠</span> No NVIDIA GPU detected</div>
|
||||
<div class="page-header"><span class="status-dot" id="sdot"></span><h1>🎮 <span id="gpumodel">NVIDIA GPU</span></h1><span class="subtitle" id="pstate">P?</span></div>
|
||||
<div id="content"><div class="grid">
|
||||
<!-- Thermal -->
|
||||
<div class="card accent-blue"><h2>🌡 Thermal</h2>
|
||||
<div class="temp-group"><div class="temp-big"><span class="temp-cold" id="gt">--</span><span class="unit">°C</span></div>
|
||||
<div class="temp-mini"><div class="temp-mini-item"><span>Fan</span><span class="val" id="gf">—</span></div><div class="temp-mini-item"><span>Util</span><span class="val" id="gu">-</span></div></div></div>
|
||||
<div class="bar-track"><div class="bar-fill temp" id="tbar" style="width:0%"></div></div>
|
||||
<div class="bar-labels"><span>0°</span><span>50°</span><span>75°</span><span>100°</span></div></div>
|
||||
<!-- CPU -->
|
||||
<div class="card accent-cyan"><h2>🔥 CPU</h2>
|
||||
<div class="temp-group"><div class="temp-big"><span class="temp-cold" id="cpu-temp">--</span><span class="unit">°C</span></div>
|
||||
<div class="temp-mini"><div class="temp-mini-item"><span>PCH</span><span class="val" id="pch-temp">-</span></div><div class="temp-mini-item"><span>ACPI</span><span class="val" id="acpi-temp">-</span></div></div></div>
|
||||
<div class="bar-track"><div class="bar-fill temp" id="cpu-bar" style="width:0%"></div></div>
|
||||
<div class="bar-labels"><span>0°</span><span>50°</span><span>75°</span><span>100°</span></div></div>
|
||||
<!-- Memory -->
|
||||
<div class="card accent-orange"><h2>💾 Memory</h2>
|
||||
<div class="mem-row"><span class="mem-used" id="mu">-</span><span class="mem-of">of <span id="mt">-</span></span></div>
|
||||
<div class="bar-track" style="height:8px"><div class="bar-fill green" id="mbar" style="width:0%"></div></div>
|
||||
<div class="mem-pct" id="mpct">-</div></div>
|
||||
<!-- Details -->
|
||||
<div class="card accent-purple"><h2>🔧 Details</h2>
|
||||
<div class="stat"><span class="stat-label">Driver</span><span class="stat-value" id="dv">-</span></div>
|
||||
<div class="stat"><span class="stat-label">CUDA Version</span><span class="stat-value" id="cv">-</span></div>
|
||||
<div class="stat"><span class="stat-label">Perf State</span><span class="stat-value" id="gp">-</span></div>
|
||||
<div class="stat"><span class="stat-label">Fan Speed</span><span class="stat-value" id="gf2">-</span></div></div>
|
||||
<!-- Power -->
|
||||
<div class="card accent-red"><h2>⚡ Power</h2>
|
||||
<div class="power-block"><div class="power-num" id="pw">N/A</div><div class="power-label">Power Draw</div></div></div>
|
||||
<!-- GPU Load -->
|
||||
<div class="card accent-green"><h2>📊 GPU Load</h2>
|
||||
<div class="stat"><span class="stat-label">Utilization</span><span class="stat-value" id="gu2">0%</span></div></div>
|
||||
<!-- Model -->
|
||||
<div class="card model-card"><div class="name" id="gpmodel2">-</div><div class="sub" id="driverver">-</div></div>
|
||||
<!-- Processes -->
|
||||
<div class="card full"><h2>🔄 Active Processes</h2>
|
||||
<table><thead><tr><th>PID</th><th>Process</th><th>Memory</th></tr></thead>
|
||||
<tbody id="pl"><tr><td colspan="3" style="text-align:center;color:#94a3b8;padding:1rem 0">No GPU processes running</td></tr></tbody></table></div>
|
||||
<!-- Footer -->
|
||||
<div class="full footer-row"><span>🕒 <span id="lu">-</span></span><span id="modelright" style="opacity:0.55">-</span></div>
|
||||
</div></div></div>
|
||||
<script>
|
||||
(function(){
|
||||
function $(i){return document.getElementById(i);}
|
||||
function fmt(m){var v=parseInt(m);if(isNaN(v))return m+' MiB';return v>=1024?(v/1024).toFixed(1)+' GiB':v+' MiB';}
|
||||
var PKG="/usr/share/cockpit/nvidia-gpu";
|
||||
function update(){
|
||||
var ts=new Date().toLocaleTimeString();
|
||||
cockpit.spawn(["cat",PKG+"/gpu.txt"],{err:"message"}).done(function(data){
|
||||
if(!data||!data.trim()){$('nogpu').style.display='flex';$('content').style.display='none';$('sdot').className='status-dot err';return;}
|
||||
$('nogpu').style.display='none';$('content').style.display='block';var p=data.trim().split(', ');if(p.length<9)return;
|
||||
var model=p[0];$('gpumodel').textContent=model;$('gpmodel2').textContent=model;$('sdot').className='status-dot ok';
|
||||
$('dv').textContent=p[1];$('driverver').textContent='Driver '+p[1];$('modelright').textContent=model+' — Driver '+p[1];
|
||||
var t=parseInt(p[2]);var tdisp=$('gt');
|
||||
if(!isNaN(t)){tdisp.textContent=t;var tcls='temp-cold';if(t>=50)tcls='temp-mild';if(t>=65)tcls='temp-warm';if(t>=80)tcls='temp-hot';
|
||||
tdisp.className=tcls;var tp=Math.min(100,Math.max(0,t));$('tbar').style.width=tp+'%';
|
||||
$('sdot').className='status-dot '+(t<50?'ok':(t<75?'warn':'err'));}
|
||||
var uPct=parseInt(p[3])||0;$('gu').textContent=uPct+'%';$('gu2').textContent=uPct+'%';
|
||||
var uMem=parseInt(p[4])||0;var tMem=parseInt(p[5])||1;$('mu').textContent=fmt(uMem);$('mt').textContent=fmt(tMem);
|
||||
var mp=Math.round(uMem/tMem*100);$('mbar').style.width=mp+'%';$('mbar').className='bar-fill '+(mp<60?'green':(mp<85?'amber':'red'));$('mpct').textContent=mp+'%';
|
||||
$('pw').textContent=p[6]||'N/A';$('gp').textContent=p[7]||'-';$('pstate').textContent=(p[7]||'').trim();
|
||||
var fan=p[8]?p[8].trim():'0';var fanDisp=(fan==='[N/A]'||fan==='0')?'—':fan+'%';$('gf').textContent=fanDisp;$('gf2').textContent=fanDisp;
|
||||
}).fail(function(){$('nogpu').style.display='flex';$('content').style.display='none';$('sdot').className='status-dot err';});
|
||||
cockpit.spawn(["cat",PKG+"/cpu.txt"],{err:"message"}).done(function(d){
|
||||
if(d&&d.trim()){var s=d.trim().split(',');if(s.length>=3){var cp=parseInt(s[0]);$('cpu-temp').textContent=isNaN(cp)?'--':cp;
|
||||
$('pch-temp').textContent=s[1]+'°';$('acpi-temp').textContent=s[2]+'°';
|
||||
var tp=Math.min(100,Math.max(0,cp));$('cpu-bar').style.width=tp+'%';
|
||||
var tc='temp-cold';if(cp>=50)tc='temp-mild';if(cp>=65)tc='temp-warm';if(cp>=80)tc='temp-hot';$('cpu-temp').className=tc;}}});
|
||||
cockpit.spawn(["cat",PKG+"/cuda.txt"],{err:"message"}).done(function(d){if(d&&d.trim())$('cv').textContent=d.trim();});
|
||||
cockpit.spawn(["cat",PKG+"/processes.txt"],{err:"message"}).done(function(d){
|
||||
if(!d||!d.trim()){$('pl').innerHTML='<tr><td colspan="3" style="text-align:center;color:#94a3b8;padding:1rem 0">No GPU processes running</td></tr>';return;}
|
||||
var ls=d.trim().split('\\n').filter(function(l){return l.trim();});
|
||||
if(!ls.length){$('pl').innerHTML='<tr><td colspan="3" style="text-align:center;color:#94a3b8;padding:1rem 0">No GPU processes running</td></tr>';return;}
|
||||
var h='';ls.forEach(function(l){var s=l.split(', ');if(s.length>=3)h+='<tr><td>'+s[0]+'</td><td>'+s[1]+'</td><td>'+fmt(s[2])+'</td></tr>';});$('pl').innerHTML=h;});
|
||||
$('lu').textContent=ts;
|
||||
}
|
||||
update();setInterval(update,5000);
|
||||
})();
|
||||
</script></body></html>
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# nvidia-gpu-monitor.sh — writes GPU stats to files every 5 seconds
|
||||
# Install: sudo cp to /usr/local/bin/, chmod +x
|
||||
# Service: systemd unit at /etc/systemd/system/nvidia-gpu-monitor.service
|
||||
#
|
||||
# Output files:
|
||||
# /usr/share/cockpit/nvidia-gpu/gpu.txt — model,driver,temp,util,mem,power,pstate,fan
|
||||
# /usr/share/cockpit/nvidia-gpu/cuda.txt — CUDA version
|
||||
# /usr/share/cockpit/nvidia-gpu/processes.txt — pid,process_name,used_memory
|
||||
|
||||
PKG="/usr/share/cockpit/nvidia-gpu"
|
||||
|
||||
while true; do
|
||||
# GPU info (name, driver, temp, util, mem used, mem total, power, perf state, fan)
|
||||
nvidia-smi --query-gpu=name,driver_version,temperature.gpu,utilization.gpu,memory.used,memory.total,power.draw,pstate,fan.speed --format=csv,noheader,nounits 2>/dev/null | head -1 > "$PKG/gpu.txt"
|
||||
|
||||
# GPU processes
|
||||
nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv,noheader,nounits 2>/dev/null > "$PKG/processes.txt"
|
||||
|
||||
# CUDA version
|
||||
nvidia-smi 2>/dev/null | grep "CUDA Version" | sed 's/.*CUDA Version: //' | head -1 > "$PKG/cuda.txt"
|
||||
|
||||
sleep 5
|
||||
done
|
||||
Reference in New Issue
Block a user