Skip to content

Commit

Permalink
Add pci_vendor, pci_device, pci_class, pci_subclass decoders
Browse files Browse the repository at this point in the history
Running the example in a vm:

    ivan@vm:~$ sudo lspci
    00:00.0 Host bridge: Red Hat, Inc. QEMU PCIe Host bridge
    00:01.0 Non-Volatile memory controller: Red Hat, Inc. QEMU NVM Express Controller (rev 02)
    00:02.0 Non-Volatile memory controller: Red Hat, Inc. QEMU NVM Express Controller (rev 02)
    00:03.0 Unclassified device [0002]: Red Hat, Inc. Virtio filesystem
    00:04.0 Unclassified device [0002]: Red Hat, Inc. Virtio filesystem

    ivan@vm:~$ curl -s http://localhost:9435/metrics | fgrep pci
    ebpf_exporter_build_info{branch="ivan/pci",goarch="arm64",goos="linux",goversion="go1.19.8",revision="072572d",tags="unknown",version="v2.2.0-15-g072572d"} 1
    ebpf_exporter_ebpf_program_info{config="pci",id="279",program="pci_user_read_config_byte",tag="d57be3affc84ce20"} 1
    ebpf_exporter_ebpf_program_info{config="pci",id="280",program="pci_user_read_config_word",tag="d57be3affc84ce20"} 1
    ebpf_exporter_ebpf_program_info{config="pci",id="281",program="pci_user_read_config_dword",tag="d57be3affc84ce20"} 1
    ebpf_exporter_enabled_configs{name="pci"} 1
    # HELP ebpf_exporter_pci_user_read_config_ops_total The number of operations reading pci configs
    # TYPE ebpf_exporter_pci_user_read_config_ops_total counter
    ebpf_exporter_pci_user_read_config_ops_total{pci_class="Bridge",pci_device="QEMU PCIe Host bridge",pci_subclass="Host bridge",pci_vendor="Red Hat, Inc."} 16
    ebpf_exporter_pci_user_read_config_ops_total{pci_class="Mass storage controller",pci_device="QEMU NVM Express Controller",pci_subclass="Non-Volatile memory controller",pci_vendor="Red Hat, Inc."} 32
    ebpf_exporter_pci_user_read_config_ops_total{pci_class="Unclassified device",pci_device="Virtio filesystem",pci_subclass="unknown pci subclass: 0x02 (class 0x00)",pci_vendor="Red Hat, Inc."} 32
  • Loading branch information
bobrik committed Aug 30, 2023
1 parent 55d45c1 commit 3d0958a
Show file tree
Hide file tree
Showing 16 changed files with 635 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ jobs:
- name: Install libelf-dev
run: sudo apt-get install -y libelf-dev

- name: Update pci.ids
run: |
curl -o /tmp/pci.ids https://raw.githubusercontent.com/pciutils/pciids/master/pci.ids
sudo mv /tmp/pci.ids /usr/share/misc/pci.ids
- name: Build
run: make build

Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,28 @@ you need to wrap it with `KPROBE_REGS_IP_FIX()` from `regs-ip.bpf.h`.
With major-minor decoder you can turn kernel's combined u32 view
of major and minor device numbers into a device name in `/dev`.

### `pci_vendor`

With `pci_vendor` decoder you can transform PCI vendor IDs like 0x8086
into human readable vendor names like `Intel Corporation`.

### `pci_device`

With `pci_vendor` decoder you can transform PCI vendor IDs like 0x80861000
into human readable names like `82542 Gigabit Ethernet Controller (Fiber)`.

Note that the you need to concatenate vendor and device id together for this.

### `pci_class`

With `pci_class` decoder you can transform PCI class ID (the lowest byte) into
the class name like `Network controller`.

### `pci_subclass`

With `pci_subclass` decoder you can transform PCI subclass (two lowest bytes)
into the subclass name like `Ethernet controller`.

#### `regexp`

