1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,95 @@
# P4 Runtime Input Plugin
P4 is a language for programming the data plane of network devices,
such as Programmable Switches or Programmable Network Interface Cards.
The P4Runtime API is a control plane specification to manage
the data plane elements of those devices dynamically by a P4 program.
The `p4runtime` plugin gathers metrics about `Counter` values
present in P4 Program loaded onto networking device.
Metrics are collected through gRPC connection with
[P4Runtime](https://github.com/p4lang/p4runtime) server.
P4Runtime Plugin uses `PkgInfo.Name` field.
If user wants to gather information about program name, please follow
[6.2.1.Annotating P4 code with PkgInfo] instruction and apply changes
to your P4 program.
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# P4Runtime telemetry input plugin
[[inputs.p4runtime]]
## Define the endpoint of P4Runtime gRPC server to collect metrics.
# endpoint = "127.0.0.1:9559"
## Set DeviceID required for Client Arbitration.
## https://p4.org/p4-spec/p4runtime/main/P4Runtime-Spec.html#sec-client-arbitration-and-controller-replication
# device_id = 1
## Filter counters by their names that should be observed.
## Example: counter_names_include=["ingressCounter", "egressCounter"]
# counter_names_include = []
## Optional TLS Config.
## Enable client-side TLS and define CA to authenticate the device.
# enable_tls = false
# tls_ca = "/etc/telegraf/ca.crt"
## Set minimal TLS version to accept by the client.
# tls_min_version = "TLS12"
## Use TLS but skip chain & host verification.
# insecure_skip_verify = true
## Define client-side TLS certificate & key to authenticate to the device.
# tls_cert = "/etc/telegraf/client.crt"
# tls_key = "/etc/telegraf/client.key"
```
## Metrics
P4Runtime gRPC server communicates using [p4runtime.proto] Protocol Buffer.
Static information about P4 program loaded into programmable switch
are collected by `GetForwardingPipelineConfigRequest` message.
Plugin gathers dynamic metrics with `Read` method.
`Readrequest` is defined with single `Entity` of type `CounterEntry`.
Since P4 Counter is array, plugin collects values of every cell of array
by [wildcard query].
Counters defined in P4 Program have unique ID and name.
Counters are arrays, thus `counter_index` informs
which cell value of array is described in metric.
Tags are constructed in given manner:
- `p4program_name`: P4 program name provided by user.
If user wants to gather information about program name, please follow
[6.2.1.Annotating P4 code with PkgInfo] instruction and apply changes
to your P4 program.
- `counter_name`: Name of given counter in P4 program.
- `counter_type`: Type of counter (BYTES, PACKETS, BOTH).
Fields are constructed in given manner:
- `bytes`: Number of bytes gathered in counter.
- `packets` Number of packets gathered in counter.
- `counter_index`: Index at which metrics are collected in P4 counter.
## Example Output
Expected output for p4runtime plugin instance
running on host named `p4runtime-host`:
```text
p4_runtime,counter_name=MyIngress.egressTunnelCounter,counter_type=BOTH,host=p4 bytes=408i,packets=4i,counter_index=200i 1675175030000000000
```
[6.2.1.Annotating P4 code with PkgInfo]: https://p4.org/p4-spec/p4runtime/main/P4Runtime-Spec.html#sec-annotating-p4-code-with-pkginfo
[p4runtime.proto]: https://github.com/p4lang/p4runtime/blob/main/proto/p4/v1/p4runtime.proto
[wildcard query]: https://github.com/p4lang/p4runtime/blob/main/proto/p4/v1/p4runtime.proto#L379

View file

@ -0,0 +1,232 @@
//go:generate ../../../tools/readme_config_includer/generator
package p4runtime
import (
"context"
"crypto/tls"
_ "embed"
"errors"
"fmt"
"io"
"slices"
"sync"
p4_config "github.com/p4lang/p4runtime/go/p4/config/v1"
p4 "github.com/p4lang/p4runtime/go/p4/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"github.com/influxdata/telegraf"
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
const (
defaultDeviceID = 1
defaultEndpoint = "127.0.0.1:9559"
)
type P4runtime struct {
Endpoint string `toml:"endpoint"`
DeviceID uint64 `toml:"device_id"`
CounterNamesInclude []string `toml:"counter_names_include"`
Log telegraf.Logger `toml:"-"`
EnableTLS bool `toml:"enable_tls"`
common_tls.ClientConfig
conn *grpc.ClientConn
client p4.P4RuntimeClient
wg sync.WaitGroup
}
func (*P4runtime) SampleConfig() string {
return sampleConfig
}
func (p *P4runtime) Init() error {
if p.Endpoint == "" {
p.Log.Debugf("Using default Endpoint: %v", defaultEndpoint)
p.Endpoint = defaultEndpoint
}
return p.newP4RuntimeClient()
}
func (p *P4runtime) Gather(acc telegraf.Accumulator) error {
p4Info, err := p.getP4Info()
if err != nil {
return err
}
if len(p4Info.Counters) == 0 {
p.Log.Warn("No counters available in P4 Program!")
return nil
}
filteredCounters := filterCounters(p4Info.Counters, p.CounterNamesInclude)
if len(filteredCounters) == 0 {
p.Log.Warn("No filtered counters available in P4 Program!")
return nil
}
for _, counter := range filteredCounters {
p.wg.Add(1)
go func(counter *p4_config.Counter) {
defer p.wg.Done()
entries, err := p.readAllEntries(counter.Preamble.Id)
if err != nil {
acc.AddError(
fmt.Errorf(
"reading counter entries with ID=%v failed with error: %w",
counter.Preamble.Id,
err,
),
)
return
}
for _, entry := range entries {
ce := entry.GetCounterEntry()
if ce == nil {
acc.AddError(fmt.Errorf("reading counter entry from entry %v failed", entry))
continue
}
if ce.Data.ByteCount == 0 && ce.Data.PacketCount == 0 {
continue
}
fields := map[string]interface{}{
"bytes": ce.Data.ByteCount,
"packets": ce.Data.PacketCount,
"counter_index": ce.Index.Index,
}
tags := map[string]string{
"p4program_name": p4Info.PkgInfo.Name,
"counter_name": counter.Preamble.Name,
"counter_type": counter.Spec.Unit.String(),
}
acc.AddFields("p4_runtime", fields, tags)
}
}(counter)
}
p.wg.Wait()
return nil
}
func (p *P4runtime) Stop() {
p.conn.Close()
p.wg.Wait()
}
func initConnection(endpoint string, tlscfg *tls.Config) (*grpc.ClientConn, error) {
var creds credentials.TransportCredentials
if tlscfg != nil {
creds = credentials.NewTLS(tlscfg)
} else {
creds = insecure.NewCredentials()
}
return grpc.NewClient(endpoint, grpc.WithTransportCredentials(creds))
}
func (p *P4runtime) getP4Info() (*p4_config.P4Info, error) {
req := &p4.GetForwardingPipelineConfigRequest{
DeviceId: p.DeviceID,
ResponseType: p4.GetForwardingPipelineConfigRequest_ALL,
}
resp, err := p.client.GetForwardingPipelineConfig(context.Background(), req)
if err != nil {
return nil, fmt.Errorf("error when retrieving forwarding pipeline config: %w", err)
}
config := resp.GetConfig()
if config == nil {
return nil, fmt.Errorf(
"error when retrieving config from forwarding pipeline - pipeline doesn't have a config yet: %w",
err,
)
}
p4info := config.GetP4Info()
if p4info == nil {
return nil, fmt.Errorf(
"error when retrieving P4Info from config - config doesn't have a P4Info: %w",
err,
)
}
return p4info, nil
}
func filterCounters(counters []*p4_config.Counter, counterNamesInclude []string) []*p4_config.Counter {
if len(counterNamesInclude) == 0 {
return counters
}
var filteredCounters []*p4_config.Counter
for _, counter := range counters {
if counter == nil {
continue
}
if slices.Contains(counterNamesInclude, counter.Preamble.Name) {
filteredCounters = append(filteredCounters, counter)
}
}
return filteredCounters
}
func (p *P4runtime) newP4RuntimeClient() error {
var tlscfg *tls.Config
var err error
if p.EnableTLS {
if tlscfg, err = p.ClientConfig.TLSConfig(); err != nil {
return err
}
}
conn, err := initConnection(p.Endpoint, tlscfg)
if err != nil {
return fmt.Errorf("cannot connect to the server: %w", err)
}
p.conn = conn
p.client = p4.NewP4RuntimeClient(conn)
return nil
}
func (p *P4runtime) readAllEntries(counterID uint32) ([]*p4.Entity, error) {
readRequest := &p4.ReadRequest{
DeviceId: p.DeviceID,
Entities: []*p4.Entity{{
Entity: &p4.Entity_CounterEntry{
CounterEntry: &p4.CounterEntry{
CounterId: counterID}}}}}
stream, err := p.client.Read(context.Background(), readRequest)
if err != nil {
return nil, err
}
rep, err := stream.Recv()
if err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
return rep.Entities, nil
}
func init() {
inputs.Add("p4runtime", func() telegraf.Input {
p4runtime := &P4runtime{
DeviceID: defaultDeviceID,
}
return p4runtime
})
}

View file

@ -0,0 +1,122 @@
package p4runtime
import (
"context"
p4 "github.com/p4lang/p4runtime/go/p4/v1"
"google.golang.org/grpc"
)
type fakeP4RuntimeClient struct {
writeFn func(
ctx context.Context,
in *p4.WriteRequest,
opts ...grpc.CallOption,
) (*p4.WriteResponse, error)
readFn func(
in *p4.ReadRequest,
) (p4.P4Runtime_ReadClient, error)
setForwardingPipelineConfigFn func(
ctx context.Context,
in *p4.SetForwardingPipelineConfigRequest,
opts ...grpc.CallOption,
) (*p4.SetForwardingPipelineConfigResponse, error)
getForwardingPipelineConfigFn func() (*p4.GetForwardingPipelineConfigResponse, error)
streamChannelFn func(
ctx context.Context,
opts ...grpc.CallOption,
) (p4.P4Runtime_StreamChannelClient, error)
capabilitiesFn func(
ctx context.Context,
in *p4.CapabilitiesRequest,
opts ...grpc.CallOption,
) (*p4.CapabilitiesResponse, error)
}
// fakeP4RuntimeClient implements the v1.P4RuntimeClient interface
var _ p4.P4RuntimeClient = &fakeP4RuntimeClient{}
func (c *fakeP4RuntimeClient) Write(
ctx context.Context,
in *p4.WriteRequest,
opts ...grpc.CallOption,
) (*p4.WriteResponse, error) {
if c.writeFn == nil {
panic("No mock defined for Write RPC")
}
return c.writeFn(ctx, in, opts...)
}
func (c *fakeP4RuntimeClient) Read(
_ context.Context,
in *p4.ReadRequest,
_ ...grpc.CallOption,
) (p4.P4Runtime_ReadClient, error) {
if c.readFn == nil {
panic("No mock defined for Read RPC")
}
return c.readFn(in)
}
func (c *fakeP4RuntimeClient) SetForwardingPipelineConfig(
ctx context.Context,
in *p4.SetForwardingPipelineConfigRequest,
opts ...grpc.CallOption,
) (*p4.SetForwardingPipelineConfigResponse, error) {
if c.setForwardingPipelineConfigFn == nil {
panic("No mock defined for SetForwardingPipelineConfig RPC")
}
return c.setForwardingPipelineConfigFn(ctx, in, opts...)
}
func (c *fakeP4RuntimeClient) GetForwardingPipelineConfig(
context.Context,
*p4.GetForwardingPipelineConfigRequest,
...grpc.CallOption,
) (*p4.GetForwardingPipelineConfigResponse, error) {
if c.getForwardingPipelineConfigFn == nil {
panic("No mock defined for GetForwardingPipelineConfig RPC")
}
return c.getForwardingPipelineConfigFn()
}
func (c *fakeP4RuntimeClient) StreamChannel(
ctx context.Context,
opts ...grpc.CallOption,
) (p4.P4Runtime_StreamChannelClient, error) {
if c.streamChannelFn == nil {
panic("No mock defined for StreamChannel")
}
return c.streamChannelFn(ctx, opts...)
}
func (c *fakeP4RuntimeClient) Capabilities(
ctx context.Context,
in *p4.CapabilitiesRequest,
opts ...grpc.CallOption,
) (*p4.CapabilitiesResponse, error) {
if c.capabilitiesFn == nil {
panic("No mock defined for Capabilities RPC")
}
return c.capabilitiesFn(ctx, in, opts...)
}
type fakeP4RuntimeReadClient struct {
grpc.ClientStream
recvFn func() (*p4.ReadResponse, error)
}
// fakeP4RuntimeReadClient implements the v1.P4Runtime_ReadClient interface
var _ p4.P4Runtime_ReadClient = &fakeP4RuntimeReadClient{}
func (c *fakeP4RuntimeReadClient) Recv() (*p4.ReadResponse, error) {
if c.recvFn == nil {
panic("No mock provided for Recv function")
}
return c.recvFn()
}

View file

@ -0,0 +1,614 @@
package p4runtime
import (
"errors"
"fmt"
"net"
"testing"
"time"
p4_config "github.com/p4lang/p4runtime/go/p4/config/v1"
p4 "github.com/p4lang/p4runtime/go/p4/v1"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
)
// CounterSpec available here https://github.com/p4lang/p4runtime/blob/main/proto/p4/config/v1/p4info.proto#L289
func createCounter(
name string,
id uint32,
unit p4_config.CounterSpec_Unit,
) *p4_config.Counter {
return &p4_config.Counter{
Preamble: &p4_config.Preamble{Name: name, Id: id},
Spec: &p4_config.CounterSpec{Unit: unit},
}
}
func createEntityCounterEntry(
counterID uint32,
index int64,
data *p4.CounterData,
) *p4.Entity_CounterEntry {
return &p4.Entity_CounterEntry{
CounterEntry: &p4.CounterEntry{
CounterId: counterID,
Index: &p4.Index{Index: index},
Data: data,
},
}
}
func newTestP4RuntimeClient(
p4RuntimeClient *fakeP4RuntimeClient,
addr string,
t *testing.T,
) *P4runtime {
conn, err := grpc.NewClient(
addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
require.NoError(t, err)
return &P4runtime{
Endpoint: addr,
DeviceID: uint64(1),
Log: testutil.Logger{},
conn: conn,
client: p4RuntimeClient,
}
}
func TestInitDefault(t *testing.T) {
plugin := &P4runtime{Log: testutil.Logger{}}
require.NoError(t, plugin.Init())
require.Equal(t, "127.0.0.1:9559", plugin.Endpoint)
require.Equal(t, uint64(0), plugin.DeviceID)
require.Empty(t, plugin.CounterNamesInclude)
require.False(t, plugin.EnableTLS)
}
func TestErrorGetP4Info(t *testing.T) {
responses := []struct {
getForwardingPipelineConfigResponse *p4.GetForwardingPipelineConfigResponse
getForwardingPipelineConfigResponseError error
}{
{
getForwardingPipelineConfigResponse: nil,
getForwardingPipelineConfigResponseError: errors.New("error when retrieving forwarding pipeline config"),
}, {
getForwardingPipelineConfigResponse: &p4.GetForwardingPipelineConfigResponse{
Config: nil,
},
getForwardingPipelineConfigResponseError: nil,
}, {
getForwardingPipelineConfigResponse: &p4.GetForwardingPipelineConfigResponse{
Config: &p4.ForwardingPipelineConfig{P4Info: nil},
},
getForwardingPipelineConfigResponseError: nil,
},
}
for _, response := range responses {
p4RtClient := &fakeP4RuntimeClient{
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return response.getForwardingPipelineConfigResponse, response.getForwardingPipelineConfigResponseError
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
var acc testutil.Accumulator
require.Error(t, plugin.Gather(&acc))
}
}
func TestOneCounterRead(t *testing.T) {
tests := []struct {
forwardingPipelineConfig *p4.ForwardingPipelineConfig
EntityCounterEntry *p4.Entity_CounterEntry
expected []telegraf.Metric
}{
{
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: []*p4_config.Counter{
createCounter("foo", 1111, p4_config.CounterSpec_BOTH),
},
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
},
EntityCounterEntry: createEntityCounterEntry(
1111,
5,
&p4.CounterData{ByteCount: 5, PacketCount: 1},
),
expected: []telegraf.Metric{testutil.MustMetric(
"p4_runtime",
map[string]string{
"p4program_name": "P4Program",
"counter_name": "foo",
"counter_type": "BOTH",
},
map[string]interface{}{
"bytes": int64(5),
"packets": int64(1),
"counter_index": 5},
time.Unix(0, 0)),
},
}, {
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: []*p4_config.Counter{
createCounter(
"foo",
2222,
p4_config.CounterSpec_BYTES,
),
},
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
},
EntityCounterEntry: createEntityCounterEntry(
2222,
5,
&p4.CounterData{ByteCount: 5},
),
expected: []telegraf.Metric{testutil.MustMetric(
"p4_runtime",
map[string]string{
"p4program_name": "P4Program",
"counter_name": "foo",
"counter_type": "BYTES",
},
map[string]interface{}{
"bytes": int64(5),
"packets": int64(0),
"counter_index": 5},
time.Unix(0, 0)),
},
}, {
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: []*p4_config.Counter{
createCounter(
"foo",
3333,
p4_config.CounterSpec_PACKETS,
),
},
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
},
EntityCounterEntry: createEntityCounterEntry(
3333,
5,
&p4.CounterData{PacketCount: 1},
),
expected: []telegraf.Metric{testutil.MustMetric(
"p4_runtime",
map[string]string{
"p4program_name": "P4Program",
"counter_name": "foo",
"counter_type": "PACKETS",
},
map[string]interface{}{
"bytes": int64(0),
"packets": int64(1),
"counter_index": 5},
time.Unix(0, 0)),
},
}, {
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: []*p4_config.Counter{
createCounter("foo", 4444, p4_config.CounterSpec_BOTH),
},
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
},
EntityCounterEntry: createEntityCounterEntry(
4444,
5,
&p4.CounterData{},
),
expected: nil,
},
}
for _, tt := range tests {
p4RtReadClient := &fakeP4RuntimeReadClient{
recvFn: func() (*p4.ReadResponse, error) {
return &p4.ReadResponse{
Entities: []*p4.Entity{{Entity: tt.EntityCounterEntry}},
}, nil
},
}
p4RtClient := &fakeP4RuntimeClient{
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
return p4RtReadClient, nil
},
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return &p4.GetForwardingPipelineConfigResponse{
Config: tt.forwardingPipelineConfig,
}, nil
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
testutil.RequireMetricsEqual(
t,
tt.expected,
acc.GetTelegrafMetrics(),
testutil.IgnoreTime(),
)
}
}
func TestMultipleEntitiesSingleCounterRead(t *testing.T) {
totalNumOfEntriesArr := [3]int{2, 10, 100}
for _, totalNumOfEntries := range totalNumOfEntriesArr {
var expected []telegraf.Metric
fmt.Println(
"Running TestMultipleEntitiesSingleCounterRead with ",
totalNumOfEntries,
"totalNumOfCounters",
)
entities := make([]*p4.Entity, 0, totalNumOfEntries)
p4InfoCounters := make([]*p4_config.Counter, 0, totalNumOfEntries)
p4InfoCounters = append(
p4InfoCounters,
createCounter("foo", 0, p4_config.CounterSpec_BOTH),
)
for i := 0; i < totalNumOfEntries; i++ {
counterEntry := &p4.Entity{
Entity: createEntityCounterEntry(
0,
int64(i),
&p4.CounterData{
ByteCount: int64(10),
PacketCount: int64(10),
},
),
}
entities = append(entities, counterEntry)
expected = append(expected, testutil.MustMetric(
"p4_runtime",
map[string]string{
"p4program_name": "P4Program",
"counter_name": "foo",
"counter_type": "BOTH",
},
map[string]interface{}{
"bytes": int64(10),
"packets": int64(10),
"counter_index": i,
},
time.Unix(0, 0),
))
}
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: p4InfoCounters,
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
}
p4RtReadClient := &fakeP4RuntimeReadClient{
recvFn: func() (*p4.ReadResponse, error) {
return &p4.ReadResponse{Entities: entities}, nil
},
}
p4RtClient := &fakeP4RuntimeClient{
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
return p4RtReadClient, nil
},
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return &p4.GetForwardingPipelineConfigResponse{
Config: forwardingPipelineConfig,
}, nil
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
acc.Wait(totalNumOfEntries)
testutil.RequireMetricsEqual(
t,
expected,
acc.GetTelegrafMetrics(),
testutil.IgnoreTime(),
)
}
}
func TestSingleEntitiesMultipleCounterRead(t *testing.T) {
totalNumOfCountersArr := [3]int{2, 10, 100}
for _, totalNumOfCounters := range totalNumOfCountersArr {
var expected []telegraf.Metric
fmt.Println(
"Running TestSingleEntitiesMultipleCounterRead with ",
totalNumOfCounters,
"totalNumOfCounters",
)
p4InfoCounters := make([]*p4_config.Counter, 0, totalNumOfCounters)
for i := 1; i <= totalNumOfCounters; i++ {
counterName := fmt.Sprintf("foo%v", i)
p4InfoCounters = append(
p4InfoCounters,
createCounter(
counterName,
uint32(i),
p4_config.CounterSpec_BOTH,
),
)
expected = append(expected, testutil.MustMetric(
"p4_runtime",
map[string]string{
"p4program_name": "P4Program",
"counter_name": counterName,
"counter_type": "BOTH",
},
map[string]interface{}{
"bytes": int64(10),
"packets": int64(10),
"counter_index": 1,
},
time.Unix(0, 0),
))
}
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: p4InfoCounters,
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
}
p4RtClient := &fakeP4RuntimeClient{
readFn: func(in *p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
counterID := in.Entities[0].GetCounterEntry().CounterId
return &fakeP4RuntimeReadClient{
recvFn: func() (*p4.ReadResponse, error) {
return &p4.ReadResponse{
Entities: []*p4.Entity{{
Entity: createEntityCounterEntry(
counterID,
1,
&p4.CounterData{
ByteCount: 10,
PacketCount: 10,
},
),
}},
}, nil
},
}, nil
},
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return &p4.GetForwardingPipelineConfigResponse{
Config: forwardingPipelineConfig,
}, nil
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
acc.Wait(totalNumOfCounters)
testutil.RequireMetricsEqual(
t,
expected,
acc.GetTelegrafMetrics(),
testutil.SortMetrics(),
testutil.IgnoreTime(),
)
}
}
func TestNoCountersAvailable(t *testing.T) {
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{Counters: make([]*p4_config.Counter, 0)},
}
p4RtClient := &fakeP4RuntimeClient{
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return &p4.GetForwardingPipelineConfigResponse{
Config: forwardingPipelineConfig,
}, nil
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
}
func TestFilterCounters(t *testing.T) {
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: []*p4_config.Counter{
createCounter("foo", 1, p4_config.CounterSpec_BOTH),
},
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
}
p4RtClient := &fakeP4RuntimeClient{
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return &p4.GetForwardingPipelineConfigResponse{
Config: forwardingPipelineConfig,
}, nil
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
plugin.CounterNamesInclude = []string{"oof"}
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
testutil.RequireMetricsEqual(
t,
nil,
acc.GetTelegrafMetrics(),
testutil.IgnoreTime(),
)
}
func TestFailReadCounterEntryFromEntry(t *testing.T) {
p4RtReadClient := &fakeP4RuntimeReadClient{
recvFn: func() (*p4.ReadResponse, error) {
return &p4.ReadResponse{
Entities: []*p4.Entity{{
Entity: &p4.Entity_TableEntry{
TableEntry: &p4.TableEntry{},
}}}}, nil
},
}
p4RtClient := &fakeP4RuntimeClient{
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
return p4RtReadClient, nil
},
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return &p4.GetForwardingPipelineConfigResponse{
Config: &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: []*p4_config.Counter{
createCounter(
"foo",
1111,
p4_config.CounterSpec_BOTH,
),
},
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
},
}, nil
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
require.Equal(
t,
errors.New("reading counter entry from entry table_entry:{} failed"),
acc.Errors[0],
)
testutil.RequireMetricsEqual(
t,
nil,
acc.GetTelegrafMetrics(),
testutil.IgnoreTime(),
)
}
func TestFailReadAllEntries(t *testing.T) {
p4RtClient := &fakeP4RuntimeClient{
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
return nil, errors.New("connection error")
},
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
return &p4.GetForwardingPipelineConfigResponse{
Config: &p4.ForwardingPipelineConfig{
P4Info: &p4_config.P4Info{
Counters: []*p4_config.Counter{
createCounter(
"foo",
1111,
p4_config.CounterSpec_BOTH,
),
},
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
},
},
}, nil
},
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
require.Equal(
t,
acc.Errors[0],
fmt.Errorf("reading counter entries with ID=1111 failed with error: %w", errors.New("connection error")),
)
testutil.RequireMetricsEqual(
t,
nil,
acc.GetTelegrafMetrics(),
testutil.IgnoreTime(),
)
}
func TestFilterCounterNamesInclude(t *testing.T) {
counters := []*p4_config.Counter{
createCounter("foo", 1, p4_config.CounterSpec_BOTH),
createCounter("bar", 2, p4_config.CounterSpec_BOTH),
nil,
createCounter("", 3, p4_config.CounterSpec_BOTH),
}
counterNamesInclude := []string{"bar"}
filteredCounters := filterCounters(counters, counterNamesInclude)
require.Equal(
t,
[]*p4_config.Counter{
createCounter("bar", 2, p4_config.CounterSpec_BOTH),
}, filteredCounters,
)
}

View file

@ -0,0 +1,23 @@
# P4Runtime telemetry input plugin
[[inputs.p4runtime]]
## Define the endpoint of P4Runtime gRPC server to collect metrics.
# endpoint = "127.0.0.1:9559"
## Set DeviceID required for Client Arbitration.
## https://p4.org/p4-spec/p4runtime/main/P4Runtime-Spec.html#sec-client-arbitration-and-controller-replication
# device_id = 1
## Filter counters by their names that should be observed.
## Example: counter_names_include=["ingressCounter", "egressCounter"]
# counter_names_include = []
## Optional TLS Config.
## Enable client-side TLS and define CA to authenticate the device.
# enable_tls = false
# tls_ca = "/etc/telegraf/ca.crt"
## Set minimal TLS version to accept by the client.
# tls_min_version = "TLS12"
## Use TLS but skip chain & host verification.
# insecure_skip_verify = true
## Define client-side TLS certificate & key to authenticate to the device.
# tls_cert = "/etc/telegraf/client.crt"
# tls_key = "/etc/telegraf/client.key"