package platform import ( "encoding/json" "os" "os/exec" "path/filepath" "sort" "strings" ) var techDumpFixedCommands = []struct { Name string Args []string File string }{ {Name: "dmidecode", Args: []string{"-t", "0"}, File: "dmidecode-type0.txt"}, {Name: "dmidecode", Args: []string{"-t", "1"}, File: "dmidecode-type1.txt"}, {Name: "dmidecode", Args: []string{"-t", "2"}, File: "dmidecode-type2.txt"}, {Name: "dmidecode", Args: []string{"-t", "4"}, File: "dmidecode-type4.txt"}, {Name: "dmidecode", Args: []string{"-t", "17"}, File: "dmidecode-type17.txt"}, {Name: "lspci", Args: []string{"-vmm", "-D"}, File: "lspci-vmm.txt"}, {Name: "lsblk", Args: []string{"-J", "-d", "-o", "NAME,TYPE,SIZE,SERIAL,MODEL,TRAN,HCTL"}, File: "lsblk.json"}, {Name: "sensors", Args: []string{"-j"}, File: "sensors.json"}, {Name: "ipmitool", Args: []string{"fru", "print"}, File: "ipmitool-fru.txt"}, {Name: "ipmitool", Args: []string{"sdr"}, File: "ipmitool-sdr.txt"}, {Name: "nvidia-smi", Args: []string{"-q"}, File: "nvidia-smi-q.txt"}, {Name: "nvidia-smi", Args: []string{"--query-gpu=index,pci.bus_id,serial,vbios_version,temperature.gpu,power.draw,ecc.errors.uncorrected.aggregate.total,ecc.errors.corrected.aggregate.total,clocks_throttle_reasons.hw_slowdown", "--format=csv,noheader,nounits"}, File: "nvidia-smi-query.csv"}, {Name: "nvme", Args: []string{"list", "-o", "json"}, File: "nvme-list.json"}, } type lsblkDumpRoot struct { Blockdevices []struct { Name string `json:"name"` Type string `json:"type"` } `json:"blockdevices"` } type nvmeDumpRoot struct { Devices []struct { DevicePath string `json:"DevicePath"` } `json:"Devices"` } func (s *System) CaptureTechnicalDump(baseDir string) error { if err := os.MkdirAll(baseDir, 0755); err != nil { return err } for _, cmd := range techDumpFixedCommands { writeCommandDump(filepath.Join(baseDir, cmd.File), cmd.Name, cmd.Args...) } for _, dev := range lsblkDumpDevices(filepath.Join(baseDir, "lsblk.json")) { writeCommandDump(filepath.Join(baseDir, "smartctl-"+sanitizeDumpName(dev)+".json"), "smartctl", "-j", "-a", "/dev/"+dev) } for _, dev := range nvmeDumpDevices(filepath.Join(baseDir, "nvme-list.json")) { writeCommandDump(filepath.Join(baseDir, "nvme-id-ctrl-"+sanitizeDumpName(dev)+".json"), "nvme", "id-ctrl", dev, "-o", "json") writeCommandDump(filepath.Join(baseDir, "nvme-smart-log-"+sanitizeDumpName(dev)+".json"), "nvme", "smart-log", dev, "-o", "json") } return nil } func writeCommandDump(path, name string, args ...string) { out, err := exec.Command(name, args...).CombinedOutput() if err != nil && len(out) == 0 { return } _ = os.WriteFile(path, out, 0644) } func lsblkDumpDevices(path string) []string { raw, err := os.ReadFile(path) if err != nil { return nil } var root lsblkDumpRoot if err := json.Unmarshal(raw, &root); err != nil { return nil } var devices []string for _, dev := range root.Blockdevices { if dev.Type == "disk" && strings.TrimSpace(dev.Name) != "" { devices = append(devices, strings.TrimSpace(dev.Name)) } } sort.Strings(devices) return devices } func nvmeDumpDevices(path string) []string { raw, err := os.ReadFile(path) if err != nil { return nil } var root nvmeDumpRoot if err := json.Unmarshal(raw, &root); err != nil { return nil } seen := map[string]bool{} var devices []string for _, dev := range root.Devices { name := strings.TrimSpace(dev.DevicePath) if name == "" || seen[name] { continue } seen[name] = true devices = append(devices, name) } sort.Strings(devices) return devices } func sanitizeDumpName(value string) string { value = strings.TrimSpace(value) value = strings.TrimPrefix(value, "/dev/") value = strings.ReplaceAll(value, "/", "_") if value == "" { return "unknown" } return value }