292 lines
9 KiB
Go
292 lines
9 KiB
Go
//go:generate ../../../tools/readme_config_includer/generator
|
|
//go:build linux
|
|
|
|
package dpdk
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/config"
|
|
"github.com/influxdata/telegraf/filter"
|
|
"github.com/influxdata/telegraf/internal/choice"
|
|
"github.com/influxdata/telegraf/internal/globpath"
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
)
|
|
|
|
//go:embed sample.conf
|
|
var sampleConfig string
|
|
|
|
const (
|
|
defaultPathToSocket = "/var/run/dpdk/rte/dpdk_telemetry.v2"
|
|
defaultAccessTimeout = config.Duration(200 * time.Millisecond)
|
|
maxCommandLength = 56
|
|
maxCommandLengthWithParams = 1024
|
|
pluginName = "dpdk"
|
|
ethdevListCommand = "/ethdev/list"
|
|
rawdevListCommand = "/rawdev/list"
|
|
|
|
dpdkMetadataFieldPidName = "pid"
|
|
dpdkMetadataFieldVersionName = "version"
|
|
|
|
dpdkPluginOptionInMemory = "in_memory"
|
|
|
|
unreachableSocketBehaviorIgnore = "ignore"
|
|
unreachableSocketBehaviorError = "error"
|
|
)
|
|
|
|
type Dpdk struct {
|
|
SocketPath string `toml:"socket_path"`
|
|
AccessTimeout config.Duration `toml:"socket_access_timeout"`
|
|
DeviceTypes []string `toml:"device_types"`
|
|
EthdevConfig ethdevConfig `toml:"ethdev"`
|
|
AdditionalCommands []string `toml:"additional_commands"`
|
|
MetadataFields []string `toml:"metadata_fields"`
|
|
PluginOptions []string `toml:"plugin_options"`
|
|
UnreachableSocketBehavior string `toml:"unreachable_socket_behavior"`
|
|
Log telegraf.Logger `toml:"-"`
|
|
|
|
connectors []*dpdkConnector
|
|
rawdevCommands []string
|
|
ethdevCommands []string
|
|
ethdevExcludedCommandsFilter filter.Filter
|
|
socketGlobPath *globpath.GlobPath
|
|
}
|
|
|
|
type ethdevConfig struct {
|
|
EthdevExcludeCommands []string `toml:"exclude_commands"`
|
|
}
|
|
|
|
func (*Dpdk) SampleConfig() string {
|
|
return sampleConfig
|
|
}
|
|
|
|
func (dpdk *Dpdk) Init() error {
|
|
dpdk.setupDefaultValues()
|
|
|
|
err := dpdk.validateAdditionalCommands()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if dpdk.AccessTimeout < 0 {
|
|
return errors.New("socket_access_timeout should be positive number or equal to 0 (to disable timeouts)")
|
|
}
|
|
|
|
if len(dpdk.AdditionalCommands) == 0 && len(dpdk.DeviceTypes) == 0 {
|
|
return errors.New("plugin was configured with nothing to read")
|
|
}
|
|
|
|
dpdk.ethdevExcludedCommandsFilter, err = filter.Compile(dpdk.EthdevConfig.EthdevExcludeCommands)
|
|
if err != nil {
|
|
return fmt.Errorf("error occurred during filter preparation for ethdev excluded commands: %w", err)
|
|
}
|
|
|
|
if err = choice.Check(dpdk.UnreachableSocketBehavior, []string{unreachableSocketBehaviorError, unreachableSocketBehaviorIgnore}); err != nil {
|
|
return fmt.Errorf("unreachable_socket_behavior: %w", err)
|
|
}
|
|
|
|
glob, err := globpath.Compile(dpdk.SocketPath + "*")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dpdk.socketGlobPath = glob
|
|
return nil
|
|
}
|
|
|
|
func (dpdk *Dpdk) Start(telegraf.Accumulator) error {
|
|
return dpdk.maintainConnections()
|
|
}
|
|
|
|
// Gather function gathers all unique commands and processes each command sequentially
|
|
// Parallel processing could be achieved by running several instances of this plugin with different settings
|
|
func (dpdk *Dpdk) Gather(acc telegraf.Accumulator) error {
|
|
if err := dpdk.Start(acc); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, dpdkConn := range dpdk.connectors {
|
|
commands := dpdk.gatherCommands(acc, dpdkConn)
|
|
for _, command := range commands {
|
|
dpdkConn.processCommand(acc, dpdk.Log, command, dpdk.MetadataFields)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (dpdk *Dpdk) Stop() {
|
|
for _, connector := range dpdk.connectors {
|
|
if err := connector.tryClose(); err != nil {
|
|
dpdk.Log.Warnf("Couldn't close connection for %q: %v", connector.pathToSocket, err)
|
|
}
|
|
}
|
|
dpdk.connectors = nil
|
|
}
|
|
|
|
// Setup default values for dpdk
|
|
func (dpdk *Dpdk) setupDefaultValues() {
|
|
if dpdk.SocketPath == "" {
|
|
dpdk.SocketPath = defaultPathToSocket
|
|
}
|
|
|
|
if dpdk.DeviceTypes == nil {
|
|
dpdk.DeviceTypes = []string{"ethdev"}
|
|
}
|
|
|
|
if dpdk.MetadataFields == nil {
|
|
dpdk.MetadataFields = []string{dpdkMetadataFieldPidName, dpdkMetadataFieldVersionName}
|
|
}
|
|
|
|
if dpdk.PluginOptions == nil {
|
|
dpdk.PluginOptions = []string{dpdkPluginOptionInMemory}
|
|
}
|
|
|
|
if len(dpdk.UnreachableSocketBehavior) == 0 {
|
|
dpdk.UnreachableSocketBehavior = unreachableSocketBehaviorError
|
|
}
|
|
|
|
dpdk.rawdevCommands = []string{"/rawdev/xstats"}
|
|
dpdk.ethdevCommands = []string{"/ethdev/stats", "/ethdev/xstats", "/ethdev/info", ethdevLinkStatusCommand}
|
|
}
|
|
|
|
func (dpdk *Dpdk) getDpdkInMemorySocketPaths() []string {
|
|
filePaths := dpdk.socketGlobPath.Match()
|
|
|
|
var results []string
|
|
for _, filePath := range filePaths {
|
|
fileInfo, err := os.Stat(filePath)
|
|
if err != nil || fileInfo.IsDir() || !strings.Contains(filePath, dpdkSocketTemplateName) {
|
|
continue
|
|
}
|
|
|
|
if isInMemorySocketPath(filePath, dpdk.SocketPath) {
|
|
results = append(results, filePath)
|
|
}
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// Checks that user-supplied commands are unique and match DPDK commands format
|
|
func (dpdk *Dpdk) validateAdditionalCommands() error {
|
|
dpdk.AdditionalCommands = uniqueValues(dpdk.AdditionalCommands)
|
|
|
|
for _, cmd := range dpdk.AdditionalCommands {
|
|
if len(cmd) == 0 {
|
|
return errors.New("got empty command")
|
|
}
|
|
|
|
if cmd[0] != '/' {
|
|
return fmt.Errorf("%q command should start with slash", cmd)
|
|
}
|
|
|
|
if commandWithoutParams := stripParams(cmd); len(commandWithoutParams) >= maxCommandLength {
|
|
return fmt.Errorf("%q command is too long. It shall be less than %v characters", commandWithoutParams, maxCommandLength)
|
|
}
|
|
|
|
if len(cmd) >= maxCommandLengthWithParams {
|
|
return fmt.Errorf("command with parameters %q shall be less than %v characters", cmd, maxCommandLengthWithParams)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Establishes connections do DPDK telemetry sockets
|
|
func (dpdk *Dpdk) maintainConnections() error {
|
|
candidates := []string{dpdk.SocketPath}
|
|
if choice.Contains(dpdkPluginOptionInMemory, dpdk.PluginOptions) {
|
|
candidates = dpdk.getDpdkInMemorySocketPaths()
|
|
}
|
|
|
|
// Find sockets in the connected-sockets list that are not among
|
|
// the candidates anymore and thus need to be removed.
|
|
for i := 0; i < len(dpdk.connectors); i++ {
|
|
connector := dpdk.connectors[i]
|
|
if !choice.Contains(connector.pathToSocket, candidates) {
|
|
dpdk.Log.Debugf("Close unused connection: %s", connector.pathToSocket)
|
|
if closeErr := connector.tryClose(); closeErr != nil {
|
|
dpdk.Log.Warnf("Failed to close unused connection: %v", closeErr)
|
|
}
|
|
dpdk.connectors = append(dpdk.connectors[:i], dpdk.connectors[i+1:]...)
|
|
i--
|
|
}
|
|
}
|
|
|
|
// Find candidates that are not yet in the connected-sockets list as we
|
|
// need to connect to those.
|
|
for _, candidate := range candidates {
|
|
var found bool
|
|
for _, connector := range dpdk.connectors {
|
|
if candidate == connector.pathToSocket {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
connector := newDpdkConnector(candidate, dpdk.AccessTimeout)
|
|
connectionInitMessage, err := connector.connect()
|
|
if err != nil {
|
|
if dpdk.UnreachableSocketBehavior == unreachableSocketBehaviorError {
|
|
return fmt.Errorf("couldn't connect to socket %s: %w", candidate, err)
|
|
}
|
|
dpdk.Log.Warnf("Couldn't connect to socket %s: %v", candidate, err)
|
|
continue
|
|
}
|
|
|
|
dpdk.Log.Debugf("Successfully connected to the socket: %s. Version: %v running as process with PID %v with len %v",
|
|
candidate, connectionInitMessage.Version, connectionInitMessage.Pid, connectionInitMessage.MaxOutputLen)
|
|
dpdk.connectors = append(dpdk.connectors, connector)
|
|
}
|
|
}
|
|
|
|
if len(dpdk.connectors) == 0 {
|
|
errMsg := "no active sockets connections present"
|
|
if dpdk.UnreachableSocketBehavior == unreachableSocketBehaviorError {
|
|
return errors.New(errMsg)
|
|
}
|
|
dpdk.Log.Warnf("Unreachable socket issue occurred: %v", errMsg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Gathers all unique commands
|
|
func (dpdk *Dpdk) gatherCommands(acc telegraf.Accumulator, dpdkConnector *dpdkConnector) []string {
|
|
var commands []string
|
|
if choice.Contains("ethdev", dpdk.DeviceTypes) {
|
|
ethdevCommands := removeSubset(dpdk.ethdevCommands, dpdk.ethdevExcludedCommandsFilter)
|
|
ethdevCommands, err := dpdkConnector.appendCommandsWithParamsFromList(ethdevListCommand, ethdevCommands)
|
|
if err != nil {
|
|
acc.AddError(fmt.Errorf("error occurred during fetching of %q params: %w", ethdevListCommand, err))
|
|
}
|
|
commands = append(commands, ethdevCommands...)
|
|
}
|
|
|
|
if choice.Contains("rawdev", dpdk.DeviceTypes) {
|
|
rawdevCommands, err := dpdkConnector.appendCommandsWithParamsFromList(rawdevListCommand, dpdk.rawdevCommands)
|
|
if err != nil {
|
|
acc.AddError(fmt.Errorf("error occurred during fetching of %q params: %w", rawdevListCommand, err))
|
|
}
|
|
commands = append(commands, rawdevCommands...)
|
|
}
|
|
|
|
commands = append(commands, dpdk.AdditionalCommands...)
|
|
return uniqueValues(commands)
|
|
}
|
|
|
|
func init() {
|
|
inputs.Add(pluginName, func() telegraf.Input {
|
|
dpdk := &Dpdk{
|
|
// Setting it here (rather than in `Init()`) to distinguish between "zero" value,
|
|
// default value and don't having value in config at all.
|
|
AccessTimeout: defaultAccessTimeout,
|
|
}
|
|
return dpdk
|
|
})
|
|
}
|