ghw
- Go HardWare library
ghw
is a Go library providing hardware inspection and discovery for Linux and
Windows. There is partial support for MacOSX.
ghw
gathers information about your hardware's capacity, capabilities,
and resource usage.
History
go-hardware/ghw
began life as jaypipes/ghw
. All
functionality from jaypipes/ghw
was ported over to go-hardware/ghw
by Jay
Pipes in an effort to bring ghw
into a non-personal Github organization and
increase the number of contributors to ghw
.
If you're a jaypipes/ghw
user, read the
documentation on changes between the
old and new ghw
.
Design Principles
-
No root privileges needed for discovery
ghw
goes the extra mile to be useful without root priveleges. 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.
-
Well-documented code and plenty of example code
The code itself should be well-documented with lots of usage examples.
-
Interfaces should be consistent across modules
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.
Usage
ghw
is primarily designed as a Go library that other projects can import.
However, we also ship a command line tool called ghw
. The ghw
CLI tool
queries system hardware information, can print that information in several
formats and can be used to create a tarball snapshot of a Linux system for use
in testing and debugging.
The ghw
library has a set of module functions that return an Info
object
about a particular hardware domain (e.g. CPU, Memory, Block storage, etc).
Use the following module functions in ghw
to inspect information about the
host hardware:
Each top-level function has the same signature. The top-level functions accept
one parameter of type context.Context
and return a pointer to a ghw.XXXInfo
struct.
A ghw.XXXInfo
struct corresponds to the name of the module being queried. For
example, the ghw.CPU
module's ghw.XXXInfo
struct is ghw.CPUInfo
.
To control ghw
's discovery behaviour, when calling a module function, pass in
a context.Context
object returned from ghw.NewContext()
that has been
modified with a ghw
With function.
Here is an example of using a with function to disable any warnings that ghw
might write to stderr
:
package main
import (
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
ctx := ghw.NewContext(ghw.WithDisableWarnings())
cpu, err := ghw.CPU(ctx)
if err != nil {
fmt.Printf("Error getting CPU info: %v", err)
}
fmt.Printf("%v\n", cpu)
}
CPU
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 contains
ghw.CPUInfo.TotalThreads
has the total number of hardware threads the
host system contains
ghw.CPUInfo.Processors
is an array of ghw.Processor
structs, one for each
physical processor package contained in the host
Each ghw.Processor
struct contains a number of fields:
ghw.Processor.ID
is the physical processor uint32
ID according to the
system
ghw.Processor.NumCores
is the number of physical cores in the processor
package
ghw.Processor.NumThreads
is the number of hardware threads in the processor
package
ghw.Processor.Vendor
is a string containing the vendor name
ghw.Processor.Model
is a string containing the vendor's model name
ghw.Processor.Capabilities
(Linux only) is an array of strings indicating
the features the processor has enabled
ghw.Processor.Cores
(Linux only) is an array of ghw.ProcessorCore
structs
that are packed onto this physical processor
A 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 10
ghw.ProcessorCore.NumThreads
is the number of hardware threads associated
with the core
ghw.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 (
"context"
"fmt"
"math"
"strings"
"github.com/go-hardware/ghw"
)
func main() {
cpu, err := ghw.CPU(context.TODO())
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]
Memory
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 host
ghw.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.TotalUsedBytes
contains the amount of memory the system is
currently using. On Linux, this value is calculated by subtracting the sum of
free, buffered, cached and slab reclaimable memory from the total amount of
usable memory.
ghw.MemoryInfo.SupportedPageSizes
is an array of integers representing the
size, in bytes, of memory pages the system supports
ghw.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 (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
memory, err := ghw.Memory(context.TODO())
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, 9GB used)
Physical versus Usable Memory
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 (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
memory, err := ghw.Memory(context.TODO())
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
Block storage
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 system
Each 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 provides
ghw.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
removable
ghw.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 NVMe
ghw.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 disk
ghw.Disk.Model
contains a string with the vendor-assigned disk model name
ghw.Disk.SerialNumber
contains a string with the disk's serial number
ghw.Disk.WWN
contains a string with the disk's
World Wide Name
ghw.Disk.Partitions
contains an array of pointers to ghw.Partition
structs, one for each partition on the disk
Each ghw.Partition
struct contains these fields:
ghw.Partition.Name
contains a string with the short name of the partition,
e.g. sda1
ghw.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
provides
ghw.Partition.MountPoint
contains a string with the partition's mount
point, or ""
if no mount point was discovered
ghw.Partition.Type
contains a string indicated the filesystem type for the
partition, or ""
if the system could not determine the type
ghw.Partition.IsReadOnly
is a bool indicating the partition is read-only
ghw.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, the
partition UUID on MacOS and nothing on Windows. On Linux systems, this is
derived from the ID_PART_ENTRY_UUID
udev entry for the partition.
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
block, err := ghw.Block(context.TODO())
if err != nil {
fmt.Printf("Error getting block storage info: %v", err)
}
fmt.Printf("%v\n", block)
for _, disk := range block.Disks {
fmt.Printf(" %v\n", disk)
for _, part := range disk.Partitions {
fmt.Printf(" %v\n", part)
}
}
}
Example output from my personal workstation:
block storage (1 disk, 2TB physical storage)
sda HDD (2TB) SCSI [@pci-0000:04:00.0-scsi-0:1:0:0 (node #0)] vendor=LSI model=Logical_Volume serial=600508e000000000f8253aac9a1abd0c WWN=0x600508e000000000f8253aac9a1abd0c
/dev/sda1 (100MB)
/dev/sda2 (187GB)
/dev/sda3 (449MB)
/dev/sda4 (1KB)
/dev/sda5 (15GB)
/dev/sda6 (2TB) [ext4] mounted@/
NOTE: ghw
looks in the udev runtime database for some information. If
you are using ghw
in a container, remember to bind mount /dev/disk
and
/run
into your container, otherwise ghw
won't be able to query the udev
DB or sysfs paths for information.
Topology
NOTE: Topology support is currently Linux-only. Windows support is
planned.
The ghw.Topology()
function returns a ghw.TopologyInfo
struct that contains
information about the host computer's architecture (NUMA vs. SMP), the host's
NUMA node layout and processor-specific memory caches.
The ghw.TopologyInfo
struct contains two fields:
ghw.TopologyInfo.Architecture
contains an enum with the value ghw.NUMA
or
ghw.SMP
depending on what the topology of the system is
ghw.TopologyInfo.Nodes
is an array of pointers to ghw.TopologyNode
structs, one for each topology node (typically physical processor package)
found by the system
Each ghw.TopologyNode
struct contains the following fields:
ghw.TopologyNode.ID
is the system's uint32
identifier for the node
ghw.TopologyNode.Memory
is a ghw.MemoryArea
struct describing the memory
attached to this node.
ghw.TopologyNode.Cores
is an array of pointers to ghw.ProcessorCore
structs that
are contained in this node
ghw.TopologyNode.Caches
is an array of pointers to ghw.MemoryCache
structs that
represent the low-level caches associated with processors and cores on the
system
ghw.TopologyNode.Distance
is an array of distances between NUMA nodes as reported
by the system.
ghw.MemoryArea
describes a collection of physical RAM on the host.
In the simplest and most common case, all system memory fits in a single memory
area. In more complex host systems, like NUMA systems, many memory
areas may be present in the host system (e.g. one for each NUMA cell).
The ghw.MemoryArea
struct contains the following fields:
ghw.MemoryArea.TotalPhysicalBytes
contains the amount of physical memory
associated with this memory area.
ghw.MemoryArea.TotalUsableBytes
contains the amount of memory of this
memory area 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.
See above in the CPU section for information about the
ghw.ProcessorCore
struct and how to use and query it.
Each ghw.MemoryCache
struct contains the following fields:
ghw.MemoryCache.Type
is an enum that contains one of ghw.DATA
,
ghw.INSTRUCTION
or ghw.UNIFIED
depending on whether the cache stores CPU
instructions, program data, or both
ghw.MemoryCache.Level
is a positive integer indicating how close the cache
is to the processor. The lower the number, the closer the cache is to the
processor and the faster the processor can access its contents
ghw.MemoryCache.SizeBytes
is an integer containing the number of bytes the
cache can contain
ghw.MemoryCache.LogicalProcessors
is an array of integers representing the
logical processors that use the cache
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
topology, err := ghw.Topology(context.TODO())
if err != nil {
fmt.Printf("Error getting topology info: %v", err)
}
fmt.Printf("%v\n", topology)
for _, node := range topology.Nodes {
fmt.Printf(" %v\n", node)
for _, cache := range node.Caches {
fmt.Printf(" %v\n", cache)
}
}
}
Example output from my personal workstation:
topology SMP (1 nodes)
node #0 (6 cores)
L1i cache (32 KB) shared with logical processors: 3,9
L1i cache (32 KB) shared with logical processors: 2,8
L1i cache (32 KB) shared with logical processors: 11,5
L1i cache (32 KB) shared with logical processors: 10,4
L1i cache (32 KB) shared with logical processors: 0,6
L1i cache (32 KB) shared with logical processors: 1,7
L1d cache (32 KB) shared with logical processors: 11,5
L1d cache (32 KB) shared with logical processors: 10,4
L1d cache (32 KB) shared with logical processors: 3,9
L1d cache (32 KB) shared with logical processors: 1,7
L1d cache (32 KB) shared with logical processors: 0,6
L1d cache (32 KB) shared with logical processors: 2,8
L2 cache (256 KB) shared with logical processors: 2,8
L2 cache (256 KB) shared with logical processors: 3,9
L2 cache (256 KB) shared with logical processors: 0,6
L2 cache (256 KB) shared with logical processors: 10,4
L2 cache (256 KB) shared with logical processors: 1,7
L2 cache (256 KB) shared with logical processors: 11,5
L3 cache (12288 KB) shared with logical processors: 0,1,10,11,2,3,4,5,6,7,8,9
Network
The ghw.Network()
function returns a ghw.NetworkInfo
struct that contains
information about the host computer's networking hardware.
The ghw.NetworkInfo
struct contains one field:
ghw.NetworkInfo.NICs
is an array of pointers to ghw.NIC
structs, one
for each network interface controller found for the systen
Each ghw.NIC
struct contains the following fields:
ghw.NIC.Name
is the system's identifier for the NIC
ghw.NIC.MACAddress
is the Media Access Control (MAC) address for the NIC,
if any
ghw.NIC.IsVirtual
is a boolean indicating if the NIC is a virtualized
device
ghw.NIC.Capabilities
(Linux only) is an array of pointers to
ghw.NICCapability
structs that can describe the things the NIC supports.
These capabilities match the returned values from the ethtool -k <DEVICE>
call on Linux as well as the AutoNegotiation and PauseFrameUse capabilities
from ethtool
.
ghw.NIC.PCIAddress
(Linux only) is the PCI device address of the device
backing the NIC. this is not-nil only if the backing device is indeed a PCI
device; more backing devices (e.g. USB) will be added in future versions.
ghw.NIC.Speed
(Linux only) is a string showing the current link speed. On
Linux, this field will be present even if ethtool
is not available.
ghw.NIC.Duplex
(Linux only) is a string showing the current link duplex. On
Linux, this field will be present even if ethtool
is not available.
ghw.NIC.SupportedLinkModes
(Linux only) is a string slice containing a list
of supported link modes, e.g. "10baseT/Half", "1000baseT/Full".
ghw.NIC.SupportedPorts
(Linux only) is a string slice containing the list
of supported port types, e.g. "MII", "TP", "FIBRE", "Twisted Pair".
ghw.NIC.SupportedFECModes
(Linux only) is a string slice containing a list
of supported Forward Error Correction (FEC) Modes.
ghw.NIC.AdvertisedLinkModes
(Linux only) is a string slice containing the
link modes being advertised during auto negotiation.
ghw.NIC.AdvertisedFECModes
(Linux only) is a string slice containing the
Forward Error Correction (FEC) modes advertised during auto negotiation.
The ghw.NICCapability
struct contains the following fields:
ghw.NICCapability.Name
is the string name of the capability (e.g.
"tcp-segmentation-offload")
ghw.NICCapability.IsEnabled
is a boolean indicating whether the capability
is currently enabled/active on the NIC
ghw.NICCapability.CanEnable
is a boolean indicating whether the capability
may be enabled
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
net, err := ghw.Network(context.TODO())
if err != nil {
fmt.Printf("Error getting network info: %v", err)
}
fmt.Printf("%v\n", net)
for _, nic := range net.NICs {
fmt.Printf(" %v\n", nic)
enabledCaps := make([]int, 0)
for x, cap := range nic.Capabilities {
if cap.IsEnabled {
enabledCaps = append(enabledCaps, x)
}
}
if len(enabledCaps) > 0 {
fmt.Printf(" enabled capabilities:\n")
for _, x := range enabledCaps {
fmt.Printf(" - %s\n", nic.Capabilities[x].Name)
}
}
}
}
Example output from my personal laptop:
net (3 NICs)
docker0
enabled capabilities:
- tx-checksumming
- tx-checksum-ip-generic
- scatter-gather
- tx-scatter-gather
- tx-scatter-gather-fraglist
- tcp-segmentation-offload
- tx-tcp-segmentation
- tx-tcp-ecn-segmentation
- tx-tcp-mangleid-segmentation
- tx-tcp6-segmentation
- udp-fragmentation-offload
- generic-segmentation-offload
- generic-receive-offload
- tx-vlan-offload
- highdma
- tx-lockless
- netns-local
- tx-gso-robust
- tx-fcoe-segmentation
- tx-gre-segmentation
- tx-gre-csum-segmentation
- tx-ipxip4-segmentation
- tx-ipxip6-segmentation
- tx-udp_tnl-segmentation
- tx-udp_tnl-csum-segmentation
- tx-gso-partial
- tx-sctp-segmentation
- tx-esp-segmentation
- tx-vlan-stag-hw-insert
enp58s0f1
enabled capabilities:
- rx-checksumming
- generic-receive-offload
- rx-vlan-offload
- tx-vlan-offload
- highdma
- auto-negotiation
wlp59s0
enabled capabilities:
- scatter-gather
- tx-scatter-gather
- generic-segmentation-offload
- generic-receive-offload
- highdma
- netns-local
PCI
ghw
contains a PCI database inspection and querying facility that allows
developers to not only gather information about devices on a local PCI bus but
also query for information about hardware device classes, vendor and product
information.
NOTE: Parsing of the PCI-IDS file database is provided by the separate
github.com/jaypipes/pcidb library. You
can read that library's README for more information about the various structs
that are exposed on the ghw.PCIInfo
struct.
The ghw.PCI()
function returns a ghw.PCIInfo
struct that contains
information about the host computer's PCI devices.
The ghw.PCIInfo
struct contains one field:
ghw.PCIInfo.Devices
is a slice of pointers to ghw.PCIDevice
structs that
describe the PCI devices on the host system
NOTE: PCI products are often referred to by their "device ID". We use the
term "product ID" in ghw
because it more accurately reflects what the
identifier is for: a specific product line produced by the vendor.
The ghw.PCIDevice
struct has the following fields:
ghw.PCIDevice.Vendor
is a pointer to a pcidb.Vendor
struct that
describes the device's primary vendor. This will always be non-nil.
ghw.PCIDevice.Product
is a pointer to a pcidb.Product
struct that
describes the device's primary product. This will always be non-nil.
ghw.PCIDevice.Subsystem
is a pointer to a pcidb.Product
struct that
describes the device's secondary/sub-product. This will always be non-nil.
ghw.PCIDevice.Class
is a pointer to a pcidb.Class
struct that
describes the device's class. This will always be non-nil.
ghw.PCIDevice.Subclass
is a pointer to a pcidb.Subclass
struct
that describes the device's subclass. This will always be non-nil.
ghw.PCIDevice.ProgrammingInterface
is a pointer to a
pcidb.ProgrammingInterface
struct that describes the device subclass'
programming interface. This will always be non-nil.
ghw.PCIDevice.Driver
is a string representing the device driver the
system is using to handle this device. Can be empty string if this
information is not available. If the information is not available, this does
not mean the device is not functioning, but rather that ghw
was not able to
retrieve driver information.
The ghw.PCIAddress
(which is an alias for the ghw.pci.address.Address
struct) contains the PCI address fields. It has a ghw.PCIAddress.String()
method that returns the canonical Domain:Bus:Device.Function ([D]BDF)
representation of this Address.
The ghw.PCIAddress
struct has the following fields:
ghw.PCIAddress.Domain
is a string representing the PCI domain component of
the address.
ghw.PCIAddress.Bus
is a string representing the PCI bus component of
the address.
ghw.PCIAddress.Device
is a string representing the PCI device component of
the address.
ghw.PCIAddress.Function
is a string representing the PCI function component of
the address.
The following code snippet shows how to list the PCI devices on the host system
and output a simple list of PCI address and vendor/product information:
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
pci, err := ghw.PCI(context.TODO())
if err != nil {
fmt.Printf("Error getting PCI info: %v", err)
}
fmt.Printf("host PCI devices:\n")
fmt.Println("====================================================")
for _, device := range pci.Devices {
vendor := device.Vendor
vendorName := vendor.Name
if len(vendor.Name) > 20 {
vendorName = string([]byte(vendorName)[0:17]) + "..."
}
product := device.Product
productName := product.Name
if len(product.Name) > 40 {
productName = string([]byte(productName)[0:37]) + "..."
}
fmt.Printf("%-12s\t%-20s\t%-40s\n", device.Address, vendorName, productName)
}
}
on my local workstation the output of the above looks like the following:
host PCI devices:
====================================================
0000:00:00.0 Intel Corporation 5520/5500/X58 I/O Hub to ESI Port
0000:00:01.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo...
0000:00:02.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo...
0000:00:03.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo...
0000:00:07.0 Intel Corporation 5520/5500/X58 I/O Hub PCI Express Roo...
0000:00:10.0 Intel Corporation 7500/5520/5500/X58 Physical and Link ...
0000:00:10.1 Intel Corporation 7500/5520/5500/X58 Routing and Protoc...
0000:00:14.0 Intel Corporation 7500/5520/5500/X58 I/O Hub System Man...
0000:00:14.1 Intel Corporation 7500/5520/5500/X58 I/O Hub GPIO and S...
0000:00:14.2 Intel Corporation 7500/5520/5500/X58 I/O Hub Control St...
0000:00:14.3 Intel Corporation 7500/5520/5500/X58 I/O Hub Throttle R...
0000:00:19.0 Intel Corporation 82567LF-2 Gigabit Network Connection
0000:00:1a.0 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr...
0000:00:1a.1 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr...
0000:00:1a.2 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr...
0000:00:1a.7 Intel Corporation 82801JI (ICH10 Family) USB2 EHCI Cont...
0000:00:1b.0 Intel Corporation 82801JI (ICH10 Family) HD Audio Contr...
0000:00:1c.0 Intel Corporation 82801JI (ICH10 Family) PCI Express Ro...
0000:00:1c.1 Intel Corporation 82801JI (ICH10 Family) PCI Express Po...
0000:00:1c.4 Intel Corporation 82801JI (ICH10 Family) PCI Express Ro...
0000:00:1d.0 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr...
0000:00:1d.1 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr...
0000:00:1d.2 Intel Corporation 82801JI (ICH10 Family) USB UHCI Contr...
0000:00:1d.7 Intel Corporation 82801JI (ICH10 Family) USB2 EHCI Cont...
0000:00:1e.0 Intel Corporation 82801 PCI Bridge
0000:00:1f.0 Intel Corporation 82801JIR (ICH10R) LPC Interface Contr...
0000:00:1f.2 Intel Corporation 82801JI (ICH10 Family) SATA AHCI Cont...
0000:00:1f.3 Intel Corporation 82801JI (ICH10 Family) SMBus Controller
0000:01:00.0 NEC Corporation uPD720200 USB 3.0 Host Controller
0000:02:00.0 Marvell Technolog... 88SE9123 PCIe SATA 6.0 Gb/s controller
0000:02:00.1 Marvell Technolog... 88SE912x IDE Controller
0000:03:00.0 NVIDIA Corporation GP107 [GeForce GTX 1050 Ti]
0000:03:00.1 NVIDIA Corporation UNKNOWN
0000:04:00.0 LSI Logic / Symbi... SAS2004 PCI-Express Fusion-MPT SAS-2 ...
0000:06:00.0 Qualcomm Atheros AR5418 Wireless Network Adapter [AR50...
0000:08:03.0 LSI Corporation FW322/323 [TrueFire] 1394a Controller
0000:3f:00.0 Intel Corporation UNKNOWN
0000:3f:00.1 Intel Corporation Xeon 5600 Series QuickPath Architectu...
0000:3f:02.0 Intel Corporation Xeon 5600 Series QPI Link 0
0000:3f:02.1 Intel Corporation Xeon 5600 Series QPI Physical 0
0000:3f:02.2 Intel Corporation Xeon 5600 Series Mirror Port Link 0
0000:3f:02.3 Intel Corporation Xeon 5600 Series Mirror Port Link 1
0000:3f:03.0 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:03.1 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:03.4 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:04.0 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:04.1 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:04.2 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:04.3 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:05.0 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:05.1 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:05.2 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:05.3 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:06.0 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:06.1 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:06.2 Intel Corporation Xeon 5600 Series Integrated Memory Co...
0000:3f:06.3 Intel Corporation Xeon 5600 Series Integrated Memory Co...
Finding a PCI device by PCI address
In addition to the above information, the ghw.PCIInfo
struct has the
following method:
ghw.PCIInfo.GetDevice(address string)
The following code snippet shows how to call the ghw.PCIInfo.GetDevice()
method and use its returned ghw.PCIDevice
struct pointer:
package main
import (
"context"
"fmt"
"os"
"github.com/go-hardware/ghw"
)
func main() {
pci, err := ghw.PCI(context.TODO())
if err != nil {
fmt.Printf("Error getting PCI info: %v", err)
}
addr := "0000:00:00.0"
if len(os.Args) == 2 {
addr = os.Args[1]
}
fmt.Printf("PCI device information for %s\n", addr)
fmt.Println("====================================================")
deviceInfo := pci.GetDevice(addr)
if deviceInfo == nil {
fmt.Printf("could not retrieve PCI device information for %s\n", addr)
return
}
vendor := deviceInfo.Vendor
fmt.Printf("Vendor: %s [%s]\n", vendor.Name, vendor.ID)
product := deviceInfo.Product
fmt.Printf("Product: %s [%s]\n", product.Name, product.ID)
subsystem := deviceInfo.Subsystem
subvendor := pci.Vendors[subsystem.VendorID]
subvendorName := "UNKNOWN"
if subvendor != nil {
subvendorName = subvendor.Name
}
fmt.Printf("Subsystem: %s [%s] (Subvendor: %s)\n", subsystem.Name, subsystem.ID, subvendorName)
class := deviceInfo.Class
fmt.Printf("Class: %s [%s]\n", class.Name, class.ID)
subclass := deviceInfo.Subclass
fmt.Printf("Subclass: %s [%s]\n", subclass.Name, subclass.ID)
progIface := deviceInfo.ProgrammingInterface
fmt.Printf("Programming Interface: %s [%s]\n", progIface.Name, progIface.ID)
}
Here's a sample output from my local workstation:
PCI device information for 0000:03:00.0
====================================================
Vendor: NVIDIA Corporation [10de]
Product: GP107 [GeForce GTX 1050 Ti] [1c82]
Subsystem: UNKNOWN [8613] (Subvendor: ASUSTeK Computer Inc.)
Class: Display controller [03]
Subclass: VGA compatible controller [00]
Programming Interface: VGA controller [00]
GPU
The ghw.GPU()
function returns a ghw.GPUInfo
struct that contains
information about the host computer's graphics hardware.
The ghw.GPUInfo
struct contains one field:
ghw.GPUInfo.GraphicCards
is an array of pointers to ghw.GraphicsCard
structs, one for each graphics card found for the systen
Each ghw.GraphicsCard
struct contains the following fields:
ghw.GraphicsCard.Index
is the system's numeric zero-based index for the
card on the bus
ghw.GraphicsCard.Address
is the PCI address for the graphics card
ghw.GraphicsCard.DeviceInfo
is a pointer to a ghw.PCIDevice
struct
describing the graphics card. This may be nil
if no PCI device information
could be determined for the card.
ghw.GraphicsCard.Node
is an pointer to a ghw.TopologyNode
struct that the
GPU/graphics card is affined to. On non-NUMA systems, this will always be
nil
.
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
gpu, err := ghw.GPU(context.TODO())
if err != nil {
fmt.Printf("Error getting GPU info: %v", err)
}
fmt.Printf("%v\n", gpu)
for _, card := range gpu.GraphicsCards {
fmt.Printf(" %v\n", card)
}
}
Example output from my personal workstation:
gpu (1 graphics card)
card #0 @0000:03:00.0 -> class: 'Display controller' vendor: 'NVIDIA Corporation' product: 'GP107 [GeForce GTX 1050 Ti]'
NOTE: You can read more about the fields of the ghw.PCIDevice
struct if you'd like to dig deeper into PCI subsystem and programming interface
information
NOTE: You can read more about the fields of the
ghw.TopologyNode
struct if you'd like to dig deeper into the NUMA/topology
subsystem
Chassis
The ghw.Chassis()
function returns a ghw.ChassisInfo
struct that contains
information about the host computer's hardware chassis.
The ghw.ChassisInfo
struct contains multiple fields:
ghw.ChassisInfo.AssetTag
is a string with the chassis asset tag
ghw.ChassisInfo.SerialNumber
is a string with the chassis serial number
ghw.ChassisInfo.Type
is a string with the chassis type code
ghw.ChassisInfo.TypeDescription
is a string with a description of the
chassis type
ghw.ChassisInfo.Vendor
is a string with the chassis vendor
ghw.ChassisInfo.Version
is a string with the chassis version
NOTE: These fields are often missing for non-server hardware. Don't be
surprised to see empty string or "None" values.
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
chassis, err := ghw.Chassis(context.TODO())
if err != nil {
fmt.Printf("Error getting chassis info: %v", err)
}
fmt.Printf("%v\n", chassis)
}
Example output from my personal workstation:
chassis type=Desktop vendor=System76 version=thelio-r1
NOTE: Some of the values such as serial numbers are shown as unknown
because the Linux kernel by default disallows access to those fields if
you're not running as root. They will be populated if it runs as root or
otherwise you may see warnings like the following:
WARNING: Unable to read chassis_serial: open /sys/class/dmi/id/chassis_serial: permission denied
You can ignore them or use the Disabling warning messages
feature to quiet things down.
BIOS
The ghw.BIOS()
function returns a ghw.BIOSInfo
struct that contains
information about the host computer's basis input/output system (BIOS).
The ghw.BIOSInfo
struct contains multiple fields:
ghw.BIOSInfo.Vendor
is a string with the BIOS vendor
ghw.BIOSInfo.Version
is a string with the BIOS version
ghw.BIOSInfo.Date
is a string with the date the BIOS was flashed/created
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
bios, err := ghw.BIOS(context.TODO())
if err != nil {
fmt.Printf("Error getting BIOS info: %v", err)
}
fmt.Printf("%v\n", bios)
}
Example output from my personal workstation:
bios vendor=System76 version=F2 Z5 date=11/14/2018
Baseboard
The ghw.Baseboard()
function returns a ghw.BaseboardInfo
struct that
contains information about the host computer's hardware baseboard.
The ghw.BaseboardInfo
struct contains multiple fields:
ghw.BaseboardInfo.AssetTag
is a string with the baseboard asset tag
ghw.BaseboardInfo.SerialNumber
is a string with the baseboard serial number
ghw.BaseboardInfo.Vendor
is a string with the baseboard vendor
ghw.BaseboardInfo.Product
is a string with the baseboard name on Linux and
Product on Windows
ghw.BaseboardInfo.Version
is a string with the baseboard version
NOTE: These fields are often missing for non-server hardware. Don't be
surprised to see empty string or "None" values.
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
baseboard, err := ghw.Baseboard(context.TODO())
if err != nil {
fmt.Printf("Error getting baseboard info: %v", err)
}
fmt.Printf("%v\n", baseboard)
}
Example output from my personal workstation:
baseboard vendor=System76 version=thelio-r1
NOTE: Some of the values such as serial numbers are shown as unknown
because the Linux kernel by default disallows access to those fields if
you're not running as root. They will be populated if it runs as root or
otherwise you may see warnings like the following:
WARNING: Unable to read board_serial: open /sys/class/dmi/id/board_serial: permission denied
You can ignore them or use the Disabling warning messages
feature to quiet things down.
Product
The ghw.Product()
function returns a ghw.ProductInfo
struct that
contains information about the host computer's hardware product line.
The ghw.ProductInfo
struct contains multiple fields:
ghw.ProductInfo.Family
is a string describing the product family
ghw.ProductInfo.Name
is a string with the product name
ghw.ProductInfo.SerialNumber
is a string with the product serial number
ghw.ProductInfo.UUID
is a string with the product UUID
ghw.ProductInfo.SKU
is a string with the product stock unit identifier
(SKU)
ghw.ProductInfo.Vendor
is a string with the product vendor
ghw.ProductInfo.Version
is a string with the product version
NOTE: These fields are often missing for non-server hardware. Don't be
surprised to see empty string, "Default string" or "None" values.
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
product, err := ghw.Product(context.TODO())
if err != nil {
fmt.Printf("Error getting product info: %v", err)
}
fmt.Printf("%v\n", product)
}
Example output from my personal workstation:
product family=Default string name=Thelio vendor=System76 sku=Default string version=thelio-r1
NOTE: Some of the values such as serial numbers are shown as unknown
because the Linux kernel by default disallows access to those fields if
you're not running as root. They will be populated if it runs as root or
otherwise you may see warnings like the following:
WARNING: Unable to read product_serial: open /sys/class/dmi/id/product_serial: permission denied
You can ignore them or use the Disabling warning messages
feature to quiet things down.
Serialization to JSON or YAML
All of the ghw
XXXInfo
structs -- e.g. ghw.CPUInfo
-- have two methods
for producing a serialized JSON or YAML string representation of the contained
information:
JSONString()
returns a string containing the information serialized into
JSON. It accepts a single boolean parameter indicating whether to use
indentation when outputting the string
YAMLString()
returns a string containing the information serialized into
YAML
package main
import (
"context"
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
mem, err := ghw.Memory(context.TODO())
if err != nil {
fmt.Printf("Error getting memory info: %v", err)
}
fmt.Printf("%s", mem.YAMLString())
}
the above example code prints the following out on my local workstation:
memory:
supported_page_sizes:
- 1073741824
- 2097152
total_physical_bytes: 25263415296
total_usable_bytes: 25263415296
With functions
ghw
's With functions allow you to modify ghw
's behaviour when discovering
hardware capabilities or resource usage. When calling a module function, you
supply a context.Context
object. Use a With function in combination with the
ghw.NewContext()
function to create a context.Context
object tailored to
your needs.
ghw.WithDisableWarnings()
tells ghw
not to print warning information to
stderr
when it is unable to determine certain information.
ghw.WithRootMountpoint()
tells ghw
to use an alternate root mountpoint.
ghw.WithPathOverrides()
tells ghw
to use one or more alternate
mountpoints (Linux only).
ghw.WithDisableExternalTools()
tells ghw
not to use certain external tools (like
ethtool
on Linux).
Disabling warning messages
When ghw
isn't able to retrieve some information, it may print certain
warning messages to stderr
. To disable these warnings, simply set the
GHW_DISABLE_WARNINGS
environs variable:
$ ghwc memory
WARNING:
Could not determine total physical bytes of memory. This may
be due to the host being a virtual machine or container with no
/var/log/syslog file, or the current user may not have necessary
privileges to read the syslog. We are falling back to setting the
total physical amount of memory to the total usable amount of memory
memory (24GB physical, 24GB usable)
$ GHW_DISABLE_WARNINGS=1 ghwc memory
memory (24GB physical, 24GB usable)
You can disable warning programmatically using the ghw.WithDisableWarnings()
function:
import (
"github.com/go-hardware/ghw"
)
ctx := ghw.NewContext(ghw.WithDisableWarnings())
mem, err := ghw.Memory(ctx)
Overriding the root mountpoint ghw
uses
When ghw
looks for information about the host system, it considers /
as its
root mountpoint. So, for example, when looking up CPU information on a Linux
system, ghw.CPU()
will use the path /proc/cpuinfo
.
If you are calling ghw
from a system that has an alternate root mountpoint,
you can either set the GHW_ROOT_MOUNTPOINT
environment variable to that alternate
path, or call one of the functions like ghw.CPU()
or ghw.Memory()
with the
ghw.WithRootMountpoint()
modifier.
For example, if you are executing from within an application container that has
bind-mounted the root host filesystem to the mount point /host
, you would set
GHW_ROOT_MOUNTPOINT
to /host
so that ghw
can find /proc/cpuinfo
at
/host/proc/cpuinfo
.
Alternately, you can use the ghw.WithRootMounpoint()
function like so:
ctx := ghw.NewContext(ghw.WithRootMountpoint("/host"))
cpu, err := ghw.CPU(ctx)
Overriding a specific mountpoint (Linux only)
When running inside containers, it can be cumbersome to only override the root
mountpoint. Inside containers, when granting access to the host file systems,
it is common to bind-mount them to a non-standard location, like /sys
on
/host-sys
or /proc
to /host-proc
. It is rarer to mount them to a common
subtree (e.g. /sys
to /host/sys
and /proc
to /host/proc
...)
To better cover this use case, ghw.WithPathOverrides()
can be used to supply
a mapping of directories to mountpoints, like this example shows:
ctx := ghw.NewContext(
ghw.WithPathOverrides(
ghw.PathOverrides{
"/proc": "/host-proc",
"/sys": "/host-sys",
},
),
)
cpu, err := ghw.CPU(ctx)
NOTE: This feature works in addition and is composable with the
ghw.WithRootMounpoint()
function and GHW_ROOT_MOUNTPOINT
environs variable.
Disable calling of external programs
By default ghw
may call external programs, for example ethtool
, to learn
about hardware capabilities. In some rare circumstances it may be useful to
opt out from this behaviour and rely only on the data provided by
pseudo-filesystems, like sysfs.
The most common use case is when we want to read a snapshot from ghw
. In
these cases the information provided by tools will be inconsistent with the
data from the snapshot - since they will be run on a different host than the
host the snapshot was created for.
To prevent ghw
from calling external tools, set the
GHW_DISABLE_EXTERNAL_TOOLS
environs variable to any value, or,
programmatically, use the ghw.WithDisableExternalTools()
function.
ctx := ghw.NewContext(ghw.WithDisableExternalTools())
cpu, err := ghw.CPU(ctx)
The default behaviour of ghw is to call external tools when available.
WARNING: on all platforms, disabling external tools makes ghw
return
less data. Unless noted otherwise, there is no fallback flow if external
tools are disabled. On MacOSX/Darwin, disabling external tools disables block
support entirely
Snapshots
ghw
snapshots are partial clones of the /proc
, /sys
(et. al.) subtrees
copied from arbitrary machines, which ghw can consume later. "partial" is
because the snapshot doesn't need to contain a complete copy of all the
filesystem subtree (that is doable but inpractical). It only needs to contain
the paths ghw cares about. The snapshot concept was introduced to make ghw
easier to test.
Create a snapshot
Create snapshots with ghw snapshot
. Called without arguments, the ghw snapshot
command produces a snapshot with a name corresponding to the host
system's OS, architecture and an MD5 of the hostname:
jaypipes@lappie:~$ ghw snapshot
successfully wrote snapshot to linux-amd64-9826df7c48cde28165ee0e69298b28e9.tar.gz
This tool is maintained by the ghw authors, and snapshots created with this
tool are guaranteed to work.
Inspect a snapshot
To have ghw
inspect a snapshot, simply pass the -s <SNAPSHOT_PATH>
flag to
the ghw
CLI tool. Here, see how if I pass the -s <SNAPSHOT_PATH>
flag with
the value of a snapshot for a different machine, I get different results for
the ghw memory
command:
jaypipes@lappie:~/$ ghw memory
memory (16GB physical, 16GB usable, 4GB used)
jaypipes@lappie:~/$ ghw memory -s testdata/snapshots/linux-amd64-intel-xeon-L5640.tar.gz
memory (66GB physical, 63GB usable, 2GB used)
There are some differences between the original jaypipes/ghw
codebase and
go-hardware/ghw
.
- Module function call signature
ghwc
CLI tool is now called just ghw
ghw-snapshot
CLI tool is now ghw snapshot
Module function call signature changes
While go-hardware/ghw
follows the same naming conventions as jaypipes/ghw
,
the module function signature changed to use the more Go idiomatic passing of a
context.Context
object.
This means that if you are porting your application to use the new
go-hardware/ghw
codebase, you will need to make the following changes:
-
Change all your imports from github.com/jaypipes/ghw
to
github.com/go-hardware/ghw
.
-
Update your module function calls to use the new
go-hardware/ghw:NewContext()
and go-hardware/ghw
With functions
Previously, your jaypipes/ghw
code would have looked like this:
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
memory, err := ghw.Memory(ghw.WithDisableWarnings())
if err != nil {
fmt.Printf("Error getting memory info: %v", err)
}
fmt.Println(memory.String())
}
you will want to update your code to the following:
package main
import (
"fmt"
"github.com/go-hardware/ghw"
)
func main() {
ctx := ghw.NewContext(ghw.WithDisableWarnings())
memory, err := ghw.Memory(ctx)
if err != nil {
fmt.Printf("Error getting memory info: %v", err)
}
fmt.Println(memory.String())
}
ghwc
is now ghw
The old jaypipes/ghw
's ghwc
CLI tool was renamed to just ghw
. There were
no changes to existing ghwc
subcommands like memory
, cpu
, etc.
ghw-snapshot
is now ghw snapshot
The old jaypipes/ghw
's ghw-snapshot
tool was made a subcommand of the main
ghw
CLI tool. See the documentation on creating and
inspecting snapshots to learn how to use this
subcommand.
Developers
Contributions to ghw
are welcomed! Fork the repo on GitHub
and submit a pull request with your proposed changes. Or, feel free to log an
issue for a feature request or bug report.