ghw is a small Golang library providing hardware inspection and discovery
for Linux and Windows. There currently exists partial support for MacOSX.
-
No root privileges needed for discovery
ghwgoes 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 likedmidecodethat require root privileges to execute.Elevated privileges are indeed required to query for some information, but
ghwwill never error out if blocked from reading that information. Instead,ghwwill print a warning message about the information that could not be retrieved. You may disable these warning messages withGHW_DISABLE_WARNINGSenvironment 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.
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.
You can use the functions in ghw to determine various hardware-related
information about the host computer:
- Memory
- CPU
- Block storage
- Topology
- Network
- PCI
- GPU
- Chassis
- BIOS
- Baseboard
- Product
- YAML and JSON serialization
The default root mountpoint that ghw uses when looking for information about
the host system is /. 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_CHROOT environment variable to that alternate
path, or call the module constructor function with the ghw.WithChroot()
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_CHROOT to /host so that ghw can find /proc/cpuinfo at
/host/proc/cpuinfo.
Alternately, you can use the ghw.WithChroot() function like so:
cpu, err := ghw.CPU(ghw.WithChroot("/host"))When running inside containers, it could be a bit cumbersome to just override
the root mountpoint. Inside containers, when granting access to the host
file systems, is more common to bind-mount them in non standard location,
like /sys on /host-sys or /proc on /host-proc.
Is rarer to mount them in a common subtree (e.g. /sys on /host/sys and
/proc on /host/proc...)
To better cover this use case, ghw allows to programmatically override
the initial component of filesystems subtrees, allowing to access sysfs
(or procfs or...) mounted on non-standard locations.
cpu, err := ghw.CPU(ghw.WithPathOverrides(ghw.PathOverrides{
"/proc": "/host-proc",
"/sys": "/host-sys",
}))Please note
- this feature works in addition and is composable with the
WithChroot/GHW_CHROOTfeature. ghwdoesn't support yet environs variable to override individual mountpoints, because this could lead to significant environs variables proliferation.
You can make ghw read from snapshots (created with ghw-snapshot) using
environment variables or programmatically.
Please check SNAPSHOT.md to learn more about how ghw creates and consumes
snapshots.
The environment variable GHW_SNAPSHOT_PATH let users specify a snapshot
that ghw will automatically consume. All the needed chroot changes will be
automatically performed. By default, the snapshot is unpacked on a temporary
directory managed by ghw, and cleaned up when no longer needed, avoiding
leftovers.
The rest of the environment variables are relevant iff GHW_SNAPSHOT_PATH is given.
GHW_SNAPSHOT_ROOT let users specify the directory
on which the snapshot should be unpacked. This moves the ownership of that
directory from ghw to users. For this reason, ghw will not clean up automatically
the content unpacked in GHW_SNAPSHOT_ROOT.
GHW_SNAPSHOT_EXCLUSIVE is relevant iff GHW_SNAPSHOT_ROOT is given.
Set it to any value to toggle it on. This tells ghw that the directory is meant
only to contain the given snapshot, thus ghw will not attempt to unpack it
(and will go ahead silently!) unless the directory is empty.
You can use both GHW_SNAPSHOT_ROOT and GHW_SNAPSHOT_EXCLUSIVE to make sure
ghw unpacks the snapshot only once regardless of how many ghw packages
(e.g. cpu, memory) access it.
Set GHW_SNAPSHOT_PRESERVE to any value to enable it. If set, ghw will not
clean up the unpacked snapshot once done, leaving it into the system.
cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{
Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz",
}))
myRoot := "/my/safe/directory"
cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{
Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz",
Root: &myRoot,
}))
myOtherRoot := "/my/other/safe/directory"
cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{
Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz",
Root: &myOtherRoot,
Exclusive: true,
}))You can create ghw snapshots in two ways.
You can just consume the ghw-snapshot tool, or you can create them programmatically
from your golang code. We explore now the latter case.
Snapshotting takes two phases:
- clone the relevant pseudofiles/pseudodirectories into a temporary tree This tree is usually deleted once the packing is successful.
- pack the cloned tree into a tar.gz
import (
"fmt"
"io/ioutil"
"os"
"github.com/jaypipes/ghw/pkg/snapshot"
)
// ...
scratchDir, err := ioutil.TempDir("", "ghw-snapshot-*")
if err != nil {
fmt.Printf("Error creating clone directory: %v", err)
}
defer os.RemoveAll(scratchDir)
// this step clones all the files and directories ghw cares about
if err := snapshot.CloneTreeInto(scratchDir); err != nil {
fmt.Printf("error cloning into %q: %v", scratchDir, err)
}
// optionally, you may add extra content into your snapshot.
// ghw will ignore the extra content.
// Glob patterns like `filepath.Glob` are supported.
fileSpecs := []string{
"/proc/cmdline",
}
// options allows the client code to optionally deference symlinks, or copy
// them into the cloned tree as symlinks
var opts *snapshot.CopyFileOptions
if err := snapshot.CopyFilesInto(fileSpecs, scratchDir, opts); err != nil {
fmt.Printf("error cloning extra files into %q: %v", scratchDir, err)
}
// automates the creation of the gzipped tarball out of the given tree.
if err := snapshot.PackFrom("my-snapshot.tgz", scratchDir); err != nil {
fmt.Printf("error packing %q into %q: %v", scratchDir, *output, err)
}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 WithDisableWarnings option:
import (
"github.com/jaypipes/ghw"
)
mem, err := ghw.Memory(ghw.WithDisableWarnings())WithDisableWarnings is a alias for the WithNullAlerter option, which in turn
leverages the more general Alerter feature of ghw.
You may supply a Alerter to ghw to redirect all the warnings there, like
logger objects (see for example golang's stdlib log.Logger).
Alerter is in fact the minimal logging interface ghw needs. To learn more, please check the option.Alerterinterface and theghw.WithAlerter()`
function.
Information about the host computer's memory can be retrieved using the
ghw.Memory() function which returns a pointer to a ghw.MemoryInfo struct.
The ghw.MemoryInfo struct contains three fields:
ghw.MemoryInfo.TotalPhysicalBytescontains the amount of physical memory on the hostghw.MemoryInfo.TotalUsableBytescontains 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 bitsghw.MemoryInfo.SupportedPageSizesis an array of integers representing the size, in bytes, of memory pages the system supportsghw.MemoryInfo.Modulesis an array of pointers toghw.MemoryModulestructs, 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.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.TotalCoreshas the total number of physical cores the host system containsghw.CPUInfo.TotalThreadshas the total number of hardware threads the host system containsghw.CPUInfo.Processorsis an array ofghw.Processorstructs, one for each physical processor package contained in the host
Each ghw.Processor struct contains a number of fields:
ghw.Processor.IDis the physical processoruint32ID according to the systemghw.Processor.NumCoresis the number of physical cores in the processor packageghw.Processor.NumThreadsis the number of hardware threads in the processor packageghw.Processor.Vendoris a string containing the vendor nameghw.Processor.Modelis a string containing the vendor's model nameghw.Processor.Capabilitiesis an array of strings indicating the features the processor has enabledghw.Processor.Coresis an array ofghw.ProcessorCorestructs that are packed onto this physical processor
A ghw.ProcessorCore has the following fields:
ghw.ProcessorCore.IDis theuint32identifier 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.Indexis the zero-based index of the core on the physical processor packageghw.ProcessorCore.NumThreadsis the number of hardware threads associated with the coreghw.ProcessorCore.LogicalProcessorsis an array of logical processor IDs assigned to any processing unit for the core
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]
Information about the host computer's local block storage is returned from the
ghw.Block() function. This function returns a pointer to a ghw.BlockInfo
struct.
The ghw.BlockInfo struct contains two fields:
ghw.BlockInfo.TotalPhysicalBytescontains the amount of physical block storage on the hostghw.BlockInfo.Disksis an array of pointers toghw.Diskstructs, one for each disk drive found by the system
Each ghw.Disk struct contains the following fields:
ghw.Disk.Namecontains a string with the short name of the disk, e.g. "sda"ghw.Disk.SizeBytescontains the amount of storage the disk providesghw.Disk.PhysicalBlockSizeBytescontains the size of the physical blocks used on the disk, in bytesghw.Disk.IsRemovablecontains a boolean indicating if the disk drive is removableghw.Disk.DriveTypeis the type of drive. It is of typeghw.DriveTypewhich has aghw.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.StorageControlleris the type of storage controller/drive. It is of typeghw.StorageControllerwhich has aghw.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.NUMANodeIDis the numeric index of the NUMA node this disk is local to, or -1ghw.Disk.Vendorcontains a string with the name of the hardware vendor for the disk driveghw.Disk.Modelcontains a string with the vendor-assigned disk model nameghw.Disk.SerialNumbercontains a string with the disk's serial numberghw.Disk.WWNcontains a string with the disk's World Wide Nameghw.Disk.Partitionscontains an array of pointers toghw.Partitionstructs, one for each partition on the disk
Each ghw.Partition struct contains these fields:
ghw.Partition.Namecontains a string with the short name of the partition, e.g. "sda1"ghw.Partition.SizeBytescontains the amount of storage the partition providesghw.Partition.MountPointcontains a string with the partition's mount point, or "" if no mount point was discoveredghw.Partition.Typecontains a string indicated the filesystem type for the partition, or "" if the system could not determine the typeghw.Partition.IsReadOnlyis a bool indicating the partition is read-onlyghw.Partition.Diskis a pointer to theghw.Diskobject associated with the partition. This will benilif theghw.Partitionstruct was returned by theghw.DiskPartitions()library function.ghw.Partition.UUIDis a string containing the volume UUID on Linux, the partition UUID on MacOS and nothing on Windows.
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
block, err := ghw.Block()
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 that
ghwlooks in the udev runtime database for some information. If you are usingghwin a container, remember to bind mount/dev/diskand/runinto your container, otherwiseghwwon't be able to query the udev DB or sysfs paths for information.
NOTE: Topology support is currently Linux-only. Windows support is planned.
Information about the host computer's architecture (NUMA vs. SMP), the host's
node layout and processor caches can be retrieved from the ghw.Topology()
function. This function returns a pointer to a ghw.TopologyInfo struct.
The ghw.TopologyInfo struct contains two fields:
ghw.TopologyInfo.Architecturecontains an enum with the valueghw.NUMAorghw.SMPdepending on what the topology of the system isghw.TopologyInfo.Nodesis an array of pointers toghw.TopologyNodestructs, one for each topology node (typically physical processor package) found by the system
Each ghw.TopologyNode struct contains the following fields:
ghw.TopologyNode.IDis the system'suint32identifier for the nodeghw.TopologyNode.Coresis an array of pointers toghw.ProcessorCorestructs that are contained in this nodeghw.TopologyNode.Cachesis an array of pointers toghw.MemoryCachestructs that represent the low-level caches associated with processors and cores on the systemghw.TopologyNode.Distanceis an array of distances between NUMA nodes as reported by the system.
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.Typeis an enum that contains one ofghw.DATA,ghw.INSTRUCTIONorghw.UNIFIEDdepending on whether the cache stores CPU instructions, program data, or bothghw.MemoryCache.Levelis a positive integer indicating how close the cache is to the processorghw.MemoryCache.SizeBytesis an integer containing the number of bytes the cache can containghw.MemoryCache.LogicalProcessorsis an array of integers representing the logical processors that use the cache
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
topology, err := ghw.Topology()
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
Information about the host computer's networking hardware is returned from the
ghw.Network() function. This function returns a pointer to a
ghw.NetworkInfo struct.
The ghw.NetworkInfo struct contains one field:
ghw.NetworkInfo.NICsis an array of pointers toghw.NICstructs, one for each network interface controller found for the systen
Each ghw.NIC struct contains the following fields:
ghw.NIC.Nameis the system's identifier for the NICghw.NIC.MacAddressis the MAC address for the NIC, if anyghw.NIC.IsVirtualis a boolean indicating if the NIC is a virtualized deviceghw.NIC.Capabilitiesis an array of pointers toghw.NICCapabilitystructs that can describe the things the NIC supports. These capabilities match the returned values from theethtool -k <DEVICE>call on Linuxghw.NIC.PCIAddressis 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.
The ghw.NICCapability struct contains the following fields:
ghw.NICCapability.Nameis the string name of the capability (e.g. "tcp-segmentation-offload")ghw.NICCapability.IsEnabledis a boolean indicating whether the capability is currently enabled/active on the NICghw.NICCapability.CanEnableis a boolean indicating whether the capability may be enabled
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
net, err := ghw.Network()
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
wlp59s0
enabled capabilities:
- scatter-gather
- tx-scatter-gather
- generic-segmentation-offload
- generic-receive-offload
- highdma
- netns-local
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. The ghw.PCIInfo
struct contains a number of fields that may be queried for PCI information:
ghw.PCIInfo.Devicesis a slice of pointers toghw.PCIDevicestructs that describe the PCI devices on the host systemghw.PCIInfo.Classesis a map, keyed by the PCI class ID (a hex-encoded string) of pointers topcidb.Classstructs, one for each class of PCI device known toghw(DEPRECATED, will be removed inghwv1.0. Use thegithub.com/jaypipes/pcidblibrary for exploring PCI database information)ghw.PCIInfo.Vendorsis a map, keyed by the PCI vendor ID (a hex-encoded string) of pointers topcidb.Vendorstructs, one for each PCI vendor known toghw(DEPRECATED, will be removed inghwv1.0. Use thegithub.com/jaypipes/pcidblibrary for exploring PCI database information)ghw.PCIInfo.Productsis a map, keyed by the PCI product ID (a hex-encoded string) of pointers topcidb.Productstructs, one for each PCI product known toghw(DEPRECATED, will be removed inghwv1.0. Use thegithub.com/jaypipes/pcidblibrary for exploring PCI database information)
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.Vendoris a pointer to apcidb.Vendorstruct that describes the device's primary vendor. This will always be non-nil.ghw.PCIDevice.Productis a pointer to apcidb.Productstruct that describes the device's primary product. This will always be non-nil.ghw.PCIDevice.Subsystemis a pointer to apcidb.Productstruct that describes the device's secondary/sub-product. This will always be non-nil.ghw.PCIDevice.Classis a pointer to apcidb.Classstruct that describes the device's class. This will always be non-nil.ghw.PCIDevice.Subclassis a pointer to apcidb.Subclassstruct that describes the device's subclass. This will always be non-nil.ghw.PCIDevice.ProgrammingInterfaceis a pointer to apcidb.ProgrammingInterfacestruct that describes the device subclass' programming interface. This will always be non-nil.ghw.PCIDevice.Driveris 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 doesn't mean at all the device is not functioning, but only the factghwwas not able to retrieve this 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.Domainis a string representing the PCI domain component of the address.ghw.PCIAddress.Busis a string representing the PCI bus component of the address.ghw.PCIAddress.Deviceis a string representing the PCI device component of the address.ghw.PCIAddress.Functionis a string representing the PCI function component of the address.
NOTE: Older versions (pre-v0.9.0) erroneously referred to the Device
field as the Slot field. As noted by @pearsonk
in #220, this was a misnomer.
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.ListDevices()
method and output a simple list of PCI address and vendor/product information:
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
pci, err := ghw.PCI()
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...
The following code snippet shows how to call the ghw.PCIInfo.GetDevice()
method and use its returned ghw.PCIDevice struct pointer:
package main
import (
"fmt"
"os"
"github.com/jaypipes/ghw"
)
func main() {
pci, err := ghw.PCI()
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]
Information about the host computer's graphics hardware is returned from the
ghw.GPU() function. This function returns a pointer to a ghw.GPUInfo
struct.
The ghw.GPUInfo struct contains one field:
ghw.GPUInfo.GraphicCardsis an array of pointers toghw.GraphicsCardstructs, one for each graphics card found for the systen
Each ghw.GraphicsCard struct contains the following fields:
ghw.GraphicsCard.Indexis the system's numeric zero-based index for the card on the busghw.GraphicsCard.Addressis the PCI address for the graphics cardghw.GraphicsCard.DeviceInfois a pointer to aghw.PCIDevicestruct describing the graphics card. This may benilif no PCI device information could be determined for the card.ghw.GraphicsCard.Nodeis an pointer to aghw.TopologyNodestruct that the GPU/graphics card is affined to. On non-NUMA systems, this will always benil.
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
gpu, err := ghw.GPU()
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
The host's chassis information is accessible with the ghw.Chassis() function. This
function returns a pointer to a ghw.ChassisInfo struct.
The ghw.ChassisInfo struct contains multiple fields:
ghw.ChassisInfo.AssetTagis a string with the chassis asset tagghw.ChassisInfo.SerialNumberis a string with the chassis serial numberghw.ChassisInfo.Typeis a string with the chassis type codeghw.ChassisInfo.TypeDescriptionis a string with a description of the chassis typeghw.ChassisInfo.Vendoris a string with the chassis vendorghw.ChassisInfo.Versionis 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 (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
chassis, err := ghw.Chassis()
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.
The host's basis input/output system (BIOS) information is accessible with the ghw.BIOS() function. This
function returns a pointer to a ghw.BIOSInfo struct.
The ghw.BIOSInfo struct contains multiple fields:
ghw.BIOSInfo.Vendoris a string with the BIOS vendorghw.BIOSInfo.Versionis a string with the BIOS versionghw.BIOSInfo.Dateis a string with the date the BIOS was flashed/created
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
bios, err := ghw.BIOS()
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
The host's baseboard information is accessible with the ghw.Baseboard() function. This
function returns a pointer to a ghw.BaseboardInfo struct.
The ghw.BaseboardInfo struct contains multiple fields:
ghw.BaseboardInfo.AssetTagis a string with the baseboard asset tagghw.BaseboardInfo.SerialNumberis a string with the baseboard serial numberghw.BaseboardInfo.Vendoris a string with the baseboard vendorghw.BaseboardInfo.Productis a string with the baseboard name on Linux and Product on Windowsghw.BaseboardInfo.Versionis 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 (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
baseboard, err := ghw.Baseboard()
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.
The host's product information is accessible with the ghw.Product() function. This
function returns a pointer to a ghw.ProductInfo struct.
The ghw.ProductInfo struct contains multiple fields:
ghw.ProductInfo.Familyis a string describing the product familyghw.ProductInfo.Nameis a string with the product nameghw.ProductInfo.SerialNumberis a string with the product serial numberghw.ProductInfo.UUIDis a string with the product UUIDghw.ProductInfo.SKUis a string with the product stock unit identifier (SKU)ghw.ProductInfo.Vendoris a string with the product vendorghw.ProductInfo.Versionis 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 (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
product, err := ghw.Product()
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.
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 stringYAMLString()returns a string containing the information serialized into YAML
package main
import (
"fmt"
"github.com/jaypipes/ghw"
)
func main() {
mem, err := ghw.Memory()
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
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 consume a snapshot from ghw. In these cases the information
provided by tools will be most likely inconsistent with the data from the snapshot - they will run on
a different host!
To prevent ghw from calling external tools, set the environs variable GHW_DISABLE_TOOLS to any value,
or, programmatically, check the WithDisableTools function.
The default behaviour of ghw is to call external tools when available.
WARNING:
- on all platforms, disabling external tools make ghw return less data. Unless noted otherwise, there is no fallback flow if external tools are disabled.
- on darwin, disabling external tools disable block support entirely
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.
You can run unit tests easily using the make test command, like so:
[jaypipes@uberbox ghw]$ make test
go test github.com/jaypipes/ghw github.com/jaypipes/ghw/cmd/ghwc
ok github.com/jaypipes/ghw 0.084s
? github.com/jaypipes/ghw/cmd/ghwc [no test files]