Regexp decoder takes list of strings from `regexp` configuration key
Expand Down
24 changes: 14 additions & 10 deletions decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,20 @@ type Set struct {
func NewSet() *Set {
return &Set{
decoders: map[string]Decoder{
"cgroup": &CGroup{},
"ksym": &KSym{},
"majorminor": &MajorMinor{},
"regexp": &Regexp{},
"static_map": &StaticMap{},
"string": &String{},
"dname": &Dname{},
"uint": &UInt{},
"inet_ip": &InetIP{},
"syscall": &Syscall{},
"cgroup": &CGroup{},
"ksym": &KSym{},
"majorminor": &MajorMinor{},
"regexp": &Regexp{},
"static_map": &StaticMap{},
"string": &String{},
"dname": &Dname{},
"uint": &UInt{},
"inet_ip": &InetIP{},
"pci_vendor": &PCIVendor{},
"pci_device": &PCIDevice{},
"pci_class": &PCIClass{},
"pci_subclass": &PCISubClass{},
"syscall": &Syscall{},
},
}
}
Expand Down
27 changes: 27 additions & 0 deletions decoder/pci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package decoder

import (
"log"
"os"

"github.com/jaypipes/pcidb"
)

const pciIdsPath = "/usr/share/misc/pci.ids"
const missingPciIdsText = "missing pci.ids db"

var pci *pcidb.PCIDB

func init() {
if _, err := os.Stat(pciIdsPath); err != nil {
log.Printf("PCI DB path %q is not accessible: %v", pciIdsPath, err)
return
}

db, err := pcidb.New()
if err != nil {
log.Fatalf("Error initializing PCI DB: %v", err)
}

pci = db
}
31 changes: 31 additions & 0 deletions decoder/pci_class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package decoder

import (
"fmt"
"strconv"

"github.com/cloudflare/ebpf_exporter/v2/config"
)

// PCIClass is a decoder that transforms PCI class id into a name
type PCIClass struct{}

// Decode transforms PCI class id into a name
func (d *PCIClass) Decode(in []byte, conf config.Decoder) ([]byte, error) {
if pci == nil {
return []byte(missingPciIdsText), nil
}

num, err := strconv.Atoi(string(in))
if err != nil {
return nil, err
}

key := fmt.Sprintf("%02x", num)

if device, ok := pci.Classes[key]; ok {
return []byte(device.Name), nil
} else {
return []byte(fmt.Sprintf("unknown pci class: 0x%s", key)), nil
}
}
82 changes: 82 additions & 0 deletions decoder/pci_class_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package decoder

import (
"bytes"
"testing"

"github.com/cloudflare/ebpf_exporter/v2/config"
)

func TestPCIClassDecoderMissing(t *testing.T) {
if pci != nil {
t.Skip("PCI DB is available")
}

cases := [][]byte{
[]byte("1"),
[]byte("2"),
[]byte("6"),
}

for _, c := range cases {
d := &PCIClass{}

out, err := d.Decode(c, config.Decoder{})
if err != nil {
t.Errorf("Error decoding %#v: %v", c, err)
}

if !bytes.Equal(out, []byte(missingPciIdsText)) {
t.Errorf("Expected %q, got %s", missingPciIdsText, out)
}
}
}

func TestPCIClassDecoderPresent(t *testing.T) {
if pci == nil {
t.Skip("PCI DB is not available")
}

cases := []struct {
in []byte
out []byte
}{
{
in: []byte("1"),
out: []byte("Mass storage controller"),
},
{
in: []byte("2"),
out: []byte("Network controller"),
},
{
in: []byte("3"),
out: []byte("Display controller"),
},
{
in: []byte("6"),
out: []byte("Bridge"),
},
{
in: []byte("12"),
out: []byte("Serial bus controller"),
},
{
in: []byte("253"),
out: []byte("unknown pci class: 0xfd"),
},
}

for _, c := range cases {
d := &PCIClass{}

out, err := d.Decode(c.in, config.Decoder{})
if err != nil {
t.Errorf("Error decoding %#v: %v", c.in, err)
}

if !bytes.Equal(out, c.out) {
t.Errorf("Expected %q, got %q", c.out, out)
}
}
}
31 changes: 31 additions & 0 deletions decoder/pci_device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package decoder

import (
"fmt"
"strconv"

"github.com/cloudflare/ebpf_exporter/v2/config"
)

// PCIDevice2 is a decoder that transforms PCI device id into a name
type PCIDevice struct{}

// Decode transforms PCI device id into a name
func (d *PCIDevice) Decode(in []byte, conf config.Decoder) ([]byte, error) {
if pci == nil {
return []byte(missingPciIdsText), nil
}

num, err := strconv.Atoi(string(in))
if err != nil {
return nil, err
}

key := fmt.Sprintf("%04x", num)

if device, ok := pci.Products[key]; ok {
return []byte(device.Name), nil
} else {
return []byte(fmt.Sprintf("unknown pci device: 0x%s", key)), nil
}
}
86 changes: 86 additions & 0 deletions decoder/pci_device_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package decoder

import (
"bytes"
"testing"

"github.com/cloudflare/ebpf_exporter/v2/config"
)

func TestPCIDeviceDecoderMissing(t *testing.T) {
if pci != nil {
t.Skip("PCI DB is available")
}

cases := [][]byte{
[]byte("2156269568"), // 0x80861000
[]byte("268596191"), // 0x100273df
[]byte("282994436"), // 0x10de2704
}

for _, c := range cases {
d := &PCIDevice{}

out, err := d.Decode(c, config.Decoder{})
if err != nil {
t.Errorf("Error decoding %#v: %v", c, err)
}

if !bytes.Equal(out, []byte(missingPciIdsText)) {
t.Errorf("Expected %q, got %s", missingPciIdsText, out)
}
}
}

func TestPCIDeviceDecoderPresent(t *testing.T) {
if pci == nil {
t.Skip("PCI DB is not available")
}

cases := []struct {
in []byte
out []byte
}{
{
in: []byte("2156269568"), // 0x80861000
out: []byte("82542 Gigabit Ethernet Controller (Fiber)"),
},
{
in: []byte("268596191"), // 0x100273df
out: []byte("Navi 22 [Radeon RX 6700/6700 XT/6750 XT / 6800M/6850M XT]"),
},
{
in: []byte("282994436"), // 0x10de2704
out: []byte("AD103 [GeForce RTX 4080]"),
},
{
in: []byte("364056607"), // 0x15b3101f
out: []byte("MT2894 Family [ConnectX-6 Lx]"),
},
{
in: []byte("340633610"), // 0x144da80a
out: []byte("NVMe SSD Controller PM9A1/PM9A3/980PRO"),
},
{
in: []byte("350492180"), // 0x14e41614
out: []byte("BCM57454 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb Ethernet"),
},
{
in: []byte("3735928559"), // 0xdeadbeef
out: []byte("unknown pci device: 0xdeadbeef"),
},
}

for _, c := range cases {
d := &PCIDevice{}

out, err := d.Decode(c.in, config.Decoder{})
if err != nil {
t.Errorf("Error decoding %#v: %v", c.in, err)
}

if !bytes.Equal(out, c.out) {
t.Errorf("Expected %q, got %q", c.out, out)
}
}
}
38 changes: 38 additions & 0 deletions decoder/pci_subclass.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package decoder

import (
"fmt"
"strconv"

"github.com/cloudflare/ebpf_exporter/v2/config"
)

// PCISubClass is a decoder that transforms PCI class id into a name
type PCISubClass struct{}

// Decode transforms PCI class id into a name
func (d *PCISubClass) Decode(in []byte, conf config.Decoder) ([]byte, error) {
if pci == nil {
return []byte(missingPciIdsText), nil
}

num, err := strconv.Atoi(string(in))
if err != nil {
return nil, err
}

classID := fmt.Sprintf("%02x", num>>8)

if class, ok := pci.Classes[classID]; ok {
subclassID := fmt.Sprintf("%02x", num&0xff)
for _, subclass := range class.Subclasses {
if subclass.ID == subclassID {
return []byte(subclass.Name), nil
}
}

return []byte(fmt.Sprintf("unknown pci subclass: 0x%s (class 0x%s)", subclassID, classID)), nil
} else {
return []byte(fmt.Sprintf("unknown pci class: 0x%s", classID)), nil
}
}
Loading

0 comments on commit 3d0958a

Please sign in to comment.