Skip to content

Add support for devlink DPIPE tables #3

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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
17 changes: 17 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ func (c *Client) Ports() ([]*Port, error) {
return c.c.Ports()
}

// DPIPETables retrieves all devlink DPIPE tables attached to specified device on this system.
func (c *Client) DPIPETables(dev *Device) ([]*DPIPETable, error) {
return c.c.DPIPETables(dev)
}

// An osClient is the operating system-specific implementation of Client.
type osClient interface {
io.Closer
Devices() ([]*Device, error)
Ports() ([]*Port, error)
DPIPETables(*Device) ([]*DPIPETable, error)
}

// A Device is a devlink device.
Expand Down Expand Up @@ -73,3 +79,14 @@ type Port struct {
Type PortType
Name string
}

// A DPIPETable is a devlink pipeline debugging (DPIPE) table.
// For more information on DPIPE, see:
// https://github.com/Mellanox/mlxsw/wiki/Pipeline-Debugging-(DPIPE)
type DPIPETable struct {
Bus string
Device string
Name string
Size uint64
CountersEnabled bool
}
97 changes: 94 additions & 3 deletions client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package devlink

import (
"fmt"

"github.com/mdlayher/genetlink"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -45,7 +47,7 @@ func (c *client) Close() error { return c.c.Close() }

// Devices implements osClient.
func (c *client) Devices() ([]*Device, error) {
msgs, err := c.execute(unix.DEVLINK_CMD_GET, netlink.Dump)
msgs, err := c.execute(unix.DEVLINK_CMD_GET, netlink.Dump, nil)
if err != nil {
return nil, err
}
Expand All @@ -55,23 +57,49 @@ func (c *client) Devices() ([]*Device, error) {

// Ports implements osClient.
func (c *client) Ports() ([]*Port, error) {
msgs, err := c.execute(unix.DEVLINK_CMD_PORT_GET, netlink.Dump)
msgs, err := c.execute(unix.DEVLINK_CMD_PORT_GET, netlink.Dump, nil)
if err != nil {
return nil, err
}

return parsePorts(msgs)
}

// DPIPETables implements osClient.
func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) {
if dev == nil {
return nil, fmt.Errorf("invalid argument")
}
ae := netlink.NewAttributeEncoder()
ae.String(unix.DEVLINK_ATTR_BUS_NAME, dev.Bus)
ae.String(unix.DEVLINK_ATTR_DEV_NAME, dev.Device)

msgs, err := c.execute(unix.DEVLINK_CMD_DPIPE_TABLE_GET, netlink.Acknowledge, ae)
if err != nil {
return nil, err
}

return parseDPIPETables(msgs)
}

// execute executes the specified command with additional header flags. The
// netlink.Request header flag is automatically set.
func (c *client) execute(cmd uint8, flags netlink.HeaderFlags) ([]genetlink.Message, error) {
func (c *client) execute(cmd uint8, flags netlink.HeaderFlags, ae *netlink.AttributeEncoder) ([]genetlink.Message, error) {
var data []byte
var err error
if ae != nil {
data, err = ae.Encode()
if err != nil {
return nil, err
}
}
return c.c.Execute(
genetlink.Message{
Header: genetlink.Header{
Command: cmd,
Version: unix.DEVLINK_GENL_VERSION,
},
Data: data,
},
// Always pass the genetlink family ID and request flag.
c.family.ID,
Expand Down Expand Up @@ -139,3 +167,66 @@ func parsePorts(msgs []genetlink.Message) ([]*Port, error) {

return ps, nil
}

// parseDPIPETables parses DPIPE tables from a slice of generic netlink messages.
func parseDPIPETables(msgs []genetlink.Message) ([]*DPIPETable, error) {
var bus, dev string
if len(msgs) == 0 {
// No devlink response found.
return nil, nil
}

ts := make([]*DPIPETable, 0, len(msgs))
for _, m := range msgs {
ad, err := netlink.NewAttributeDecoder(m.Data)
if err != nil {
return nil, err
}

for ad.Next() {
switch ad.Type() {
case unix.DEVLINK_ATTR_BUS_NAME:
bus = ad.String()
case unix.DEVLINK_ATTR_DEV_NAME:
dev = ad.String()
case unix.DEVLINK_ATTR_DPIPE_TABLES:
// Netlink array of DPIPE tables.
// Errors while parsing are propagated up to top-level ad.Err check.
ad.Nested(func(nad *netlink.AttributeDecoder) error {
for nad.Next() {
t := parseDPIPETable(nad)
t.Bus = bus
t.Device = dev
ts = append(ts, t)
}
return nil
})
}

}
if err := ad.Err(); err != nil {
return nil, err
}
}
return ts, nil
}

// parseDPIPETable parses a single DPIPE table from a netlink attribute payload.
func parseDPIPETable(ad *netlink.AttributeDecoder) *DPIPETable {
var t DPIPETable
ad.Nested(func(nad *netlink.AttributeDecoder) error {
// Netlink entry for a single DPIPE table.
for nad.Next() {
switch nad.Type() {
case unix.DEVLINK_ATTR_DPIPE_TABLE_NAME:
t.Name = nad.String()
case unix.DEVLINK_ATTR_DPIPE_TABLE_SIZE:
t.Size = nad.Uint64()
case unix.DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED:
t.CountersEnabled = nad.Uint8() != 0
}
}
return nil
})
return &t
}
21 changes: 21 additions & 0 deletions client_linux_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,25 @@ func TestLinuxClientIntegration(t *testing.T) {
t.Logf("port: %+v", p)
}
})

t.Run("dpipe_tables", func(t *testing.T) {
var tables []*devlink.DPIPETable
devices, err := c.Devices()
if err != nil {
t.Fatalf("failed to get devices: %v", err)
}

for _, d := range devices {
tt, err := c.DPIPETables(d)
if err != nil {
t.Errorf("failed to get DPIPE table from device %v: %v", d, err)
}
tables = append(tables, tt...)
}

// Just print all DPIPE tables that are available.
for _, table := range tables {
t.Logf("dpipe_table: %+v", table)
}
})
}
18 changes: 18 additions & 0 deletions client_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import (
)

func TestLinuxClientEmptyResponse(t *testing.T) {
const (
bus = "pci"
device = "0000:01:00.0"
)
tests := []struct {
name string
fn func(t *testing.T, c *client)
Expand Down Expand Up @@ -46,6 +50,20 @@ func TestLinuxClientEmptyResponse(t *testing.T) {
}
},
},
{
name: "dpipe_tables",
fn: func(t *testing.T, c *client) {
dev := Device{bus, device}
tables, err := c.DPIPETables(&dev)
if err != nil {
t.Fatalf("failed to get DPIPE tables: %v", err)
}

if diff := cmp.Diff(0, len(tables)); diff != "" {
t.Fatalf("unexpected number of DPIPE tables (-want +got):\n%s", diff)
}
},
},
}

for _, tt := range tests {
Expand Down
5 changes: 5 additions & 0 deletions client_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ func (c *client) Devices() ([]*Device, error) {
func (c *client) Ports() ([]*Port, error) {
return nil, errUnimplemented
}

// PID implements osClient.
func (c *client) DPIPETables(dev *Device) ([]*DPIPETable, error) {
return nil, errUnimplemented
}