Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for processing accelerators hardware #385

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ hardware:
* [`ghw.Network()`](#network)
* [`ghw.PCI()`](#pci)
* [`ghw.GPU()`](#gpu) (graphical processing unit)
* [`ghw.Accelerator()`](#accelerator) (processing accelerators, AI)
* [`ghw.Chassis()`](#chassis)
* [`ghw.BIOS()`](#bios)
* [`ghw.Baseboard()`](#baseboard)
Expand Down Expand Up @@ -893,7 +894,7 @@ information about the host computer's graphics hardware.
The `ghw.GPUInfo` struct contains one field:

* `ghw.GPUInfo.GraphicCards` is an array of pointers to `ghw.GraphicsCard`
structs, one for each graphics card found for the systen
structs, one for each graphics card found for the system

Each `ghw.GraphicsCard` struct contains the following fields:

Expand Down Expand Up @@ -945,6 +946,60 @@ information
`ghw.TopologyNode` struct if you'd like to dig deeper into the NUMA/topology
subsystem

### Accelerator

The `ghw.Accelerator()` function returns a `ghw.AcceleratorInfo` struct that contains
information about the host computer's processing accelerator hardware. In this category
we can find used hardware for AI. The hardware detected in this category will be
processing accelerators (PCI class `1200`), 3D controllers (`0302`) and Display
controllers (`0380`).

The `ghw.AcceleratorInfo` struct contains one field:

* `ghw.AcceleratorInfo.Devices` is an array of pointers to `ghw.AcceleratorDevice`
structs, one for each processing accelerator card found for the system.

Each `ghw.AcceleratorDevice` struct contains the following fields:

* `ghw.AcceleratorDevice.Address` is the PCI address for the processing accelerator card.
* `ghw.AcceleratorDevice.PCIDevice` is a pointer to a `ghw.PCIDevice` struct.
describing the processing accelerator card. This may be `nil` if no PCI device
information could be determined for the card.

```go
package main

import (
"fmt"

"github.com/jaypipes/ghw"
)

func main() {
accel, err := ghw.Accelerator()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to extend (maybe later?) this API to accept additive user-provided filters?
I'm referring to the wide variety of devices out there and how they present themselves on PCI bus.
By "additive" filter I mean

  • users cannot rule out a device ghw is really sure it's an accelerator (this means ghw must be conservative in the core logic, which I think it's good anyway)
  • users can opt-in devices if they know better than ghw for whatever reasons (maybe old ghw version, maybe rare/custom devices)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. @mlorenzofr was going to submit a PR adding that filtering functionality. He originally added it in this PR but I asked him to separate it out into a new one.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perfect! I'll be on the lookout this time!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we'll extend it, but in a different PR. @jaypipes asked to separate it from the original development so he could analyze it step by step.

Welcome back, btw 😉

if err != nil {
fmt.Printf("Error getting processing accelerator info: %v", err)
}

fmt.Printf("%v\n", accel)

for _, card := range accel.Devices {
fmt.Printf(" %v\n", device)
}
}
```

Example output from a testing machine:

```
processing accelerators (1 device)
device @0000:00:04.0 -> driver: 'fake_pci_driver' class: 'Processing accelerators' vendor: 'Red Hat, Inc.' product: 'QEMU PCI Test Device'
```

**NOTE**: You can [read more](#pci) about the fields of the `ghw.PCIDevice`
struct if you'd like to dig deeper into PCI subsystem and programming interface
information

### Chassis

The `ghw.Chassis()` function returns a `ghw.ChassisInfo` struct that contains
Expand Down
8 changes: 8 additions & 0 deletions alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package ghw

import (
"github.com/jaypipes/ghw/pkg/accelerator"
"github.com/jaypipes/ghw/pkg/baseboard"
"github.com/jaypipes/ghw/pkg/bios"
"github.com/jaypipes/ghw/pkg/block"
Expand Down Expand Up @@ -183,3 +184,10 @@ type GraphicsCard = gpu.GraphicsCard
var (
GPU = gpu.New
)

type AcceleratorInfo = accelerator.Info
type AcceleratorDevice = accelerator.AcceleratorDevice

var (
Accelerator = accelerator.New
)
48 changes: 48 additions & 0 deletions cmd/ghwc/commands/accelerator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package commands

import (
"fmt"

"github.com/jaypipes/ghw"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

// acceleratorCmd represents the install command
var acceleratorCmd = &cobra.Command{
Use: "accelerator",
Short: "Show processing accelerators information for the host system",
RunE: showGPU,
}

// showAccelerator show processing accelerators information for the host system.
func showAccelerator(cmd *cobra.Command, args []string) error {
accel, err := ghw.Accelerator()
if err != nil {
return errors.Wrap(err, "error getting Accelerator info")
}

switch outputFormat {
case outputFormatHuman:
fmt.Printf("%v\n", accel)

for _, card := range accel.Devices {
fmt.Printf(" %v\n", card)
}
case outputFormatJSON:
fmt.Printf("%s\n", accel.JSONString(pretty))
case outputFormatYAML:
fmt.Printf("%s", accel.YAMLString())
}
return nil
}

func init() {
rootCmd.AddCommand(acceleratorCmd)
}
3 changes: 3 additions & 0 deletions cmd/ghwc/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ func showAll(cmd *cobra.Command, args []string) error {
if err := showProduct(cmd, args); err != nil {
return err
}
if err := showAccelerator(cmd, args); err != nil {
return err
}
case outputFormatJSON:
host, err := ghw.Host()
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/samber/lo v1.47.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
58 changes: 33 additions & 25 deletions host.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/jaypipes/ghw/pkg/context"

"github.com/jaypipes/ghw/pkg/accelerator"
"github.com/jaypipes/ghw/pkg/baseboard"
"github.com/jaypipes/ghw/pkg/bios"
"github.com/jaypipes/ghw/pkg/block"
Expand All @@ -28,18 +29,19 @@ import (
// HostInfo is a wrapper struct containing information about the host system's
// memory, block storage, CPU, etc
type HostInfo struct {
ctx *context.Context
Memory *memory.Info `json:"memory"`
Block *block.Info `json:"block"`
CPU *cpu.Info `json:"cpu"`
Topology *topology.Info `json:"topology"`
Network *net.Info `json:"network"`
GPU *gpu.Info `json:"gpu"`
Chassis *chassis.Info `json:"chassis"`
BIOS *bios.Info `json:"bios"`
Baseboard *baseboard.Info `json:"baseboard"`
Product *product.Info `json:"product"`
PCI *pci.Info `json:"pci"`
ctx *context.Context
Memory *memory.Info `json:"memory"`
Block *block.Info `json:"block"`
CPU *cpu.Info `json:"cpu"`
Topology *topology.Info `json:"topology"`
Network *net.Info `json:"network"`
GPU *gpu.Info `json:"gpu"`
Accelerator *accelerator.Info `json:"accelerator"`
Chassis *chassis.Info `json:"chassis"`
BIOS *bios.Info `json:"bios"`
Baseboard *baseboard.Info `json:"baseboard"`
Product *product.Info `json:"product"`
PCI *pci.Info `json:"pci"`
}

// Host returns a pointer to a HostInfo struct that contains fields with
Expand Down Expand Up @@ -71,6 +73,10 @@ func Host(opts ...*WithOption) (*HostInfo, error) {
if err != nil {
return nil, err
}
acceleratorInfo, err := accelerator.New(opts...)
if err != nil {
return nil, err
}
chassisInfo, err := chassis.New(opts...)
if err != nil {
return nil, err
Expand All @@ -92,29 +98,31 @@ func Host(opts ...*WithOption) (*HostInfo, error) {
return nil, err
}
return &HostInfo{
ctx: ctx,
CPU: cpuInfo,
Memory: memInfo,
Block: blockInfo,
Topology: topologyInfo,
Network: netInfo,
GPU: gpuInfo,
Chassis: chassisInfo,
BIOS: biosInfo,
Baseboard: baseboardInfo,
Product: productInfo,
PCI: pciInfo,
ctx: ctx,
CPU: cpuInfo,
Memory: memInfo,
Block: blockInfo,
Topology: topologyInfo,
Network: netInfo,
GPU: gpuInfo,
Accelerator: acceleratorInfo,
Chassis: chassisInfo,
BIOS: biosInfo,
Baseboard: baseboardInfo,
Product: productInfo,
PCI: pciInfo,
}, nil
}

// String returns a newline-separated output of the HostInfo's component
// structs' String-ified output
func (info *HostInfo) String() string {
return fmt.Sprintf(
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
info.Block.String(),
info.CPU.String(),
info.GPU.String(),
info.Accelerator.String(),
info.Memory.String(),
info.Network.String(),
info.Topology.String(),
Expand Down
7 changes: 7 additions & 0 deletions host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,11 @@ func TestHost(t *testing.T) {
if gpu == nil {
t.Fatalf("Expected non-nil GPU but got nil.")
}

// Processing accelerator cards are not common nowadays.
// You may not have one in your machine, so this check displays a message but does not interrupt the test.
accel := host.Accelerator
if accel == nil {
t.Logf("WARNING: Processing accelerator cards not detected.")
}
}
84 changes: 84 additions & 0 deletions pkg/accelerator/accelerator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package accelerator

import (
"fmt"

"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/marshal"
"github.com/jaypipes/ghw/pkg/option"
"github.com/jaypipes/ghw/pkg/pci"
)

type AcceleratorDevice struct {
// the PCI address where the accelerator device can be found
Address string `json:"address"`
// pointer to a PCIDevice struct that describes the vendor and product
// model, etc
PCIDevice *pci.Device `json:"pci_device"`
}

func (dev *AcceleratorDevice) String() string {
deviceStr := dev.Address
if dev.PCIDevice != nil {
deviceStr = dev.PCIDevice.String()
}
nodeStr := ""
return fmt.Sprintf(
"device %s@%s",
nodeStr,
deviceStr,
)
}

type Info struct {
ctx *context.Context
Devices []*AcceleratorDevice `json:"devices"`
}

// New returns a pointer to an Info struct that contains information about the
// accelerator devices on the host system
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
info := &Info{ctx: ctx}

if err := ctx.Do(info.load); err != nil {
return nil, err
}
return info, nil
}

func (i *Info) String() string {
numDevsStr := "devices"
if len(i.Devices) == 1 {
numDevsStr = "device"
}
return fmt.Sprintf(
"processing accelerators (%d %s)",
len(i.Devices),
numDevsStr,
)
}

// simple private struct used to encapsulate processing accelerators information in a top-level
// "accelerator" YAML/JSON map/object key
type acceleratorPrinter struct {
Info *Info `json:"accelerator"`
}

// YAMLString returns a string with the processing accelerators information formatted as YAML
// under a top-level "accelerator:" key
func (i *Info) YAMLString() string {
return marshal.SafeYAML(i.ctx, acceleratorPrinter{i})
}

// JSONString returns a string with the processing accelerators information formatted as JSON
// under a top-level "accelerator:" key
func (i *Info) JSONString(indent bool) string {
return marshal.SafeJSON(i.ctx, acceleratorPrinter{i}, indent)
}
Loading
Loading