Rez Moss

Rez Moss

Digital Reflections: Exploring Tech, Innovation & Ideas

macOS Hardware Detection with Go

Aug 2025

Getting detailed hardware information from macOS requires working with various system utilities like sysctl, system_profiler, and vm_stat. Each of these tools outputs data in different formats, making it challenging to build a unified interface for hardware detection.

This tutorial demonstrates building a Go CLI tool that extracts comprehensive hardware details from macOS systems. We’ll work with the raw output from macOS-specific commands, parse their varied formats, and present the information in a structured way.

The implementation leverages Go’s ability to execute system commands and parse their text output. We’ll handle the complexities of different command formats - from sysctl’s key-value pairs to system_profiler’s nested text structure. The tool uses concurrent execution to gather different types of hardware information simultaneously, making it both fast and efficient.

You’ll learn how to reliably parse command output, handle edge cases in system data, and structure Go code for robust hardware detection on macOS systems.

2. Project Setup and Data Structures

Before diving into hardware detection, we need to establish the foundation with proper data structures that represent different hardware components. Each struct corresponds to a specific category of system information we’ll be collecting.

package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

The import list covers everything we’ll need: os/exec for running system commands, regexp for parsing complex output patterns, bufio for reading command output line by line, and sync for concurrent execution.

Our data structures mirror the hardware components we’re targeting:

type OSInfo struct {
	Name         string
	Version      string
	Kernel       string
	Hostname     string
	Architecture string
	GoVersion    string
	NumCPU       int
}

type CPUInfo struct {
	Model     string
	Cores     int
	Threads   int
	Frequency string
	VendorID  string
	ModelName string
	L2Cache   string
	L3Cache   string
}

type MemoryInfo struct {
	TotalRAM     string
	UsedRAM      string
	AvailableRAM string
	TotalSwap    string
	UsedSwap     string
	FreeSwap     string
}

type DiskInfo struct {
	FileSystem string
	Size       string
	Used       string
	Available  string
	UsePercent string
	MountedOn  string
}

type NetworkInterface struct {
	Name        string
	MACAddress  string
	IPAddresses []string
	MTU         string
}

type GPUInfo struct {
	Vendor string
	Model  string
	VRAM   string
}

Each struct is designed to hold the specific information we can extract from macOS commands. The DiskInfo struct maps directly to df command output, while NetworkInterface accommodates the multiple IP addresses that a single interface might have. The GPUInfo struct handles the varying levels of detail that system_profiler provides for different GPU types.

3. Helper Functions

The core of our hardware detection relies on several utility functions that handle command execution, text parsing, and data formatting. These functions abstract away the complexity of working with system command outputs.

func runCommand(name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("command '%s %s' failed: %w\nOutput: %s", name, strings.Join(args, " "), err, string(output))
	}
	return strings.TrimSpace(string(output)), nil
}

The runCommand function executes system commands and returns their output as a trimmed string. Using CombinedOutput() captures both stdout and stderr, which is crucial when commands might write error information to stderr while still producing useful output. The error message includes the full command and its output for debugging purposes.

func parseLine(line, key string) string {
	if strings.HasPrefix(line, key) {
		parts := strings.SplitN(line, ":", 2)
		if len(parts) == 2 {
			return strings.TrimSpace(parts[1])
		}
	}
	return ""
}

Many macOS commands output data in “key: value” format. The parseLine function extracts values from these lines by splitting on the colon and returning the trimmed value portion. This pattern appears frequently when parsing /proc/cpuinfo style outputs and sysctl results.

