mirror of
https://github.com/henrygd/beszel.git
synced 2026-10-22 06:12:33 +00:00
feat: show CPU socket count on the system page (#2646)
This commit is contained in:
@@ -84,6 +84,7 @@ func (a *Agent) refreshSystemDetails() {
|
||||
// cpu model
|
||||
if info, err := cpu.Info(); err == nil && len(info) > 0 {
|
||||
a.systemDetails.CpuModel = info[0].ModelName
|
||||
a.systemDetails.Sockets = cpuSockets(info)
|
||||
}
|
||||
// gopsutil doesn't parse the "cpu model" field from /proc/cpuinfo, which
|
||||
// is the only source of the CPU model name on MIPS. Fall back to reading
|
||||
@@ -277,6 +278,25 @@ func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
|
||||
// on various embedded architectures.
|
||||
var cpuModelFallbackKeys = []string{"cpu model", "system type"}
|
||||
|
||||
// cpuSockets returns the number of physical CPU packages, or 0 if unknown.
|
||||
// PhysicalID is only a socket id on x86 Linux; on Windows gopsutil returns
|
||||
// one entry per package.
|
||||
func cpuSockets(info []cpu.InfoStat) int {
|
||||
switch {
|
||||
case runtime.GOOS == "windows":
|
||||
return len(info)
|
||||
case runtime.GOOS == "linux" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "386"):
|
||||
ids := make(map[string]struct{})
|
||||
for _, c := range info {
|
||||
if c.PhysicalID != "" {
|
||||
ids[c.PhysicalID] = struct{}{}
|
||||
}
|
||||
}
|
||||
return len(ids)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// getCpuModelFromCpuinfo reads /proc/cpuinfo and returns a CPU model string.
|
||||
// This is a fallback for architectures where gopsutil's cpu.Info() does not
|
||||
// populate ModelName, most notably MIPS.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/henrygd/beszel/internal/common"
|
||||
"github.com/henrygd/beszel/internal/entities/system"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -192,3 +194,18 @@ BogoMIPS : 300.00`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCpuSockets(t *testing.T) {
|
||||
// two packages with several logical CPUs each, as gopsutil reports on x86 Linux
|
||||
dual := []cpu.InfoStat{{PhysicalID: "0"}, {PhysicalID: "0"}, {PhysicalID: "1"}, {PhysicalID: "1"}}
|
||||
got := cpuSockets(dual)
|
||||
switch {
|
||||
case runtime.GOOS == "windows":
|
||||
assert.Equal(t, 4, got)
|
||||
case runtime.GOOS == "linux" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "386"):
|
||||
assert.Equal(t, 2, got)
|
||||
assert.Equal(t, 0, cpuSockets([]cpu.InfoStat{{}, {}}), "empty PhysicalID is unknown")
|
||||
default:
|
||||
assert.Equal(t, 0, got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@ type Details struct {
|
||||
MemoryTotal uint64 `cbor:"9,keyasint"`
|
||||
SmartInterval time.Duration `cbor:"10,keyasint,omitempty"`
|
||||
ZfsInterval time.Duration `cbor:"11,keyasint,omitempty"` // interval for ZFS detail refresh
|
||||
Sockets int `cbor:"12,keyasint,omitempty"` // physical CPU packages, 0 if unknown
|
||||
}
|
||||
|
||||
// Final data structure to return to the hub
|
||||
|
||||
@@ -381,6 +381,7 @@ func createSystemDetailsRecord(app core.App, data *system.Details, systemId stri
|
||||
"kernel": data.Kernel,
|
||||
"cores": data.Cores,
|
||||
"threads": data.Threads,
|
||||
"sockets": data.Sockets,
|
||||
"cpu": data.CpuModel,
|
||||
"os": data.Os,
|
||||
"os_name": data.OsName,
|
||||
|
||||
@@ -259,3 +259,12 @@ func TestSSHFallbackDialsAgentUnlessDisabled(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRecordsSavesCpuSockets(t *testing.T) {
|
||||
sys, app := newTestSystemWithHub(t)
|
||||
_, err := sys.createRecords(&system.CombinedData{Details: &system.Details{Hostname: "host", Sockets: 2}})
|
||||
require.NoError(t, err)
|
||||
record, err := app.FindRecordById("system_details", sys.Id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, record.GetInt("sockets"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("system_details")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Fields.Add(&core.NumberField{Id: "system_details_sockets", Name: "sockets", OnlyInt: true})
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("system_details")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Fields.RemoveByName("sockets")
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
@@ -61,6 +61,7 @@ export default function InfoBar({
|
||||
const cores = details?.cores ?? system.info.c
|
||||
const threads = details?.threads ?? system.info.t ?? 0
|
||||
const cpuModel = details?.cpu ?? system.info.m
|
||||
const sockets = details?.sockets ?? 0
|
||||
const os = details?.os ?? system.info.os ?? Os.Linux
|
||||
const osName = details?.os_name
|
||||
const arch = details?.arch
|
||||
@@ -101,7 +102,8 @@ export default function InfoBar({
|
||||
{ value: secondsToUptimeString(system.info.u), Icon: ClockArrowUp, label: t`Uptime`, hide: !system.info.u },
|
||||
osInfo[os],
|
||||
{
|
||||
value: cpuModel,
|
||||
// 0 means the agent did not report a socket count
|
||||
value: sockets > 1 ? `${sockets}x ${cpuModel}` : cpuModel,
|
||||
Icon: CpuIcon,
|
||||
hide: !cpuModel,
|
||||
label: `${plural(cores, { one: "# core", other: "# cores" })} / ${plural(threads, { one: "# thread", other: "# threads" })}${arch ? ` / ${arch}` : ""}`,
|
||||
|
||||
@@ -132,7 +132,7 @@ export function useSystemData(id: string) {
|
||||
}
|
||||
pb.collection<SystemDetailsRecord>("system_details")
|
||||
.getOne(system.id, {
|
||||
fields: "hostname,kernel,cores,threads,cpu,os,os_name,arch,memory,podman",
|
||||
fields: "hostname,kernel,cores,threads,sockets,cpu,os,os_name,arch,memory,podman",
|
||||
headers: {
|
||||
"Cache-Control": "public, max-age=60",
|
||||
},
|
||||
|
||||
Vendored
+2
@@ -511,6 +511,8 @@ export interface SystemDetailsRecord extends RecordModel {
|
||||
kernel: string
|
||||
cores: number
|
||||
threads: number
|
||||
/** physical CPU packages, 0 if unknown */
|
||||
sockets?: number
|
||||
cpu: string
|
||||
os: Os
|
||||
os_name: string
|
||||
|
||||
Reference in New Issue
Block a user