Disks: one card per physical drive (NVMe namespaces folded under their controller), partitions summarised on one line instead of listed
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
a372378098
commit
0af24f41c3
+95
-12
@@ -35,17 +35,18 @@ type Part struct {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"` // nvme | ssd | hdd | usb | other
|
||||
Model string `json:"model,omitempty"`
|
||||
Vendor string `json:"vendor,omitempty"`
|
||||
Serial string `json:"serial,omitempty"`
|
||||
Tran string `json:"tran,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Rotational bool `json:"rotational"`
|
||||
Removable bool `json:"removable"`
|
||||
Temp float64 `json:"temp,omitempty"`
|
||||
Parts []Part `json:"parts"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"` // nvme | ssd | hdd | usb | other
|
||||
Model string `json:"model,omitempty"`
|
||||
Vendor string `json:"vendor,omitempty"`
|
||||
Serial string `json:"serial,omitempty"`
|
||||
Tran string `json:"tran,omitempty"`
|
||||
Size uint64 `json:"size"`
|
||||
Rotational bool `json:"rotational"`
|
||||
Removable bool `json:"removable"`
|
||||
Temp float64 `json:"temp,omitempty"`
|
||||
Parts []Part `json:"parts"`
|
||||
Namespaces []string `json:"namespaces,omitempty"` // NVMe: the block devices behind this controller
|
||||
}
|
||||
|
||||
type lsblkNode struct {
|
||||
@@ -121,10 +122,89 @@ func Devices(ctx context.Context) ([]Device, error) {
|
||||
walk(n.Children)
|
||||
devs = append(devs, d)
|
||||
}
|
||||
devs = foldNVMe(devs)
|
||||
sort.Slice(devs, func(i, j int) bool { return devs[i].Name < devs[j].Name })
|
||||
return devs, nil
|
||||
}
|
||||
|
||||
var nsRe = regexp.MustCompile(`^(nvme\d+)n\d+$`)
|
||||
|
||||
// foldNVMe merges nvme0n1, nvme0n2, … into one physical device "nvme0": one SSD, one
|
||||
// SMART record, however many namespaces the controller presents (Apple exposes three).
|
||||
func foldNVMe(devs []Device) []Device {
|
||||
byCtrl := map[string]*Device{}
|
||||
var out []Device
|
||||
for _, d := range devs {
|
||||
m := nsRe.FindStringSubmatch(d.Name)
|
||||
if m == nil {
|
||||
out = append(out, d)
|
||||
continue
|
||||
}
|
||||
ctrl := m[1]
|
||||
c, ok := byCtrl[ctrl]
|
||||
if !ok {
|
||||
nd := d
|
||||
nd.Name = ctrl
|
||||
nd.Parts = []Part{}
|
||||
nd.Namespaces = nil
|
||||
if mdl := strings.TrimSpace(readStrFile("/sys/class/nvme/" + ctrl + "/model")); mdl != "" {
|
||||
nd.Model = mdl
|
||||
}
|
||||
if sn := strings.TrimSpace(readStrFile("/sys/class/nvme/" + ctrl + "/serial")); sn != "" {
|
||||
nd.Serial = sn
|
||||
}
|
||||
if t := ctrlTemp(ctrl); t > 0 {
|
||||
nd.Temp = t
|
||||
}
|
||||
nd.Size = 0
|
||||
c = &nd
|
||||
byCtrl[ctrl] = c
|
||||
out = append(out, Device{}) // placeholder keeps order; filled below
|
||||
c.Namespaces = []string{}
|
||||
}
|
||||
c.Size += d.Size
|
||||
c.Namespaces = append(c.Namespaces, d.Name)
|
||||
c.Parts = append(c.Parts, d.Parts...)
|
||||
}
|
||||
// replace placeholders with the folded controllers, in first-seen order
|
||||
res := out[:0]
|
||||
seen := map[string]bool{}
|
||||
for _, d := range out {
|
||||
if d.Name != "" {
|
||||
res = append(res, d)
|
||||
continue
|
||||
}
|
||||
for _, c := range byCtrl {
|
||||
if !seen[c.Name] {
|
||||
seen[c.Name] = true
|
||||
res = append(res, *c)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func readStrFile(p string) string {
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func ctrlTemp(ctrl string) float64 {
|
||||
dirs, _ := filepath.Glob("/sys/class/nvme/" + ctrl + "/hwmon*")
|
||||
for _, d := range dirs {
|
||||
if b, err := os.ReadFile(filepath.Join(d, "temp1_input")); err == nil {
|
||||
if v, err := strconv.Atoi(strings.TrimSpace(string(b))); err == nil {
|
||||
return float64(v) / 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func blockTemp(name string) float64 {
|
||||
for _, pat := range []string{"/sys/block/" + name + "/device/hwmon*", "/sys/block/" + name + "/device/hwmon/hwmon*"} {
|
||||
dirs, _ := filepath.Glob(pat)
|
||||
@@ -145,7 +225,10 @@ func validDevice(name string) bool {
|
||||
if !devRe.MatchString(name) {
|
||||
return false
|
||||
}
|
||||
_, err := os.Stat("/sys/block/" + name)
|
||||
if _, err := os.Stat("/sys/block/" + name); err == nil {
|
||||
return true
|
||||
}
|
||||
_, err := os.Stat("/sys/class/nvme/" + name) // NVMe controller (nvme0), which is what smartctl wants anyway
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user