package drivers import ( "bufio" "fmt" "os/exec" "strconv" "strings" ) type DmiDecodeDriver struct{} func NewDmiDecodeDriver() (*DmiDecodeDriver, error) { r := DmiDecodeDriver{} _, err := r.Read() return &r, err } func (r *DmiDecodeDriver) Render(console *strings.Builder) error { h, err := r.Read() if err != nil { return err } hardware := h.(MemoryHardware) for i := range len(hardware.ModelNames) { fmt.Fprintf(console, "--- RAM Module %d ---\n", i) modelName := hardware.ModelNames[i] fmt.Fprintf(console, "%-16s | %-16s \033[K\n", "Model Name", modelName) generation := hardware.Generations[i] speed := hardware.Speeds[i] fmt.Fprintf(console, "%-16s | %s@%s \033[K\n", "Speed", generation, speed) formFactor := hardware.FormFactors[i] fmt.Fprintf(console, "%-16s | %s \033[K\n", "Form Factor", formFactor) } return nil } // Parses dmidecode to extract physical hardware limits, slot counts, and module details func (r *DmiDecodeDriver) Read() (any, error) { hw := MemoryHardware{} // Query Memory Array (Type 16) for Max Capacity and Slot Count arrayCmd := exec.Command("dmidecode", "-t", "16") arrayOutput, err := arrayCmd.Output() if err != nil { return hw, fmt.Errorf("dmidecode -t 16 failed: %w", err) } // Query Memory Device (Type 17) for active slots, speed, type, form factor, and model name deviceCmd := exec.Command("dmidecode", "-t", "17") deviceOutput, err := deviceCmd.Output() if err != nil { return hw, fmt.Errorf("dmidecode -t 17 failed: %w", err) } // Parse Type 16 Output scanner := bufio.NewScanner(strings.NewReader(string(arrayOutput))) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.HasPrefix(line, "Maximum Capacity:") { hw.MaxCapacityGB = parseCapacityToGB(line) } else if strings.HasPrefix(line, "Number Of Devices:") { parts := strings.Split(line, ":") if len(parts) == 2 { hw.TotalSlots, _ = strconv.Atoi(strings.TrimSpace(parts[1])) } else { return hw, fmt.Errorf("parsing dmidecode -t 16 failed") } } } // Parse Type 17 Output var currentSize, currentType, currentSpeed, currentForm, currentVendor, currentPart string deviceScanner := bufio.NewScanner(strings.NewReader(string(deviceOutput))) for deviceScanner.Scan() { line := strings.TrimSpace(deviceScanner.Text()) if strings.HasPrefix(line, "Memory Device") { processDeviceBlock(currentSize, currentType, currentSpeed, currentForm, currentVendor, currentPart, &hw) currentSize, currentType, currentSpeed, currentForm, currentVendor, currentPart = "", "", "", "", "", "" continue } if strings.HasPrefix(line, "Size:") { currentSize = strings.TrimSpace(strings.TrimPrefix(line, "Size:")) } else if strings.HasPrefix(line, "Type:") { currentType = strings.TrimSpace(strings.TrimPrefix(line, "Type:")) } else if strings.HasPrefix(line, "Speed:") || strings.HasPrefix(line, "Configured Memory Speed:") { val := strings.TrimSpace(strings.Split(line, ":")[1]) if val != "Unknown" && val != "" && currentSpeed == "" { currentSpeed = val } } else if strings.HasPrefix(line, "Form Factor:") { currentForm = strings.TrimSpace(strings.TrimPrefix(line, "Form Factor:")) } else if strings.HasPrefix(line, "Manufacturer:") { currentVendor = strings.TrimSpace(strings.TrimPrefix(line, "Manufacturer:")) } else if strings.HasPrefix(line, "Part Number:") { currentPart = strings.TrimSpace(strings.TrimPrefix(line, "Part Number:")) } } // Process the final block processDeviceBlock(currentSize, currentType, currentSpeed, currentForm, currentVendor, currentPart, &hw) return hw, nil } func processDeviceBlock(size, memType, speed, form, vendor, part string, hw *MemoryHardware) { if size != "" && size != "No Module Installed" && size != "Unknown" { hw.SlotsUsed++ if memType != "" && memType != "Unknown" { hw.Generations = append(hw.Generations, memType) } else { hw.Generations = append(hw.Generations, "Unknown") } if speed != "" && speed != "Unknown" { hw.Speeds = append(hw.Speeds, speed) } else { hw.Speeds = append(hw.Speeds, "Unknown") } if form != "" && form != "Unknown" { hw.FormFactors = append(hw.FormFactors, form) } else { hw.FormFactors = append(hw.FormFactors, "Unknown") } // Construct model/part identifier partClean := cleanString(part) vendorClean := cleanString(vendor) if partClean != "" && vendorClean != "" { hw.ModelNames = append(hw.ModelNames, fmt.Sprintf("%s (%s)", partClean, vendorClean)) } else if partClean != "" { hw.ModelNames = append(hw.ModelNames, partClean) } else if vendorClean != "" { hw.ModelNames = append(hw.ModelNames, vendorClean) } else { hw.ModelNames = append(hw.ModelNames, "Unknown") } } } func cleanString(val string) string { v := strings.TrimSpace(val) if v == "" || v == "Unknown" || v == "NO DIMM" || strings.HasPrefix(v, "0x") { return "" } return v } func parseCapacityToGB(line string) float64 { parts := strings.Split(line, ":") if len(parts) < 2 { return 0 } valStr := strings.TrimSpace(parts[1]) fields := strings.Fields(valStr) if len(fields) < 2 { return 0 } val, err := strconv.ParseFloat(fields[0], 64) if err != nil { return 0 } unit := strings.ToUpper(fields[1]) if strings.HasPrefix(unit, "TB") { return val * 1024 } else if strings.HasPrefix(unit, "MB") { return val / 1024 } return val } func formatSlice(s []string) string { if len(s) == 0 { return "N/A" } return strings.Join(s, ", ") }