diff --git a/client.go b/client.go index c2a570c..1da67f8 100644 --- a/client.go +++ b/client.go @@ -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. @@ -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 +} diff --git a/client_linux.go b/client_linux.go index eee4aa3..ee10bd0 100644 --- a/client_linux.go +++ b/client_linux.go @@ -3,6 +3,8 @@ package devlink import ( + "fmt" + "github.com/mdlayher/genetlink" "github.com/mdlayher/netlink" "golang.org/x/sys/unix" @@ -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 } @@ -55,7 +57,7 @@ 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 } @@ -63,15 +65,41 @@ func (c *client) Ports() ([]*Port, error) { 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, @@ -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 +} diff --git a/client_linux_integration_test.go b/client_linux_integration_test.go index 5a5bb31..796743e 100644 --- a/client_linux_integration_test.go +++ b/client_linux_integration_test.go @@ -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) + } + }) } diff --git a/client_linux_test.go b/client_linux_test.go index 8f6e8a8..f42f000 100644 --- a/client_linux_test.go +++ b/client_linux_test.go @@ -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) @@ -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 { diff --git a/client_others.go b/client_others.go index 3750961..9d2d238 100644 --- a/client_others.go +++ b/client_others.go @@ -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 +}