github.com/jaypipes/ghw@v0.21.1/README.md (about)

     1  # `ghw` - Go HardWare discovery/inspection library
     2  
     3  [![Go Reference](https://pkg.go.dev/badge/github.com/jaypipes/ghw.svg)](https://pkg.go.dev/github.com/jaypipes/ghw)
     4  [![Go Report Card](https://goreportcard.com/badge/github.com/jaypipes/ghw)](https://goreportcard.com/report/github.com/jaypipes/ghw)
     5  [![Build Status](https://github.com/jaypipes/ghw/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jaypipes/ghw/actions)
     6  [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
     7  
     8  ![ghw mascot](images/ghw-gopher.png)
     9  
    10  `ghw` is a Go library providing hardware inspection and discovery for Linux and
    11  Windows. There currently exists partial support for MacOSX.
    12  
    13  ## Design Principles
    14  
    15  * No root privileges needed for discovery
    16  
    17    `ghw` goes the extra mile to be useful without root privileges. We query for
    18    host hardware information as directly as possible without relying on shellouts
    19    to programs like `dmidecode` that require root privileges to execute.
    20  
    21    Elevated privileges are indeed required to query for some information, but
    22    `ghw` will never error out if blocked from reading that information. Instead,
    23    `ghw` will print a warning message about the information that could not be
    24    retrieved. You may disable these warning messages with the
    25    `GHW_DISABLE_WARNINGS` environment variable.
    26  
    27  * Well-documented code and plenty of example code
    28  
    29    The code itself should be well-documented with lots of usage examples.
    30  
    31  * Interfaces should be consistent across modules
    32  
    33    Each module in the library should be structured in a consistent fashion, and
    34    the structs returned by various library functions should have consistent
    35    attribute and method names.
    36  
    37  ## Inspecting != Monitoring
    38  
    39  `ghw` is a tool for gathering information about your hardware's **capacity**
    40  and **capabilities**.
    41  
    42  It is important to point out that `ghw` does **NOT** report information that is
    43  temporary or variable. It is **NOT** a system monitor nor is it an appropriate
    44  tool for gathering data points for metrics that change over time.  If you are
    45  looking for a system that tracks **usage** of CPU, memory, network I/O or disk
    46  I/O, there are plenty of great open source tools that do this! Check out the
    47  [Prometheus project](https://prometheus.io/) for a great example.
    48  
    49  ## Usage
    50  
    51  `ghw` has functions that return an `Info` object about a particular hardware
    52  domain (e.g. CPU, Memory, Block storage, etc).
    53  
    54  Use the following functions in `ghw` to inspect information about the host
    55  hardware:
    56  
    57  * [`ghw.CPU()`](#cpu)
    58  * [`ghw.Memory()`](#memory)
    59  * [`ghw.Block()`](#block-storage) (block storage)
    60  * [`ghw.Topology()`](#topology) (processor architecture, NUMA topology and
    61    memory cache hierarchy)
    62  * [`ghw.Network()`](#network)
    63  * [`ghw.PCI()`](#pci)
    64  * [`ghw.GPU()`](#gpu) (graphical processing unit)
    65  * [`ghw.Accelerator()`](#accelerator) (processing accelerators, AI)
    66  * [`ghw.Chassis()`](#chassis)
    67  * [`ghw.BIOS()`](#bios)
    68  * [`ghw.Baseboard()`](#baseboard)
    69  * [`ghw.Product()`](#product)
    70  
    71  ### CPU
    72  
    73  The `ghw.CPU()` function returns a `ghw.CPUInfo` struct that contains
    74  information about the CPUs on the host system.
    75  
    76  `ghw.CPUInfo` contains the following fields:
    77  
    78  * `ghw.CPUInfo.TotalCores` has the total number of physical cores the host
    79    system contains
    80  * `ghw.CPUInfo.TotalHardwareThreads` has the total number of hardware threads
    81    the host system contains
    82  * `ghw.CPUInfo.Processors` is an array of `ghw.Processor` structs, one for each
    83    physical processor package contained in the host
    84  
    85  Each `ghw.Processor` struct contains a number of fields:
    86  
    87  * `ghw.Processor.ID` is the physical processor `uint32` ID according to the
    88    system
    89  * `ghw.Processor.TotalCores` is the number of physical cores in the processor
    90    package
    91  * `ghw.Processor.TotalHardwareThreads` is the number of hardware threads in the
    92    processor package
    93  * `ghw.Processor.Vendor` is a string containing the vendor name
    94  * `ghw.Processor.Model` is a string containing the vendor's model name
    95  * `ghw.Processor.Capabilities` (Linux only) is an array of strings indicating
    96    the features the processor has enabled
    97  * `ghw.Processor.Cores` (Linux only) is an array of `ghw.ProcessorCore` structs
    98    that are packed onto this physical processor
    99  
   100  A `ghw.ProcessorCore` has the following fields:
   101  
   102  * `ghw.ProcessorCore.ID` is the `uint32` identifier that the host gave this
   103    core. Note that this does *not* necessarily equate to a zero-based index of
   104    the core within a physical package. For example, the core IDs for an Intel Core
   105    i7 are 0, 1, 2, 8, 9, and 10
   106  * `ghw.ProcessorCore.TotalHardwareThreads` is the number of hardware threads
   107    associated with the core
   108  * `ghw.ProcessorCore.LogicalProcessors` is an array of ints representing the
   109    logical processor IDs assigned to any processing unit for the core. These are
   110    sometimes called the "thread siblings". Logical processor IDs are the
   111    *zero-based* index of the processor on the host and are *not* related to the
   112    core ID.
   113  
   114  ```go
   115  package main
   116  
   117  import (
   118  	"fmt"
   119  	"math"
   120  	"strings"
   121  
   122  	"github.com/jaypipes/ghw"
   123  )
   124  
   125  func main() {
   126  	cpu, err := ghw.CPU()
   127  	if err != nil {
   128  		fmt.Printf("Error getting CPU info: %v", err)
   129  	}
   130  
   131  	fmt.Printf("%v\n", cpu)
   132  
   133  	for _, proc := range cpu.Processors {
   134  		fmt.Printf(" %v\n", proc)
   135  		for _, core := range proc.Cores {
   136  			fmt.Printf("  %v\n", core)
   137  		}
   138  		if len(proc.Capabilities) > 0 {
   139  			// pretty-print the (large) block of capability strings into rows
   140  			// of 6 capability strings
   141  			rows := int(math.Ceil(float64(len(proc.Capabilities)) / float64(6)))
   142  			for row := 1; row < rows; row = row + 1 {
   143  				rowStart := (row * 6) - 1
   144  				rowEnd := int(math.Min(float64(rowStart+6), float64(len(proc.Capabilities))))
   145  				rowElems := proc.Capabilities[rowStart:rowEnd]
   146  				capStr := strings.Join(rowElems, " ")
   147  				if row == 1 {
   148  					fmt.Printf("  capabilities: [%s\n", capStr)
   149  				} else if rowEnd < len(proc.Capabilities) {
   150  					fmt.Printf("                 %s\n", capStr)
   151  				} else {
   152  					fmt.Printf("                 %s]\n", capStr)
   153  				}
   154  			}
   155  		}
   156  	}
   157  }
   158  ```
   159  
   160  Example output from my personal workstation:
   161  
   162  ```
   163  cpu (1 physical package, 6 cores, 12 hardware threads)
   164   physical package #0 (6 cores, 12 hardware threads)
   165    processor core #0 (2 threads), logical processors [0 6]
   166    processor core #1 (2 threads), logical processors [1 7]
   167    processor core #2 (2 threads), logical processors [2 8]
   168    processor core #3 (2 threads), logical processors [3 9]
   169    processor core #4 (2 threads), logical processors [4 10]
   170    processor core #5 (2 threads), logical processors [5 11]
   171    capabilities: [msr pae mce cx8 apic sep
   172                   mtrr pge mca cmov pat pse36
   173                   clflush dts acpi mmx fxsr sse
   174                   sse2 ss ht tm pbe syscall
   175                   nx pdpe1gb rdtscp lm constant_tsc arch_perfmon
   176                   pebs bts rep_good nopl xtopology nonstop_tsc
   177                   cpuid aperfmperf pni pclmulqdq dtes64 monitor
   178                   ds_cpl vmx est tm2 ssse3 cx16
   179                   xtpr pdcm pcid sse4_1 sse4_2 popcnt
   180                   aes lahf_lm pti retpoline tpr_shadow vnmi
   181                   flexpriority ept vpid dtherm ida arat]
   182  ```
   183  
   184  ### Memory
   185  
   186  The `ghw.Memory()` function returns a `ghw.MemoryInfo` struct that contains
   187  information about the RAM on the host system.
   188  
   189  `ghw.MemoryInfo` contains the following fields:
   190  
   191  * `ghw.MemoryInfo.TotalPhysicalBytes` contains the amount of physical memory on
   192    the host
   193  * `ghw.MemoryInfo.TotalUsableBytes` contains the amount of memory the
   194    system can actually use. Usable memory accounts for things like the kernel's
   195    resident memory size and some reserved system bits. Please note this value is
   196    **NOT** the amount of memory currently in use by processes in the system. See
   197    [the discussion][#physical-versus-usage-memory] about the difference.
   198  * `ghw.MemoryInfo.SupportedPageSizes` is an array of integers representing the
   199    size, in bytes, of memory pages the system supports
   200  * `ghw.MemoryInfo.Modules` is an array of pointers to `ghw.MemoryModule`
   201    structs, one for each physical [DIMM](https://en.wikipedia.org/wiki/DIMM).
   202    Currently, this information is only included on Windows, with Linux support
   203    [planned](https://github.com/jaypipes/ghw/pull/171#issuecomment-597082409).
   204  
   205  ```go
   206  package main
   207  
   208  import (
   209  	"fmt"
   210  
   211  	"github.com/jaypipes/ghw"
   212  )
   213  
   214  func main() {
   215  	memory, err := ghw.Memory()
   216  	if err != nil {
   217  		fmt.Printf("Error getting memory info: %v", err)
   218  	}
   219  
   220  	fmt.Println(memory.String())
   221  }
   222  ```
   223  
   224  Example output from my personal workstation:
   225  
   226  ```
   227  memory (24GB physical, 24GB usable)
   228  ```
   229  
   230  #### Physical versus Usable Memory
   231  
   232  There has been [some](https://github.com/jaypipes/ghw/pull/171)
   233  [confusion](https://github.com/jaypipes/ghw/issues/183) regarding the
   234  difference between the total physical bytes versus total usable bytes of
   235  memory.
   236  
   237  Some of this confusion has been due to a misunderstanding of the term "usable".
   238  As mentioned [above](#inspection!=monitoring), `ghw` does inspection of the
   239  system's capacity.
   240  
   241  A host computer has two capacities when it comes to RAM. The first capacity is
   242  the amount of RAM that is contained in all memory banks (DIMMs) that are
   243  attached to the motherboard. `ghw.MemoryInfo.TotalPhysicalBytes` refers to this
   244  first capacity.
   245  
   246  There is a (usually small) amount of RAM that is consumed by the bootloader
   247  before the operating system is started (booted). Once the bootloader has booted
   248  the operating system, the amount of RAM that may be used by the operating
   249  system and its applications is fixed. `ghw.MemoryInfo.TotalUsableBytes` refers
   250  to this second capacity.
   251  
   252  You can determine the amount of RAM that the bootloader used (that is not made
   253  available to the operating system) by subtracting
   254  `ghw.MemoryInfo.TotalUsableBytes` from `ghw.MemoryInfo.TotalPhysicalBytes`:
   255  
   256  ```go
   257  package main
   258  
   259  import (
   260  	"fmt"
   261  
   262  	"github.com/jaypipes/ghw"
   263  )
   264  
   265  func main() {
   266  	memory, err := ghw.Memory()
   267  	if err != nil {
   268  		fmt.Printf("Error getting memory info: %v", err)
   269  	}
   270  
   271          phys := memory.TotalPhysicalBytes
   272          usable := memory.TotalUsableBytes
   273  
   274  	fmt.Printf("The bootloader consumes %d bytes of RAM\n", phys - usable)
   275  }
   276  ```
   277  
   278  Example output from my personal workstation booted into a Windows10 operating
   279  system with a Linux GRUB bootloader:
   280  
   281  ```
   282  The bootloader consumes 3832720 bytes of RAM
   283  ```
   284  
   285  ### Block storage
   286  
   287  The `ghw.Block()` function returns a `ghw.BlockInfo` struct that contains
   288  information about the block storage on the host system.
   289  
   290  `ghw.BlockInfo` contains the following fields:
   291  
   292  * `ghw.BlockInfo.TotalSizeBytes` contains the amount of physical block storage
   293    on the host.
   294  * `ghw.BlockInfo.Disks` is an array of pointers to `ghw.Disk` structs, one for
   295    each disk found by the system
   296  
   297  Each `ghw.Disk` struct contains the following fields:
   298  
   299  * `ghw.Disk.Name` contains a string with the short name of the disk, e.g. "sda"
   300  * `ghw.Disk.SizeBytes` contains the amount of storage the disk provides
   301  * `ghw.Disk.PhysicalBlockSizeBytes` contains the size of the physical blocks
   302    used on the disk, in bytes. This is typically the minimum amount of data that
   303    will be written in a single write operation for the disk.
   304  * `ghw.Disk.IsRemovable` contains a boolean indicating if the disk drive is
   305    removable
   306  * `ghw.Disk.DriveType` is the type of drive. It is of type `ghw.DriveType`
   307    which has a `ghw.DriveType.String()` method that can be called to return a
   308    string representation of the bus. This string will be `HDD`, `FDD`, `ODD`,
   309    or `SSD`, which correspond to a hard disk drive (rotational), floppy drive,
   310    optical (CD/DVD) drive and solid-state drive.
   311  * `ghw.Disk.StorageController` is the type of storage controller. It is of type
   312    `ghw.StorageController` which has a `ghw.StorageController.String()` method
   313    that can be called to return a string representation of the bus. This string
   314    will be `SCSI`, `IDE`, `virtio`, `MMC`, or `NVMe`
   315  * `ghw.Disk.BusPath` (Linux, Darwin only) is the filepath to the bus used by
   316    the disk.
   317  * `ghw.Disk.NUMANodeID` (Linux only) is the numeric index of the NUMA node this
   318    disk is local to, or -1 if the host system is not a NUMA system or is not
   319    Linux.
   320  * `ghw.Disk.Vendor` contains a string with the name of the hardware vendor for
   321    the disk
   322  * `ghw.Disk.Model` contains a string with the vendor-assigned disk model name
   323  * `ghw.Disk.SerialNumber` contains a string with the disk's serial number
   324  * `ghw.Disk.WWN` contains a string with the disk's
   325    [World Wide Name](https://en.wikipedia.org/wiki/World_Wide_Name)
   326  * `ghw.Disk.Partitions` contains an array of pointers to `ghw.Partition`
   327    structs, one for each partition on the disk
   328  
   329  Each `ghw.Partition` struct contains these fields:
   330  
   331  * `ghw.Partition.Name` contains a string with the short name of the partition,
   332    e.g. `sda1`
   333  * `ghw.Partition.Label` contains the label for the partition itself. On Linux
   334    systems, this is derived from the `ID_PART_ENTRY_NAME` [udev][udev] entry for
   335    the partition.
   336  * `ghw.Partition.FilesystemLabel` contains the label for the filesystem housed
   337    on the partition. On Linux systems, this is derived from the `ID_FS_NAME`
   338    [udev][udev] entry for the partition.
   339  * `ghw.Partition.SizeBytes` contains the amount of storage the partition
   340    provides
   341  * `ghw.Partition.MountPoint` contains a string with the partition's mount
   342    point, or `""` if no mount point was discovered
   343  * `ghw.Partition.Type` contains a string indicated the filesystem type for the
   344    partition, or `""` if the system could not determine the type
   345  * `ghw.Partition.IsReadOnly` is a bool indicating the partition is read-only
   346  * `ghw.Partition.Disk` is a pointer to the `ghw.Disk` object associated with
   347    the partition.
   348  * `ghw.Partition.UUID` is a string containing the partition UUID on Linux and MacOS,
   349    and the `VolumeSerialNumber` on Windows (e.g. "A8C3D032"). On Linux systems, this is
   350    derived from the `ID_PART_ENTRY_UUID` [udev][udev] entry for the partition.
   351  
   352  [udev]: https://en.wikipedia.org/wiki/Udev
   353  
   354  ```go
   355  package main
   356  
   357  import (
   358  	"fmt"
   359  
   360  	"github.com/jaypipes/ghw"
   361  )
   362  
   363  func main() {
   364  	block, err := ghw.Block()
   365  	if err != nil {
   366  		fmt.Printf("Error getting block storage info: %v", err)
   367  	}
   368  
   369  	fmt.Printf("%v\n", block)
   370  
   371  	for _, disk := range block.Disks {
   372  		fmt.Printf(" %v\n", disk)
   373  		for _, part := range disk.Partitions {
   374  			fmt.Printf("  %v\n", part)
   375  		}
   376  	}
   377  }
   378  ```
   379  
   380  Example output from my personal workstation:
   381  
   382  ```
   383  block storage (1 disk, 2TB physical storage)
   384   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
   385    /dev/sda1 (100MB)
   386    /dev/sda2 (187GB)
   387    /dev/sda3 (449MB)
   388    /dev/sda4 (1KB)
   389    /dev/sda5 (15GB)
   390    /dev/sda6 (2TB) [ext4] mounted@/
   391  ```
   392  
   393  > **NOTE**: `ghw` looks in the udev runtime database for some information. If
   394  > you are using `ghw` in a container, remember to bind mount `/dev/disk` and
   395  > `/run` into your container, otherwise `ghw` won't be able to query the udev
   396  > DB or sysfs paths for information.
   397  
   398  ### Topology
   399  
   400  > **NOTE**: Topology support is currently Linux-only. Windows support is
   401  > [planned](https://github.com/jaypipes/ghw/issues/166).
   402  
   403  The `ghw.Topology()` function returns a `ghw.TopologyInfo` struct that contains
   404  information about the host computer's architecture (NUMA vs. SMP), the host's
   405  NUMA node layout and processor-specific memory caches.
   406  
   407  The `ghw.TopologyInfo` struct contains two fields:
   408  
   409  * `ghw.TopologyInfo.Architecture` contains an enum with the value `ghw.NUMA` or
   410    `ghw.SMP` depending on what the topology of the system is
   411  * `ghw.TopologyInfo.Nodes` is an array of pointers to `ghw.TopologyNode`
   412    structs, one for each topology node (typically physical processor package)
   413    found by the system
   414  
   415  Each `ghw.TopologyNode` struct contains the following fields:
   416  
   417  * `ghw.TopologyNode.ID` is the system's `uint32` identifier for the node
   418  * `ghw.TopologyNode.Memory` is a `ghw.MemoryArea` struct describing the memory
   419    attached to this node.
   420  * `ghw.TopologyNode.Cores` is an array of pointers to `ghw.ProcessorCore` structs that
   421    are contained in this node
   422  * `ghw.TopologyNode.Caches` is an array of pointers to `ghw.MemoryCache` structs that
   423    represent the low-level caches associated with processors and cores on the
   424    system
   425  * `ghw.TopologyNode.Distance` is an array of distances between NUMA nodes as reported
   426    by the system.
   427  
   428  `ghw.MemoryArea` describes a collection of *physical* RAM on the host.
   429  
   430  In the simplest and most common case, all system memory fits in a single memory
   431  area. In more complex host systems, like [NUMA systems][numa], many memory
   432  areas may be present in the host system (e.g. one for each NUMA cell).
   433  
   434  [numa]: https://en.wikipedia.org/wiki/Non-uniform_memory_access
   435  
   436  The `ghw.MemoryArea` struct contains the following fields:
   437  
   438  * `ghw.MemoryArea.TotalPhysicalBytes` contains the amount of physical memory
   439    associated with this memory area.
   440  * `ghw.MemoryArea.TotalUsableBytes` contains the amount of memory of this
   441    memory area the system can actually use. Usable memory accounts for things
   442    like the kernel's resident memory size and some reserved system bits. Please
   443    note this value is **NOT** the amount of memory currently in use by processes
   444    in the system. See [the discussion][#physical-versus-usage-memory] about
   445    the difference.
   446  
   447  See above in the [CPU](#cpu) section for information about the
   448  `ghw.ProcessorCore` struct and how to use and query it.
   449  
   450  Each `ghw.MemoryCache` struct contains the following fields:
   451  
   452  * `ghw.MemoryCache.Type` is an enum that contains one of `ghw.DATA`,
   453    `ghw.INSTRUCTION` or `ghw.UNIFIED` depending on whether the cache stores CPU
   454    instructions, program data, or both
   455  * `ghw.MemoryCache.Level` is a positive integer indicating how close the cache
   456    is to the processor. The lower the number, the closer the cache is to the
   457    processor and the faster the processor can access its contents
   458  * `ghw.MemoryCache.SizeBytes` is an integer containing the number of bytes the
   459    cache can contain
   460  * `ghw.MemoryCache.LogicalProcessors` is an array of integers representing the
   461    logical processors that use the cache
   462  
   463  ```go
   464  package main
   465  
   466  import (
   467  	"fmt"
   468  
   469  	"github.com/jaypipes/ghw"
   470  )
   471  
   472  func main() {
   473  	topology, err := ghw.Topology()
   474  	if err != nil {
   475  		fmt.Printf("Error getting topology info: %v", err)
   476  	}
   477  
   478  	fmt.Printf("%v\n", topology)
   479  
   480  	for _, node := range topology.Nodes {
   481  		fmt.Printf(" %v\n", node)
   482  		for _, cache := range node.Caches {
   483  			fmt.Printf("  %v\n", cache)
   484  		}
   485  	}
   486  }
   487  ```
   488  
   489  Example output from my personal workstation:
   490  
   491  ```
   492  topology SMP (1 nodes)
   493   node #0 (6 cores)
   494    L1i cache (32 KB) shared with logical processors: 3,9
   495    L1i cache (32 KB) shared with logical processors: 2,8
   496    L1i cache (32 KB) shared with logical processors: 11,5
   497    L1i cache (32 KB) shared with logical processors: 10,4
   498    L1i cache (32 KB) shared with logical processors: 0,6
   499    L1i cache (32 KB) shared with logical processors: 1,7
   500    L1d cache (32 KB) shared with logical processors: 11,5
   501    L1d cache (32 KB) shared with logical processors: 10,4
   502    L1d cache (32 KB) shared with logical processors: 3,9
   503    L1d cache (32 KB) shared with logical processors: 1,7
   504    L1d cache (32 KB) shared with logical processors: 0,6
   505    L1d cache (32 KB) shared with logical processors: 2,8
   506    L2 cache (256 KB) shared with logical processors: 2,8
   507    L2 cache (256 KB) shared with logical processors: 3,9
   508    L2 cache (256 KB) shared with logical processors: 0,6
   509    L2 cache (256 KB) shared with logical processors: 10,4
   510    L2 cache (256 KB) shared with logical processors: 1,7
   511    L2 cache (256 KB) shared with logical processors: 11,5
   512    L3 cache (12288 KB) shared with logical processors: 0,1,10,11,2,3,4,5,6,7,8,9
   513  ```
   514  
   515  ### Network
   516  
   517  The `ghw.Network()` function returns a `ghw.NetworkInfo` struct that contains
   518  information about the host computer's networking hardware.
   519  
   520  The `ghw.NetworkInfo` struct contains one field:
   521  
   522  * `ghw.NetworkInfo.NICs` is an array of pointers to `ghw.NIC` structs, one
   523    for each network interface controller found for the system
   524  
   525  Each `ghw.NIC` struct contains the following fields:
   526  
   527  * `ghw.NIC.Name` is the system's identifier for the NIC
   528  * `ghw.NIC.MACAddress` is the Media Access Control (MAC) address for the NIC,
   529    if any
   530  * `ghw.NIC.IsVirtual` is a boolean indicating if the NIC is a virtualized
   531    device
   532  * `ghw.NIC.Capabilities` (Linux only) is an array of pointers to
   533    `ghw.NICCapability` structs that can describe the things the NIC supports.
   534    These capabilities match the returned values from the `ethtool -k <DEVICE>`
   535    call on Linux as well as the AutoNegotiation and PauseFrameUse capabilities
   536    from `ethtool`.
   537  * `ghw.NIC.PCIAddress` (Linux only) is the PCI device address of the device
   538    backing the NIC.  this is not-nil only if the backing device is indeed a PCI
   539    device; more backing devices (e.g. USB) will be added in future versions.
   540  * `ghw.NIC.Speed` (Linux only) is a string showing the current link speed.  On
   541    Linux, this field will be present even if `ethtool` is not available.
   542  * `ghw.NIC.Duplex` (Linux only) is a string showing the current link duplex. On
   543    Linux, this field will be present even if `ethtool` is not available.
   544  * `ghw.NIC.SupportedLinkModes` (Linux only) is a string slice containing a list
   545    of supported link modes, e.g. "10baseT/Half", "1000baseT/Full".
   546  * `ghw.NIC.SupportedPorts` (Linux only) is a string slice containing the list
   547    of supported port types, e.g. "MII", "TP", "FIBRE", "Twisted Pair".
   548  * `ghw.NIC.SupportedFECModes` (Linux only) is a string slice containing a list
   549    of supported Forward Error Correction (FEC) Modes.
   550  * `ghw.NIC.AdvertisedLinkModes` (Linux only) is a string slice containing the
   551    link modes being advertised during auto negotiation.
   552  * `ghw.NIC.AdvertisedFECModes` (Linux only) is a string slice containing the
   553    Forward Error Correction (FEC) modes advertised during auto negotiation.
   554  
   555  The `ghw.NICCapability` struct contains the following fields:
   556  
   557  * `ghw.NICCapability.Name` is the string name of the capability (e.g.
   558    "tcp-segmentation-offload")
   559  * `ghw.NICCapability.IsEnabled` is a boolean indicating whether the capability
   560    is currently enabled/active on the NIC
   561  * `ghw.NICCapability.CanEnable` is a boolean indicating whether the capability
   562    may be enabled
   563  
   564  ```go
   565  package main
   566  
   567  import (
   568      "fmt"
   569  
   570      "github.com/jaypipes/ghw"
   571  )
   572  
   573  func main() {
   574      net, err := ghw.Network()
   575      if err != nil {
   576          fmt.Printf("Error getting network info: %v", err)
   577      }
   578  
   579      fmt.Printf("%v\n", net)
   580  
   581      for _, nic := range net.NICs {
   582          fmt.Printf(" %v\n", nic)
   583  
   584          enabledCaps := make([]int, 0)
   585          for x, cap := range nic.Capabilities {
   586              if cap.IsEnabled {
   587                  enabledCaps = append(enabledCaps, x)
   588              }
   589          }
   590          if len(enabledCaps) > 0 {
   591              fmt.Printf("  enabled capabilities:\n")
   592              for _, x := range enabledCaps {
   593                  fmt.Printf("   - %s\n", nic.Capabilities[x].Name)
   594              }
   595          }
   596      }
   597  }
   598  ```
   599  
   600  Example output from my personal laptop:
   601  
   602  ```
   603  net (3 NICs)
   604   docker0
   605    enabled capabilities:
   606     - tx-checksumming
   607     - tx-checksum-ip-generic
   608     - scatter-gather
   609     - tx-scatter-gather
   610     - tx-scatter-gather-fraglist
   611     - tcp-segmentation-offload
   612     - tx-tcp-segmentation
   613     - tx-tcp-ecn-segmentation
   614     - tx-tcp-mangleid-segmentation
   615     - tx-tcp6-segmentation
   616     - udp-fragmentation-offload
   617     - generic-segmentation-offload
   618     - generic-receive-offload
   619     - tx-vlan-offload
   620     - highdma
   621     - tx-lockless
   622     - netns-local
   623     - tx-gso-robust
   624     - tx-fcoe-segmentation
   625     - tx-gre-segmentation
   626     - tx-gre-csum-segmentation
   627     - tx-ipxip4-segmentation
   628     - tx-ipxip6-segmentation
   629     - tx-udp_tnl-segmentation
   630     - tx-udp_tnl-csum-segmentation
   631     - tx-gso-partial
   632     - tx-sctp-segmentation
   633     - tx-esp-segmentation
   634     - tx-vlan-stag-hw-insert
   635   enp58s0f1
   636    enabled capabilities:
   637     - rx-checksumming
   638     - generic-receive-offload
   639     - rx-vlan-offload
   640     - tx-vlan-offload
   641     - highdma
   642     - auto-negotiation
   643   wlp59s0
   644    enabled capabilities:
   645     - scatter-gather
   646     - tx-scatter-gather
   647     - generic-segmentation-offload
   648     - generic-receive-offload
   649     - highdma
   650     - netns-local
   651  ```
   652  
   653  ### PCI
   654  
   655  `ghw` contains a PCI database inspection and querying facility that allows
   656  developers to not only gather information about devices on a local PCI bus but
   657  also query for information about hardware device classes, vendor and product
   658  information.
   659  
   660  > **NOTE**: Parsing of the PCI-IDS file database is provided by the separate
   661  > [github.com/jaypipes/pcidb library](http://github.com/jaypipes/pcidb). You
   662  > can read that library's README for more information about the various structs
   663  > that are exposed on the `ghw.PCIInfo` struct.
   664  
   665  The `ghw.PCI()` function returns a `ghw.PCIInfo` struct that contains
   666  information about the host computer's PCI devices.
   667  
   668  The `ghw.PCIInfo` struct contains one field:
   669  
   670  * `ghw.PCIInfo.Devices` is a slice of pointers to `ghw.PCIDevice` structs that
   671    describe the PCI devices on the host system
   672  
   673  > **NOTE**: PCI products are often referred to by their "device ID". We use the
   674  > term "product ID" in `ghw` because it more accurately reflects what the
   675  > identifier is for: a specific product line produced by the vendor.
   676  
   677  The `ghw.PCIDevice` struct has the following fields:
   678  
   679  * `ghw.PCIDevice.Vendor` is a pointer to a `pcidb.Vendor` struct that
   680    describes the device's primary vendor. This will always be non-nil.
   681  * `ghw.PCIDevice.Product` is a pointer to a `pcidb.Product` struct that
   682    describes the device's primary product. This will always be non-nil.
   683  * `ghw.PCIDevice.Subsystem` is a pointer to a `pcidb.Product` struct that
   684    describes the device's secondary/sub-product. This will always be non-nil.
   685  * `ghw.PCIDevice.Class` is a pointer to a `pcidb.Class` struct that
   686    describes the device's class. This will always be non-nil.
   687  * `ghw.PCIDevice.Subclass` is a pointer to a `pcidb.Subclass` struct
   688    that describes the device's subclass. This will always be non-nil.
   689  * `ghw.PCIDevice.ProgrammingInterface` is a pointer to a
   690    `pcidb.ProgrammingInterface` struct that describes the device subclass'
   691    programming interface. This will always be non-nil.
   692  * `ghw.PCIDevice.Driver` is a string representing the device driver the
   693    system is using to handle this device. Can be empty string if this
   694    information is not available. If the information is not available, this does
   695    not mean the device is not functioning, but rather that `ghw` was not able to
   696    retrieve driver information.
   697  
   698  The `ghw.PCIAddress` (which is an alias for the `ghw.pci.address.Address`
   699  struct) contains the PCI address fields. It has a `ghw.PCIAddress.String()`
   700  method that returns the canonical Domain:Bus:Device.Function ([D]BDF)
   701  representation of this Address.
   702  
   703  The `ghw.PCIAddress` struct has the following fields:
   704  
   705  * `ghw.PCIAddress.Domain` is a string representing the PCI domain component of
   706    the address.
   707  * `ghw.PCIAddress.Bus` is a string representing the PCI bus component of
   708    the address.
   709  * `ghw.PCIAddress.Device` is a string representing the PCI device component of
   710    the address.
   711  * `ghw.PCIAddress.Function` is a string representing the PCI function component of
   712    the address.
   713  
   714  > **NOTE**: Older versions (pre-`v0.9.0`) erroneously referred to the `Device`
   715  > field as the `Slot` field. As noted by [@pearsonk](https://github.com/pearsonk)
   716  > in [#220](https://github.com/jaypipes/ghw/issues/220), this was a misnomer.
   717  
   718  The following code snippet shows how to list the PCI devices on the host system
   719  and output a simple list of PCI address and vendor/product information:
   720  
   721  ```go
   722  package main
   723  
   724  import (
   725  	"fmt"
   726  
   727  	"github.com/jaypipes/ghw"
   728  )
   729  
   730  func main() {
   731  	pci, err := ghw.PCI()
   732  	if err != nil {
   733  		fmt.Printf("Error getting PCI info: %v", err)
   734  	}
   735  	fmt.Printf("host PCI devices:\n")
   736  	fmt.Println("====================================================")
   737  
   738  	for _, device := range pci.Devices {
   739  		vendor := device.Vendor
   740  		vendorName := vendor.Name
   741  		if len(vendor.Name) > 20 {
   742  			vendorName = string([]byte(vendorName)[0:17]) + "..."
   743  		}
   744  		product := device.Product
   745  		productName := product.Name
   746  		if len(product.Name) > 40 {
   747  			productName = string([]byte(productName)[0:37]) + "..."
   748  		}
   749  		fmt.Printf("%-12s\t%-20s\t%-40s\n", device.Address, vendorName, productName)
   750  	}
   751  }
   752  ```
   753  
   754  on my local workstation the output of the above looks like the following:
   755  
   756  ```
   757  host PCI devices:
   758  ====================================================
   759  0000:00:00.0	Intel Corporation   	5520/5500/X58 I/O Hub to ESI Port
   760  0000:00:01.0	Intel Corporation   	5520/5500/X58 I/O Hub PCI Express Roo...
   761  0000:00:02.0	Intel Corporation   	5520/5500/X58 I/O Hub PCI Express Roo...
   762  0000:00:03.0	Intel Corporation   	5520/5500/X58 I/O Hub PCI Express Roo...
   763  0000:00:07.0	Intel Corporation   	5520/5500/X58 I/O Hub PCI Express Roo...
   764  0000:00:10.0	Intel Corporation   	7500/5520/5500/X58 Physical and Link ...
   765  0000:00:10.1	Intel Corporation   	7500/5520/5500/X58 Routing and Protoc...
   766  0000:00:14.0	Intel Corporation   	7500/5520/5500/X58 I/O Hub System Man...
   767  0000:00:14.1	Intel Corporation   	7500/5520/5500/X58 I/O Hub GPIO and S...
   768  0000:00:14.2	Intel Corporation   	7500/5520/5500/X58 I/O Hub Control St...
   769  0000:00:14.3	Intel Corporation   	7500/5520/5500/X58 I/O Hub Throttle R...
   770  0000:00:19.0	Intel Corporation   	82567LF-2 Gigabit Network Connection
   771  0000:00:1a.0	Intel Corporation   	82801JI (ICH10 Family) USB UHCI Contr...
   772  0000:00:1a.1	Intel Corporation   	82801JI (ICH10 Family) USB UHCI Contr...
   773  0000:00:1a.2	Intel Corporation   	82801JI (ICH10 Family) USB UHCI Contr...
   774  0000:00:1a.7	Intel Corporation   	82801JI (ICH10 Family) USB2 EHCI Cont...
   775  0000:00:1b.0	Intel Corporation   	82801JI (ICH10 Family) HD Audio Contr...
   776  0000:00:1c.0	Intel Corporation   	82801JI (ICH10 Family) PCI Express Ro...
   777  0000:00:1c.1	Intel Corporation   	82801JI (ICH10 Family) PCI Express Po...
   778  0000:00:1c.4	Intel Corporation   	82801JI (ICH10 Family) PCI Express Ro...
   779  0000:00:1d.0	Intel Corporation   	82801JI (ICH10 Family) USB UHCI Contr...
   780  0000:00:1d.1	Intel Corporation   	82801JI (ICH10 Family) USB UHCI Contr...
   781  0000:00:1d.2	Intel Corporation   	82801JI (ICH10 Family) USB UHCI Contr...
   782  0000:00:1d.7	Intel Corporation   	82801JI (ICH10 Family) USB2 EHCI Cont...
   783  0000:00:1e.0	Intel Corporation   	82801 PCI Bridge
   784  0000:00:1f.0	Intel Corporation   	82801JIR (ICH10R) LPC Interface Contr...
   785  0000:00:1f.2	Intel Corporation   	82801JI (ICH10 Family) SATA AHCI Cont...
   786  0000:00:1f.3	Intel Corporation   	82801JI (ICH10 Family) SMBus Controller
   787  0000:01:00.0	NEC Corporation     	uPD720200 USB 3.0 Host Controller
   788  0000:02:00.0	Marvell Technolog...	88SE9123 PCIe SATA 6.0 Gb/s controller
   789  0000:02:00.1	Marvell Technolog...	88SE912x IDE Controller
   790  0000:03:00.0	NVIDIA Corporation  	GP107 [GeForce GTX 1050 Ti]
   791  0000:03:00.1	NVIDIA Corporation  	UNKNOWN
   792  0000:04:00.0	LSI Logic / Symbi...	SAS2004 PCI-Express Fusion-MPT SAS-2 ...
   793  0000:06:00.0	Qualcomm Atheros    	AR5418 Wireless Network Adapter [AR50...
   794  0000:08:03.0	LSI Corporation     	FW322/323 [TrueFire] 1394a Controller
   795  0000:3f:00.0	Intel Corporation   	UNKNOWN
   796  0000:3f:00.1	Intel Corporation   	Xeon 5600 Series QuickPath Architectu...
   797  0000:3f:02.0	Intel Corporation   	Xeon 5600 Series QPI Link 0
   798  0000:3f:02.1	Intel Corporation   	Xeon 5600 Series QPI Physical 0
   799  0000:3f:02.2	Intel Corporation   	Xeon 5600 Series Mirror Port Link 0
   800  0000:3f:02.3	Intel Corporation   	Xeon 5600 Series Mirror Port Link 1
   801  0000:3f:03.0	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   802  0000:3f:03.1	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   803  0000:3f:03.4	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   804  0000:3f:04.0	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   805  0000:3f:04.1	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   806  0000:3f:04.2	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   807  0000:3f:04.3	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   808  0000:3f:05.0	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   809  0000:3f:05.1	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   810  0000:3f:05.2	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   811  0000:3f:05.3	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   812  0000:3f:06.0	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   813  0000:3f:06.1	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   814  0000:3f:06.2	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   815  0000:3f:06.3	Intel Corporation   	Xeon 5600 Series Integrated Memory Co...
   816  ```
   817  
   818  #### Finding a PCI device by PCI address
   819  
   820  In addition to the above information, the `ghw.PCIInfo` struct has the
   821  following method:
   822  
   823  * `ghw.PCIInfo.GetDevice(address string)`
   824  
   825  The following code snippet shows how to call the `ghw.PCIInfo.GetDevice()`
   826  method and use its returned `ghw.PCIDevice` struct pointer:
   827  
   828  ```go
   829  package main
   830  
   831  import (
   832  	"fmt"
   833  	"os"
   834  
   835  	"github.com/jaypipes/ghw"
   836  )
   837  
   838  func main() {
   839  	pci, err := ghw.PCI()
   840  	if err != nil {
   841  		fmt.Printf("Error getting PCI info: %v", err)
   842  	}
   843  
   844  	addr := "0000:00:00.0"
   845  	if len(os.Args) == 2 {
   846  		addr = os.Args[1]
   847  	}
   848  	fmt.Printf("PCI device information for %s\n", addr)
   849  	fmt.Println("====================================================")
   850  	deviceInfo := pci.GetDevice(addr)
   851  	if deviceInfo == nil {
   852  		fmt.Printf("could not retrieve PCI device information for %s\n", addr)
   853  		return
   854  	}
   855  
   856  	vendor := deviceInfo.Vendor
   857  	fmt.Printf("Vendor: %s [%s]\n", vendor.Name, vendor.ID)
   858  	product := deviceInfo.Product
   859  	fmt.Printf("Product: %s [%s]\n", product.Name, product.ID)
   860  	subsystem := deviceInfo.Subsystem
   861  	subvendor := pci.Vendors[subsystem.VendorID]
   862  	subvendorName := "UNKNOWN"
   863  	if subvendor != nil {
   864  		subvendorName = subvendor.Name
   865  	}
   866  	fmt.Printf("Subsystem: %s [%s] (Subvendor: %s)\n", subsystem.Name, subsystem.ID, subvendorName)
   867  	class := deviceInfo.Class
   868  	fmt.Printf("Class: %s [%s]\n", class.Name, class.ID)
   869  	subclass := deviceInfo.Subclass
   870  	fmt.Printf("Subclass: %s [%s]\n", subclass.Name, subclass.ID)
   871  	progIface := deviceInfo.ProgrammingInterface
   872  	fmt.Printf("Programming Interface: %s [%s]\n", progIface.Name, progIface.ID)
   873  }
   874  ```
   875  
   876  Here's a sample output from my local workstation:
   877  
   878  ```
   879  PCI device information for 0000:03:00.0
   880  ====================================================
   881  Vendor: NVIDIA Corporation [10de]
   882  Product: GP107 [GeForce GTX 1050 Ti] [1c82]
   883  Subsystem: UNKNOWN [8613] (Subvendor: ASUSTeK Computer Inc.)
   884  Class: Display controller [03]
   885  Subclass: VGA compatible controller [00]
   886  Programming Interface: VGA controller [00]
   887  ```
   888  
   889  ### GPU
   890  
   891  The `ghw.GPU()` function returns a `ghw.GPUInfo` struct that contains
   892  information about the host computer's graphics hardware.
   893  
   894  The `ghw.GPUInfo` struct contains one field:
   895  
   896  * `ghw.GPUInfo.GraphicCards` is an array of pointers to `ghw.GraphicsCard`
   897    structs, one for each graphics card found for the system
   898  
   899  Each `ghw.GraphicsCard` struct contains the following fields:
   900  
   901  * `ghw.GraphicsCard.Index` is the system's numeric zero-based index for the
   902    card on the bus
   903  * `ghw.GraphicsCard.Address` is the PCI address for the graphics card
   904  * `ghw.GraphicsCard.DeviceInfo` is a pointer to a `ghw.PCIDevice` struct
   905    describing the graphics card. This may be `nil` if no PCI device information
   906    could be determined for the card.
   907  * `ghw.GraphicsCard.Node` is an pointer to a `ghw.TopologyNode` struct that the
   908    GPU/graphics card is affined to. On non-NUMA systems, this will always be
   909    `nil`.
   910  
   911  ```go
   912  package main
   913  
   914  import (
   915  	"fmt"
   916  
   917  	"github.com/jaypipes/ghw"
   918  )
   919  
   920  func main() {
   921  	gpu, err := ghw.GPU()
   922  	if err != nil {
   923  		fmt.Printf("Error getting GPU info: %v", err)
   924  	}
   925  
   926  	fmt.Printf("%v\n", gpu)
   927  
   928  	for _, card := range gpu.GraphicsCards {
   929  		fmt.Printf(" %v\n", card)
   930  	}
   931  }
   932  ```
   933  
   934  Example output from my personal workstation:
   935  
   936  ```
   937  gpu (1 graphics card)
   938   card #0 @0000:03:00.0 -> class: 'Display controller' vendor: 'NVIDIA Corporation' product: 'GP107 [GeForce GTX 1050 Ti]'
   939  ```
   940  
   941  **NOTE**: You can [read more](#pci) about the fields of the `ghw.PCIDevice`
   942  struct if you'd like to dig deeper into PCI subsystem and programming interface
   943  information
   944  
   945  **NOTE**: You can [read more](#topology) about the fields of the
   946  `ghw.TopologyNode` struct if you'd like to dig deeper into the NUMA/topology
   947  subsystem
   948  
   949  ### Accelerator
   950  
   951  The `ghw.Accelerator()` function returns a `ghw.AcceleratorInfo` struct that contains
   952  information about the host computer's processing accelerator hardware. In this category
   953  we can find used hardware for AI. The hardware detected in this category will be
   954  processing accelerators (PCI class `1200`), 3D controllers (`0302`) and Display
   955  controllers (`0380`).
   956  
   957  The `ghw.AcceleratorInfo` struct contains one field:
   958  
   959  * `ghw.AcceleratorInfo.Devices` is an array of pointers to `ghw.AcceleratorDevice`
   960    structs, one for each processing accelerator card found for the system.
   961  
   962  Each `ghw.AcceleratorDevice` struct contains the following fields:
   963  
   964  * `ghw.AcceleratorDevice.Address` is the PCI address for the processing accelerator card.
   965  * `ghw.AcceleratorDevice.PCIDevice` is a pointer to a `ghw.PCIDevice` struct.
   966    describing the processing accelerator card. This may be `nil` if no PCI device
   967    information could be determined for the card.
   968  
   969  ```go
   970  package main
   971  
   972  import (
   973  	"fmt"
   974  
   975  	"github.com/jaypipes/ghw"
   976  )
   977  
   978  func main() {
   979  	accel, err := ghw.Accelerator()
   980  	if err != nil {
   981  		fmt.Printf("Error getting processing accelerator info: %v", err)
   982  	}
   983  
   984  	fmt.Printf("%v\n", accel)
   985  
   986  	for _, card := range accel.Devices {
   987  		fmt.Printf(" %v\n", device)
   988  	}
   989  }
   990  ```
   991  
   992  Example output from a testing machine:
   993  
   994  ```
   995  processing accelerators (1 device)
   996   device @0000:00:04.0 -> driver: 'fake_pci_driver' class: 'Processing accelerators' vendor: 'Red Hat, Inc.' product: 'QEMU PCI Test Device'
   997  ```
   998  
   999  **NOTE**: You can [read more](#pci) about the fields of the `ghw.PCIDevice`
  1000  struct if you'd like to dig deeper into PCI subsystem and programming interface
  1001  information
  1002  
  1003  ### Chassis
  1004  
  1005  The `ghw.Chassis()` function returns a `ghw.ChassisInfo` struct that contains
  1006  information about the host computer's hardware chassis.
  1007  
  1008  The `ghw.ChassisInfo` struct contains multiple fields:
  1009  
  1010  * `ghw.ChassisInfo.AssetTag` is a string with the chassis asset tag
  1011  * `ghw.ChassisInfo.SerialNumber` is a string with the chassis serial number
  1012  * `ghw.ChassisInfo.Type` is a string with the chassis type *code*
  1013  * `ghw.ChassisInfo.TypeDescription` is a string with a description of the
  1014    chassis type
  1015  * `ghw.ChassisInfo.Vendor` is a string with the chassis vendor
  1016  * `ghw.ChassisInfo.Version` is a string with the chassis version
  1017  
  1018  > **NOTE**: These fields are often missing for non-server hardware. Don't be
  1019  > surprised to see empty string or "None" values.
  1020  
  1021  ```go
  1022  package main
  1023  
  1024  import (
  1025  	"fmt"
  1026  
  1027  	"github.com/jaypipes/ghw"
  1028  )
  1029  
  1030  func main() {
  1031  	chassis, err := ghw.Chassis()
  1032  	if err != nil {
  1033  		fmt.Printf("Error getting chassis info: %v", err)
  1034  	}
  1035  
  1036  	fmt.Printf("%v\n", chassis)
  1037  }
  1038  ```
  1039  
  1040  Example output from my personal workstation:
  1041  
  1042  ```
  1043  chassis type=Desktop vendor=System76 version=thelio-r1
  1044  ```
  1045  
  1046  > **NOTE**: Some of the values such as serial numbers are shown as unknown
  1047  > because the Linux kernel by default disallows access to those fields if
  1048  > you're not running as root. They will be populated if it runs as root or
  1049  > otherwise you may see warnings like the following:
  1050  
  1051  ```
  1052  WARNING: Unable to read chassis_serial: open /sys/class/dmi/id/chassis_serial: permission denied
  1053  ```
  1054  
  1055  You can ignore them or use the [Disabling warning messages](#disabling-warning-messages)
  1056  feature to quiet things down.
  1057  
  1058  ### BIOS
  1059  
  1060  The `ghw.BIOS()` function returns a `ghw.BIOSInfo` struct that contains
  1061  information about the host computer's basis input/output system (BIOS).
  1062  
  1063  The `ghw.BIOSInfo` struct contains multiple fields:
  1064  
  1065  * `ghw.BIOSInfo.Vendor` is a string with the BIOS vendor
  1066  * `ghw.BIOSInfo.Version` is a string with the BIOS version
  1067  * `ghw.BIOSInfo.Date` is a string with the date the BIOS was flashed/created
  1068  
  1069  ```go
  1070  package main
  1071  
  1072  import (
  1073  	"fmt"
  1074  
  1075  	"github.com/jaypipes/ghw"
  1076  )
  1077  
  1078  func main() {
  1079  	bios, err := ghw.BIOS()
  1080  	if err != nil {
  1081  		fmt.Printf("Error getting BIOS info: %v", err)
  1082  	}
  1083  
  1084  	fmt.Printf("%v\n", bios)
  1085  }
  1086  ```
  1087  
  1088  Example output from my personal workstation:
  1089  
  1090  ```
  1091  bios vendor=System76 version=F2 Z5 date=11/14/2018
  1092  ```
  1093  
  1094  ### Baseboard
  1095  
  1096  The `ghw.Baseboard()` function returns a `ghw.BaseboardInfo` struct that
  1097  contains information about the host computer's hardware baseboard.
  1098  
  1099  The `ghw.BaseboardInfo` struct contains multiple fields:
  1100  
  1101  * `ghw.BaseboardInfo.AssetTag` is a string with the baseboard asset tag
  1102  * `ghw.BaseboardInfo.SerialNumber` is a string with the baseboard serial number
  1103  * `ghw.BaseboardInfo.Vendor` is a string with the baseboard vendor
  1104  * `ghw.BaseboardInfo.Product` is a string with the baseboard name on Linux and
  1105    Product on Windows
  1106  * `ghw.BaseboardInfo.Version` is a string with the baseboard version
  1107  
  1108  > **NOTE**: These fields are often missing for non-server hardware. Don't be
  1109  > surprised to see empty string or "None" values.
  1110  
  1111  ```go
  1112  package main
  1113  
  1114  import (
  1115  	"fmt"
  1116  
  1117  	"github.com/jaypipes/ghw"
  1118  )
  1119  
  1120  func main() {
  1121  	baseboard, err := ghw.Baseboard()
  1122  	if err != nil {
  1123  		fmt.Printf("Error getting baseboard info: %v", err)
  1124  	}
  1125  
  1126  	fmt.Printf("%v\n", baseboard)
  1127  }
  1128  ```
  1129  
  1130  Example output from my personal workstation:
  1131  
  1132  ```
  1133  baseboard vendor=System76 version=thelio-r1
  1134  ```
  1135  
  1136  > **NOTE**: Some of the values such as serial numbers are shown as unknown
  1137  > because the Linux kernel by default disallows access to those fields if
  1138  > you're not running as root. They will be populated if it runs as root or
  1139  > otherwise you may see warnings like the following:
  1140  
  1141  ```
  1142  WARNING: Unable to read board_serial: open /sys/class/dmi/id/board_serial: permission denied
  1143  ```
  1144  
  1145  You can ignore them or use the [Disabling warning messages](#disabling-warning-messages)
  1146  feature to quiet things down.
  1147  
  1148  ### Product
  1149  
  1150  The `ghw.Product()` function returns a `ghw.ProductInfo` struct that
  1151  contains information about the host computer's hardware product line.
  1152  
  1153  The `ghw.ProductInfo` struct contains multiple fields:
  1154  
  1155  * `ghw.ProductInfo.Family` is a string describing the product family
  1156  * `ghw.ProductInfo.Name` is a string with the product name
  1157  * `ghw.ProductInfo.SerialNumber` is a string with the product serial number
  1158  * `ghw.ProductInfo.UUID` is a string with the product UUID
  1159  * `ghw.ProductInfo.SKU` is a string with the product stock unit identifier
  1160    (SKU)
  1161  * `ghw.ProductInfo.Vendor` is a string with the product vendor
  1162  * `ghw.ProductInfo.Version` is a string with the product version
  1163  
  1164  > **NOTE**: These fields are often missing for non-server hardware. Don't be
  1165  > surprised to see empty string, "Default string" or "None" values.
  1166  
  1167  ```go
  1168  package main
  1169  
  1170  import (
  1171  	"fmt"
  1172  
  1173  	"github.com/jaypipes/ghw"
  1174  )
  1175  
  1176  func main() {
  1177  	product, err := ghw.Product()
  1178  	if err != nil {
  1179  		fmt.Printf("Error getting product info: %v", err)
  1180  	}
  1181  
  1182  	fmt.Printf("%v\n", product)
  1183  }
  1184  ```
  1185  
  1186  Example output from my personal workstation:
  1187  
  1188  ```
  1189  product family=Default string name=Thelio vendor=System76 sku=Default string version=thelio-r1
  1190  ```
  1191  
  1192  > **NOTE**: Some of the values such as serial numbers are shown as unknown
  1193  > because the Linux kernel by default disallows access to those fields if
  1194  > you're not running as root.  They will be populated if it runs as root or
  1195  > otherwise you may see warnings like the following:
  1196  
  1197  ```
  1198  WARNING: Unable to read product_serial: open /sys/class/dmi/id/product_serial: permission denied
  1199  ```
  1200  
  1201  You can ignore them or use the [Disabling warning messages](#disabling-warning-messages)
  1202  feature to quiet things down.
  1203  
  1204  ## Advanced Usage
  1205  
  1206  ### Disabling warning messages
  1207  
  1208  When `ghw` isn't able to retrieve some information, it may print certain
  1209  warning messages to `stderr`. To disable these warnings, simply set the
  1210  `GHW_DISABLE_WARNINGS` environs variable:
  1211  
  1212  ```
  1213  $ ghwc memory
  1214  WARNING:
  1215  Could not determine total physical bytes of memory. This may
  1216  be due to the host being a virtual machine or container with no
  1217  /var/log/syslog file, or the current user may not have necessary
  1218  privileges to read the syslog. We are falling back to setting the
  1219  total physical amount of memory to the total usable amount of memory
  1220  memory (24GB physical, 24GB usable)
  1221  ```
  1222  
  1223  ```
  1224  $ GHW_DISABLE_WARNINGS=1 ghwc memory
  1225  memory (24GB physical, 24GB usable)
  1226  ```
  1227  
  1228  You can disable warning programmatically using the `WithDisableWarnings` option:
  1229  
  1230  ```go
  1231  
  1232  import (
  1233  	"github.com/jaypipes/ghw"
  1234  )
  1235  
  1236  mem, err := ghw.Memory(ghw.WithDisableWarnings())
  1237  ```
  1238  
  1239  `WithDisableWarnings` is a alias for the `WithNullAlerter` option, which in turn
  1240  leverages the more general `Alerter` feature of ghw.
  1241  
  1242  You may supply a `Alerter` to ghw to redirect all the warnings there, like
  1243  logger objects (see for example golang's stdlib `log.Logger`).
  1244  `Alerter` is in fact the minimal logging interface `ghw needs.
  1245  To learn more, please check the `option.Alerter` interface and the `ghw.WithAlerter()`
  1246  function.
  1247  
  1248  ### Overriding the root mountpoint `ghw` uses
  1249  
  1250  When `ghw` looks for information about the host system, it considers `/` as its
  1251  root mountpoint. So, for example, when looking up CPU information on a Linux
  1252  system, `ghw.CPU()` will use the path `/proc/cpuinfo`.
  1253  
  1254  If you are calling `ghw` from a system that has an alternate root mountpoint,
  1255  you can either set the `GHW_CHROOT` environment variable to that alternate
  1256  path, or call one of the functions like `ghw.CPU()` or `ghw.Memory()` with the
  1257  `ghw.WithChroot()` modifier.
  1258  
  1259  For example, if you are executing from within an application container that has
  1260  bind-mounted the root host filesystem to the mount point `/host`, you would set
  1261  `GHW_CHROOT` to `/host` so that `ghw` can find `/proc/cpuinfo` at
  1262  `/host/proc/cpuinfo`.
  1263  
  1264  Alternately, you can use the `ghw.WithChroot()` function like so:
  1265  
  1266  ```go
  1267  cpu, err := ghw.CPU(ghw.WithChroot("/host"))
  1268  ```
  1269  
  1270  ### Serialization to JSON or YAML
  1271  
  1272  All of the `ghw` `XXXInfo` structs -- e.g. `ghw.CPUInfo` -- have two methods
  1273  for producing a serialized JSON or YAML string representation of the contained
  1274  information:
  1275  
  1276  * `JSONString()` returns a string containing the information serialized into
  1277    JSON. It accepts a single boolean parameter indicating whether to use
  1278    indentation when outputting the string
  1279  * `YAMLString()` returns a string containing the information serialized into
  1280    YAML
  1281  
  1282  ```go
  1283  package main
  1284  
  1285  import (
  1286  	"fmt"
  1287  
  1288  	"github.com/jaypipes/ghw"
  1289  )
  1290  
  1291  func main() {
  1292  	mem, err := ghw.Memory()
  1293  	if err != nil {
  1294  		fmt.Printf("Error getting memory info: %v", err)
  1295  	}
  1296  
  1297  	fmt.Printf("%s", mem.YAMLString())
  1298  }
  1299  ```
  1300  
  1301  the above example code prints the following out on my local workstation:
  1302  
  1303  ```
  1304  memory:
  1305    supported_page_sizes:
  1306    - 1073741824
  1307    - 2097152
  1308    total_physical_bytes: 25263415296
  1309    total_usable_bytes: 25263415296
  1310  ```
  1311  
  1312  ### Overriding a specific mountpoint (Linux only)
  1313  
  1314  When running inside containers, it can be cumbersome to only override the root
  1315  mountpoint. Inside containers, when granting access to the host file systems,
  1316  it is common to bind-mount them to a non-standard location, like `/sys` on
  1317  `/host-sys` or `/proc` to `/host-proc`.  It is rarer to mount them to a common
  1318  subtree (e.g. `/sys` to `/host/sys` and `/proc` to `/host/proc`...)
  1319  
  1320  To better cover this use case, `ghw.WithPathOverrides()` can be used to supply
  1321  a mapping of directories to mountpoints, like this example shows:
  1322  
  1323  ```go
  1324  cpu, err := ghw.CPU(ghw.WithPathOverrides(ghw.PathOverrides{
  1325  	"/proc": "/host-proc",
  1326  	"/sys": "/host-sys",
  1327  }))
  1328  ```
  1329  
  1330  **NOTE**: This feature works in addition and is composable with the
  1331  `ghw.WithChroot()` function and `GHW_CHROOT` environment variable.
  1332  
  1333  ### Reading hardware information from a `ghw` snapshot (Linux only)
  1334  
  1335  The `ghw-snapshot` tool can create a snapshot of a host's hardware information.
  1336  
  1337  Please read [`SNAPSHOT.md`](SNAPSHOT.md) to learn about creating snapshots with
  1338  the `ghw-snapshot` tool.
  1339  
  1340  You can make `ghw` read hardware information from a snapshot created with
  1341  `ghw-snapshot` using environment variables or programmatically.
  1342  
  1343  Use the `GHW_SNAPSHOT_PATH` environment variable to specify the filepath to a
  1344  snapshot that `ghw` will read to determine hardware information. All the needed
  1345  chroot changes will be automatically performed. By default, the snapshot is
  1346  unpacked into a temporary directory managed by `ghw`. This temporary directory
  1347  is automatically deleted when `ghw` is finished reading the snapshot.
  1348  
  1349  Three other environment variables are relevant if and only if `GHW_SNAPSHOT_PATH`
  1350  is not empty:
  1351  
  1352  * `GHW_SNAPSHOT_ROOT` let users specify the directory on which the snapshot
  1353    should be unpacked. This moves the ownership of that directory from `ghw` to
  1354    users. For this reason, `ghw` will *not* automatically clean up the content
  1355    unpacked into `GHW_SNAPSHOT_ROOT`.
  1356  * `GHW_SNAPSHOT_EXCLUSIVE` tells `ghw` that the directory is meant only to
  1357    contain the given snapshot, thus `ghw` will *not* attempt to unpack it unless
  1358    the directory is empty.  You can use both `GHW_SNAPSHOT_ROOT` and
  1359    `GHW_SNAPSHOT_EXCLUSIVE` to make sure `ghw` unpacks the snapshot only once
  1360    regardless of how many `ghw` packages (e.g. cpu, memory) access it. Set the
  1361    value of this environment variable to any non-empty string.
  1362  * `GHW_SNAPSHOT_PRESERVE` tells `ghw` not to clean up the unpacked snapshot.
  1363    Set the value of this environment variable to any non-empty string.
  1364  
  1365  ```go
  1366  cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{
  1367  	Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz",
  1368  }))
  1369  
  1370  
  1371  myRoot := "/my/safe/directory"
  1372  cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{
  1373  	Path: "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz",
  1374  	Root: &myRoot,
  1375  }))
  1376  
  1377  myOtherRoot := "/my/other/safe/directory"
  1378  cpu, err := ghw.CPU(ghw.WithSnapshot(ghw.SnapshotOptions{
  1379  	Path:      "/path/to/linux-amd64-d4771ed3300339bc75f856be09fc6430.tar.gz",
  1380  	Root:      &myOtherRoot,
  1381  	Exclusive: true,
  1382  }))
  1383  ```
  1384  
  1385  ### Creating snapshots
  1386  
  1387  You can create `ghw` snapshots using the `ghw-snapshot` tool or
  1388  programmatically using the `pkg/snapshot` package.
  1389  
  1390  Below is an example of creating a `ghw` snapshot using the `pkg/snapshot`
  1391  package.
  1392  
  1393  ```go
  1394  
  1395  import (
  1396  	"fmt"
  1397  	"os"
  1398  
  1399  	"github.com/jaypipes/ghw/pkg/snapshot"
  1400  )
  1401  
  1402  // ...
  1403  
  1404  scratchDir, err := os.MkdirTemp("", "ghw-snapshot-*")
  1405  if err != nil {
  1406  	fmt.Printf("Error creating clone directory: %v", err)
  1407  }
  1408  defer os.RemoveAll(scratchDir)
  1409  
  1410  // this step clones all the files and directories ghw cares about
  1411  if err := snapshot.CloneTreeInto(scratchDir); err != nil {
  1412  	fmt.Printf("error cloning into %q: %v", scratchDir, err)
  1413  }
  1414  
  1415  // optionally, you may add extra content into your snapshot.
  1416  // ghw will ignore the extra content.
  1417  // Glob patterns like `filepath.Glob` are supported.
  1418  fileSpecs := []string{
  1419  	"/proc/cmdline",
  1420  }
  1421  
  1422  // options allows the client code to optionally deference symlinks, or copy
  1423  // them into the cloned tree as symlinks
  1424  var opts *snapshot.CopyFileOptions
  1425  if err := snapshot.CopyFilesInto(fileSpecs, scratchDir, opts); err != nil {
  1426  	fmt.Printf("error cloning extra files into %q: %v", scratchDir, err)
  1427  }
  1428  
  1429  // automates the creation of the gzipped tarball out of the given tree.
  1430  if err := snapshot.PackFrom("my-snapshot.tgz", scratchDir); err != nil {
  1431  	fmt.Printf("error packing %q into %q: %v", scratchDir, *output, err)
  1432  }
  1433  ```
  1434  
  1435  ## Calling external programs
  1436  
  1437  By default `ghw` may call external programs, for example `ethtool`, to learn
  1438  about hardware capabilities.  In some rare circumstances it may be useful to
  1439  opt out from this behaviour and rely only on the data provided by
  1440  pseudo-filesystems, like sysfs.
  1441  
  1442  The most common use case is when we want to read a snapshot from `ghw`. In
  1443  these cases the information provided by tools will be inconsistent with the
  1444  data from the snapshot - since they will be run on a different host than the
  1445  host the snapshot was created for.
  1446  
  1447  To prevent `ghw` from calling external tools, set the `GHW_DISABLE_TOOLS`
  1448  environment variable to any value, or, programmatically, use the
  1449  `ghw.WithDisableTools()` function.  The default behaviour of ghw is to call
  1450  external tools when available.
  1451  
  1452  > **WARNING**: on all platforms, disabling external tools make ghw return less
  1453  > data.  Unless noted otherwise, there is _no fallback flow_ if external tools
  1454  > are disabled. On MacOSX/Darwin, disabling external tools disables block
  1455  > support entirely
  1456  
  1457  ## Developers
  1458  
  1459  [Contributions](CONTRIBUTING.md) to `ghw` are welcomed! Fork the repo on GitHub
  1460  and submit a pull request with your proposed changes. Or, feel free to log an
  1461  issue for a feature request or bug report.