Two-socket machines: Hardware and Top show each CPU, not one with every core
Hardware counts CPUs, cores and threads per CPU. Top groups threads under a row per socket, reads each core's temperature from its own socket's coretemp chip, and the CPU temperature is the hotter package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4b238ea93e
commit
1e2dcacb2c
@@ -0,0 +1,16 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// a two-socket board: two coretemp chips, and the CPU's temperature is the hotter package
|
||||||
|
func TestCPUTempHottestPackage(t *testing.T) {
|
||||||
|
temps := []Temp{
|
||||||
|
{Chip: "coretemp", Label: "Core 0", C: 31},
|
||||||
|
{Chip: "coretemp", Label: "Core 0", C: 44, Pkg: 1},
|
||||||
|
{Chip: "coretemp", Label: "Package id 0", C: 35},
|
||||||
|
{Chip: "coretemp", Label: "Package id 1", C: 41, Pkg: 1},
|
||||||
|
}
|
||||||
|
if got := cpuTemp(temps); got != 41 {
|
||||||
|
t.Fatalf("cpuTemp = %v, want 41", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,7 @@ type Temp struct {
|
|||||||
C float64 `json:"c"`
|
C float64 `json:"c"`
|
||||||
Max float64 `json:"max,omitempty"`
|
Max float64 `json:"max,omitempty"`
|
||||||
Crit float64 `json:"crit,omitempty"`
|
Crit float64 `json:"crit,omitempty"`
|
||||||
|
Pkg int `json:"pkg,omitempty"` // coretemp: the CPU package the chip belongs to — a two-socket board has two chips, both with a "Core 0"
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fan struct {
|
type Fan struct {
|
||||||
@@ -544,6 +545,19 @@ func hwmonDir(d string) (chip string, temps []Temp, fans []Fan) {
|
|||||||
fans = append(fans, Fan{Chip: chip, Label: label, RPM: int(raw)})
|
fans = append(fans, Fan{Chip: chip, Label: label, RPM: int(raw)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// coretemp is one chip per CPU package, and only its "Package id N" says which
|
||||||
|
if chip == "coretemp" {
|
||||||
|
for _, t := range temps {
|
||||||
|
if n, ok := strings.CutPrefix(t.Label, "Package id "); ok {
|
||||||
|
if pkg, err := strconv.Atoi(n); err == nil {
|
||||||
|
for i := range temps {
|
||||||
|
temps[i].Pkg = pkg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +573,10 @@ func hwmon() ([]Temp, []Fan) {
|
|||||||
if temps[i].Chip != temps[j].Chip {
|
if temps[i].Chip != temps[j].Chip {
|
||||||
return temps[i].Chip < temps[j].Chip
|
return temps[i].Chip < temps[j].Chip
|
||||||
}
|
}
|
||||||
return temps[i].Label < temps[j].Label
|
if temps[i].Label != temps[j].Label {
|
||||||
|
return temps[i].Label < temps[j].Label
|
||||||
|
}
|
||||||
|
return temps[i].Pkg < temps[j].Pkg
|
||||||
})
|
})
|
||||||
return temps, fans
|
return temps, fans
|
||||||
}
|
}
|
||||||
@@ -1112,6 +1129,38 @@ func coreIDs(n int) []int {
|
|||||||
return coreIDsVal[:n]
|
return coreIDsVal[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// corePkgs maps logical CPUs to the CPU package (socket) each is on, and is nil on a
|
||||||
|
// one-socket machine: there is nothing to tell apart.
|
||||||
|
var (
|
||||||
|
corePkgsOnce sync.Once
|
||||||
|
corePkgsVal []int
|
||||||
|
)
|
||||||
|
|
||||||
|
func corePkgs(n int) []int {
|
||||||
|
corePkgsOnce.Do(func() {
|
||||||
|
seen := map[int]bool{}
|
||||||
|
var out []int
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
b, err := os.ReadFile("/sys/devices/system/cpu/cpu" + strconv.Itoa(i) + "/topology/physical_package_id")
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(strings.TrimSpace(string(b)))
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out, seen[id] = append(out, id), true
|
||||||
|
}
|
||||||
|
if len(seen) > 1 {
|
||||||
|
corePkgsVal = out
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if len(corePkgsVal) < n {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return corePkgsVal[:n]
|
||||||
|
}
|
||||||
|
|
||||||
// ProcTable is the Processes tab payload.
|
// ProcTable is the Processes tab payload.
|
||||||
type ProcTable struct {
|
type ProcTable struct {
|
||||||
T int64 `json:"t"`
|
T int64 `json:"t"`
|
||||||
@@ -1123,7 +1172,8 @@ type ProcTable struct {
|
|||||||
CPUMaxMHz float64 `json:"cpu_max_mhz,omitempty"`
|
CPUMaxMHz float64 `json:"cpu_max_mhz,omitempty"`
|
||||||
CPUPowerW float64 `json:"cpu_power_w,omitempty"`
|
CPUPowerW float64 `json:"cpu_power_w,omitempty"`
|
||||||
CPUTemp float64 `json:"cpu_temp,omitempty"`
|
CPUTemp float64 `json:"cpu_temp,omitempty"`
|
||||||
CoreIDs []int `json:"core_ids,omitempty"` // for each logical CPU, the physical core it is a thread of (what the temperature sensors are named by)
|
CoreIDs []int `json:"core_ids,omitempty"` // for each logical CPU, the physical core it is a thread of (what the temperature sensors are named by)
|
||||||
|
CorePkgs []int `json:"core_pkgs,omitempty"` // for each logical CPU, the socket it is on; only on a machine with more than one
|
||||||
Load [3]float64 `json:"load"`
|
Load [3]float64 `json:"load"`
|
||||||
Mem Mem `json:"mem"`
|
Mem Mem `json:"mem"`
|
||||||
Swap Mem `json:"swap"`
|
Swap Mem `json:"swap"`
|
||||||
@@ -1182,18 +1232,24 @@ func cpuMHz() float64 {
|
|||||||
|
|
||||||
// cpuTemp picks the sensor that best represents the CPU package.
|
// cpuTemp picks the sensor that best represents the CPU package.
|
||||||
func cpuTemp(temps []Temp) float64 {
|
func cpuTemp(temps []Temp) float64 {
|
||||||
best, max := 0.0, 0.0
|
// the hottest package: a two-socket board has two, and the hotter one is the one to watch
|
||||||
|
pkg, max := 0.0, 0.0
|
||||||
for _, t := range temps {
|
for _, t := range temps {
|
||||||
l := strings.ToLower(t.Label)
|
l := strings.ToLower(t.Label)
|
||||||
if t.Chip == "coretemp" && strings.HasPrefix(l, "package") || t.Chip == "k10temp" && (l == "tctl" || l == "tdie") ||
|
if t.Chip == "coretemp" && strings.HasPrefix(l, "package") || t.Chip == "k10temp" && (l == "tctl" || l == "tdie") ||
|
||||||
t.Chip == "thermal" && strings.Contains(l, "cpu") || t.Chip == "cpu_thermal" || t.Chip == "soc_thermal" {
|
t.Chip == "thermal" && strings.Contains(l, "cpu") || t.Chip == "cpu_thermal" || t.Chip == "soc_thermal" {
|
||||||
return t.C
|
if t.C > pkg {
|
||||||
|
pkg = t.C
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (t.Chip == "coretemp" || t.Chip == "k10temp") && t.C > max {
|
if (t.Chip == "coretemp" || t.Chip == "k10temp") && t.C > max {
|
||||||
max, best = t.C, t.C
|
max = t.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return best
|
if pkg > 0 {
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
return max
|
||||||
}
|
}
|
||||||
|
|
||||||
var bootTime = func() int64 {
|
var bootTime = func() int64 {
|
||||||
@@ -1323,6 +1379,7 @@ func (c *Collector) Processes() ProcTable {
|
|||||||
last := c.ring[n-1]
|
last := c.ring[n-1]
|
||||||
t.CPU, t.Cores, t.Net, t.CPUTemp, t.CPUPowerW = last.CPU, last.Cores, last.Net, cpuTemp(last.Temps), last.CPUPower
|
t.CPU, t.Cores, t.Net, t.CPUTemp, t.CPUPowerW = last.CPU, last.Cores, last.Net, cpuTemp(last.Temps), last.CPUPower
|
||||||
t.CoreIDs = coreIDs(len(last.Cores))
|
t.CoreIDs = coreIDs(len(last.Cores))
|
||||||
|
t.CorePkgs = corePkgs(len(last.Cores))
|
||||||
from := n - 90
|
from := n - 90
|
||||||
if from < 0 {
|
if from < 0 {
|
||||||
from = 0
|
from = 0
|
||||||
|
|||||||
+13
-4
@@ -158,6 +158,10 @@ export default function Hardware() {
|
|||||||
const capacity = drives.reduce((n, d) => n + d.size, 0)
|
const capacity = drives.reduce((n, d) => n + d.size, 0)
|
||||||
const threads = cpu.threads ?? 0
|
const threads = cpu.threads ?? 0
|
||||||
const cores = cpu.cores ?? 0
|
const cores = cpu.cores ?? 0
|
||||||
|
// a two-socket board is two processors, not one with twice the cores: count and name each
|
||||||
|
const sockets = Math.max(1, cpu.sockets ?? 1)
|
||||||
|
const multi = sockets > 1
|
||||||
|
const per = (n: number) => Math.round(n / sockets)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dash">
|
<div className="dash">
|
||||||
@@ -171,8 +175,11 @@ export default function Hardware() {
|
|||||||
<div className="kpis">
|
<div className="kpis">
|
||||||
<div className="tile">
|
<div className="tile">
|
||||||
<div className="label">Processor</div>
|
<div className="label">Processor</div>
|
||||||
<div className="value" title={cpu.model} style={(cpu.model?.length ?? 0) > 20 ? { fontSize: 16 } : undefined}>{cpu.model}</div>
|
<div className="value" title={cpu.model} style={(cpu.model?.length ?? 0) > (multi ? 16 : 20) ? { fontSize: 16 } : undefined}>{multi ? `${sockets} × ${cpu.model}` : cpu.model}</div>
|
||||||
<div className="sub">{[cores ? `${cores} core${cores === 1 ? '' : 's'}` : '', threads && threads !== cores ? `${threads} threads` : '', cpu.max_mhz ? `up to ${ghz(cpu.max_mhz)}` : ''].filter(Boolean).join(' · ')}</div>
|
<div className="sub">{(multi
|
||||||
|
? [`${sockets} CPUs`, cores ? `${per(cores)} cores${threads && threads !== cores ? ` / ${per(threads)} threads` : ''} each` : '', cpu.max_mhz ? `up to ${ghz(cpu.max_mhz)}` : '']
|
||||||
|
: [cores ? `${cores} core${cores === 1 ? '' : 's'}` : '', threads && threads !== cores ? `${threads} threads` : '', cpu.max_mhz ? `up to ${ghz(cpu.max_mhz)}` : '']
|
||||||
|
).filter(Boolean).join(' · ')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tile">
|
<div className="tile">
|
||||||
<div className="label">Memory</div>
|
<div className="label">Memory</div>
|
||||||
@@ -199,9 +206,11 @@ export default function Hardware() {
|
|||||||
<div className="props">
|
<div className="props">
|
||||||
<P k="Model" v={cpu.model} />
|
<P k="Model" v={cpu.model} />
|
||||||
<P k="Vendor" v={cpu.vendor} />
|
<P k="Vendor" v={cpu.vendor} />
|
||||||
<P k="Cores" v={cores ? `${cores}${cpu.threads_per_core && cpu.threads_per_core > 1 ? ` · ${cpu.threads_per_core} threads each` : ''}` : ''} />
|
{multi && <P k="CPUs" v={`${sockets}`} />}
|
||||||
|
<P k="Cores" v={cores ? `${multi ? `${per(cores)} per CPU · ${cores} in all` : cores}${cpu.threads_per_core && cpu.threads_per_core > 1 ? ` · ${cpu.threads_per_core} threads each` : ''}` : ''} />
|
||||||
|
{multi && threads > 0 && <P k="Threads" v={`${per(threads)} per CPU · ${threads} in all`} />}
|
||||||
<P k="Clock" v={cpu.min_mhz && cpu.max_mhz ? `${ghz(cpu.min_mhz)} – ${ghz(cpu.max_mhz)}` : ghz(cpu.max_mhz)} />
|
<P k="Clock" v={cpu.min_mhz && cpu.max_mhz ? `${ghz(cpu.min_mhz)} – ${ghz(cpu.max_mhz)}` : ghz(cpu.max_mhz)} />
|
||||||
<P k="Socket" v={(cpu.socket_names ?? []).join(', ')} />
|
<P k={multi ? 'Sockets' : 'Socket'} v={(cpu.socket_names ?? []).join(', ')} />
|
||||||
<P k="Arch" v={cpu.arch} />
|
<P k="Arch" v={cpu.arch} />
|
||||||
<P k="Family" v={[cpu.family && `family ${cpu.family}`, cpu.model_id && `model ${cpu.model_id}`, cpu.stepping && `stepping ${cpu.stepping}`].filter(Boolean).join(' · ')} />
|
<P k="Family" v={[cpu.family && `family ${cpu.family}`, cpu.model_id && `model ${cpu.model_id}`, cpu.stepping && `stepping ${cpu.stepping}`].filter(Boolean).join(' · ')} />
|
||||||
<P k="Microcode" v={cpu.microcode} mono />
|
<P k="Microcode" v={cpu.microcode} mono />
|
||||||
|
|||||||
+37
-15
@@ -3,7 +3,7 @@
|
|||||||
// as they climb. The numbers are Ham Lab's own — the sensor stream for everything measured and
|
// as they climb. The numbers are Ham Lab's own — the sensor stream for everything measured and
|
||||||
// /api/procs for the table — so this works on any machine the console can see, whether or not
|
// /api/procs for the table — so this works on any machine the console can see, whether or not
|
||||||
// btop is installed on it.
|
// btop is installed on it.
|
||||||
import { createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
import { Fragment, createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { api, GPU, ProcTable, Proc, Sample } from './api'
|
import { api, GPU, ProcTable, Proc, Sample } from './api'
|
||||||
import { Catalog, MONITOR_SHAPE, Values, mergeValues, sampleOf, stale } from './sampleAdapter'
|
import { Catalog, MONITOR_SHAPE, Values, mergeValues, sampleOf, stale } from './sampleAdapter'
|
||||||
import { fetchCatalog, fetchHistories, useSensorStream } from './sensors'
|
import { fetchCatalog, fetchHistories, useSensorStream } from './sensors'
|
||||||
@@ -360,7 +360,13 @@ export default function Top() {
|
|||||||
const model = (pt?.cpu_model ?? '').replace(/\(R\)|\(TM\)|CPU|Processor/g, '').replace(/@.*$/, '').replace(/^\d+th Gen\s*/i, '').replace(/\s+/g, ' ').trim()
|
const model = (pt?.cpu_model ?? '').replace(/\(R\)|\(TM\)|CPU|Processor/g, '').replace(/@.*$/, '').replace(/^\d+th Gen\s*/i, '').replace(/\s+/g, ' ').trim()
|
||||||
// the hottest thing that looks like a CPU sensor, and one per core where the chip names them;
|
// the hottest thing that looks like a CPU sensor, and one per core where the chip names them;
|
||||||
// a second thread of a core (CPU 8 on an 8-core chip with hyper-threading) wears its core's
|
// a second thread of a core (CPU 8 on an 8-core chip with hyper-threading) wears its core's
|
||||||
const coreTemp = (i: number) => { const id = pt?.core_ids?.[i] ?? i; return s?.temps.find((t) => new RegExp(`core ${id}\\b`, 'i').test(t.label))?.c }
|
// On a two-socket board each CPU has its own sensor chip, both counting from "Core 0", so a
|
||||||
|
// thread's core is looked up on its own socket's chip.
|
||||||
|
const pkgs = pt?.core_pkgs?.length === cores.length ? pt.core_pkgs : undefined
|
||||||
|
const sockets = pkgs ? [...new Set(pkgs)].sort((a, b) => a - b) : []
|
||||||
|
const coreTemp = (i: number) => { const id = pt?.core_ids?.[i] ?? i, pk = pkgs?.[i]; return s?.temps.find((t) => new RegExp(`core ${id}\\b`, 'i').test(t.label) && (pk === undefined || (t.pkg ?? 0) === pk))?.c }
|
||||||
|
const sockTemp = (pk: number) => s?.temps.find((t) => t.chip === 'coretemp' && new RegExp(`^package id ${pk}$`, 'i').test(t.label))?.c
|
||||||
|
const sockUse = (pk: number) => { const v = cores.filter((_, i) => pkgs?.[i] === pk); return v.length ? v.reduce((a, b) => a + b, 0) / v.length : 0 }
|
||||||
const cpuTemp = pt?.cpu_temp ?? s?.temps.find((t) => /package|tctl|tdie|cpu/i.test(t.label))?.c ?? coreTemp(0)
|
const cpuTemp = pt?.cpu_temp ?? s?.temps.find((t) => /package|tctl|tdie|cpu/i.test(t.label))?.c ?? coreTemp(0)
|
||||||
// the temperature columns are there for every row once any row has one, so the % column lines up
|
// the temperature columns are there for every row once any row has one, so the % column lines up
|
||||||
const anyTemp = cpuTemp !== undefined || cores.some((_, i) => coreTemp(i) !== undefined)
|
const anyTemp = cpuTemp !== undefined || cores.some((_, i) => coreTemp(i) !== undefined)
|
||||||
@@ -403,8 +409,8 @@ export default function Top() {
|
|||||||
<Graph values={curves.get('cpu.usage') ?? []} tick={tick} advance={advance} now={now} cap={cap} max={100} grad="cpu" />
|
<Graph values={curves.get('cpu.usage') ?? []} tick={tick} advance={advance} now={now} cap={cap} max={100} grad="cpu" />
|
||||||
<div className="t-up">up {upfor(uptime)}</div>
|
<div className="t-up">up {upfor(uptime)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="t-cores">
|
<div className={'t-cores' + (sockets.length > 1 ? ' multi' : '')}>
|
||||||
<div className="t-cores-head"><b>{model || 'cpu'}</b><span className="spacer" />{pt?.cpu_mhz ? `${Math.round(pt.cpu_mhz)} MHz` : ''}
|
<div className="t-cores-head"><b>{sockets.length > 1 ? `${sockets.length} × ` : ''}{model || 'cpu'}</b><span className="spacer" />{pt?.cpu_mhz ? `${Math.round(pt.cpu_mhz)} MHz` : ''}
|
||||||
{pt?.cpu_power_w ? <span title="the CPU's own power reading: cores, built-in graphics and memory controller, not the whole machine">{pt.cpu_mhz ? ' · ' : ''}{pt.cpu_power_w < 10 ? pt.cpu_power_w.toFixed(1) : Math.round(pt.cpu_power_w)} W</span> : null}</div>
|
{pt?.cpu_power_w ? <span title="the CPU's own power reading: cores, built-in graphics and memory controller, not the whole machine">{pt.cpu_mhz ? ' · ' : ''}{pt.cpu_power_w < 10 ? pt.cpu_power_w.toFixed(1) : Math.round(pt.cpu_power_w)} W</span> : null}</div>
|
||||||
<div className="t-core">
|
<div className="t-core">
|
||||||
<span className="t-core-n">CPU</span>
|
<span className="t-core-n">CPU</span>
|
||||||
@@ -412,17 +418,33 @@ export default function Top() {
|
|||||||
<span className="t-core-p">{Math.round(cpu)}%</span>
|
<span className="t-core-p">{Math.round(cpu)}%</span>
|
||||||
{anyTemp && <><span className="t-core-t">{cpuTemp !== undefined && <Meter pct={Math.min(100, (cpuTemp / 100) * 100)} grad="temp" />}</span><span className="t-core-c">{cpuTemp !== undefined ? `${Math.round(cpuTemp)}°C` : ''}</span></>}
|
{anyTemp && <><span className="t-core-t">{cpuTemp !== undefined && <Meter pct={Math.min(100, (cpuTemp / 100) * 100)} grad="temp" />}</span><span className="t-core-c">{cpuTemp !== undefined ? `${Math.round(cpuTemp)}°C` : ''}</span></>}
|
||||||
</div>
|
</div>
|
||||||
{cores.map((v, i) => {
|
{(sockets.length > 1 ? sockets : [undefined]).map((pk) => (
|
||||||
const t = coreTemp(i)
|
<Fragment key={pk ?? 'all'}>
|
||||||
return (
|
{pk !== undefined && (() => {
|
||||||
<div className="t-core" key={i}>
|
const u = sockUse(pk), t = sockTemp(pk)
|
||||||
<span className="t-core-n">C{i}</span>
|
return (
|
||||||
<span className="t-core-g" title={pt?.core_ids && pt.core_ids[i] !== i ? `cpu ${i} · a thread of core ${pt.core_ids[i]}` : `core ${i}`}>{braille(recent.get(`cpu.core.${i}.usage`) ?? [], 9)}</span>
|
<div className="t-core t-sock">
|
||||||
<span className="t-core-p">{Math.round(v)}%</span>
|
<span className="t-core-n">CPU{pk + 1}</span>
|
||||||
{anyTemp && <><span className="t-core-t">{t !== undefined && <Meter pct={Math.min(100, t)} grad="temp" />}</span><span className="t-core-c">{t !== undefined ? `${Math.round(t)}°C` : ''}</span></>}
|
<Meter pct={u} grad="cpu" />
|
||||||
</div>
|
<span className="t-core-p">{Math.round(u)}%</span>
|
||||||
)
|
{anyTemp && <><span className="t-core-t">{t !== undefined && <Meter pct={Math.min(100, t)} grad="temp" />}</span><span className="t-core-c">{t !== undefined ? `${Math.round(t)}°C` : ''}</span></>}
|
||||||
})}
|
</div>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
{cores.map((v, i) => {
|
||||||
|
if (pk !== undefined && pkgs?.[i] !== pk) return null
|
||||||
|
const t = coreTemp(i)
|
||||||
|
return (
|
||||||
|
<div className="t-core" key={i}>
|
||||||
|
<span className="t-core-n">C{i}</span>
|
||||||
|
<span className="t-core-g" title={(pk !== undefined ? `CPU ${pk + 1} · ` : '') + (pt?.core_ids && pt.core_ids[i] !== i ? `cpu ${i} · a thread of core ${pt.core_ids[i]}` : `core ${i}`)}>{braille(recent.get(`cpu.core.${i}.usage`) ?? [], 9)}</span>
|
||||||
|
<span className="t-core-p">{Math.round(v)}%</span>
|
||||||
|
{anyTemp && <><span className="t-core-t">{t !== undefined && <Meter pct={Math.min(100, t)} grad="temp" />}</span><span className="t-core-c">{t !== undefined ? `${Math.round(t)}°C` : ''}</span></>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
<div className="t-load">Load AVG:<span className="spacer" />{load.map((x) => x.toFixed(2)).join(' ')}</div>
|
<div className="t-load">Load AVG:<span className="spacer" />{load.map((x) => x.toFixed(2)).join(' ')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-2
@@ -71,7 +71,7 @@ export const titleOf = (name: string | null, machines: MachineStatus[], hubHost:
|
|||||||
export interface MachineStatus { name: string; url: string; fingerprint?: string; added: string; online: boolean; host?: string; version?: string; load?: string; uptime?: number; reboot_needed?: boolean; last_seen?: string; error?: string }
|
export interface MachineStatus { name: string; url: string; fingerprint?: string; added: string; online: boolean; host?: string; version?: string; load?: string; uptime?: number; reboot_needed?: boolean; last_seen?: string; error?: string }
|
||||||
export interface Term { id: string; name: string; running: boolean; exit_code: number; created: string; clients: number }
|
export interface Term { id: string; name: string; running: boolean; exit_code: number; created: string; clients: number }
|
||||||
|
|
||||||
export interface Temp { chip: string; label: string; c: number; max?: number; crit?: number }
|
export interface Temp { chip: string; label: string; c: number; max?: number; crit?: number; pkg?: number }
|
||||||
export interface Fan { chip: string; label: string; rpm: number }
|
export interface Fan { chip: string; label: string; rpm: number }
|
||||||
export interface NetIf { iface: string; up: boolean; rx_bps: number; tx_bps: number; rx_total: number; tx_total: number }
|
export interface NetIf { iface: string; up: boolean; rx_bps: number; tx_bps: number; rx_total: number; tx_total: number }
|
||||||
export interface Disk { name: string; model?: string; size: number; read_bps: number; write_bps: number; util_pct: number; temp?: number }
|
export interface Disk { name: string; model?: string; size: number; read_bps: number; write_bps: number; util_pct: number; temp?: number }
|
||||||
@@ -90,7 +90,7 @@ export interface GPU {
|
|||||||
mhz?: number; req_mhz?: number; max_mhz?: number; mem_used?: number; mem_total?: number
|
mhz?: number; req_mhz?: number; max_mhz?: number; mem_used?: number; mem_total?: number
|
||||||
clients: GPUClient[]; clients_ok: boolean; stalled?: boolean
|
clients: GPUClient[]; clients_ok: boolean; stalled?: boolean
|
||||||
}
|
}
|
||||||
export interface ProcTable { t: number; cpu: number; cores: number[]; cpu_hist: number[]; cpu_model?: string; cpu_mhz?: number; cpu_max_mhz?: number; cpu_power_w?: number; cpu_temp?: number; core_ids?: number[]; load: [number, number, number]; mem: MemInfo; swap: MemInfo; net: NetIf[]; gpus?: GPU[]; uptime: number; procs: Proc[]; total: number; running: number }
|
export interface ProcTable { t: number; cpu: number; cores: number[]; cpu_hist: number[]; cpu_model?: string; cpu_mhz?: number; cpu_max_mhz?: number; cpu_power_w?: number; cpu_temp?: number; core_ids?: number[]; core_pkgs?: number[]; load: [number, number, number]; mem: MemInfo; swap: MemInfo; net: NetIf[]; gpus?: GPU[]; uptime: number; procs: Proc[]; total: number; running: number }
|
||||||
export interface Sample {
|
export interface Sample {
|
||||||
t: number; interval_ms: number; cpu: number; cores: number[]; load: [number, number, number]
|
t: number; interval_ms: number; cpu: number; cores: number[]; load: [number, number, number]
|
||||||
mem: { total: number; used: number; avail: number }; swap: { total: number; used: number; avail: number }
|
mem: { total: number; used: number; avail: number }; swap: { total: number; used: number; avail: number }
|
||||||
|
|||||||
@@ -794,6 +794,8 @@ pre.diff .add { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%,
|
|||||||
.t-cores-head b { color: var(--fg); font-weight: 400; overflow: hidden; text-overflow: ellipsis; }
|
.t-cores-head b { color: var(--fg); font-weight: 400; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.t-core { display: flex; align-items: center; gap: 6px; }
|
.t-core { display: flex; align-items: center; gap: 6px; }
|
||||||
.t-core-n { width: 30px; } .t-core-p { width: 34px; text-align: right; }
|
.t-core-n { width: 30px; } .t-core-p { width: 34px; text-align: right; }
|
||||||
|
.t-cores.multi .t-core-n { width: 38px; } /* "CPU2" — a two-socket board has a row per socket above its threads */
|
||||||
|
.t-sock { color: var(--fg); margin-top: 2px; }
|
||||||
.t-core-g { flex: 1; color: var(--ok); letter-spacing: 0; overflow: hidden; white-space: nowrap; }
|
.t-core-g { flex: 1; color: var(--ok); letter-spacing: 0; overflow: hidden; white-space: nowrap; }
|
||||||
.t-core .tm { flex: 1; } .t-core-t { flex: 0 0 62px; display: flex; } .t-core-c { width: 42px; text-align: right; color: var(--t-blue); }
|
.t-core .tm { flex: 1; } .t-core-t { flex: 0 0 62px; display: flex; } .t-core-c { width: 42px; text-align: right; color: var(--t-blue); }
|
||||||
.t-load { display: flex; margin-top: 2px; }
|
.t-load { display: flex; margin-top: 2px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user