ghw - Go HardWare discovery/inspection library
ghw is a Go library providing hardware inspection and discovery for Linux and
Windows. There currently exists partial support for MacOSX.
ghw goes the extra mile to be useful without root privileges. We query for
host hardware information as directly as possible without relying on shellouts
to programs like dmidecode that require root privileges to execute.
Elevated privileges are indeed required to query for some information, but
ghw will never error out if blocked from reading that information. Instead,
ghw will print a warning message about the information that could not be
retrieved. You may disable these warning messages with the
GHW_DISABLE_WARNINGS environment variable.
The code itself should be well-documented with lots of usage examples.
Each module in the library should be structured in a consistent fashion, and the structs returned by various library functions should have consistent attribute and method names.
ghw is a tool for gathering information about your hardware's capacity
and capabilities.
It is important to point out that ghw does NOT report information that is
temporary or variable. It is NOT a system monitor nor is it an appropriate
tool for gathering data points for metrics that change over time. If you are
looking for a system that tracks usage of CPU, memory, network I/O or disk
I/O, there are plenty of great open source tools that do this! Check out the
Prometheus project for a great example.
ghw has functions that return an Info object about a particular hardware
domain (e.g. CPU, Memory, Block storage, etc).
Use the following functions in ghw to inspect information about the host
hardware:
ghw.CPU()ghw.Memory()ghw.Block() (block storage)ghw.Topology() (processor architecture, NUMA topology and
memory cache hierarchy)ghw.Network()ghw.PCI()ghw.GPU() (graphical processing unit)ghw.Accelerator() (processing accelerators, AI)ghw.Chassis()ghw.BIOS()ghw.Baseboard()ghw.Product()The ghw.CPU() function returns a ghw.CPUInfo struct that contains
information about the CPUs on the host system.
ghw.CPUInfo contains the following fields:
ghw.CPUInfo.TotalCores has the total number of physical cores the host
system containsghw.CPUInfo.TotalHardwareThreads has the total number of hardware threads
the host system containsghw.CPUInfo.Processors is an array of ghw.Processor structs, one for each
physical processor package contained in the hostEach ghw.Processor struct contains a number of fields:
ghw.Processor.ID is the physical processor uint32 ID according to the
systemghw.Processor.TotalCores is the number of physical cores in the processor
packageghw.Processor.TotalHardwareThreads is the number of hardware threads in the
processor packageghw.Processor.Vendor is a string containing the vendor nameghw.Processor.Model is a string containing the vendor's model nameghw.Processor.Capabilities (Linux only) is an array of strings indicating
the features the processor has enabledghw.Processor.Cores (Linux only) is an array of ghw.ProcessorCore structs
that are packed onto this physical processorA ghw.ProcessorCore has the following fields:
ghw.ProcessorCore.ID is the uint32 identifier that the host gave this
core. Note that this does not necessarily equate to a zero-based index of
the core within a physical package. For example, the core IDs for an Intel Core
i7 are 0, 1, 2, 8, 9, and 10ghw.ProcessorCore.TotalHardwareThreads is the number of hardware threads
associated with the coreghw.ProcessorCore.LogicalProcessors is an array of ints representing the
logical processor IDs assigned to any processing unit for the core. These are
sometimes called the "thread siblings". Logical processor IDs are the
zero-based index of the processor on the host and are not related to the
core ID.package main
import (
"fmt"
"math"
"strings"
"github.com/jaypipes/ghw"
)
func main() {
cpu, err := ghw.CPU()
if err != nil {
fmt.Printf("Error getting CPU info: %v", err)
}
fmt.Printf("%v\n", cpu)
for _, proc := range cpu.Processors {
fmt.Printf(" %v\n", proc)
for _, core := range proc.Cores {
fmt.Printf(" %v\n", core)
}
if len(proc.Capabilities) > 0 {
// pretty-print the (large) block of capability strings into rows
// of 6 capability strings
rows := int(math.Ceil(float64(len(proc.Capabilities)) / float64(6)))
for row := 1; row < rows; row = row + 1 {
rowStart := (row * 6) - 1
rowEnd := int(math.Min(float64(rowStart+6), float64(len(proc.Capabilities))))
rowElems := proc.Capabilities[rowStart:rowEnd]
capStr := strings.Join(rowElems, " ")
if row == 1 {
fmt.Printf(" capabilities: [%s\n", capStr)
} else if rowEnd < len(proc.Capabilities) {
fmt.Printf(" %s\n", capStr)
} else {
fmt.Printf(" %s]\n", capStr)
}
}
}
}
}
Example output from my personal workstation:
cpu (1 physical package, 6 cores, 12 hardware threads)
physical package #0 (6 cores, 12 hardware threads)
processor core #0 (2 threads), logical processors [0 6]
processor core #1 (2 threads), logical processors [1 7]
processor core #2 (2 threads), logical processors [2 8]
processor core #3 (2 threads), logical processors [3 9]
processor core #4 (2 threads), logical processors [4 10]
processor core #5 (2 threads), logical processors [5 11]
capabilities: [msr pae mce cx8 apic sep
mtrr pge mca cmov pat pse36
clflush dts acpi mmx fxsr sse
sse2 ss ht tm pbe syscall
nx pdpe1gb rdtscp lm constant_tsc arch_perfmon
pebs bts rep_good nopl xtopology nonstop_tsc
cpuid aperfmperf pni pclmulqdq dtes64 monitor
ds_cpl vmx est tm2 ssse3 cx16
xtpr pdcm pcid sse4_1 sse4_2 popcnt
aes lahf_lm pti retpoline tpr_shadow vnmi
flexpriority ept vpid dtherm ida arat]
The ghw.Memory() function returns a ghw.MemoryInfo struct that contains
information about the RAM on the host system.
ghw.MemoryInfo contains the following fields:
ghw.MemoryInfo.TotalPhysicalBytes contains the amount of physical memory on
the hostghw.MemoryInfo.TotalUsableBytes contains the amount of memory the
system can actually use. Usable memory accounts for things like the kernel's
resident memory size and some reserved system bits. Please note this value is
NOT the amount of memory currently in use by processes in the system. See
[the discussion][#physical-versus-usage-memory] about the difference.ghw.MemoryInfo.SupportedPageSizes is an array of integers representing the
size, in bytes, of memory pages the system supportsghw.MemoryInfo.Modules is an array of pointers to ghw.MemoryModule
structs, one for each physical DIMM.
Currently, this information is only included on Windows, with Linux support
planned.package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
memory, err := ghw.Memory()
if err != nil {
fmt.Printf("Error getting memory info: %v", err)
}
fmt.Println(memory.String())
}
Example output from my personal workstation:
memory (24GB physical, 24GB usable)
There has been some confusion regarding the difference between the total physical bytes versus total usable bytes of memory.
Some of this confusion has been due to a misunderstanding of the term "usable".
As mentioned above, ghw does inspection of the
system's capacity.
A host computer has two capacities when it comes to RAM. The first capacity is
the amount of RAM that is contained in all memory banks (DIMMs) that are
attached to the motherboard. ghw.MemoryInfo.TotalPhysicalBytes refers to this
first capacity.
There is a (usually small) amount of RAM that is consumed by the bootloader
before the operating system is started (booted). Once the bootloader has booted
the operating system, the amount of RAM that may be used by the operating
system and its applications is fixed. ghw.MemoryInfo.TotalUsableBytes refers
to this second capacity.
You can determine the amount of RAM that the bootloader used (that is not made
available to the operating system) by subtracting
ghw.MemoryInfo.TotalUsableBytes from ghw.MemoryInfo.TotalPhysicalBytes:
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
memory, err := ghw.Memory()
if err != nil {
fmt.Printf("Error getting memory info: %v", err)
}
phys := memory.TotalPhysicalBytes
usable := memory.TotalUsableBytes
fmt.Printf("The bootloader consumes %d bytes of RAM\n", phys - usable)
}
Example output from my personal workstation booted into a Windows10 operating system with a Linux GRUB bootloader:
The bootloader consumes 3832720 bytes of RAM
The ghw.Block() function returns a ghw.BlockInfo struct that contains
information about the block storage on the host system.
ghw.BlockInfo contains the following fields:
ghw.BlockInfo.TotalSizeBytes contains the amount of physical block storage
on the host.ghw.BlockInfo.Disks is an array of pointers to ghw.Disk structs, one for
each disk found by the systemEach ghw.Disk struct contains the following fields:
ghw.Disk.Name contains a string with the short name of the disk, e.g. "sda"ghw.Disk.SizeBytes contains the amount of storage the disk providesghw.Disk.PhysicalBlockSizeBytes contains the size of the physical blocks
used on the disk, in bytes. This is typically the minimum amount of data that
will be written in a single write operation for the disk.ghw.Disk.IsRemovable contains a boolean indicating if the disk drive is
removableghw.Disk.DriveType is the type of drive. It is of type ghw.DriveType
which has a ghw.DriveType.String() method that can be called to return a
string representation of the bus. This string will be HDD, FDD, ODD,
or SSD, which correspond to a hard disk drive (rotational), floppy drive,
optical (CD/DVD) drive and solid-state drive.ghw.Disk.StorageController is the type of storage controller. It is of type
ghw.StorageController which has a ghw.StorageController.String() method
that can be called to return a string representation of the bus. This string
will be SCSI, IDE, virtio, MMC, or NVMeghw.Disk.BusPath (Linux, Darwin only) is the filepath to the bus used by
the disk.ghw.Disk.NUMANodeID (Linux only) is the numeric index of the NUMA node this
disk is local to, or -1 if the host system is not a NUMA system or is not
Linux.ghw.Disk.Vendor contains a string with the name of the hardware vendor for
the diskghw.Disk.Model contains a string with the vendor-assigned disk model nameghw.Disk.SerialNumber contains a string with the disk's serial numberghw.Disk.WWN contains a string with the disk's
World Wide Nameghw.Disk.Partitions contains an array of pointers to ghw.Partition
structs, one for each partition on the diskEach ghw.Partition struct contains these fields:
ghw.Partition.Name contains a string with the short name of the partition,
e.g. sda1ghw.Partition.Label contains the label for the partition itself. On Linux
systems, this is derived from the ID_PART_ENTRY_NAME udev entry for
the partition.ghw.Partition.FilesystemLabel contains the label for the filesystem housed
on the partition. On Linux systems, this is derived from the ID_FS_NAME
udev entry for the partition.ghw.Partition.SizeBytes contains the amount of storage the partition
providesghw.Partition.MountPoint contains a string with the partition's mount
point, or "" if no mount point was discoveredghw.Partition.Type contains a string indicated the filesystem type for the
partition, or "" if the system could not determine the typeghw.Partition.IsReadOnly is a bool indicating the partition is read-onlyghw.Partition.Disk is a pointer to the ghw.Disk object associated with
the partition.ghw.Partition.UUID is a string containing the partition UUID on Linux and MacOS,
and the VolumeSerialNumber on Windows (e.g. "A8C3D032"). On Linux systems, this is
derived from the ID_PART_ENTRY_UUID udev entry for the partition.```go package main
import ( "fmt"
"github.com/jaypipes/ghw"
)
func main() { bloc