mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:04:42 +00:00
Reconstruct the structure of go
This commit is contained in:
199
cmd/status/metrics_disk.go
Normal file
199
cmd/status/metrics_disk.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
)
|
||||
|
||||
var skipDiskMounts = map[string]bool{
|
||||
"/System/Volumes/VM": true,
|
||||
"/System/Volumes/Preboot": true,
|
||||
"/System/Volumes/Update": true,
|
||||
"/System/Volumes/xarts": true,
|
||||
"/System/Volumes/Hardware": true,
|
||||
"/System/Volumes/Data": true,
|
||||
"/dev": true,
|
||||
}
|
||||
|
||||
func collectDisks() ([]DiskStatus, error) {
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
disks []DiskStatus
|
||||
seenDevice = make(map[string]bool)
|
||||
seenVolume = make(map[string]bool)
|
||||
)
|
||||
for _, part := range partitions {
|
||||
if strings.HasPrefix(part.Device, "/dev/loop") {
|
||||
continue
|
||||
}
|
||||
if skipDiskMounts[part.Mountpoint] {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(part.Mountpoint, "/System/Volumes/") {
|
||||
continue
|
||||
}
|
||||
// Skip private volumes
|
||||
if strings.HasPrefix(part.Mountpoint, "/private/") {
|
||||
continue
|
||||
}
|
||||
baseDevice := baseDeviceName(part.Device)
|
||||
if baseDevice == "" {
|
||||
baseDevice = part.Device
|
||||
}
|
||||
if seenDevice[baseDevice] {
|
||||
continue
|
||||
}
|
||||
usage, err := disk.Usage(part.Mountpoint)
|
||||
if err != nil || usage.Total == 0 {
|
||||
continue
|
||||
}
|
||||
// Skip small volumes (< 1GB)
|
||||
if usage.Total < 1<<30 {
|
||||
continue
|
||||
}
|
||||
// For APFS volumes, use a more precise dedup key (bytes level)
|
||||
// to handle shared storage pools properly
|
||||
volKey := fmt.Sprintf("%s:%d", part.Fstype, usage.Total)
|
||||
if seenVolume[volKey] {
|
||||
continue
|
||||
}
|
||||
disks = append(disks, DiskStatus{
|
||||
Mount: part.Mountpoint,
|
||||
Device: part.Device,
|
||||
Used: usage.Used,
|
||||
Total: usage.Total,
|
||||
UsedPercent: usage.UsedPercent,
|
||||
Fstype: part.Fstype,
|
||||
})
|
||||
seenDevice[baseDevice] = true
|
||||
seenVolume[volKey] = true
|
||||
}
|
||||
|
||||
annotateDiskTypes(disks)
|
||||
|
||||
sort.Slice(disks, func(i, j int) bool {
|
||||
return disks[i].Total > disks[j].Total
|
||||
})
|
||||
|
||||
if len(disks) > 3 {
|
||||
disks = disks[:3]
|
||||
}
|
||||
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
func annotateDiskTypes(disks []DiskStatus) {
|
||||
if len(disks) == 0 || runtime.GOOS != "darwin" || !commandExists("diskutil") {
|
||||
return
|
||||
}
|
||||
cache := make(map[string]bool)
|
||||
for i := range disks {
|
||||
base := baseDeviceName(disks[i].Device)
|
||||
if base == "" {
|
||||
base = disks[i].Device
|
||||
}
|
||||
if val, ok := cache[base]; ok {
|
||||
disks[i].External = val
|
||||
continue
|
||||
}
|
||||
external, err := isExternalDisk(base)
|
||||
if err != nil {
|
||||
external = strings.HasPrefix(disks[i].Mount, "/Volumes/")
|
||||
}
|
||||
disks[i].External = external
|
||||
cache[base] = external
|
||||
}
|
||||
}
|
||||
|
||||
func baseDeviceName(device string) string {
|
||||
device = strings.TrimPrefix(device, "/dev/")
|
||||
if !strings.HasPrefix(device, "disk") {
|
||||
return device
|
||||
}
|
||||
for i := 4; i < len(device); i++ {
|
||||
if device[i] == 's' {
|
||||
return device[:i]
|
||||
}
|
||||
}
|
||||
return device
|
||||
}
|
||||
|
||||
func isExternalDisk(device string) (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := runCmd(ctx, "diskutil", "info", device)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var (
|
||||
found bool
|
||||
external bool
|
||||
)
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
trim := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trim, "Internal:") {
|
||||
found = true
|
||||
external = strings.Contains(trim, "No")
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(trim, "Device Location:") {
|
||||
found = true
|
||||
external = strings.Contains(trim, "External")
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false, errors.New("diskutil info missing Internal field")
|
||||
}
|
||||
return external, nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectDiskIO(now time.Time) DiskIOStatus {
|
||||
counters, err := disk.IOCounters()
|
||||
if err != nil || len(counters) == 0 {
|
||||
return DiskIOStatus{}
|
||||
}
|
||||
|
||||
var total disk.IOCountersStat
|
||||
for _, v := range counters {
|
||||
total.ReadBytes += v.ReadBytes
|
||||
total.WriteBytes += v.WriteBytes
|
||||
}
|
||||
|
||||
if c.lastDiskAt.IsZero() {
|
||||
c.prevDiskIO = total
|
||||
c.lastDiskAt = now
|
||||
return DiskIOStatus{}
|
||||
}
|
||||
|
||||
elapsed := now.Sub(c.lastDiskAt).Seconds()
|
||||
if elapsed <= 0 {
|
||||
elapsed = 1
|
||||
}
|
||||
|
||||
readRate := float64(total.ReadBytes-c.prevDiskIO.ReadBytes) / 1024 / 1024 / elapsed
|
||||
writeRate := float64(total.WriteBytes-c.prevDiskIO.WriteBytes) / 1024 / 1024 / elapsed
|
||||
|
||||
c.prevDiskIO = total
|
||||
c.lastDiskAt = now
|
||||
|
||||
if readRate < 0 {
|
||||
readRate = 0
|
||||
}
|
||||
if writeRate < 0 {
|
||||
writeRate = 0
|
||||
}
|
||||
|
||||
return DiskIOStatus{ReadRate: readRate, WriteRate: writeRate}
|
||||
}
|
||||
Reference in New Issue
Block a user