func formatBytes(bytes uint64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

The formatBytes function converts raw byte values into human-readable format using binary prefixes (KiB, MiB, GiB). This is essential since many system commands return memory and disk sizes as raw numbers, and we need consistent formatting across different data sources.

func isLikelyRawNumeric(s string) bool {
	if s == "" {
		return false
	}
	_, err := strconv.ParseUint(s, 10, 64)
	return err == nil
}

This validation function helps determine whether a string represents a raw numeric value, which is particularly useful when parsing df output that might return either human-readable sizes or raw block counts depending on the flags used.

4. Operating System Information

The getOSInfo() function gathers basic system details using macOS-specific commands. This information provides the foundation for understanding what system we’re running on and its basic characteristics.

func getOSInfo() OSInfo {
	hostname, _ := os.Hostname()
	kernel := "N/A"
	osName := runtime.GOOS
	osVersion := "N/A"

We start with Go’s built-in functions to get the hostname and GOOS value, then enhance this with macOS-specific details.

	if runtime.GOOS == "darwin" {
		osNameFull, err := runCommand("sw_vers", "-productName")
		if err == nil {
			osName = osNameFull
		}
		osVer, err := runCommand("sw_vers", "-productVersion")
		if err == nil {
			osVersion = osVer
		}
		kern, err := runCommand("uname", "-r")
		if err == nil {
			kernel = kern
		}
	}

The sw_vers command is macOS-specific and provides clean, parseable output for the product name and version. For example, sw_vers -productName returns “macOS” while sw_vers -productVersion returns something like “13.2.1”. The uname -r command gives us the Darwin kernel version.

	return OSInfo{
		Name:         osName, 
		Version:      osVersion, 
		Kernel:       kernel, 
		Hostname:     hostname, 
		Architecture: runtime.GOARCH, 
		GoVersion:    runtime.Version(), 
		NumCPU:       runtime.NumCPU()
	}

The function returns a complete OSInfo struct combining both Go runtime information and macOS system details. The runtime.GOARCH gives us the processor architecture (like “amd64” or “arm64”), while runtime.NumCPU() provides the number of logical CPUs visible to the Go runtime.

This approach handles cases where system commands might fail gracefully by falling back to “N/A” values, ensuring the function always returns usable data even when some commands are unavailable or fail to execute.

5. CPU Information

The getCPUInfo() function extracts detailed processor information using macOS’s sysctl command, which provides access to kernel state and hardware details through a hierarchical namespace.

func getCPUInfo() CPUInfo {
	info := CPUInfo{Threads: runtime.NumCPU()}

We initialize with the thread count from Go’s runtime as a fallback, then enhance it with macOS-specific details.

	if runtime.GOOS == "darwin" {
		model, _ := runCommand("sysctl", "-n", "machdep.cpu.brand_string")
		info.Model = model
		
		if c, e := runCommand("sysctl", "-n", "hw.physicalcpu"); e == nil {
			if v, e2 := strconv.Atoi(c); e2 == nil {
				info.Cores = v
			}
		}
		if t, e := runCommand("sysctl", "-n", "hw.logicalcpu"); e == nil {
			if v, e2 := strconv.Atoi(t); e2 == nil {
				info.Threads = v
			}
		}

The sysctl -n flag returns just the value without the key name. machdep.cpu.brand_string gives us the full CPU model name like “Apple M1 Pro” or “Intel® Core™ i7-9750H CPU @ 2.60GHz”. The distinction between hw.physicalcpu (actual cores) and hw.logicalcpu (threads including hyperthreading) is crucial for understanding CPU topology.

		freqB, e := runCommand("sysctl", "-n", "hw.cpufrequency_max")
		if e != nil || freqB == "" {
			freqB, e = runCommand("sysctl", "-n", "hw.cpufrequency")
		}
		if e == nil {
			if fq, e2 := strconv.ParseUint(freqB, 10, 64); e2 == nil {
				info.Frequency = fmt.Sprintf("%.2f GHz", float64(fq)/1000000000.0)
			}
		}

CPU frequency detection tries hw.cpufrequency_max first, falling back to hw.cpufrequency. The raw value comes in Hz, so we convert to GHz for readability. On Apple Silicon Macs, these values might not be available due to dynamic frequency scaling.

		if strings.Contains(info.Model, "Intel") {
			info.VendorID = "GenuineIntel"
		} else if strings.Contains(info.Model, "AMD") {
			info.VendorID = "AuthenticAMD"
		} else if strings.Contains(info.Model, "Apple") {
			info.VendorID = "Apple"
		}

Since macOS doesn’t expose vendor IDs through sysctl like Linux does, we parse the model string to determine the vendor. This handles Intel, AMD, and Apple Silicon processors.

		l2B, e := runCommand("sysctl", "-n", "hw.l2cachesizepercore")
		if e != nil || l2B == "" {
			l2B, e = runCommand("sysctl", "-n", "hw.l2cachesize")
		}
		if e == nil {
			if l2, e2 := strconv.ParseUint(l2B, 10, 64); e2 == nil {
				info.L2Cache = formatBytes(l2)
			}
		}
		
		l3B, e := runCommand("sysctl", "-n", "hw.l3cachesize")
		if e == nil {
			if l3, e2 := strconv.ParseUint(l3B, 10, 64); e2 == nil {
				info.L3Cache = formatBytes(l3)
			}
		}
	}

Cache information attempts per-core L2 cache size first, then falls back to total L2 cache. The raw values are in bytes and get formatted using our formatBytes helper function. L3 cache is typically shared across cores, so there’s usually only one sysctl entry for it.

The function includes fallback logic to ensure we always return reasonable values even when some sysctl queries fail, which can happen on different Mac models or macOS versions.

6. Memory Information

The getMemoryInfo() function collects RAM and swap usage data using macOS-specific commands. Memory information on macOS requires combining data from multiple sources due to how the system manages memory differently than traditional Unix systems.

func getMemoryInfo() MemoryInfo {
	memInfo := MemoryInfo{}
	
	if runtime.GOOS == "darwin" {
		var totMemB uint64
		totMemS, e := runCommand("sysctl", "-n", "hw.memsize")
		if e == nil {
			if t, e2 := strconv.ParseUint(totMemS, 10, 64); e2 == nil {
				totMemB = t
				memInfo.TotalRAM = formatBytes(totMemB)
			}
		}

Total memory is straightforward - hw.memsize returns the physical RAM in bytes. This value represents the actual hardware memory installed in the system.

		vmStatO, e := runCommand("vm_stat")
		if e == nil && totMemB > 0 {
			var pgSz uint64 = 16384
			pgSzS, psE := runCommand("sysctl", "-n", "hw.pagesize")
			if psE == nil {
				if ps, ce := strconv.ParseUint(pgSzS, 10, 64); ce == nil {
					pgSz = ps
				}
			}

The vm_stat command provides memory statistics in pages, so we need the page size to convert to bytes. On Apple Silicon Macs, the page size is typically 16KB, while Intel Macs use 4KB pages. We default to 16KB and query the actual value.

			var actC, inactC, wireC, comprC, freeC uint64
			scan := bufio.NewScanner(strings.NewReader(vmStatO))
			for scan.Scan() {
				ln := scan.Text()
				flds := strings.Fields(ln)
				if len(flds) < 2 {
					continue
				}
				vS := strings.TrimRight(flds[len(flds)-1], ".")
				v, _ := strconv.ParseUint(vS, 10, 64)
				
				switch {
				case strings.HasPrefix(ln, "Pages active:"):
					actC = v
				case strings.HasPrefix(ln, "Pages inactive:"):
					inactC = v
				case strings.HasPrefix(ln, "Pages wired down:"):
					wireC = v
				case strings.HasPrefix(ln, "Pages occupied by compressor:"):
					comprC = v
				case strings.HasPrefix(ln, "Pages free:"):
					freeC = v
				}
			}

macOS memory management uses several categories: active pages (recently used), inactive pages (not recently used but cached), wired pages (kernel memory that can’t be swapped), compressed pages (memory compressed to save space), and free pages. Each line in vm_stat output ends with a period that we need to trim.

			usedB := (actC + wireC + comprC) * pgSz
			memInfo.UsedRAM = formatBytes(usedB)
			if totMemB >= usedB {
				memInfo.AvailableRAM = formatBytes(totMemB - usedB)
			} else {
				memInfo.AvailableRAM = formatBytes((freeC + inactC) * pgSz)
			}

Used memory includes active, wired, and compressed pages since these represent memory actively consumed by the system and applications. Available memory is calculated as total minus used, with a fallback calculation using free and inactive pages if the math doesn’t work out.

		swUseO, e := runCommand("sysctl", "-n", "vm.swapusage")
		if e == nil {
			re := regexp.MustCompile(`total\s*=\s*([\d\.]+)(\w+)\s*used\s*=\s*([\d\.]+)(\w+)\s*free\s*=\s*([\d\.]+)(\w+)`)
			m := re.FindStringSubmatch(swUseO)
			if len(m) == 7 {
				memInfo.TotalSwap = m[1] + m[2]
				memInfo.UsedSwap = m[3] + m[4]
				memInfo.FreeSwap = m[5] + m[6]
			}
		}
	}

Swap information comes from vm.swapusage, which outputs a formatted string like “total = 2.00G used = 1.25G free = 768.00M”. We use a regex to extract the numeric values and units, keeping them as formatted strings since they’re already human-readable.

The function ensures all fields have values by setting empty strings to “N/A” at the end, providing consistent output even when some commands fail.

7. Disk Information

The getDiskInfo() function retrieves filesystem information using the df command with multiple fallback strategies to handle different macOS configurations and command availability.

func getDiskInfo() []DiskInfo {
	var disks []DiskInfo
	var out string
	var err error
	commandToUse := []string{"df", "-hP"}
	humanReadableExpected := true

	out, err = runCommand(commandToUse[0], commandToUse[1:]...)
	if err != nil {
		commandToUse = []string{"df", "-Pk"}
		out, err = runCommand(commandToUse[0], commandToUse[1:]...)
		if err != nil {
			commandToUse = []string{"df"}
			humanReadableExpected = false
			out, err = runCommand(commandToUse[0], commandToUse[1:]...)
			if err != nil {
				return disks
			}
		}
	}

The function tries three different df command variations. First, it attempts df -hP which provides human-readable output (-h) in POSIX format (-P ensures each filesystem appears on one line). If that fails, it tries df -Pk for kilobyte blocks in POSIX format. Finally, it falls back to plain df with raw block output.

	lines := strings.Split(out, "\n")
	if len(lines) > 1 {
		for _, line := range lines[1:] {
			fields := strings.Fields(line)
			if len(fields) < 6 {
				continue
			}

			disk := DiskInfo{
				FileSystem: fields[0],
				Size:       fields[1],
				Used:       fields[2],
				Available:  fields[3],
				UsePercent: fields[4],
				MountedOn:  strings.Join(fields[5:], " "),
			}

We skip the header line and parse each filesystem entry. The df output format is consistent: filesystem name, total size, used space, available space, usage percentage, and mount point. Mount points with spaces in their names require joining all remaining fields.

			needsConversion := !humanReadableExpected
			if humanReadableExpected && isLikelyRawNumeric(disk.Size) {
				needsConversion = true
			}

			if needsConversion {
				var blockSizeMultiplier uint64 = 512
				if commandToUse[0] == "df" && len(commandToUse) > 1 && commandToUse[1] == "-Pk" {
					blockSizeMultiplier = 1024
				}

				if sizeVal, sErr := strconv.ParseUint(disk.Size, 10, 64); sErr == nil {
					disk.Size = formatBytes(sizeVal * blockSizeMultiplier)
				}
				if usedVal, uErr := strconv.ParseUint(disk.Used, 10, 64); uErr == nil {
					disk.Used = formatBytes(usedVal * blockSizeMultiplier)
				}
				if availVal, aErr := strconv.ParseUint(disk.Available, 10, 64); aErr == nil {
					disk.Available = formatBytes(availVal * blockSizeMultiplier)
				}
			}
			disks = append(disks, disk)
		}
	}
	return disks

When we don’t expect human-readable output, or when the supposedly human-readable output contains raw numbers, we need to convert block counts to bytes. The df -Pk command uses 1024-byte blocks, while plain df typically uses 512-byte blocks on macOS. Our isLikelyRawNumeric() helper detects when the -h flag didn’t work as expected.

This multi-layered approach ensures we get filesystem information regardless of which df options are supported on the specific macOS version, and handles the complexity of different output formats by normalizing everything to human-readable byte values.

8. Network Interfaces

The getNetworkInterfaces() function extracts network interface information using ifconfig, parsing its complex output format to identify active interfaces with their configuration details.

func getNetworkInterfaces() []NetworkInterface {
	var interfaces []NetworkInterface
	
	if runtime.GOOS == "darwin" {
		out, e := runCommand("ifconfig", "-a")
		if e != nil {
			return interfaces
		}

The ifconfig -a command shows all network interfaces, including inactive ones. We’ll filter for active interfaces during parsing.

		mcDRx := regexp.MustCompile(`ether\s+([0-9a-fA-F:]{17})`)
		ipDRx := regexp.MustCompile(`inet\s+([0-9\.]+)\s+netmask`)
		ip6DRx := regexp.MustCompile(`inet6\s+([0-9a-fA-F:]+)%?\w*\s+prefixlen\s+(\d+)`)
		mtuDRx := regexp.MustCompile(`mtu\s+(\d+)`)
		stActRx := regexp.MustCompile(`status:\s+active`)

These regex patterns match the different data elements in ifconfig output. The MAC address pattern looks for the “ether” keyword followed by a colon-separated hex string. IPv4 addresses appear after “inet” with netmask information. IPv6 addresses include prefix length and sometimes zone identifiers (the % character). MTU and status information help determine interface characteristics.

		var curN, curMtu, curMac string
		var curIps []string
		isAct := false
		scan := bufio.NewScanner(strings.NewReader(out))
		
		for scan.Scan() {
			ln := scan.Text()
			if !strings.HasPrefix(ln, "\t") && strings.Contains(ln, ": flags=") {
				if curN != "" && isAct && (len(curIps) > 0 || curMac != "") {
					interfaces = append(interfaces, NetworkInterface{Name: curN, MACAddress: curMac, IPAddresses: curIps, MTU: curMtu})
				}
				pts := strings.SplitN(ln, ":", 2)
				curN = pts[0]
				curIps = []string{}
				curMac = ""
				curMtu = ""
				isAct = false
				mtuM := mtuDRx.FindStringSubmatch(ln)
				if len(mtuM) > 1 {
					curMtu = mtuM[1]
				}
			}

Interface entries in ifconfig output start with the interface name and flags line (not indented). When we encounter a new interface, we save the previous one if it was active and had either IP addresses or a MAC address. The MTU often appears on the same line as the interface name.

			if curN == "" {
				continue
			}
			if stActRx.MatchString(ln) {
				isAct = true
			}
			mcM := mcDRx.FindStringSubmatch(ln)
			if len(mcM) > 1 {
				curMac = mcM[1]
			}
			ipM := ipDRx.FindStringSubmatch(ln)
			if len(ipM) > 1 {
				curIps = append(curIps, ipM[1])
			}
			ip6M := ip6DRx.FindStringSubmatch(ln)
			if len(ip6M) > 2 {
				curIps = append(curIps, ip6M[1]+"/"+ip6M[2])
			}
		}
		
		if curN != "" && isAct && (len(curIps) > 0 || curMac != "") {
			interfaces = append(interfaces, NetworkInterface{Name: curN, MACAddress: curMac, IPAddresses: curIps, MTU: curMtu})
		}
	}
	return interfaces

For each line belonging to the current interface, we check for status information, MAC addresses, and IP addresses. IPv6 addresses get formatted with their prefix length (like “fe80::164”). We only include interfaces that are marked as active and have either IP addresses or MAC addresses, filtering out inactive or virtual interfaces that don’t provide useful network information.

The final check ensures we don’t miss the last interface in the output, since the loop only processes completed interfaces when it encounters a new one.

9. GPU Information

The getGPUInfo() function extracts graphics card information using macOS’s system_profiler command, which provides detailed hardware inventory data in a structured text format.

func getGPUInfo() []GPUInfo {
	var gpus []GPUInfo
	
	if runtime.GOOS == "darwin" {
		out, e := runCommand("system_profiler", "SPDisplaysDataType")
		if e != nil {
			gpus = append(gpus, GPUInfo{Model: "N/A (system_profiler failed)"})
			return gpus
		}

The SPDisplaysDataType argument tells system_profiler to return only display/graphics information, which is much faster than generating a complete system report. If the command fails, we return a placeholder entry rather than an empty slice.

		scan := bufio.NewScanner(strings.NewReader(out))
		var curGPU *GPUInfo
		chipRx := regexp.MustCompile(`Chipset Model:\s*(.*)`)
		vendRx := regexp.MustCompile(`Vendor:\s*(.*)`)
		vramTRx := regexp.MustCompile(`VRAM \(Total\):\s*(.*)`)
		vramDRx := regexp.MustCompile(`VRAM \(Dynamic, Max\):\s*(.*)`)

The system_profiler output uses a hierarchical format with key-value pairs. Each GPU appears as a separate section with various attributes. The VRAM patterns handle both discrete GPUs (which have dedicated VRAM) and integrated GPUs (which use shared system memory).

		for scan.Scan() {
			ln := strings.TrimSpace(scan.Text())
			chipM := chipRx.FindStringSubmatch(ln)
			if len(chipM) > 1 {
				if curGPU != nil && curGPU.Model != "" {
					gpus = append(gpus, *curGPU)
				}
				curGPU = &GPUInfo{Model: chipM[1]}
			}

When we find a “Chipset Model” line, it indicates the start of a new GPU entry. We save the previous GPU (if it exists and has a model name) and create a new GPUInfo struct. The chipset model becomes the GPU model name.

			if curGPU == nil {
				continue
			}
			vendM := vendRx.FindStringSubmatch(ln)
			if len(vendM) > 1 {
				curGPU.Vendor = vendM[1]
			}
			vramM := vramTRx.FindStringSubmatch(ln)
			if len(vramM) > 1 {
				curGPU.VRAM = vramM[1]
			} else {
				vramDM := vramDRx.FindStringSubmatch(ln)
				if len(vramDM) > 1 && curGPU.VRAM == "" {
					curGPU.VRAM = vramDM[1] + " (Shared/Dynamic)"
				}
			}
		}

For subsequent lines, we look for vendor and VRAM information. Discrete GPUs typically show “VRAM (Total)” while integrated GPUs show “VRAM (Dynamic, Max)”. We prefer the total VRAM value and annotate dynamic VRAM to clarify that it’s shared system memory.

		if curGPU != nil && curGPU.Model != "" {
			if curGPU.Vendor != "" {
				if reV := regexp.MustCompile(`(.*)\s*\(0x\w+\)`); reV.MatchString(curGPU.Vendor) {
					curGPU.Vendor = strings.TrimSpace(reV.FindStringSubmatch(curGPU.Vendor)[1])
				}
			}
			if curGPU.VRAM == "" {
				curGPU.VRAM = "N/A (macOS)"
			}
			gpus = append(gpus, *curGPU)
		}
		
		if len(gpus) == 0 {
			gpus = append(gpus, GPUInfo{Model: "N/A (No display found)"})
		}
	}
	return gpus

After processing all lines, we handle the final GPU entry. Vendor names sometimes include hex identifiers in parentheses that we strip for cleaner output. If no VRAM information was found, we set it to “N/A (macOS)” to indicate this is a macOS-specific limitation rather than missing hardware.

The function ensures we always return at least one GPU entry, even if no display hardware is detected, which helps maintain consistent output formatting in the main program.

10. Concurrent Execution and Main Function

The main function orchestrates the hardware information gathering using Go’s concurrency features to collect data from multiple sources simultaneously, significantly reducing execution time.

func main() {
	startTime := time.Now()
	fmt.Println("Gathering system information...")

	var wg sync.WaitGroup
	var osInfo OSInfo
	var cpuInfo CPUInfo
	var memInfo MemoryInfo
	var diskInfo []DiskInfo
	var netInterfaces []NetworkInterface
	var gpuInfo []GPUInfo

We declare variables to hold results from each hardware detection function and use sync.WaitGroup to coordinate the concurrent execution. Recording the start time allows us to measure how much time the concurrent approach saves.

	wg.Add(6)
	go func() { defer wg.Done(); osInfo = getOSInfo() }()
	go func() { defer wg.Done(); cpuInfo = getCPUInfo() }()
	go func() { defer wg.Done(); memInfo = getMemoryInfo() }()
	go func() { defer wg.Done(); diskInfo = getDiskInfo() }()
	go func() { defer wg.Done(); netInterfaces = getNetworkInterfaces() }()
	go func() { defer wg.Done(); gpuInfo = getGPUInfo() }()
	wg.Wait()

Each hardware detection function runs in its own goroutine. The defer wg.Done() ensures the WaitGroup counter decreases when each function completes, regardless of whether it succeeds or fails. This concurrent execution is safe because each function operates independently and writes to different variables.

	fmt.Println("\n--- Operating System ---")
	fmt.Printf("Name:           %s\nVersion:        %s\nKernel:         %s\nHostname:       %s\nArchitecture:   %s\nGo Version:     %s\nLogical CPUs:   %d\n",
		osInfo.Name, osInfo.Version, osInfo.Kernel, osInfo.Hostname, osInfo.Architecture, osInfo.GoVersion, osInfo.NumCPU)
		
	fmt.Println("\n--- CPU ---")
	fmt.Printf("Model:          %s\nVendor:         %s\nPhysical Cores: %d\nLogical Cores:  %d\nFrequency:      %s\nL2 Cache:       %s\nL3 Cache:       %s\n",
		cpuInfo.Model, cpuInfo.VendorID, cpuInfo.Cores, cpuInfo.Threads, cpuInfo.Frequency, cpuInfo.L2Cache, cpuInfo.L3Cache)

The output formatting uses aligned labels for consistent presentation. Each section has a clear header and structured field display that makes the information easy to scan.

	fmt.Println("\n--- Memory (RAM) ---")
	fmt.Printf("Total:          %s\nUsed:           %s\nAvailable:      %s\n", memInfo.TotalRAM, memInfo.UsedRAM, memInfo.AvailableRAM)
	fmt.Println("--- Memory (Swap) ---")
	fmt.Printf("Total:          %s\nUsed:           %s\nFree:           %s\n", memInfo.TotalSwap, memInfo.UsedSwap, memInfo.FreeSwap)

	fmt.Println("\n--- Disk Usage ---")
	fmt.Printf("%-30s %-10s %-10s %-10s %-10s %s\n", "Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on")
	fmt.Println(strings.Repeat("-", 90))
	for _, d := range diskInfo {
		fs := d.FileSystem
		if len(fs) > 28 {
			fs = fs[:25] + "..."
		}
		if strings.HasPrefix(d.FileSystem, "map") && (d.Size == "0 B" || d.Size == "0") {
			continue
		}
		fmt.Printf("%-30s %-10s %-10s %-10s %-10s %s\n", fs, d.Size, d.Used, d.Available, d.UsePercent, d.MountedOn)
	}

The disk information uses tabular formatting with fixed-width columns. Long filesystem names get truncated with ellipses, and we filter out map-based filesystems with zero size (like some virtual filesystems) to keep the output focused on actual storage devices.

	fmt.Println("\n--- Network Interfaces (Active) ---")
	for _, iface := range netInterfaces {
		fmt.Printf("Interface:      %s\n", iface.Name)
		if iface.MACAddress != "" {
			fmt.Printf("  MAC Address:  %s\n", iface.MACAddress)
		}
		if iface.MTU != "" {
			fmt.Printf("  MTU:          %s\n", iface.MTU)
		}
		if len(iface.IPAddresses) > 0 {
			fmt.Printf("  IP Addresses: %s\n", strings.Join(iface.IPAddresses, ", "))
		} else {
			fmt.Printf("  IP Addresses: N/A\n")
		}
	}

	fmt.Println("\n--- GPU(s) ---")
	if len(gpuInfo) > 0 {
		for i, gpu := range gpuInfo {
			fmt.Printf("GPU %d:\n  Vendor:       %s\n  Model:        %s\n  VRAM:         %s\n", i, gpu.Vendor, gpu.Model, gpu.VRAM)
		}
	} else {
		fmt.Println("  No GPU information found or an error occurred.")
	}
	
	fmt.Printf("\nReport generated in %s\n", time.Since(startTime))

Network interfaces and GPUs use hierarchical formatting with indented sub-fields. Multiple IP addresses get joined with commas, and GPU entries are numbered for systems with multiple graphics cards. The final timing information shows the performance benefit of concurrent execution - typically completing in under a second versus several seconds if run sequentially.

11. Final Implementation

The complete implementation brings together all the components we’ve built into a functional hardware detection tool. Here’s the full source code that demonstrates how each piece works together:To run this tool, save the code as main.go and execute:

package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type OSInfo struct {
	Name         string
	Version      string
	Kernel       string
	Hostname     string
	Architecture string
	GoVersion    string
	NumCPU       int
}

type CPUInfo struct {
	Model     string
	Cores     int
	Threads   int
	Frequency string
	VendorID  string
	ModelName string
	L2Cache   string
	L3Cache   string
}

type MemoryInfo struct {
	TotalRAM     string
	UsedRAM      string
	AvailableRAM string
	TotalSwap    string
	UsedSwap     string
	FreeSwap     string
}

type DiskInfo struct {
	FileSystem string
	Size       string
	Used       string
	Available  string
	UsePercent string
	MountedOn  string
}

type NetworkInterface struct {
	Name        string
	MACAddress  string
	IPAddresses []string
	MTU         string
}

type GPUInfo struct {
	Vendor string
	Model  string
	VRAM   string
}

func runCommand(name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("command '%s %s' failed: %w\nOutput: %s", name, strings.Join(args, " "), err, string(output))
	}
	return strings.TrimSpace(string(output)), nil
}

func parseLine(line, key string) string {
	if strings.HasPrefix(line, key) {
		parts := strings.SplitN(line, ":", 2)
		if len(parts) == 2 {
			return strings.TrimSpace(parts[1])
		}
	}
	return ""
}

func formatBytes(bytes uint64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

func isLikelyRawNumeric(s string) bool {
	if s == "" {
		return false
	}
	_, err := strconv.ParseUint(s, 10, 64)
	return err == nil
}

func getOSInfo() OSInfo {
	hostname, _ := os.Hostname()
	kernel := "N/A"
	osName := runtime.GOOS
	osVersion := "N/A"
	if runtime.GOOS == "linux" {
		content, err := os.ReadFile("/etc/os-release")
		if err == nil {
			scanner := bufio.NewScanner(strings.NewReader(string(content)))
			for scanner.Scan() {
				line := scanner.Text()
				if strings.HasPrefix(line, "PRETTY_NAME=") {
					osName = strings.Trim(strings.SplitN(line, "=", 2)[1], `"`)
				} else if strings.HasPrefix(line, "VERSION=") && osVersion == "N/A" {
					osVersion = strings.Trim(strings.SplitN(line, "=", 2)[1], `"`)
				} else if strings.HasPrefix(line, "NAME=") && osName == runtime.GOOS {
					osName = strings.Trim(strings.SplitN(line, "=", 2)[1], `"`)
				}
			}
		}
		out, err := runCommand("uname", "-r")
		if err == nil {
			kernel = out
		}
		if osVersion == "N/A" {
			out, err = runCommand("lsb_release", "-ds")
			if err == nil {
				osName = strings.Trim(out, `"`)
			} else {
				out, err = runCommand("uname", "-v")
				if err == nil {
					osVersion = strings.Split(out, " ")[0]
				}
			}
		}
	} else if runtime.GOOS == "darwin" {
		osNameFull, err := runCommand("sw_vers", "-productName")
		if err == nil {
			osName = osNameFull
		}
		osVer, err := runCommand("sw_vers", "-productVersion")
		if err == nil {
			osVersion = osVer
		}
		kern, err := runCommand("uname", "-r")
		if err == nil {
			kernel = kern
		}
	}
	return OSInfo{Name: osName, Version: osVersion, Kernel: kernel, Hostname: hostname, Architecture: runtime.GOARCH, GoVersion: runtime.Version(), NumCPU: runtime.NumCPU()}
}

func getCPUInfo() CPUInfo {
	info := CPUInfo{Threads: runtime.NumCPU()}
	if runtime.GOOS == "linux" {
		content, err := os.ReadFile("/proc/cpuinfo")
		if err == nil {
			scanner := bufio.NewScanner(strings.NewReader(string(content)))
			var currentPhysicalID string
			physicalCoresMap := make(map[string]bool)
			for scanner.Scan() {
				line := scanner.Text()
				if val := parseLine(line, "vendor_id"); val != "" && info.VendorID == "" {
					info.VendorID = val
				}
				if val := parseLine(line, "model name"); val != "" && info.ModelName == "" {
					info.ModelName = val
				}
				if val := parseLine(line, "cpu MHz"); val != "" && info.Frequency == "" {
					info.Frequency = val + " MHz"
				}
				if strings.HasPrefix(line, "physical id") {
					if pVal := parseLine(line, "physical id"); pVal != "" {
						currentPhysicalID = pVal
					}
				}
				if strings.HasPrefix(line, "core id") {
					if cVal := parseLine(line, "core id"); cVal != "" {
						physicalCoresMap[currentPhysicalID+":"+cVal] = true
					}
				}
				if val := parseLine(line, "cache size"); val != "" {
					if info.L3Cache == "" {
						info.L3Cache = val
					} else if info.L2Cache == "" {
						info.L2Cache = val
					}
				}
			}
			info.Cores = len(physicalCoresMap)
			if info.Cores == 0 {
				cpuCoresStr := ""
				proc0content := ""
				if len(string(content)) > 0 {
					proc0content = strings.Split(string(content), "\n\n")[0]
				}
				scannerProc0 := bufio.NewScanner(strings.NewReader(proc0content))
				for scannerProc0.Scan() {
					if val := parseLine(scannerProc0.Text(), "cpu cores"); val != "" {
						cpuCoresStr = val
						break
					}
				}
				if c, err := strconv.Atoi(cpuCoresStr); err == nil {
					info.Cores = c
				} else if info.Threads > 0 {
					info.Cores = info.Threads / 2
					if info.Cores == 0 {
						info.Cores = 1
					}
				} else {
					info.Cores = 1
				}
			}
		}
		lscpuOut, err := runCommand("lscpu")
		if err == nil {
			scanner := bufio.NewScanner(strings.NewReader(lscpuOut))
			var coresPerSocketStr, socketsStr string
			for scanner.Scan() {
				line := scanner.Text()
				if val := parseLine(line, "Model name"); val != "" {
					info.ModelName = val
				}
				if val := parseLine(line, "CPU(s)"); val != "" {
					if t, e := strconv.Atoi(val); e == nil {
						info.Threads = t
					}
				}
				if val := parseLine(line, "Core(s) per socket"); val != "" {
					coresPerSocketStr = val
				}
				if val := parseLine(line, "Socket(s)"); val != "" {
					socketsStr = val
				}
				if val := parseLine(line, "CPU max MHz"); val != "" {
					info.Frequency = val + " MHz (Max)"
				} else if val := parseLine(line, "CPU MHz"); val != "" && (info.Frequency == "" || !strings.Contains(info.Frequency, "Max")) {
					info.Frequency = val + " MHz"
				}
				if val := parseLine(line, "Vendor ID"); val != "" {
					info.VendorID = val
				}
				if val := parseLine(line, "L2 cache"); val != "" {
					info.L2Cache = val
				}
				if val := parseLine(line, "L3 cache"); val != "" {
					info.L3Cache = val
				}
			}
			if cps, e1 := strconv.Atoi(coresPerSocketStr); e1 == nil {
				if sks, e2 := strconv.Atoi(socketsStr); e2 == nil && cps > 0 && sks > 0 {
					info.Cores = cps * sks
				}
			}
		}
		info.Model = info.ModelName
	} else if runtime.GOOS == "darwin" {
		model, _ := runCommand("sysctl", "-n", "machdep.cpu.brand_string")
		info.Model = model
		if c, e := runCommand("sysctl", "-n", "hw.physicalcpu"); e == nil {
			if v, e2 := strconv.Atoi(c); e2 == nil {
				info.Cores = v
			}
		}
		if t, e := runCommand("sysctl", "-n", "hw.logicalcpu"); e == nil {
			if v, e2 := strconv.Atoi(t); e2 == nil {
				info.Threads = v
			}
		}
		freqB, e := runCommand("sysctl", "-n", "hw.cpufrequency_max")
		if e != nil || freqB == "" {
			freqB, e = runCommand("sysctl", "-n", "hw.cpufrequency")
		}
		if e == nil {
			if fq, e2 := strconv.ParseUint(freqB, 10, 64); e2 == nil {
				info.Frequency = fmt.Sprintf("%.2f GHz", float64(fq)/1000000000.0)
			}
		}
		if strings.Contains(info.Model, "Intel") {
			info.VendorID = "GenuineIntel"
		} else if strings.Contains(info.Model, "AMD") {
			info.VendorID = "AuthenticAMD"
		} else if strings.Contains(info.Model, "Apple") {
			info.VendorID = "Apple"
		}
		l2B, e := runCommand("sysctl", "-n", "hw.l2cachesizepercore")
		if e != nil || l2B == "" {
			l2B, e = runCommand("sysctl", "-n", "hw.l2cachesize")
		}
		if e == nil {
			if l2, e2 := strconv.ParseUint(l2B, 10, 64); e2 == nil {
				info.L2Cache = formatBytes(l2)
			}
		}
		l3B, e := runCommand("sysctl", "-n", "hw.l3cachesize")
		if e == nil {
			if l3, e2 := strconv.ParseUint(l3B, 10, 64); e2 == nil {
				info.L3Cache = formatBytes(l3)
			}
		}
	}
	if info.Model == "" {
		info.Model = "N/A"
	}
	if info.Cores == 0 && info.Threads > 0 {
		info.Cores = info.Threads / 2
		if info.Cores == 0 {
			info.Cores = 1
		}
	}
	if info.Threads == 0 && info.Cores > 0 {
		info.Threads = info.Cores
	}
	if info.Threads == 0 && info.Cores == 0 {
		info.Threads = 1
		info.Cores = 1
	}
	if info.Frequency == "" {
		info.Frequency = "N/A"
	}
	if info.L2Cache == "" {
		info.L2Cache = "N/A"
	}
	if info.L3Cache == "" {
		info.L3Cache = "N/A"
	}
	return info
}

func getMemoryInfo() MemoryInfo {
	memInfo := MemoryInfo{}
	if runtime.GOOS == "linux" {
		content, err := os.ReadFile("/proc/meminfo")
		if err == nil {
			var tot, free, avail, buf, cach, swTot, swFree uint64
			scan := bufio.NewScanner(strings.NewReader(string(content)))
			for scan.Scan() {
				ln := scan.Text()
				flds := strings.Fields(ln)
				if len(flds) < 2 {
					continue
				}
				vS := flds[1]
				v, _ := strconv.ParseUint(vS, 10, 64)
				vB := v * 1024
				switch flds[0] {
				case "MemTotal:":
					tot = vB
				case "MemFree:":
					free = vB
				case "MemAvailable:":
					avail = vB
				case "Buffers:":
					buf = vB
				case "Cached:":
					cach = vB
				case "SwapTotal:":
					swTot = vB
				case "SwapFree:":
					swFree = vB
				}
			}
			memInfo.TotalRAM = formatBytes(tot)
			if avail > 0 {
				memInfo.AvailableRAM = formatBytes(avail)
				memInfo.UsedRAM = formatBytes(tot - avail)
			} else {
				usd := tot - free - buf - cach
				if usd > tot || usd < 0 {
					usd = tot - free
				}
				memInfo.AvailableRAM = formatBytes(free + buf + cach)
				memInfo.UsedRAM = formatBytes(usd)
			}
			memInfo.TotalSwap = formatBytes(swTot)
			memInfo.FreeSwap = formatBytes(swFree)
			if swTot > 0 {
				memInfo.UsedSwap = formatBytes(swTot - swFree)
			} else {
				memInfo.UsedSwap = "0 B"
			}
		}
	} else if runtime.GOOS == "darwin" {
		var totMemB uint64
		totMemS, e := runCommand("sysctl", "-n", "hw.memsize")
		if e == nil {
			if t, e2 := strconv.ParseUint(totMemS, 10, 64); e2 == nil {
				totMemB = t
				memInfo.TotalRAM = formatBytes(totMemB)
			}
		}
		vmStatO, e := runCommand("vm_stat")
		if e == nil && totMemB > 0 {
			var pgSz uint64 = 16384
			pgSzS, psE := runCommand("sysctl", "-n", "hw.pagesize")
			if psE == nil {
				if ps, ce := strconv.ParseUint(pgSzS, 10, 64); ce == nil {
					pgSz = ps
				}
			}
			var actC, inactC, wireC, comprC, freeC uint64
			scan := bufio.NewScanner(strings.NewReader(vmStatO))
			for scan.Scan() {
				ln := scan.Text()
				flds := strings.Fields(ln)
				if len(flds) < 2 {
					continue
				}
				vS := strings.TrimRight(flds[len(flds)-1], ".")
				v, _ := strconv.ParseUint(vS, 10, 64)
				if strings.HasPrefix(ln, "Page size:") && len(flds) >= 4 {
					if ps, e2 := strconv.ParseUint(flds[3], 10, 64); e2 == nil {
						pgSz = ps
					}
				}
				switch {
				case strings.HasPrefix(ln, "Pages active:"):
					actC = v
				case strings.HasPrefix(ln, "Pages inactive:"):
					inactC = v
				case strings.HasPrefix(ln, "Pages wired down:"):
					wireC = v
				case strings.HasPrefix(ln, "Pages occupied by compressor:"):
					comprC = v
				case strings.HasPrefix(ln, "Pages free:"):
					freeC = v
				}
			}
			usedB := (actC + wireC + comprC) * pgSz
			memInfo.UsedRAM = formatBytes(usedB)
			if totMemB >= usedB {
				memInfo.AvailableRAM = formatBytes(totMemB - usedB)
			} else {
				memInfo.AvailableRAM = formatBytes((freeC + inactC) * pgSz)
			}
		} else {
			memInfo.UsedRAM = "N/A"
			memInfo.AvailableRAM = "N/A"
		}
		swUseO, e := runCommand("sysctl", "-n", "vm.swapusage")
		if e == nil {
			re := regexp.MustCompile(`total\s*=\s*([\d\.]+)(\w+)\s*used\s*=\s*([\d\.]+)(\w+)\s*free\s*=\s*([\d\.]+)(\w+)`)
			m := re.FindStringSubmatch(swUseO)
			if len(m) == 7 {
				memInfo.TotalSwap = m[1] + m[2]
				memInfo.UsedSwap = m[3] + m[4]
				memInfo.FreeSwap = m[5] + m[6]
			} else {
				memInfo.TotalSwap = "N/A"
				memInfo.UsedSwap = "N/A"
				memInfo.FreeSwap = "N/A"
			}
		}
	}
	s := []string{memInfo.TotalRAM, memInfo.UsedRAM, memInfo.AvailableRAM, memInfo.TotalSwap, memInfo.UsedSwap, memInfo.FreeSwap}
	for i, v_ := range s {
		if v_ == "" {
			s[i] = "N/A"
		}
	}
	memInfo.TotalRAM, memInfo.UsedRAM, memInfo.AvailableRAM = s[0], s[1], s[2]
	memInfo.TotalSwap, memInfo.UsedSwap, memInfo.FreeSwap = s[3], s[4], s[5]
	return memInfo
}

func getDiskInfo() []DiskInfo {
	var disks []DiskInfo
	var out string
	var err error
	commandToUse := []string{"df", "-hP"}
	humanReadableExpected := true

	out, err = runCommand(commandToUse[0], commandToUse[1:]...)
	if err != nil {
		commandToUse = []string{"df", "-Pk"}
		out, err = runCommand(commandToUse[0], commandToUse[1:]...)
		if err != nil {
			commandToUse = []string{"df"}
			humanReadableExpected = false
			out, err = runCommand(commandToUse[0], commandToUse[1:]...)
			if err != nil {
				return disks
			}
		}
	}

	lines := strings.Split(out, "\n")
	if len(lines) > 1 {
		for _, line := range lines[1:] {
			fields := strings.Fields(line)
			if len(fields) < 6 {
				continue
			}

			disk := DiskInfo{
				FileSystem: fields[0],
				Size:       fields[1],
				Used:       fields[2],
				Available:  fields[3],
				UsePercent: fields[4],
				MountedOn:  strings.Join(fields[5:], " "),
			}

			needsConversion := !humanReadableExpected
			if humanReadableExpected && isLikelyRawNumeric(disk.Size) {
				needsConversion = true
			}

			if needsConversion {
				var blockSizeMultiplier uint64 = 512
				if commandToUse[0] == "df" && len(commandToUse) > 1 && commandToUse[1] == "-Pk" {
					blockSizeMultiplier = 1024
				}

				if sizeVal, sErr := strconv.ParseUint(disk.Size, 10, 64); sErr == nil {
					disk.Size = formatBytes(sizeVal * blockSizeMultiplier)
				}
				if usedVal, uErr := strconv.ParseUint(disk.Used, 10, 64); uErr == nil {
					disk.Used = formatBytes(usedVal * blockSizeMultiplier)
				}
				if availVal, aErr := strconv.ParseUint(disk.Available, 10, 64); aErr == nil {
					disk.Available = formatBytes(availVal * blockSizeMultiplier)
				}
			}
			disks = append(disks, disk)
		}
	}
	return disks
}

func getNetworkInterfaces() []NetworkInterface {
	var interfaces []NetworkInterface
	if runtime.GOOS == "linux" {
		out, e := runCommand("ip", "addr")
		if e != nil {
			return interfaces
		}
		var curI *NetworkInterface
		scan := bufio.NewScanner(strings.NewReader(out))
		nmRx := regexp.MustCompile(`^\d+:\s+([\w.-]+):\s+<.*,UP.*>.*mtu\s+(\d+)`)
		mcRx := regexp.MustCompile(`link/\w+\s+([0-9a-fA-F:]{17})`)
		ipRx := regexp.MustCompile(`inet\s+([0-9\.]+/\d+)`)
		ip6Rx := regexp.MustCompile(`inet6\s+([0-9a-fA-F:]+/\d+)`)
		for scan.Scan() {
			ln := strings.TrimSpace(scan.Text())
			nmM := nmRx.FindStringSubmatch(ln)
			if len(nmM) == 3 {
				if curI != nil && (len(curI.IPAddresses) > 0 || curI.MACAddress != "") {
					interfaces = append(interfaces, *curI)
				}
				curI = &NetworkInterface{Name: nmM[1], MTU: nmM[2]}
			}
			if curI != nil {
				mcM := mcRx.FindStringSubmatch(ln)
				if len(mcM) == 2 {
					curI.MACAddress = mcM[1]
				}
				ipM := ipRx.FindStringSubmatch(ln)
				if len(ipM) == 2 {
					curI.IPAddresses = append(curI.IPAddresses, ipM[1])
				}
				ip6M := ip6Rx.FindStringSubmatch(ln)
				if len(ip6M) == 2 {
					curI.IPAddresses = append(curI.IPAddresses, ip6M[1])
				}
			}
		}
		if curI != nil && (len(curI.IPAddresses) > 0 || curI.MACAddress != "") {
			interfaces = append(interfaces, *curI)
		}
	} else if runtime.GOOS == "darwin" {
		out, e := runCommand("ifconfig", "-a")
		if e != nil {
			return interfaces
		}
		mcDRx := regexp.MustCompile(`ether\s+([0-9a-fA-F:]{17})`)
		ipDRx := regexp.MustCompile(`inet\s+([0-9\.]+)\s+netmask`)
		ip6DRx := regexp.MustCompile(`inet6\s+([0-9a-fA-F:]+)%?\w*\s+prefixlen\s+(\d+)`)
		mtuDRx := regexp.MustCompile(`mtu\s+(\d+)`)
		stActRx := regexp.MustCompile(`status:\s+active`)
		var curN, curMtu, curMac string
		var curIps []string
		isAct := false
		scan := bufio.NewScanner(strings.NewReader(out))
		for scan.Scan() {
			ln := scan.Text()
			if !strings.HasPrefix(ln, "\t") && strings.Contains(ln, ": flags=") {
				if curN != "" && isAct && (len(curIps) > 0 || curMac != "") {
					interfaces = append(interfaces, NetworkInterface{Name: curN, MACAddress: curMac, IPAddresses: curIps, MTU: curMtu})
				}
				pts := strings.SplitN(ln, ":", 2)
				curN = pts[0]
				curIps = []string{}
				curMac = ""
				curMtu = ""
				isAct = false
				mtuM := mtuDRx.FindStringSubmatch(ln)
				if len(mtuM) > 1 {
					curMtu = mtuM[1]
				}
			}
			if curN == "" {
				continue
			}
			if stActRx.MatchString(ln) {
				isAct = true
			}
			mcM := mcDRx.FindStringSubmatch(ln)
			if len(mcM) > 1 {
				curMac = mcM[1]
			}
			ipM := ipDRx.FindStringSubmatch(ln)
			if len(ipM) > 1 {
				curIps = append(curIps, ipM[1])
			}
			ip6M := ip6DRx.FindStringSubmatch(ln)
			if len(ip6M) > 2 {
				curIps = append(curIps, ip6M[1]+"/"+ip6M[2])
			}
		}
		if curN != "" && isAct && (len(curIps) > 0 || curMac != "") {
			interfaces = append(interfaces, NetworkInterface{Name: curN, MACAddress: curMac, IPAddresses: curIps, MTU: curMtu})
		}
	}
	return interfaces
}

func getGPUInfo() []GPUInfo {
	var gpus []GPUInfo
	if runtime.GOOS == "linux" {
		out, e := runCommand("lspci", "-nnk")
		if e != nil {
			out, e = runCommand("lspci")
			if e != nil {
				gpus = append(gpus, GPUInfo{Model: "N/A (lspci failed)"})
				return gpus
			}
		}
		scan := bufio.NewScanner(strings.NewReader(out))
		var curGPU *GPUInfo
		vgaRx := regexp.MustCompile(`VGA compatible controller|3D controller`)
		devRx := regexp.MustCompile(`:\s*(.*)\s*\[(\w{4}:\w{4})\]`)
		for scan.Scan() {
			ln := scan.Text()
			if vgaRx.MatchString(ln) {
				if curGPU != nil {
					if curGPU.VRAM == "" {
						curGPU.VRAM = "N/A (Linux)"
					}
					gpus = append(gpus, *curGPU)
				}
				curGPU = &GPUInfo{VRAM: "N/A (Linux)"}
				m := devRx.FindStringSubmatch(ln)
				if len(m) > 1 {
					fd := strings.TrimSpace(m[1])
					pts := strings.SplitN(fd, ": ", 2)
					if len(pts) == 2 {
						curGPU.Vendor = strings.TrimSpace(pts[0])
						curGPU.Model = strings.TrimSpace(pts[1])
					} else {
						curGPU.Model = fd
					}
				}
			}
		}
		if curGPU != nil {
			if curGPU.Model != "" && curGPU.VRAM == "" {
				curGPU.VRAM = "N/A (Linux)"
			}
			if curGPU.Model != "" {
				gpus = append(gpus, *curGPU)
			}
		}
		if len(gpus) == 0 {
			gpus = append(gpus, GPUInfo{Model: "N/A (No VGA/3D found)"})
		}
	} else if runtime.GOOS == "darwin" {
		out, e := runCommand("system_profiler", "SPDisplaysDataType")
		if e != nil {
			gpus = append(gpus, GPUInfo{Model: "N/A (system_profiler failed)"})
			return gpus
		}
		scan := bufio.NewScanner(strings.NewReader(out))
		var curGPU *GPUInfo
		chipRx := regexp.MustCompile(`Chipset Model:\s*(.*)`)
		vendRx := regexp.MustCompile(`Vendor:\s*(.*)`)
		vramTRx := regexp.MustCompile(`VRAM \(Total\):\s*(.*)`)
		vramDRx := regexp.MustCompile(`VRAM \(Dynamic, Max\):\s*(.*)`)
		for scan.Scan() {
			ln := strings.TrimSpace(scan.Text())
			chipM := chipRx.FindStringSubmatch(ln)
			if len(chipM) > 1 {
				if curGPU != nil && curGPU.Model != "" {
					gpus = append(gpus, *curGPU)
				}
				curGPU = &GPUInfo{Model: chipM[1]}
			}
			if curGPU == nil {
				continue
			}
			vendM := vendRx.FindStringSubmatch(ln)
			if len(vendM) > 1 {
				curGPU.Vendor = vendM[1]
			}
			vramM := vramTRx.FindStringSubmatch(ln)
			if len(vramM) > 1 {
				curGPU.VRAM = vramM[1]
			} else {
				vramDM := vramDRx.FindStringSubmatch(ln)
				if len(vramDM) > 1 && curGPU.VRAM == "" {
					curGPU.VRAM = vramDM[1] + " (Shared/Dynamic)"
				}
			}
		}
		if curGPU != nil && curGPU.Model != "" {
			if curGPU.Vendor != "" {
				if reV := regexp.MustCompile(`(.*)\s*\(0x\w+\)`); reV.MatchString(curGPU.Vendor) {
					curGPU.Vendor = strings.TrimSpace(reV.FindStringSubmatch(curGPU.Vendor)[1])
				}
			}
			if curGPU.VRAM == "" {
				curGPU.VRAM = "N/A (macOS)"
			}
			gpus = append(gpus, *curGPU)
		}
		if len(gpus) == 0 {
			gpus = append(gpus, GPUInfo{Model: "N/A (No display found)"})
		}
	}
	return gpus
}

func main() {
	startTime := time.Now()
	fmt.Println("Gathering system information...")

	var wg sync.WaitGroup
	var osInfo OSInfo
	var cpuInfo CPUInfo
	var memInfo MemoryInfo
	var diskInfo []DiskInfo
	var netInterfaces []NetworkInterface
	var gpuInfo []GPUInfo
	wg.Add(6)
	go func() { defer wg.Done(); osInfo = getOSInfo() }()
	go func() { defer wg.Done(); cpuInfo = getCPUInfo() }()
	go func() { defer wg.Done(); memInfo = getMemoryInfo() }()
	go func() { defer wg.Done(); diskInfo = getDiskInfo() }()
	go func() { defer wg.Done(); netInterfaces = getNetworkInterfaces() }()
	go func() { defer wg.Done(); gpuInfo = getGPUInfo() }()
	wg.Wait()

	fmt.Println("\n--- Operating System ---")
	fmt.Printf("Name:           %s\nVersion:        %s\nKernel:         %s\nHostname:       %s\nArchitecture:   %s\nGo Version:     %s\nLogical CPUs:   %d\n",
		osInfo.Name, osInfo.Version, osInfo.Kernel, osInfo.Hostname, osInfo.Architecture, osInfo.GoVersion, osInfo.NumCPU)
	fmt.Println("\n--- CPU ---")
	fmt.Printf("Model:          %s\nVendor:         %s\nPhysical Cores: %d\nLogical Cores:  %d\nFrequency:      %s\nL2 Cache:       %s\nL3 Cache:       %s\n",
		cpuInfo.Model, cpuInfo.VendorID, cpuInfo.Cores, cpuInfo.Threads, cpuInfo.Frequency, cpuInfo.L2Cache, cpuInfo.L3Cache)
	fmt.Println("\n--- Memory (RAM) ---")
	fmt.Printf("Total:          %s\nUsed:           %s\nAvailable:      %s\n", memInfo.TotalRAM, memInfo.UsedRAM, memInfo.AvailableRAM)
	fmt.Println("--- Memory (Swap) ---")
	fmt.Printf("Total:          %s\nUsed:           %s\nFree:           %s\n", memInfo.TotalSwap, memInfo.UsedSwap, memInfo.FreeSwap)
	fmt.Println("\n--- Disk Usage ---")
	fmt.Printf("%-30s %-10s %-10s %-10s %-10s %s\n", "Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on")
	fmt.Println(strings.Repeat("-", 90))
	for _, d := range diskInfo {
		fs := d.FileSystem
		if len(fs) > 28 {
			fs = fs[:25] + "..."
		}
		if strings.HasPrefix(d.FileSystem, "map") && (d.Size == "0 B" || d.Size == "0") {
			continue
		}
		fmt.Printf("%-30s %-10s %-10s %-10s %-10s %s\n", fs, d.Size, d.Used, d.Available, d.UsePercent, d.MountedOn)
	}
	fmt.Println("\n--- Network Interfaces (Active) ---")
	for _, iface := range netInterfaces {
		fmt.Printf("Interface:      %s\n", iface.Name)
		if iface.MACAddress != "" {
			fmt.Printf("  MAC Address:  %s\n", iface.MACAddress)
		}
		if iface.MTU != "" {
			fmt.Printf("  MTU:          %s\n", iface.MTU)
		}
		if len(iface.IPAddresses) > 0 {
			fmt.Printf("  IP Addresses: %s\n", strings.Join(iface.IPAddresses, ", "))
		} else {
			fmt.Printf("  IP Addresses: N/A\n")
		}
	}
	fmt.Println("\n--- GPU(s) ---")
	if len(gpuInfo) > 0 {
		for i, gpu := range gpuInfo {
			fmt.Printf("GPU %d:\n  Vendor:       %s\n  Model:        %s\n  VRAM:         %s\n", i, gpu.Vendor, gpu.Model, gpu.VRAM)
		}
	} else {
		fmt.Println("  No GPU information found or an error occurred.")
	}
	fmt.Printf("\nReport generated in %s\n", time.Since(startTime))
}

Run commands:

go run main.go

Or build and run as a binary:

go build -o hwinfo main.go
./hwinfo

The tool provides comprehensive hardware information including CPU details, memory usage, disk space, active network interfaces, and GPU specifications. The concurrent execution typically completes in under a second, making it efficient for scripting or monitoring applications.

This implementation demonstrates practical system programming in Go, showing how to execute system commands, parse their output, handle errors gracefully, and use concurrency effectively. The code is structured to be easily extensible - you can add new hardware detection functions by following the same pattern of system command execution and output parsing.

$ ./hwinfo
Gathering system information...

--- Operating System ---
Name:           macOS
Version:        13.2.1
Kernel:         22.3.0
Hostname:       MacBook-Pro.local
Architecture:   arm64
Go Version:     go1.21.0
Logical CPUs:   10

--- CPU ---
Model:          Apple M1 Pro
Vendor:         Apple
Physical Cores: 10
Logical Cores:  10
Frequency:      N/A
L2 Cache:       12.0 MiB
L3 Cache:       N/A

--- Memory (RAM) ---
Total:          16.0 GiB
Used:           8.2 GiB
Available:      7.8 GiB
--- Memory (Swap) ---
Total:          1.0G
Used:           512.00M
Free:           512.00M

--- Disk Usage ---
Filesystem                     Size       Used       Avail      Use%       Mounted on
------------------------------------------------------------------------------------------
/dev/disk3s1s1                 460.4 GiB  15.2 GiB   178.4 GiB  8%         /
/dev/disk3s6                   460.4 GiB  1.2 GiB    178.4 GiB  1%         /System/Volumes/VM
/dev/disk3s2                   460.4 GiB  384.9 MiB  178.4 GiB  1%         /System/Volumes/Preboot
/dev/disk3s4                   460.4 GiB  14.9 MiB   178.4 GiB  1%         /System/Volumes/Update
/dev/disk1s2                   500.1 MiB  6.1 MiB    481.5 MiB  2%         /System/Volumes/xART
/dev/disk1s1                   500.1 MiB  7.4 MiB    481.5 MiB  2%         /System/Volumes/iSCPreboot
/dev/disk1s3                   500.1 MiB  1.2 MiB    481.5 MiB  1%         /System/Volumes/Hardware

--- Network Interfaces (Active) ---
Interface:      en0
  MAC Address:  a4:83:e7:2a:5f:c8
  MTU:          1500
  IP Addresses: 192.168.1.145, fe80::1c85:2ff4:a8e3:d127/64

Interface:      awdl0
  MAC Address:  b2:f4:29:8d:3a:e1
  MTU:          1500
  IP Addresses: fe80::b0f4:29ff:fe8d:3ae1/64

Interface:      llw0
  MAC Address:  b2:f4:29:8d:3a:e1
  MTU:          1500
  IP Addresses: fe80::b0f4:29ff:fe8d:3ae1/64

Interface:      utun0
  MTU:          1380
  IP Addresses: fe80::a4c3:d9ff:fe7b:1234/64

--- GPU(s) ---
GPU 0:
  Vendor:       Apple
  Model:        Apple M1 Pro
  VRAM:         N/A (macOS)

Report generated in 487ms

GitHub Repository: You can find the complete source code at: https://github.com/rezmoss/macos-hardware-detection-go

comments powered by Disqus