Top: each CPU's threads are counted from 0 under it on a two-socket machine
The kernel numbers the whole machine's threads in one run and deals them out a socket at a time, twice: nexus's first CPU listed C0–C9 and C20–C29, its second C10–C19 and C30–C39. Each CPU now lists its own twenty as C0–C19; the kernel's number is in the row's tooltip. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
0e57419d8b
commit
c40de5067b
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc --noEmit && vite build",
|
"build": "tsc --noEmit && vite build",
|
||||||
"icons": "node scripts/icons.mjs",
|
"icons": "node scripts/icons.mjs",
|
||||||
"test": "node scripts/test-graphcols.mjs && node scripts/test-css-imports.mjs && node scripts/test-resolve-path.mjs"
|
"test": "node scripts/test-graphcols.mjs && node scripts/test-sockets.mjs && node scripts/test-css-imports.mjs && node scripts/test-resolve-path.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// node scripts/test-sockets.mjs — src/sockets.ts: each CPU's threads, counted under it.
|
||||||
|
import { buildSync } from 'esbuild'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import { mkdtempSync } from 'node:fs'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
const out = join(mkdtempSync(join(tmpdir(), 'sockets-')), 'sockets.mjs')
|
||||||
|
buildSync({ entryPoints: [new URL('../src/sockets.ts', import.meta.url).pathname], outfile: out, format: 'esm', logLevel: 'error' })
|
||||||
|
const { socketThreads } = await import(pathToFileURL(out).href)
|
||||||
|
|
||||||
|
const fails = []
|
||||||
|
const eq = (got, want, what) => { if (JSON.stringify(got) !== JSON.stringify(want)) fails.push(`${what} — got ${JSON.stringify(got)}, want ${JSON.stringify(want)}`) }
|
||||||
|
const range = (a, b) => Array.from({ length: b - a }, (_, i) => a + i)
|
||||||
|
|
||||||
|
// nexus: 2 × E5-2680 v2, as /sys/devices/system/cpu/cpu*/topology/physical_package_id reads
|
||||||
|
const nexus = [...Array(10).fill(0), ...Array(10).fill(1), ...Array(10).fill(0), ...Array(10).fill(1)]
|
||||||
|
const got = socketThreads(nexus)
|
||||||
|
eq(got.map((s) => s.pkg), [0, 1], 'two sockets, in order')
|
||||||
|
eq(got[0].cpus, [...range(0, 10), ...range(20, 30)], "CPU 1's threads")
|
||||||
|
eq(got[1].cpus, [...range(10, 20), ...range(30, 40)], "CPU 2's threads")
|
||||||
|
eq(got.map((s) => s.cpus.length), [20, 20], 'twenty threads each')
|
||||||
|
// every thread is under exactly one CPU
|
||||||
|
eq(got.flatMap((s) => s.cpus).sort((a, b) => a - b), range(0, 40), 'every thread once')
|
||||||
|
// a thread's number under its CPU runs 0…19 on both
|
||||||
|
for (const s of got) eq(s.cpus.map((_, n) => n), range(0, 20), `CPU ${s.pkg + 1} counts from 0`)
|
||||||
|
|
||||||
|
// a board that numbers each socket's threads together, and one whose socket ids are not 0 and 1
|
||||||
|
eq(socketThreads([0, 0, 0, 0, 1, 1, 1, 1]), [{ pkg: 0, cpus: [0, 1, 2, 3] }, { pkg: 1, cpus: [4, 5, 6, 7] }], 'contiguous numbering')
|
||||||
|
eq(socketThreads([3, 1, 3, 1]), [{ pkg: 1, cpus: [1, 3] }, { pkg: 3, cpus: [0, 2] }], 'socket ids sorted')
|
||||||
|
eq(socketThreads([]), [], 'nothing')
|
||||||
|
|
||||||
|
if (fails.length) { console.error('sockets: FAIL\n ' + fails.join('\n ')); process.exit(1) }
|
||||||
|
console.log('sockets: ok')
|
||||||
+11
-7
@@ -11,6 +11,7 @@ import { fmtMs, nextPoint, REFRESH_OPTIONS, setRefreshMs, strideMs, useHistorySe
|
|||||||
import { Loading, askYes, toast } from './ui'
|
import { Loading, askYes, toast } from './ui'
|
||||||
import { Theme, currentThemeId, themeById } from './themes'
|
import { Theme, currentThemeId, themeById } from './themes'
|
||||||
import { columnsAt } from './graphcols'
|
import { columnsAt } from './graphcols'
|
||||||
|
import { socketThreads } from './sockets'
|
||||||
import { savePrefs } from './accents'
|
import { savePrefs } from './accents'
|
||||||
|
|
||||||
const MAX = 300 // points kept per curve, as the machines keep them
|
const MAX = 300 // points kept per curve, as the machines keep them
|
||||||
@@ -370,7 +371,10 @@ export default function Top() {
|
|||||||
// On a two-socket board each CPU has its own sensor chip, both counting from "Core 0", so a
|
// 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.
|
// 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 pkgs = pt?.core_pkgs?.length === cores.length ? pt.core_pkgs : undefined
|
||||||
const sockets = pkgs ? [...new Set(pkgs)].sort((a, b) => a - b) : []
|
// each CPU's own threads, counted from 0 under it (sockets.ts): the kernel's one numbering
|
||||||
|
// of the whole machine puts cpu 0–9 and 20–29 on the first and 10–19 and 30–39 on the second
|
||||||
|
const socks = pkgs ? socketThreads(pkgs) : []
|
||||||
|
const sockets = socks.map((x) => x.pkg)
|
||||||
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 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 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 sockUse = (pk: number) => { const v = cores.filter((_, i) => pkgs?.[i] === pk); return v.length ? v.reduce((a, b) => a + b, 0) / v.length : 0 }
|
||||||
@@ -425,7 +429,7 @@ 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>}
|
||||||
{(sockets.length > 1 ? sockets : [undefined]).map((pk) => (
|
{(sockets.length > 1 ? socks : [{ pkg: undefined, cpus: cores.map((_, i) => i) }]).map(({ pkg: pk, cpus }) => (
|
||||||
<Fragment key={pk ?? 'all'}>
|
<Fragment key={pk ?? 'all'}>
|
||||||
{pk !== undefined && (() => {
|
{pk !== undefined && (() => {
|
||||||
const u = sockUse(pk), t = sockTemp(pk), w = pt?.cpu_power_pkgs_w?.[pk], open = !folded.includes(pk)
|
const u = sockUse(pk), t = sockTemp(pk), w = pt?.cpu_power_pkgs_w?.[pk], open = !folded.includes(pk)
|
||||||
@@ -439,13 +443,13 @@ export default function Top() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
{cores.map((v, i) => {
|
{(pk !== undefined && folded.includes(pk) ? [] : cpus).map((i, n) => {
|
||||||
if (pk !== undefined && (pkgs?.[i] !== pk || folded.includes(pk))) return null
|
// n is the thread's number under its own CPU, i the kernel's for the whole machine
|
||||||
const t = coreTemp(i)
|
const t = coreTemp(i), v = cores[i] ?? 0
|
||||||
return (
|
return (
|
||||||
<div className="t-core" key={i}>
|
<div className="t-core" key={i}>
|
||||||
<span className="t-core-n">C{i}</span>
|
<span className="t-core-n">C{n}</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-g" title={pk !== undefined ? `CPU ${pk + 1} · thread ${n} · cpu ${i} to the kernel${pt?.core_ids ? ` · a thread of core ${pt.core_ids[i]}` : ''}` : 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>
|
<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></>}
|
{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>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Which threads belong to which CPU, on a machine with more than one.
|
||||||
|
//
|
||||||
|
// The kernel numbers every thread of the machine in one run, and on a two-socket board with
|
||||||
|
// hyper-threading it deals them out a socket at a time and then goes round again: cpu 0–9 are
|
||||||
|
// CPU 1's cores, 10–19 CPU 2's, 20–29 the second threads of CPU 1's, 30–39 of CPU 2's. Listed
|
||||||
|
// under its CPU by that number, a 20-thread processor reads "C0…C9, C20…C29" and the one
|
||||||
|
// beside it "C10…C19, C30…C39" — neither looks like twenty of anything. So each CPU's threads
|
||||||
|
// are counted from 0 under it, in the kernel's order, the way a one-socket machine's are: the
|
||||||
|
// first half are its cores, the second half their second threads.
|
||||||
|
//
|
||||||
|
// `pkgs[i]` is the socket logical CPU `i` is on. The answer is, for each socket (ascending),
|
||||||
|
// the logical CPUs on it (ascending); a thread's number under its CPU is its place in that list.
|
||||||
|
export function socketThreads(pkgs: number[]): { pkg: number; cpus: number[] }[] {
|
||||||
|
const by = new Map<number, number[]>()
|
||||||
|
pkgs.forEach((pk, i) => { const l = by.get(pk); if (l) l.push(i); else by.set(pk, [i]) })
|
||||||
|
return [...by.entries()].sort((a, b) => a[0] - b[0]).map(([pkg, cpus]) => ({ pkg, cpus }))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user