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,305 @@
# Data Plane Development Kit (DPDK) Input Plugin
This plugin collects metrics exposed by applications built with the
[Data Plane Development Kit][dpdk] which is an extensive set of open
source libraries designed for accelerating packet processing workloads.
> [!NOTE]
> Since DPDK will most likely run with root privileges, the telemetry socket
> exposed by DPDK will also require root access. Please adjust permissions
> accordingly!
Refer to the [Telemetry User Guide][user_guide] for details and examples on how
to use DPDK in your application.
> [!IMPORTANT]
> This plugin uses the `v2` interface to read telemetry > data from applications
> and required DPDK version `v20.05` or higher. Some metrics might require later
> versions.
> The recommended version, especially in conjunction with the `in_memory`
> option is `DPDK 21.11.2` or higher.
⭐ Telegraf v1.19.0
🏷️ applications, network
💻 linux
[dpdk]: https://www.dpdk.org
[user_guide]: https://doc.dpdk.org/guides/howto/telemetry.html
## 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
# Reads metrics from DPDK applications using v2 telemetry interface.
# This plugin ONLY supports Linux
[[inputs.dpdk]]
## Path to DPDK telemetry socket. This shall point to v2 version of DPDK
## telemetry interface.
# socket_path = "/var/run/dpdk/rte/dpdk_telemetry.v2"
## Duration that defines how long the connected socket client will wait for
## a response before terminating connection.
## This includes both writing to and reading from socket. Since it's local
## socket access to a fast packet processing application, the timeout should
## be sufficient for most users.
## Setting the value to 0 disables the timeout (not recommended)
# socket_access_timeout = "200ms"
## Enables telemetry data collection for selected device types.
## Adding "ethdev" enables collection of telemetry from DPDK NICs (stats, xstats, link_status, info).
## Adding "rawdev" enables collection of telemetry from DPDK Raw Devices (xstats).
# device_types = ["ethdev"]
## List of custom, application-specific telemetry commands to query
## The list of available commands depend on the application deployed.
## Applications can register their own commands via telemetry library API
## https://doc.dpdk.org/guides/prog_guide/telemetry_lib.html#registering-commands
## For L3 Forwarding with Power Management Sample Application this could be:
## additional_commands = ["/l3fwd-power/stats"]
# additional_commands = []
## List of plugin options.
## Supported options:
## - "in_memory" option enables reading for multiple sockets when a dpdk application is running with --in-memory option.
## When option is enabled plugin will try to find additional socket paths related to provided socket_path.
## Details: https://doc.dpdk.org/guides/howto/telemetry.html#connecting-to-different-dpdk-processes
# plugin_options = ["in_memory"]
## Specifies plugin behavior regarding unreachable socket (which might not have been initialized yet).
## Available choices:
## - error: Telegraf will return an error during the startup and gather phases if socket is unreachable
## - ignore: Telegraf will ignore error regarding unreachable socket on both startup and gather
# unreachable_socket_behavior = "error"
## List of metadata fields which will be added to every metric produced by the plugin.
## Supported options:
## - "pid" - exposes PID of DPDK process. Example: pid=2179660i
## - "version" - exposes version of DPDK. Example: version="DPDK 21.11.2"
# metadata_fields = ["pid", "version"]
## Allows turning off collecting data for individual "ethdev" commands.
## Remove "/ethdev/link_status" from list to gather link status metrics.
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/link_status"]
## When running multiple instances of the plugin it's recommended to add a
## unique tag to each instance to identify metrics exposed by an instance
## of DPDK application. This is useful when multiple DPDK apps run on a
## single host.
## [inputs.dpdk.tags]
## dpdk_instance = "my-fwd-app"
```
This plugin offers multiple configuration options, please review examples below
for additional usage information.
### Example: Minimal Configuration for NIC metrics
This configuration allows getting metrics for all devices reported via
`/ethdev/list` command:
* `/ethdev/info` - device information: name, MAC address, buffers size, etc
(since `DPDK 21.11`)
* `/ethdev/stats` - basic device statistics (since `DPDK 20.11`)
* `/ethdev/xstats` - extended device statistics
* `/ethdev/link_status` - up/down link status
```toml
[[inputs.dpdk]]
device_types = ["ethdev"]
```
Since this configuration will query `/ethdev/link_status` it's recommended to
increase timeout to `socket_access_timeout = "10s"`.
The [plugin collecting interval](../../../docs/CONFIGURATION.md#input-plugins)
should be adjusted accordingly (e.g. `interval = "30s"`).
### Example: Excluding NIC link status from being collected
Checking link status depending on underlying implementation may take more time
to complete. This configuration can be used to exclude this telemetry command
to allow faster response for metrics.
```toml
[[inputs.dpdk]]
device_types = ["ethdev"]
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/link_status"]
```
A separate plugin instance with higher timeout settings can be used to get
`/ethdev/link_status` independently. Consult [Independent NIC link status
configuration](#example-independent-nic-link-status-configuration) and [Getting
metrics from multiple DPDK instances running on same
host](#example-getting-metrics-from-multiple-dpdk-instances-on-same-host)
examples for further details.
### Example: Independent NIC link status configuration
This configuration allows getting `/ethdev/link_status` using separate
configuration, with higher timeout.
```toml
[[inputs.dpdk]]
interval = "30s"
socket_access_timeout = "10s"
device_types = ["ethdev"]
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/info", "/ethdev/stats", "/ethdev/xstats"]
```
### Example: Getting application-specific metrics
This configuration allows reading custom metrics exposed by
applications. Example telemetry command obtained from
[L3 Forwarding with Power Management Sample Application][sample].
```toml
[[inputs.dpdk]]
device_types = ["ethdev"]
additional_commands = ["/l3fwd-power/stats"]
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/link_status"]
```
Command entries specified in `additional_commands` should match DPDK command
format:
* Command entry format: either `command` or `command,params` for commands that
expect parameters, where comma (`,`) separates command from params.
* Command entry length (command with params) should be `< 1024` characters.
* Command length (without params) should be `< 56` characters.
* Commands have to start with `/`.
Providing invalid commands will prevent the plugin from starting. Additional
commands allow duplicates, but they will be removed during execution, so each
command will be executed only once during each metric gathering interval.
[sample]: https://doc.dpdk.org/guides/sample_app_ug/l3_forward_power_man.html
### Example: Getting metrics from multiple DPDK instances on same host
This configuration allows getting metrics from two separate applications
exposing their telemetry interfaces via separate sockets. For each plugin
instance a unique tag `[inputs.dpdk.tags]` allows distinguishing between them.
```toml
# Instance #1 - L3 Forwarding with Power Management Application
[[inputs.dpdk]]
socket_path = "/var/run/dpdk/rte/l3fwd-power_telemetry.v2"
device_types = ["ethdev"]
additional_commands = ["/l3fwd-power/stats"]
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/link_status"]
[inputs.dpdk.tags]
dpdk_instance = "l3fwd-power"
# Instance #2 - L2 Forwarding with Intel Cache Allocation Technology (CAT)
# Application
[[inputs.dpdk]]
socket_path = "/var/run/dpdk/rte/l2fwd-cat_telemetry.v2"
device_types = ["ethdev"]
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/link_status"]
[inputs.dpdk.tags]
dpdk_instance = "l2fwd-cat"
```
This utilizes Telegraf's standard capability of [adding custom
tags](../../../docs/CONFIGURATION.md#input-plugins) to input plugin's
measurements.
## Metrics
The DPDK socket accepts `command,params` requests and returns metric data in
JSON format. All metrics from DPDK socket become flattened using [Telegraf's
JSON Flattener](../../parsers/json/README.md) and exposed as fields. If DPDK
response contains no information (is empty or is null) then such response will
be discarded.
> **NOTE:** Since DPDK allows registering custom metrics in its telemetry
> framework the JSON response from DPDK may contain various sets of metrics.
> While metrics from `/ethdev/stats` should be most stable, the `/ethdev/xstats`
> may contain driver-specific metrics (depending on DPDK application
> configuration). The application-specific commands like `/l3fwd-power/stats`
> can return their own specific set of metrics.
## Example Output
The output consists of plugin name (`dpdk`), and a set of tags that identify
querying hierarchy:
```text
dpdk,host=dpdk-host,dpdk_instance=l3fwd-power,command=/ethdev/stats,params=0 [fields] [timestamp]
```
| Tag | Description |
|-----|-------------|
| `host` | hostname of the machine (consult [Telegraf Agent configuration](https://github.com/influxdata/telegraf/blob/master/docs/CONFIGURATION.md#agent) for additional details) |
| `dpdk_instance` | custom tag from `[inputs.dpdk.tags]` (optional) |
| `command` | executed command (without params) |
| `params` | command parameter, e.g. for `/ethdev/stats` it is the ID of NIC as exposed by `/ethdev/list`. For DPDK app that uses 2 NICs the metrics will output e.g. `params=0`, `params=1`. |
When running plugin configuration below...
```toml
[[inputs.dpdk]]
device_types = ["ethdev"]
additional_commands = ["/l3fwd-power/stats"]
metadata_fields = []
[inputs.dpdk.tags]
dpdk_instance = "l3fwd-power"
```
...expected output for `dpdk` plugin instance running on host named
`host=dpdk-host`:
```text
dpdk,command=/ethdev/info,dpdk_instance=l3fwd-power,host=dpdk-host,params=0 all_multicast=0,dev_configured=1,dev_flags=74,dev_started=1,ethdev_rss_hf=0,lro=0,mac_addr="E4:3D:1A:DD:13:31",mtu=1500,name="0000:ca:00.1",nb_rx_queues=1,nb_tx_queues=1,numa_node=1,port_id=0,promiscuous=1,rx_mbuf_alloc_fail=0,rx_mbuf_size_min=2176,rx_offloads=0,rxq_state_0=1,scattered_rx=0,state=1,tx_offloads=65536,txq_state_0=1 1659017414000000000
dpdk,command=/ethdev/stats,dpdk_instance=l3fwd-power,host=dpdk-host,params=0 q_opackets_0=0,q_ipackets_5=0,q_errors_11=0,ierrors=0,q_obytes_5=0,q_obytes_10=0,q_opackets_10=0,q_ipackets_4=0,q_ipackets_7=0,q_ipackets_15=0,q_ibytes_5=0,q_ibytes_6=0,q_ibytes_9=0,obytes=0,q_opackets_1=0,q_opackets_11=0,q_obytes_7=0,q_errors_5=0,q_errors_10=0,q_ibytes_4=0,q_obytes_6=0,q_errors_1=0,q_opackets_5=0,q_errors_3=0,q_errors_12=0,q_ipackets_11=0,q_ipackets_12=0,q_obytes_14=0,q_opackets_15=0,q_obytes_2=0,q_errors_8=0,q_opackets_12=0,q_errors_0=0,q_errors_9=0,q_opackets_14=0,q_ibytes_3=0,q_ibytes_15=0,q_ipackets_13=0,q_ipackets_14=0,q_obytes_3=0,q_errors_13=0,q_opackets_3=0,q_ibytes_0=7092,q_ibytes_2=0,q_ibytes_8=0,q_ipackets_8=0,q_ipackets_10=0,q_obytes_4=0,q_ibytes_10=0,q_ibytes_13=0,q_ibytes_1=0,q_ibytes_12=0,opackets=0,q_obytes_1=0,q_errors_15=0,q_opackets_2=0,oerrors=0,rx_nombuf=0,q_opackets_8=0,q_ibytes_11=0,q_ipackets_3=0,q_obytes_0=0,q_obytes_12=0,q_obytes_11=0,q_obytes_13=0,q_errors_6=0,q_ipackets_1=0,q_ipackets_6=0,q_ipackets_9=0,q_obytes_15=0,q_opackets_7=0,q_ibytes_14=0,ipackets=98,q_ipackets_2=0,q_opackets_6=0,q_ibytes_7=0,imissed=0,q_opackets_4=0,q_opackets_9=0,q_obytes_8=0,q_obytes_9=0,q_errors_4=0,q_errors_14=0,q_opackets_13=0,ibytes=7092,q_ipackets_0=98,q_errors_2=0,q_errors_7=0 1606310780000000000
dpdk,command=/ethdev/stats,dpdk_instance=l3fwd-power,host=dpdk-host,params=1 q_opackets_0=0,q_ipackets_5=0,q_errors_11=0,ierrors=0,q_obytes_5=0,q_obytes_10=0,q_opackets_10=0,q_ipackets_4=0,q_ipackets_7=0,q_ipackets_15=0,q_ibytes_5=0,q_ibytes_6=0,q_ibytes_9=0,obytes=0,q_opackets_1=0,q_opackets_11=0,q_obytes_7=0,q_errors_5=0,q_errors_10=0,q_ibytes_4=0,q_obytes_6=0,q_errors_1=0,q_opackets_5=0,q_errors_3=0,q_errors_12=0,q_ipackets_11=0,q_ipackets_12=0,q_obytes_14=0,q_opackets_15=0,q_obytes_2=0,q_errors_8=0,q_opackets_12=0,q_errors_0=0,q_errors_9=0,q_opackets_14=0,q_ibytes_3=0,q_ibytes_15=0,q_ipackets_13=0,q_ipackets_14=0,q_obytes_3=0,q_errors_13=0,q_opackets_3=0,q_ibytes_0=7092,q_ibytes_2=0,q_ibytes_8=0,q_ipackets_8=0,q_ipackets_10=0,q_obytes_4=0,q_ibytes_10=0,q_ibytes_13=0,q_ibytes_1=0,q_ibytes_12=0,opackets=0,q_obytes_1=0,q_errors_15=0,q_opackets_2=0,oerrors=0,rx_nombuf=0,q_opackets_8=0,q_ibytes_11=0,q_ipackets_3=0,q_obytes_0=0,q_obytes_12=0,q_obytes_11=0,q_obytes_13=0,q_errors_6=0,q_ipackets_1=0,q_ipackets_6=0,q_ipackets_9=0,q_obytes_15=0,q_opackets_7=0,q_ibytes_14=0,ipackets=98,q_ipackets_2=0,q_opackets_6=0,q_ibytes_7=0,imissed=0,q_opackets_4=0,q_opackets_9=0,q_obytes_8=0,q_obytes_9=0,q_errors_4=0,q_errors_14=0,q_opackets_13=0,ibytes=7092,q_ipackets_0=98,q_errors_2=0,q_errors_7=0 1606310780000000000
dpdk,command=/ethdev/xstats,dpdk_instance=l3fwd-power,host=dpdk-host,params=0 out_octets_encrypted=0,rx_fcoe_mbuf_allocation_errors=0,tx_q1packets=0,rx_priority0_xoff_packets=0,rx_priority7_xoff_packets=0,rx_errors=0,mac_remote_errors=0,in_pkts_invalid=0,tx_priority3_xoff_packets=0,tx_errors=0,rx_fcoe_bytes=0,rx_flow_control_xon_packets=0,rx_priority4_xoff_packets=0,tx_priority2_xoff_packets=0,rx_illegal_byte_errors=0,rx_xoff_packets=0,rx_management_packets=0,rx_priority7_dropped=0,rx_priority4_dropped=0,in_pkts_unchecked=0,rx_error_bytes=0,rx_size_256_to_511_packets=0,tx_priority4_xoff_packets=0,rx_priority6_xon_packets=0,tx_priority4_xon_to_xoff_packets=0,in_pkts_delayed=0,rx_priority0_mbuf_allocation_errors=0,out_octets_protected=0,tx_priority7_xon_to_xoff_packets=0,tx_priority1_xon_to_xoff_packets=0,rx_fcoe_no_direct_data_placement_ext_buff=0,tx_priority6_xon_to_xoff_packets=0,flow_director_filter_add_errors=0,rx_total_packets=99,rx_crc_errors=0,flow_director_filter_remove_errors=0,rx_missed_errors=0,tx_size_64_packets=0,rx_priority3_dropped=0,flow_director_matched_filters=0,tx_priority2_xon_to_xoff_packets=0,rx_priority1_xon_packets=0,rx_size_65_to_127_packets=99,rx_fragment_errors=0,in_pkts_notusingsa=0,rx_q0bytes=7162,rx_fcoe_dropped=0,rx_priority1_dropped=0,rx_fcoe_packets=0,rx_priority5_xoff_packets=0,out_pkts_protected=0,tx_total_packets=0,rx_priority2_dropped=0,in_pkts_late=0,tx_q1bytes=0,in_pkts_badtag=0,rx_multicast_packets=99,rx_priority6_xoff_packets=0,tx_flow_control_xoff_packets=0,rx_flow_control_xoff_packets=0,rx_priority0_xon_packets=0,in_pkts_untagged=0,tx_fcoe_packets=0,rx_priority7_mbuf_allocation_errors=0,tx_priority0_xon_to_xoff_packets=0,tx_priority5_xon_to_xoff_packets=0,tx_flow_control_xon_packets=0,tx_q0packets=0,tx_xoff_packets=0,rx_size_512_to_1023_packets=0,rx_priority3_xon_packets=0,rx_q0errors=0,rx_oversize_errors=0,tx_priority4_xon_packets=0,tx_priority5_xoff_packets=0,rx_priority5_xon_packets=0,rx_total_missed_packets=0,rx_priority4_mbuf_allocation_errors=0,tx_priority1_xon_packets=0,tx_management_packets=0,rx_priority5_mbuf_allocation_errors=0,rx_fcoe_no_direct_data_placement=0,rx_undersize_errors=0,tx_priority1_xoff_packets=0,rx_q0packets=99,tx_q2packets=0,tx_priority6_xon_packets=0,rx_good_packets=99,tx_priority5_xon_packets=0,tx_size_256_to_511_packets=0,rx_priority6_dropped=0,rx_broadcast_packets=0,tx_size_512_to_1023_packets=0,tx_priority3_xon_to_xoff_packets=0,in_pkts_unknownsci=0,in_octets_validated=0,tx_priority6_xoff_packets=0,tx_priority7_xoff_packets=0,rx_jabber_errors=0,tx_priority7_xon_packets=0,tx_priority0_xon_packets=0,in_pkts_unusedsa=0,tx_priority0_xoff_packets=0,mac_local_errors=33,rx_total_bytes=7162,in_pkts_notvalid=0,rx_length_errors=0,in_octets_decrypted=0,rx_size_128_to_255_packets=0,rx_good_bytes=7162,tx_size_65_to_127_packets=0,rx_mac_short_packet_dropped=0,tx_size_1024_to_max_packets=0,rx_priority2_mbuf_allocation_errors=0,flow_director_added_filters=0,tx_multicast_packets=0,rx_fcoe_crc_errors=0,rx_priority1_xoff_packets=0,flow_director_missed_filters=0,rx_xon_packets=0,tx_size_128_to_255_packets=0,out_pkts_encrypted=0,rx_priority4_xon_packets=0,rx_priority0_dropped=0,rx_size_1024_to_max_packets=0,tx_good_bytes=0,rx_management_dropped=0,rx_mbuf_allocation_errors=0,tx_xon_packets=0,rx_priority3_xoff_packets=0,tx_good_packets=0,tx_fcoe_bytes=0,rx_priority6_mbuf_allocation_errors=0,rx_priority2_xon_packets=0,tx_broadcast_packets=0,tx_q2bytes=0,rx_priority7_xon_packets=0,out_pkts_untagged=0,rx_priority2_xoff_packets=0,rx_priority1_mbuf_allocation_errors=0,tx_q0bytes=0,rx_size_64_packets=0,rx_priority5_dropped=0,tx_priority2_xon_packets=0,in_pkts_nosci=0,flow_director_removed_filters=0,in_pkts_ok=0,rx_l3_l4_xsum_error=0,rx_priority3_mbuf_allocation_errors=0,tx_priority3_xon_packets=0 1606310780000000000
dpdk,command=/ethdev/xstats,dpdk_instance=l3fwd-power,host=dpdk-host,params=1 tx_priority5_xoff_packets=0,in_pkts_unknownsci=0,tx_q0packets=0,tx_total_packets=0,rx_crc_errors=0,rx_priority4_xoff_packets=0,rx_priority5_dropped=0,tx_size_65_to_127_packets=0,rx_good_packets=98,tx_priority6_xoff_packets=0,tx_fcoe_bytes=0,out_octets_protected=0,out_pkts_encrypted=0,rx_priority1_xon_packets=0,tx_size_128_to_255_packets=0,rx_flow_control_xoff_packets=0,rx_priority7_xoff_packets=0,tx_priority0_xon_to_xoff_packets=0,rx_broadcast_packets=0,tx_priority1_xon_packets=0,rx_xon_packets=0,rx_fragment_errors=0,tx_flow_control_xoff_packets=0,tx_q0bytes=0,out_pkts_untagged=0,rx_priority4_xon_packets=0,tx_priority5_xon_packets=0,rx_priority1_xoff_packets=0,rx_good_bytes=7092,rx_priority4_mbuf_allocation_errors=0,in_octets_decrypted=0,tx_priority2_xon_to_xoff_packets=0,rx_priority3_dropped=0,tx_multicast_packets=0,mac_local_errors=33,in_pkts_ok=0,rx_illegal_byte_errors=0,rx_xoff_packets=0,rx_q0errors=0,flow_director_added_filters=0,rx_size_256_to_511_packets=0,rx_priority3_xon_packets=0,rx_l3_l4_xsum_error=0,rx_priority6_dropped=0,in_pkts_notvalid=0,rx_size_64_packets=0,tx_management_packets=0,rx_length_errors=0,tx_priority7_xon_to_xoff_packets=0,rx_mbuf_allocation_errors=0,rx_missed_errors=0,rx_priority1_mbuf_allocation_errors=0,rx_fcoe_no_direct_data_placement=0,tx_priority3_xoff_packets=0,in_pkts_delayed=0,tx_errors=0,rx_size_512_to_1023_packets=0,tx_priority4_xon_packets=0,rx_q0bytes=7092,in_pkts_unchecked=0,tx_size_512_to_1023_packets=0,rx_fcoe_packets=0,in_pkts_nosci=0,rx_priority6_mbuf_allocation_errors=0,rx_priority1_dropped=0,tx_q2packets=0,rx_priority7_dropped=0,tx_size_1024_to_max_packets=0,rx_management_packets=0,rx_multicast_packets=98,rx_total_bytes=7092,mac_remote_errors=0,tx_priority3_xon_packets=0,rx_priority2_mbuf_allocation_errors=0,rx_priority5_mbuf_allocation_errors=0,tx_q2bytes=0,rx_size_128_to_255_packets=0,in_pkts_badtag=0,out_pkts_protected=0,rx_management_dropped=0,rx_fcoe_bytes=0,flow_director_removed_filters=0,tx_priority2_xoff_packets=0,rx_fcoe_crc_errors=0,rx_priority0_mbuf_allocation_errors=0,rx_priority0_xon_packets=0,rx_fcoe_dropped=0,tx_priority1_xon_to_xoff_packets=0,rx_size_65_to_127_packets=98,rx_q0packets=98,tx_priority0_xoff_packets=0,rx_priority6_xon_packets=0,rx_total_packets=98,rx_undersize_errors=0,flow_director_missed_filters=0,rx_jabber_errors=0,in_pkts_invalid=0,in_pkts_late=0,rx_priority5_xon_packets=0,tx_priority4_xoff_packets=0,out_octets_encrypted=0,tx_q1packets=0,rx_priority5_xoff_packets=0,rx_priority6_xoff_packets=0,rx_errors=0,in_octets_validated=0,rx_priority3_xoff_packets=0,tx_priority4_xon_to_xoff_packets=0,tx_priority5_xon_to_xoff_packets=0,tx_flow_control_xon_packets=0,rx_priority0_dropped=0,flow_director_filter_add_errors=0,tx_q1bytes=0,tx_priority6_xon_to_xoff_packets=0,flow_director_matched_filters=0,tx_priority2_xon_packets=0,rx_fcoe_mbuf_allocation_errors=0,rx_priority2_xoff_packets=0,tx_priority7_xoff_packets=0,rx_priority0_xoff_packets=0,rx_oversize_errors=0,in_pkts_notusingsa=0,tx_size_64_packets=0,rx_size_1024_to_max_packets=0,tx_priority6_xon_packets=0,rx_priority2_dropped=0,rx_priority4_dropped=0,rx_priority7_mbuf_allocation_errors=0,rx_flow_control_xon_packets=0,tx_good_bytes=0,tx_priority3_xon_to_xoff_packets=0,rx_total_missed_packets=0,rx_error_bytes=0,tx_priority7_xon_packets=0,rx_mac_short_packet_dropped=0,tx_priority1_xoff_packets=0,tx_good_packets=0,tx_broadcast_packets=0,tx_xon_packets=0,in_pkts_unusedsa=0,rx_priority2_xon_packets=0,in_pkts_untagged=0,tx_fcoe_packets=0,flow_director_filter_remove_errors=0,rx_priority3_mbuf_allocation_errors=0,tx_priority0_xon_packets=0,rx_priority7_xon_packets=0,rx_fcoe_no_direct_data_placement_ext_buff=0,tx_xoff_packets=0,tx_size_256_to_511_packets=0 1606310780000000000
dpdk,command=/ethdev/link_status,dpdk_instance=l3fwd-power,host=dpdk-host,params=0 status="UP",link_status=1,speed=10000,duplex="full-duplex" 1606310780000000000
dpdk,command=/ethdev/link_status,dpdk_instance=l3fwd-power,host=dpdk-host,params=1 status="UP",link_status=1,speed=10000,duplex="full-duplex" 1606310780000000000
dpdk,command=/l3fwd-power/stats,dpdk_instance=l3fwd-power,host=dpdk-host empty_poll=49506395979901,full_poll=0,busy_percent=0 1606310780000000000
```
When running plugin configuration below...
```toml
[[inputs.dpdk]]
interval = "30s"
socket_access_timeout = "10s"
device_types = ["ethdev"]
metadata_fields = ["version", "pid"]
plugin_options = ["in_memory"]
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/info", "/ethdev/stats", "/ethdev/xstats"]
```
Expected output for `dpdk` plugin instance running with `link_status` command
and all metadata fields enabled, additionally `link_status` field will be
exposed to represent string value of `status` field (`DOWN`=0,`UP`=1):
```text
dpdk,command=/ethdev/link_status,host=dpdk-host,params=0 pid=100988i,version="DPDK 21.11.2",status="DOWN",link_status=0i 1660295749000000000
dpdk,command=/ethdev/link_status,host=dpdk-host,params=0 pid=2401624i,version="DPDK 21.11.2",status="UP",link_status=1i 1660295749000000000
```

292
plugins/inputs/dpdk/dpdk.go Normal file
View file

@ -0,0 +1,292 @@
//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
})
}

View file

@ -0,0 +1,55 @@
//go:build linux
package dpdk
import (
"fmt"
"strings"
)
type linkStatus int64
const (
down linkStatus = iota
up
)
const (
ethdevLinkStatusCommand = "/ethdev/link_status"
linkStatusStringFieldName = "status"
linkStatusIntegerFieldName = "link_status"
)
var (
linkStatusMap = map[string]linkStatus{
"down": down,
"up": up,
}
)
func processCommandResponse(command string, data map[string]interface{}) error {
if command == ethdevLinkStatusCommand {
return processLinkStatusCmd(data)
}
return nil
}
func processLinkStatusCmd(data map[string]interface{}) error {
status, ok := data[linkStatusStringFieldName].(string)
if !ok {
return fmt.Errorf("can't find or parse %q field", linkStatusStringFieldName)
}
parsedLinkStatus, ok := parseLinkStatus(status)
if !ok {
return fmt.Errorf("can't parse linkStatus: unknown value: %q", status)
}
data[linkStatusIntegerFieldName] = int64(parsedLinkStatus)
return nil
}
func parseLinkStatus(s string) (linkStatus, bool) {
value, ok := linkStatusMap[strings.ToLower(s)]
return value, ok
}

View file

@ -0,0 +1,131 @@
//go:build linux
package dpdk
import (
"fmt"
"testing"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
)
func Test_LinkStatusCommand(t *testing.T) {
t.Run("when 'status' field is DOWN then return 'link_status'=0", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q:{%q: "DOWN"}}`, ethdevLinkStatusCommand, linkStatusStringFieldName)
simulateResponse(mockConn, response, nil)
dpdkConn := dpdk.connectors[0]
dpdkConn.processCommand(mockAcc, testutil.Logger{}, ethdevLinkStatusCommand+",1", nil)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": ethdevLinkStatusCommand,
"params": "1",
},
map[string]interface{}{
linkStatusStringFieldName: "DOWN",
linkStatusIntegerFieldName: int64(0),
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
t.Run("when 'status' field is UP then return 'link_status'=1", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q:{%q: "UP"}}`, ethdevLinkStatusCommand, linkStatusStringFieldName)
simulateResponse(mockConn, response, nil)
dpdkConn := dpdk.connectors[0]
dpdkConn.processCommand(mockAcc, testutil.Logger{}, ethdevLinkStatusCommand+",1", nil)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": ethdevLinkStatusCommand,
"params": "1",
},
map[string]interface{}{
linkStatusStringFieldName: "UP",
linkStatusIntegerFieldName: int64(1),
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
t.Run("when link status output doesn't have any fields then don't return 'link_status' field", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q:{}}`, ethdevLinkStatusCommand)
simulateResponse(mockConn, response, nil)
dpdkConn := dpdk.connectors[0]
dpdkConn.processCommand(mockAcc, testutil.Logger{}, ethdevLinkStatusCommand+",1", nil)
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, nil, actual, testutil.IgnoreTime())
})
t.Run("when link status output doesn't have status field then don't return 'link_status' field", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q:{"tag1": 1}}`, ethdevLinkStatusCommand)
simulateResponse(mockConn, response, nil)
dpdkConn := dpdk.connectors[0]
dpdkConn.processCommand(mockAcc, testutil.Logger{}, ethdevLinkStatusCommand+",1", nil)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": ethdevLinkStatusCommand,
"params": "1",
},
map[string]interface{}{
"tag1": float64(1),
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
t.Run("when link status output is invalid then don't return 'link_status' field", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q:{%q: "BOB"}}`, ethdevLinkStatusCommand, linkStatusStringFieldName)
simulateResponse(mockConn, response, nil)
dpdkConn := dpdk.connectors[0]
dpdkConn.processCommand(mockAcc, testutil.Logger{}, ethdevLinkStatusCommand+",1", nil)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": ethdevLinkStatusCommand,
"params": "1",
},
map[string]interface{}{
linkStatusStringFieldName: "BOB",
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
}

View file

@ -0,0 +1,243 @@
//go:build linux
package dpdk
import (
"encoding/json"
"errors"
"fmt"
"net"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
parsers_json "github.com/influxdata/telegraf/plugins/parsers/json"
)
const (
maxInitMessageLength = 1024 // based on https://github.com/DPDK/dpdk/blob/v22.07/lib/telemetry/telemetry.c#L352
dpdkSocketTemplateName = "dpdk_telemetry"
)
type initMessage struct {
Version string `json:"version"`
Pid int `json:"pid"`
MaxOutputLen uint32 `json:"max_output_len"`
}
type dpdkConnector struct {
pathToSocket string
accessTimeout time.Duration
connection net.Conn
initMessage *initMessage
}
func newDpdkConnector(pathToSocket string, accessTimeout config.Duration) *dpdkConnector {
return &dpdkConnector{
pathToSocket: pathToSocket,
accessTimeout: time.Duration(accessTimeout),
}
}
// Connects to the socket
// Since DPDK is a local unix socket, it is instantly returns error or connection, so there's no need to set timeout for it
func (conn *dpdkConnector) connect() (*initMessage, error) {
if err := isSocket(conn.pathToSocket); err != nil {
return nil, err
}
connection, err := net.Dial("unixpacket", conn.pathToSocket)
if err != nil {
return nil, fmt.Errorf("failed to connect to the socket: %w", err)
}
conn.connection = connection
conn.initMessage, err = conn.readInitMessage()
if err != nil {
if closeErr := conn.tryClose(); closeErr != nil {
return nil, fmt.Errorf("%w and failed to close connection: %w", err, closeErr)
}
return nil, err
}
return conn.initMessage, nil
}
// Add metadata fields to data
func (conn *dpdkConnector) addMetadataFields(metadataFields []string, data map[string]interface{}) {
if conn.initMessage == nil {
return
}
for _, field := range metadataFields {
switch field {
case dpdkMetadataFieldPidName:
data[dpdkMetadataFieldPidName] = conn.initMessage.Pid
case dpdkMetadataFieldVersionName:
data[dpdkMetadataFieldVersionName] = conn.initMessage.Version
}
}
}
// Fetches all identifiers of devices and then creates all possible combinations of commands for each device
func (conn *dpdkConnector) appendCommandsWithParamsFromList(listCommand string, commands []string) ([]string, error) {
response, err := conn.getCommandResponse(listCommand)
if err != nil {
return nil, err
}
params, err := jsonToArray(response, listCommand)
if err != nil {
return nil, err
}
result := make([]string, 0, len(commands)*len(params))
for _, command := range commands {
for _, param := range params {
result = append(result, commandWithParams(command, param))
}
}
return result, nil
}
// Executes command using provided connection and returns response
// If error (such as timeout) occurred, then connection is discarded and recreated
// because otherwise behavior of connection is undefined (e.g. it could return result of timed out command instead of latest)
func (conn *dpdkConnector) getCommandResponse(fullCommand string) ([]byte, error) {
connection, err := conn.getConnection()
if err != nil {
return nil, fmt.Errorf("failed to get connection to execute %q command: %w", fullCommand, err)
}
err = conn.setTimeout()
if err != nil {
return nil, fmt.Errorf("failed to set timeout for %q command: %w", fullCommand, err)
}
_, err = connection.Write([]byte(fullCommand))
if err != nil {
if closeErr := conn.tryClose(); closeErr != nil {
return nil, fmt.Errorf("failed to send %q command: %w and failed to close connection: %w", fullCommand, err, closeErr)
}
return nil, fmt.Errorf("failed to send %q command: %w", fullCommand, err)
}
buf := make([]byte, conn.initMessage.MaxOutputLen)
messageLength, err := connection.Read(buf)
if err != nil {
if closeErr := conn.tryClose(); closeErr != nil {
return nil, fmt.Errorf("failed read response of %q command: %w and failed to close connection: %w", fullCommand, err, closeErr)
}
return nil, fmt.Errorf("failed to read response of %q command: %w", fullCommand, err)
}
if messageLength == 0 {
return nil, fmt.Errorf("got empty response during execution of %q command", fullCommand)
}
return buf[:messageLength], nil
}
// Executes command, parses response and creates/writes metrics from response to accumulator
func (conn *dpdkConnector) processCommand(acc telegraf.Accumulator, log telegraf.Logger, commandWithParams string, metadataFields []string) {
buf, err := conn.getCommandResponse(commandWithParams)
if err != nil {
acc.AddError(err)
return
}
var parsedResponse map[string]interface{}
err = json.Unmarshal(buf, &parsedResponse)
if err != nil {
acc.AddError(fmt.Errorf("failed to unmarshal json response from %q command: %w", commandWithParams, err))
return
}
command := stripParams(commandWithParams)
value := parsedResponse[command]
if isEmpty(value) {
log.Warnf("got empty json on %q command", commandWithParams)
return
}
jf := parsers_json.JSONFlattener{}
err = jf.FullFlattenJSON("", value, true, true)
if err != nil {
acc.AddError(fmt.Errorf("failed to flatten response: %w", err))
return
}
err = processCommandResponse(command, jf.Fields)
if err != nil {
log.Warnf("Failed to process a response of the command: %s. Error: %v. Continue to handle data", command, err)
}
// Add metadata fields if required
conn.addMetadataFields(metadataFields, jf.Fields)
// Add common fields
acc.AddFields(pluginName, jf.Fields, map[string]string{
"command": command,
"params": getParams(commandWithParams),
})
}
func (conn *dpdkConnector) tryClose() error {
if conn.connection == nil {
return nil
}
err := conn.connection.Close()
conn.connection = nil
if err != nil {
return err
}
return nil
}
func (conn *dpdkConnector) setTimeout() error {
if conn.connection == nil {
return errors.New("connection had not been established before")
}
if conn.accessTimeout == 0 {
return conn.connection.SetDeadline(time.Time{})
}
return conn.connection.SetDeadline(time.Now().Add(conn.accessTimeout))
}
// Returns connections, if connection is not created then function tries to recreate it
func (conn *dpdkConnector) getConnection() (net.Conn, error) {
if conn.connection == nil {
_, err := conn.connect()
if err != nil {
return nil, err
}
}
return conn.connection, nil
}
// Reads InitMessage for connection. Should be read for each connection, otherwise InitMessage is returned as response for first command.
func (conn *dpdkConnector) readInitMessage() (*initMessage, error) {
buf := make([]byte, maxInitMessageLength)
err := conn.setTimeout()
if err != nil {
return nil, fmt.Errorf("failed to set timeout: %w", err)
}
messageLength, err := conn.connection.Read(buf)
if err != nil {
return nil, fmt.Errorf("failed to read InitMessage: %w", err)
}
var connectionInitMessage initMessage
err = json.Unmarshal(buf[:messageLength], &connectionInitMessage)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if connectionInitMessage.MaxOutputLen == 0 {
return nil, errors.New("failed to read maxOutputLen information")
}
return &connectionInitMessage, nil
}

View file

@ -0,0 +1,240 @@
//go:build linux
package dpdk
import (
"encoding/json"
"errors"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/plugins/inputs/dpdk/mocks"
"github.com/influxdata/telegraf/testutil"
)
func Test_readMaxOutputLen(t *testing.T) {
t.Run("should return error if timeout occurred", func(t *testing.T) {
conn := &mocks.Conn{}
conn.On("Read", mock.Anything).Return(0, errors.New("timeout"))
conn.On("SetDeadline", mock.Anything).Return(nil)
connector := dpdkConnector{connection: conn}
initMessage, err := connector.readInitMessage()
require.Error(t, err)
require.Contains(t, err.Error(), "timeout")
require.Empty(t, initMessage)
})
t.Run("should pass and set maxOutputLen if provided with valid InitMessage", func(t *testing.T) {
maxOutputLen := uint32(4567)
initMessage := initMessage{
Version: "DPDK test version",
Pid: 1234,
MaxOutputLen: maxOutputLen,
}
message, err := json.Marshal(initMessage)
require.NoError(t, err)
conn := &mocks.Conn{}
conn.On("Read", mock.Anything).Run(func(arg mock.Arguments) {
elem := arg.Get(0).([]byte)
copy(elem, message)
}).Return(len(message), nil)
conn.On("SetDeadline", mock.Anything).Return(nil)
connector := dpdkConnector{connection: conn}
initMsg, err := connector.readInitMessage()
require.NoError(t, err)
require.Equal(t, maxOutputLen, initMsg.MaxOutputLen)
})
t.Run("should fail if received invalid json", func(t *testing.T) {
message := `{notAJson}`
conn := &mocks.Conn{}
conn.On("Read", mock.Anything).Run(func(arg mock.Arguments) {
elem := arg.Get(0).([]byte)
copy(elem, message)
}).Return(len(message), nil)
conn.On("SetDeadline", mock.Anything).Return(nil)
connector := dpdkConnector{connection: conn}
_, err := connector.readInitMessage()
require.Error(t, err)
require.Contains(t, err.Error(), "looking for beginning of object key string")
})
t.Run("should fail if received maxOutputLen equals to 0", func(t *testing.T) {
message, err := json.Marshal(initMessage{
Version: "test",
Pid: 1,
MaxOutputLen: 0,
})
require.NoError(t, err)
conn := &mocks.Conn{}
conn.On("Read", mock.Anything).Run(func(arg mock.Arguments) {
elem := arg.Get(0).([]byte)
copy(elem, message)
}).Return(len(message), nil)
conn.On("SetDeadline", mock.Anything).Return(nil)
connector := dpdkConnector{connection: conn}
_, err = connector.readInitMessage()
require.Error(t, err)
require.Contains(t, err.Error(), "failed to read maxOutputLen information")
})
}
func Test_connect(t *testing.T) {
t.Run("should pass if PathToSocket points to socket", func(t *testing.T) {
pathToSocket, socket := createSocketForTest(t, "")
dpdk := Dpdk{
SocketPath: pathToSocket,
connectors: []*dpdkConnector{newDpdkConnector(pathToSocket, 0)},
}
go simulateSocketResponse(socket, t)
_, err := dpdk.connectors[0].connect()
require.NoError(t, err)
})
}
func Test_getCommandResponse(t *testing.T) {
command := "/"
response := "myResponseString"
t.Run("should return proper buffer size and value if no error occurred", func(t *testing.T) {
mockConn, dpdk, _ := prepareEnvironment()
defer mockConn.AssertExpectations(t)
simulateResponse(mockConn, response, nil)
for _, connector := range dpdk.connectors {
buf, err := connector.getCommandResponse(command)
require.NoError(t, err)
require.Len(t, buf, len(response))
require.Equal(t, response, string(buf))
}
})
t.Run("should return error if failed to get connection handler", func(t *testing.T) {
_, dpdk, _ := prepareEnvironment()
dpdk.connectors[0].connection = nil
buf, err := dpdk.connectors[0].getCommandResponse(command)
require.Error(t, err)
require.Contains(t, err.Error(), "failed to get connection to execute \"/\" command")
require.Empty(t, buf)
})
t.Run("should return error if failed to set timeout duration", func(t *testing.T) {
mockConn, dpdk, _ := prepareEnvironment()
defer mockConn.AssertExpectations(t)
mockConn.On("SetDeadline", mock.Anything).Return(errors.New("deadline error"))
buf, err := dpdk.connectors[0].getCommandResponse(command)
require.Error(t, err)
require.Contains(t, err.Error(), "deadline error")
require.Empty(t, buf)
})
t.Run("should return error if timeout occurred during Write operation", func(t *testing.T) {
mockConn, dpdk, _ := prepareEnvironment()
defer mockConn.AssertExpectations(t)
mockConn.On("Write", mock.Anything).Return(0, errors.New("write timeout"))
mockConn.On("SetDeadline", mock.Anything).Return(nil)
mockConn.On("Close").Return(nil)
buf, err := dpdk.connectors[0].getCommandResponse(command)
require.Error(t, err)
require.Contains(t, err.Error(), "write timeout")
require.Empty(t, buf)
})
t.Run("should return error if timeout occurred during Read operation", func(t *testing.T) {
mockConn, dpdk, _ := prepareEnvironment()
defer mockConn.AssertExpectations(t)
simulateResponse(mockConn, "", errors.New("read timeout"))
buf, err := dpdk.connectors[0].getCommandResponse(command)
require.Error(t, err)
require.Contains(t, err.Error(), "read timeout")
require.Empty(t, buf)
})
t.Run("should return error if got empty response", func(t *testing.T) {
mockConn, dpdk, _ := prepareEnvironment()
defer mockConn.AssertExpectations(t)
simulateResponse(mockConn, "", nil)
buf, err := dpdk.connectors[0].getCommandResponse(command)
require.Error(t, err)
require.Empty(t, buf)
require.Contains(t, err.Error(), "got empty response during execution of")
})
}
func Test_processCommand(t *testing.T) {
t.Run("should pass if received valid response", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := `{"/": ["/", "/eal/app_params", "/eal/params", "/ethdev/link_status, /ethdev/info"]}`
simulateResponse(mockConn, response, nil)
for _, dpdkConn := range dpdk.connectors {
dpdkConn.processCommand(mockAcc, testutil.Logger{}, "/", nil)
}
require.Empty(t, mockAcc.Errors)
})
t.Run("if received a non-JSON object then should return error", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := `notAJson`
simulateResponse(mockConn, response, nil)
for _, dpdkConn := range dpdk.connectors {
dpdkConn.processCommand(mockAcc, testutil.Logger{}, "/", nil)
}
require.Len(t, mockAcc.Errors, 1)
require.Contains(t, mockAcc.Errors[0].Error(), "invalid character")
})
t.Run("if failed to get command response then accumulator should contain error", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
mockConn.On("Write", mock.Anything).Return(0, errors.New("deadline exceeded"))
mockConn.On("SetDeadline", mock.Anything).Return(nil)
mockConn.On("Close").Return(nil)
for _, dpdkConn := range dpdk.connectors {
dpdkConn.processCommand(mockAcc, testutil.Logger{}, "/", nil)
}
require.Len(t, mockAcc.Errors, 1)
require.Contains(t, mockAcc.Errors[0].Error(), "deadline exceeded")
})
t.Run("if response contains nil or empty value then error shouldn't be returned in accumulator", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := `{"/test": null}`
simulateResponse(mockConn, response, nil)
for _, dpdkConn := range dpdk.connectors {
dpdkConn.processCommand(mockAcc, testutil.Logger{}, "/test,param", nil)
}
require.Empty(t, mockAcc.Errors)
})
}

View file

@ -0,0 +1,33 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build !linux
package dpdk
import (
_ "embed"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type Dpdk struct {
Log telegraf.Logger `toml:"-"`
}
func (*Dpdk) SampleConfig() string { return sampleConfig }
func (d *Dpdk) Init() error {
d.Log.Warn("Current platform is not supported")
return nil
}
func (*Dpdk) Gather(_ telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("dpdk", func() telegraf.Input {
return &Dpdk{}
})
}

View file

@ -0,0 +1,918 @@
//go:build linux
package dpdk
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/internal/globpath"
"github.com/influxdata/telegraf/plugins/inputs/dpdk/mocks"
"github.com/influxdata/telegraf/testutil"
)
func Test_Init(t *testing.T) {
t.Run("when SocketPath field isn't set then it should be set to default value", func(t *testing.T) {
dpdk := Dpdk{
Log: testutil.Logger{},
SocketPath: "",
}
require.Empty(t, dpdk.SocketPath)
require.NoError(t, dpdk.Init())
require.Equal(t, defaultPathToSocket, dpdk.SocketPath)
})
t.Run("when Metadata Fields isn't set then it should be set to default value (dpdk_pid)", func(t *testing.T) {
dpdk := Dpdk{
Log: testutil.Logger{},
}
require.Nil(t, dpdk.MetadataFields)
require.NoError(t, dpdk.Init())
require.Equal(t, []string{dpdkMetadataFieldPidName, dpdkMetadataFieldVersionName}, dpdk.MetadataFields)
})
t.Run("when PluginOptions field isn't set then it should be set to default value (in_memory)", func(t *testing.T) {
dpdk := Dpdk{
Log: testutil.Logger{},
}
require.Nil(t, dpdk.PluginOptions)
require.NoError(t, dpdk.Init())
require.Equal(t, []string{dpdkPluginOptionInMemory}, dpdk.PluginOptions)
})
t.Run("when commands are in invalid format (doesn't start with '/') then error should be returned", func(t *testing.T) {
pathToSocket, _ := createSocketForTest(t, "")
dpdk := Dpdk{
Log: testutil.Logger{},
SocketPath: pathToSocket,
AdditionalCommands: []string{"invalid"},
}
err := dpdk.Init()
require.Error(t, err)
require.Contains(t, err.Error(), "command should start with slash")
})
t.Run("when AccessTime is < 0 then error should be returned", func(t *testing.T) {
dpdk := Dpdk{
Log: testutil.Logger{},
AccessTimeout: -1,
}
err := dpdk.Init()
require.Error(t, err)
require.Contains(t, err.Error(), "socket_access_timeout should be positive number")
})
t.Run("when device_types and additional_commands are empty, then error should be returned", func(t *testing.T) {
pathToSocket, _ := createSocketForTest(t, "")
dpdk := Dpdk{
SocketPath: pathToSocket,
DeviceTypes: make([]string, 0),
Log: testutil.Logger{},
}
err := dpdk.Init()
require.Error(t, err)
require.Contains(t, err.Error(), "plugin was configured with nothing to read")
})
t.Run("when UnreachableSocketBehavior specified with unknown value - err should be returned", func(t *testing.T) {
dpdk := Dpdk{
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
UnreachableSocketBehavior: "whatisthat",
}
err := dpdk.Init()
require.Error(t, err)
require.Contains(t, err.Error(), "unreachable_socket_behavior")
})
}
func Test_Start(t *testing.T) {
t.Run("when socket doesn't exist err should be returned", func(t *testing.T) {
dpdk := Dpdk{
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
}
err := dpdk.Init()
require.NoError(t, err)
err = dpdk.Start(nil)
require.Error(t, err)
require.Contains(t, err.Error(), "no active sockets connections present")
})
t.Run("when socket doesn't exist, but UnreachableSocketBehavior is Ignore err shouldn't be returned", func(t *testing.T) {
dpdk := Dpdk{
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
UnreachableSocketBehavior: unreachableSocketBehaviorIgnore,
}
err := dpdk.Init()
require.NoError(t, err)
err = dpdk.Start(nil)
require.NoError(t, err)
})
t.Run("when all values are valid, then no error should be returned", func(t *testing.T) {
pathToSocket, socket := createSocketForTest(t, "")
dpdk := Dpdk{
SocketPath: pathToSocket,
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
}
err := dpdk.Init()
require.NoError(t, err)
go simulateSocketResponse(socket, t)
err = dpdk.Start(nil)
require.NoError(t, err)
})
}
func TestMaintainConnections(t *testing.T) {
t.Run("maintainConnections should return the error if socket doesn't exist", func(t *testing.T) {
dpdk := Dpdk{
SocketPath: "/tmp/justrandompath",
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
UnreachableSocketBehavior: unreachableSocketBehaviorError,
}
require.Empty(t, dpdk.connectors)
err := dpdk.maintainConnections()
defer dpdk.Stop()
require.Error(t, err)
require.Contains(t, err.Error(), "couldn't connect to socket")
})
t.Run("maintainConnections should return the error if socket not found with dpdkPluginOptionInMemory", func(t *testing.T) {
dpdk := Dpdk{
SocketPath: defaultPathToSocket,
Log: testutil.Logger{},
PluginOptions: []string{dpdkPluginOptionInMemory},
UnreachableSocketBehavior: unreachableSocketBehaviorError,
}
var err error
dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath)
require.NoError(t, err)
require.Empty(t, dpdk.connectors)
err = dpdk.maintainConnections()
require.Error(t, err)
require.Contains(t, err.Error(), "no active sockets connections present")
})
t.Run("maintainConnections shouldn't return error with 1 socket", func(t *testing.T) {
pathToSocket, socket := createSocketForTest(t, "")
dpdk := Dpdk{
SocketPath: pathToSocket,
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
}
go simulateSocketResponse(socket, t)
require.Empty(t, dpdk.connectors)
err := dpdk.maintainConnections()
defer dpdk.Stop()
require.NoError(t, err)
require.Len(t, dpdk.connectors, 1)
})
t.Run("maintainConnections shouldn't return error with multiple sockets", func(t *testing.T) {
numSockets := rand.Intn(5) + 1
pathToSockets, sockets := createMultipleSocketsForTest(t, numSockets, "")
dpdk := Dpdk{
SocketPath: pathToSockets[0],
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
PluginOptions: []string{dpdkPluginOptionInMemory},
}
var err error
dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath)
require.NoError(t, err)
for _, socket := range sockets {
go simulateSocketResponse(socket, t)
}
require.Empty(t, dpdk.connectors)
err = dpdk.maintainConnections()
defer dpdk.Stop()
require.NoError(t, err)
require.Len(t, dpdk.connectors, numSockets)
})
t.Run("Test maintainConnections without dpdkPluginOptionInMemory option", func(t *testing.T) {
pathToSocket, socket := createSocketForTest(t, "")
dpdk := Dpdk{
SocketPath: pathToSocket,
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
}
go simulateSocketResponse(socket, t)
require.Empty(t, dpdk.connectors)
err := dpdk.maintainConnections()
require.NoError(t, err)
require.Len(t, dpdk.connectors, 1)
dpdk.Stop()
require.Empty(t, dpdk.connectors)
})
t.Run("Test maintainConnections with dpdkPluginOptionInMemory option", func(t *testing.T) {
pathToSocket1, socket1 := createSocketForTest(t, "")
go simulateSocketResponse(socket1, t)
dpdk := Dpdk{
SocketPath: pathToSocket1,
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
PluginOptions: []string{dpdkPluginOptionInMemory},
}
var err error
dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath)
require.NoError(t, err)
require.Empty(t, dpdk.connectors)
err = dpdk.maintainConnections()
require.NoError(t, err)
require.Len(t, dpdk.connectors, 1)
// Adding 2 sockets more
pathToSocket2, socket2 := createSocketForTest(t, filepath.Dir(pathToSocket1))
pathToSocket3, socket3 := createSocketForTest(t, filepath.Dir(pathToSocket1))
require.NotEqual(t, pathToSocket2, pathToSocket3)
go simulateSocketResponse(socket2, t)
go simulateSocketResponse(socket3, t)
err = dpdk.maintainConnections()
require.NoError(t, err)
require.Len(t, dpdk.connectors, 3)
// Close 2 new sockets
socket2.Close()
socket3.Close()
err = dpdk.maintainConnections()
require.NoError(t, err)
require.Len(t, dpdk.connectors, 1)
require.Equal(t, pathToSocket1, dpdk.connectors[0].pathToSocket)
dpdk.Stop()
require.Empty(t, dpdk.connectors)
})
}
func TestClose(t *testing.T) {
t.Run("Num of connections should be 0 after Stop func", func(t *testing.T) {
pathToSocket, socket := createSocketForTest(t, "")
dpdk := Dpdk{
SocketPath: pathToSocket,
DeviceTypes: []string{"ethdev"},
Log: testutil.Logger{},
}
go simulateSocketResponse(socket, t)
require.Empty(t, dpdk.connectors)
err := dpdk.maintainConnections()
require.NoError(t, err)
require.Len(t, dpdk.connectors, 1)
dpdk.Stop()
require.Empty(t, dpdk.connectors)
})
}
func Test_validateAdditionalCommands(t *testing.T) {
t.Run("when validating commands in correct format then no error should be returned", func(t *testing.T) {
dpdk := Dpdk{
AdditionalCommands: []string{"/test", "/help"},
}
err := dpdk.validateAdditionalCommands()
require.NoError(t, err)
})
t.Run("when validating command that doesn't begin with slash then error should be returned", func(t *testing.T) {
dpdk := Dpdk{
AdditionalCommands: []string{
"/test", "commandWithoutSlash",
},
}
err := dpdk.validateAdditionalCommands()
require.Error(t, err)
require.Contains(t, err.Error(), "command should start with slash")
})
t.Run("when validating long command (without parameters) then error should be returned", func(t *testing.T) {
dpdk := Dpdk{
AdditionalCommands: []string{
"/test", "/" + strings.Repeat("a", maxCommandLength),
},
}
err := dpdk.validateAdditionalCommands()
require.Error(t, err)
require.Contains(t, err.Error(), "command is too long")
})
t.Run("when validating long command (with params) then error should be returned", func(t *testing.T) {
dpdk := Dpdk{
AdditionalCommands: []string{
"/test", "/," + strings.Repeat("a", maxCommandLengthWithParams),
},
}
err := dpdk.validateAdditionalCommands()
require.Error(t, err)
require.Contains(t, err.Error(), "shall be less than 1024 characters")
})
t.Run("when validating empty command then error should be returned", func(t *testing.T) {
dpdk := Dpdk{
AdditionalCommands: []string{
"/test", "",
},
}
err := dpdk.validateAdditionalCommands()
require.Error(t, err)
require.Contains(t, err.Error(), "got empty command")
})
t.Run("when validating commands with duplicates then duplicates should be removed and no error should be returned", func(t *testing.T) {
dpdk := Dpdk{
AdditionalCommands: []string{
"/test", "/test",
},
}
require.Len(t, dpdk.AdditionalCommands, 2)
err := dpdk.validateAdditionalCommands()
require.Len(t, dpdk.AdditionalCommands, 1)
require.NoError(t, err)
})
}
func prepareEnvironment() (*mocks.Conn, Dpdk, *testutil.Accumulator) {
mockConnection := &mocks.Conn{}
dpdk := Dpdk{
connectors: []*dpdkConnector{{
connection: mockConnection,
initMessage: &initMessage{
Version: "mockedDPDK",
Pid: 1,
MaxOutputLen: 1024,
},
accessTimeout: 2 * time.Second,
}},
Log: testutil.Logger{},
}
mockAcc := &testutil.Accumulator{}
return mockConnection, dpdk, mockAcc
}
func prepareEnvironmentWithMultiSockets() ([]*mocks.Conn, Dpdk, *testutil.Accumulator) {
mockConnections := []*mocks.Conn{{}, {}}
dpdk := Dpdk{
connectors: []*dpdkConnector{
{
connection: mockConnections[0],
initMessage: &initMessage{
Version: "mockedDPDK",
Pid: 1,
MaxOutputLen: 1024,
},
accessTimeout: 2 * time.Second,
},
{
connection: mockConnections[1],
initMessage: &initMessage{
Version: "mockedDPDK",
Pid: 2,
MaxOutputLen: 1024,
},
accessTimeout: 2 * time.Second,
},
},
Log: testutil.Logger{},
}
mockAcc := &testutil.Accumulator{}
return mockConnections, dpdk, mockAcc
}
func prepareEnvironmentWithInitializedMessage(initMsg *initMessage) (*mocks.Conn, Dpdk, *testutil.Accumulator) {
mockConnection := &mocks.Conn{}
dpdk := Dpdk{
connectors: []*dpdkConnector{{
connection: mockConnection,
accessTimeout: 2 * time.Second,
initMessage: initMsg,
}},
Log: testutil.Logger{},
}
mockAcc := &testutil.Accumulator{}
return mockConnection, dpdk, mockAcc
}
func Test_appendCommandsWithParams(t *testing.T) {
t.Run("when got valid data, then valid commands with params should be created", func(t *testing.T) {
mockConn, dpdk, _ := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := `{"/testendpoint": [1,123]}`
simulateResponse(mockConn, response, nil)
expectedCommands := []string{"/action1,1", "/action1,123", "/action2,1", "/action2,123"}
for _, dpdkConn := range dpdk.connectors {
result, err := dpdkConn.appendCommandsWithParamsFromList("/testendpoint", []string{"/action1", "/action2"})
require.NoError(t, err)
require.Len(t, result, 4)
require.ElementsMatch(t, result, expectedCommands)
}
})
}
func Test_getCommandsAndParamsCombinations(t *testing.T) {
t.Run("when 2 ethdev commands are enabled, then 2*numberOfIds new commands should be appended", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q: [1, 123]}`, ethdevListCommand)
simulateResponse(mockConn, response, nil)
expectedCommands := []string{"/ethdev/stats,1", "/ethdev/stats,123", "/ethdev/xstats,1", "/ethdev/xstats,123"}
dpdk.DeviceTypes = []string{"ethdev"}
dpdk.ethdevCommands = []string{"/ethdev/stats", "/ethdev/xstats"}
commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0])
require.ElementsMatch(t, commands, expectedCommands)
require.Empty(t, mockAcc.Errors)
})
t.Run("when 1 rawdev command is enabled, then 2*numberOfIds new commands should be appended", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q: [1, 123]}`, rawdevListCommand)
simulateResponse(mockConn, response, nil)
expectedCommands := []string{"/rawdev/xstats,1", "/rawdev/xstats,123"}
dpdk.DeviceTypes = []string{"rawdev"}
dpdk.rawdevCommands = []string{"/rawdev/xstats"}
commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0])
require.ElementsMatch(t, commands, expectedCommands)
require.Empty(t, mockAcc.Errors)
})
t.Run("when 2 ethdev commands are enabled but one command is disabled, then numberOfIds new commands should be appended", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
response := fmt.Sprintf(`{%q: [1, 123]}`, ethdevListCommand)
simulateResponse(mockConn, response, nil)
expectedCommands := []string{"/ethdev/stats,1", "/ethdev/stats,123"}
dpdk.DeviceTypes = []string{"ethdev"}
dpdk.ethdevCommands = []string{"/ethdev/stats", "/ethdev/xstats"}
var err error
dpdk.ethdevExcludedCommandsFilter, err = filter.Compile([]string{"/ethdev/xstats"})
require.NoError(t, err)
commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0])
require.ElementsMatch(t, commands, expectedCommands)
require.Empty(t, mockAcc.Errors)
})
t.Run("when ethdev commands are enabled but params fetching command returns error then error should be logged in accumulator", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
simulateResponse(mockConn, `{notAJson}`, errors.New("some error"))
dpdk.DeviceTypes = []string{"ethdev"}
dpdk.ethdevCommands = []string{"/ethdev/stats", "/ethdev/xstats"}
commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0])
require.Empty(t, commands)
require.Len(t, mockAcc.Errors, 1)
})
}
func Test_getDpdkInMemorySocketPaths(t *testing.T) {
var err error
t.Run("Should return nil if path doesn't exist", func(t *testing.T) {
dpdk := Dpdk{
SocketPath: "/tmp/nothing-should-exist-here/test.socket",
Log: testutil.Logger{},
}
dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath)
require.NoError(t, err)
socketsPaths := dpdk.getDpdkInMemorySocketPaths()
require.Nil(t, socketsPaths)
})
t.Run("Should return nil if can't read the dir", func(t *testing.T) {
dpdk := Dpdk{
SocketPath: "/root/no_access",
Log: testutil.Logger{},
}
dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath)
require.NoError(t, err)
socketsPaths := dpdk.getDpdkInMemorySocketPaths()
require.Nil(t, socketsPaths)
})
t.Run("Should return one socket from socket path", func(t *testing.T) {
socketPath, _ := createSocketForTest(t, "")
dpdk := Dpdk{
SocketPath: socketPath,
Log: testutil.Logger{},
}
dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath)
require.NoError(t, err)
socketsPaths := dpdk.getDpdkInMemorySocketPaths()
require.Len(t, socketsPaths, 1)
require.Equal(t, socketPath, socketsPaths[0])
})
t.Run("Should return 2 sockets from socket path", func(t *testing.T) {
socketPaths, _ := createMultipleSocketsForTest(t, 2, "")
dpdk := Dpdk{
SocketPath: socketPaths[0],
Log: testutil.Logger{},
}
dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath)
require.NoError(t, err)
socketsPathsFromFunc := dpdk.getDpdkInMemorySocketPaths()
require.Len(t, socketsPathsFromFunc, 2)
require.Equal(t, socketPaths, socketsPathsFromFunc)
})
}
func Test_Gather(t *testing.T) {
t.Run("Gather should return error, because socket weren't created", func(t *testing.T) {
mockAcc := &testutil.Accumulator{}
dpdk := Dpdk{
Log: testutil.Logger{},
PluginOptions: make([]string, 0),
}
require.NoError(t, dpdk.Init())
err := dpdk.Gather(mockAcc)
require.Error(t, err)
require.Contains(t, err.Error(), "couldn't connect to socket")
})
t.Run("Gather shouldn't return error with UnreachableSocketBehavior: Ignore option, because socket weren't created", func(t *testing.T) {
mockAcc := &testutil.Accumulator{}
dpdk := Dpdk{
Log: testutil.Logger{},
UnreachableSocketBehavior: unreachableSocketBehaviorIgnore,
}
require.NoError(t, dpdk.Init())
err := dpdk.Gather(mockAcc)
require.NoError(t, err)
})
t.Run("When parsing a plain json without nested object, then its key should be equal to \"\"", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
dpdk.AdditionalCommands = []string{"/endpoint1"}
simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil)
err := dpdk.Gather(mockAcc)
require.NoError(t, err)
require.Empty(t, mockAcc.Errors)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": "/endpoint1",
"params": "",
},
map[string]interface{}{
"": "myvalue",
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
t.Run("When parsing a list of value in nested object then list should be flattened", func(t *testing.T) {
mockConn, dpdk, mockAcc := prepareEnvironment()
defer mockConn.AssertExpectations(t)
dpdk.AdditionalCommands = []string{"/endpoint1"}
simulateResponse(mockConn, `{"/endpoint1":{"myvalue":[0,1,123]}}`, nil)
err := dpdk.Gather(mockAcc)
require.NoError(t, err)
require.Empty(t, mockAcc.Errors)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": "/endpoint1",
"params": "",
},
map[string]interface{}{
"myvalue_0": float64(0),
"myvalue_1": float64(1),
"myvalue_2": float64(123),
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
t.Run("Test Gather with Metadata Fields dpdk pid and version", func(t *testing.T) {
testInitMessage := &initMessage{
Pid: 100,
Version: "DPDK 21.11.11",
MaxOutputLen: 1024,
}
mockConn, dpdk, mockAcc := prepareEnvironmentWithInitializedMessage(testInitMessage)
dpdk.MetadataFields = []string{dpdkMetadataFieldPidName, dpdkMetadataFieldVersionName}
defer mockConn.AssertExpectations(t)
dpdk.AdditionalCommands = []string{"/endpoint1"}
simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil)
err := dpdk.Gather(mockAcc)
require.NoError(t, err)
require.Empty(t, mockAcc.Errors)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": "/endpoint1",
"params": "",
},
map[string]interface{}{
"": "myvalue",
dpdkMetadataFieldPidName: testInitMessage.Pid,
dpdkMetadataFieldVersionName: testInitMessage.Version,
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
t.Run("Test Gather with Metadata Fields dpdk_pid", func(t *testing.T) {
testInitMessage := &initMessage{
Pid: 100,
Version: "DPDK 21.11.11",
MaxOutputLen: 1024,
}
mockConn, dpdk, mockAcc := prepareEnvironmentWithInitializedMessage(testInitMessage)
dpdk.MetadataFields = []string{dpdkMetadataFieldPidName}
defer mockConn.AssertExpectations(t)
dpdk.AdditionalCommands = []string{"/endpoint1"}
simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil)
err := dpdk.Gather(mockAcc)
require.NoError(t, err)
require.Empty(t, mockAcc.Errors)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": "/endpoint1",
"params": "",
},
map[string]interface{}{
"": "myvalue",
dpdkMetadataFieldPidName: testInitMessage.Pid,
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
t.Run("Test Gather without Metadata Fields", func(t *testing.T) {
testInitMessage := &initMessage{
Pid: 100,
Version: "DPDK 21.11.11",
MaxOutputLen: 1024,
}
mockConn, dpdk, mockAcc := prepareEnvironmentWithInitializedMessage(testInitMessage)
defer mockConn.AssertExpectations(t)
dpdk.AdditionalCommands = []string{"/endpoint1"}
simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil)
err := dpdk.Gather(mockAcc)
require.NoError(t, err)
require.Empty(t, mockAcc.Errors)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": "/endpoint1",
"params": "",
},
map[string]interface{}{
"": "myvalue",
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
}
func Test_Gather_MultiSocket(t *testing.T) {
t.Run("Test Gather without Metadata Fields", func(t *testing.T) {
mockConns, dpdk, mockAcc := prepareEnvironmentWithMultiSockets()
defer func() {
for _, mockConn := range mockConns {
mockConn.AssertExpectations(t)
}
}()
dpdk.AdditionalCommands = []string{"/endpoint1"}
for _, mockConn := range mockConns {
simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil)
}
err := dpdk.Gather(mockAcc)
require.NoError(t, err)
require.Empty(t, mockAcc.Errors)
expected := []telegraf.Metric{
testutil.MustMetric(
"dpdk",
map[string]string{
"command": "/endpoint1",
"params": "",
},
map[string]interface{}{
"": "myvalue",
},
time.Unix(0, 0),
),
testutil.MustMetric(
"dpdk",
map[string]string{
"command": "/endpoint1",
"params": "",
},
map[string]interface{}{
"": "myvalue",
},
time.Unix(0, 0),
),
}
actual := mockAcc.GetTelegrafMetrics()
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
})
}
func simulateResponse(mockConn *mocks.Conn, response string, readErr error) {
mockConn.On("Write", mock.Anything).Return(0, nil)
mockConn.On("Read", mock.Anything).Run(func(arg mock.Arguments) {
elem := arg.Get(0).([]byte)
copy(elem, response)
}).Return(len(response), readErr)
mockConn.On("SetDeadline", mock.Anything).Return(nil)
if readErr != nil {
mockConn.On("Close").Return(nil)
}
}
func createSocketForTest(t *testing.T, dirPath string) (string, net.Listener) {
var err error
var pathToSocket string
if len(dirPath) == 0 {
// The Maximum length of the socket path is 104/108 characters, path created with t.TempDir() is too long for some cases
// (it combines test name with subtest name and some random numbers in the path). Therefore, in this case, it is safer to stick with `os.MkdirTemp()`.
//nolint:usetesting // Ignore "os.MkdirTemp() could be replaced by t.TempDir() in createSocketForTest" finding.
dirPath, err = os.MkdirTemp("", "dpdk-test-socket")
require.NoError(t, err)
pathToSocket = filepath.Join(dirPath, dpdkSocketTemplateName)
} else {
// Create a socket in provided dirPath without duplication (similar to os.CreateTemp without creating a file)
try := 1
for {
pathToSocket = fmt.Sprintf("%s:%d", filepath.Join(dirPath, dpdkSocketTemplateName), try)
if _, err = os.Stat(pathToSocket); err == nil {
if try++; try < 1000 {
continue
}
t.Fatalf("Can't create a temporary file for socket")
}
require.ErrorIs(t, err, os.ErrNotExist)
break
}
}
socket, err := net.Listen("unixpacket", pathToSocket)
require.NoError(t, err)
t.Cleanup(func() {
socket.Close()
os.RemoveAll(dirPath)
})
return pathToSocket, socket
}
func createMultipleSocketsForTest(t *testing.T, numSockets int, dirPath string) (socketsPaths []string, sockets []net.Listener) {
for i := 0; i < numSockets; i++ {
pathToSocket, socket := createSocketForTest(t, dirPath)
dirPath = filepath.Dir(pathToSocket)
socketsPaths = append(socketsPaths, pathToSocket)
sockets = append(sockets, socket)
}
return socketsPaths, sockets
}
func simulateSocketResponse(socket net.Listener, t *testing.T) {
conn, err := socket.Accept()
if err != nil {
t.Error(err)
return
}
initMessage, err := json.Marshal(initMessage{MaxOutputLen: 1})
if err != nil {
t.Error(err)
return
}
if _, err = conn.Write(initMessage); err != nil {
t.Error(err)
return
}
}
func prepareGlob(path string) (*globpath.GlobPath, error) {
return globpath.Compile(path + "*")
}

View file

@ -0,0 +1,136 @@
//go:build linux
package dpdk
import (
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"github.com/influxdata/telegraf/filter"
)
func commandWithParams(command, params string) string {
if params != "" {
return command + "," + params
}
return command
}
func stripParams(command string) string {
index := strings.IndexRune(command, ',')
if index == -1 {
return command
}
return command[:index]
}
// Since DPDK is an open-source project, developers can use their own format of params
// so it could "/command,1,3,5,123" or "/command,userId=1, count=1234".
// To avoid issues with different formats of params, all params are returned as single string
func getParams(command string) string {
index := strings.IndexRune(command, ',')
if index == -1 {
return ""
}
return command[index+1:]
}
// Checks if the provided filePath contains in-memory socket
func isInMemorySocketPath(filePath, socketPath string) bool {
if filePath == socketPath {
return true
}
socketPathPrefix := socketPath + ":"
if strings.HasPrefix(filePath, socketPathPrefix) {
suffix := filePath[len(socketPathPrefix):]
if number, err := strconv.Atoi(suffix); err == nil {
if number > 0 {
return true
}
}
}
return false
}
// Checks if provided path points to socket
func isSocket(path string) error {
pathInfo, err := os.Lstat(path)
if os.IsNotExist(err) {
return fmt.Errorf("provided path does not exist: %q", path)
}
if err != nil {
return fmt.Errorf("cannot get system information of %q file: %w", path, err)
}
if pathInfo.Mode()&os.ModeSocket != os.ModeSocket {
return fmt.Errorf("provided path does not point to a socket file: %q", path)
}
return nil
}
// Converts JSON array containing devices identifiers from DPDK response to string slice
func jsonToArray(input []byte, command string) ([]string, error) {
if len(input) == 0 {
return nil, errors.New("got empty object instead of json")
}
var rawMessage map[string]json.RawMessage
err := json.Unmarshal(input, &rawMessage)
if err != nil {
return nil, err
}
var intArray []int64
err = json.Unmarshal(rawMessage[command], &intArray)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal json response: %w", err)
}
stringArray := make([]string, 0, len(intArray))
for _, value := range intArray {
stringArray = append(stringArray, strconv.FormatInt(value, 10))
}
return stringArray, nil
}
func removeSubset(elements []string, excludedFilter filter.Filter) []string {
if excludedFilter == nil {
return elements
}
var result []string
for _, element := range elements {
if !excludedFilter.Match(element) {
result = append(result, element)
}
}
return result
}
func uniqueValues(values []string) []string {
in := make(map[string]bool)
result := make([]string, 0, len(values))
for _, value := range values {
if !in[value] {
in[value] = true
result = append(result, value)
}
}
return result
}
func isEmpty(value interface{}) bool {
return value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil())
}

View file

@ -0,0 +1,129 @@
//go:build linux
package dpdk
import (
"fmt"
"os"
"strconv"
"testing"
"github.com/stretchr/testify/require"
)
func Test_isSocket(t *testing.T) {
t.Run("when path points to non-existing file then error should be returned", func(t *testing.T) {
err := isSocket("/tmp/file-that-doesnt-exists")
require.Error(t, err)
require.Contains(t, err.Error(), "provided path does not exist")
})
t.Run("Should pass if path points to socket", func(t *testing.T) {
pathToSocket, socket := createSocketForTest(t, "")
defer socket.Close()
err := isSocket(pathToSocket)
require.NoError(t, err)
})
t.Run("if path points to regular file instead of socket then error should be returned", func(t *testing.T) {
pathToFile := "/tmp/dpdk-text-file.txt"
_, err := os.Create(pathToFile)
require.NoError(t, err)
defer os.Remove(pathToFile)
err = isSocket(pathToFile)
require.Error(t, err)
require.Contains(t, err.Error(), "provided path does not point to a socket file")
})
}
func Test_stripParams(t *testing.T) {
command := "/mycommand"
params := "myParams"
t.Run("when passed string without params then passed string should be returned", func(t *testing.T) {
strippedCommand := stripParams(command)
require.Equal(t, command, strippedCommand)
})
t.Run("when passed string with params then string without params should be returned", func(t *testing.T) {
strippedCommand := stripParams(commandWithParams(command, params))
require.Equal(t, command, strippedCommand)
})
}
func Test_commandWithParams(t *testing.T) {
command := "/mycommand"
params := "myParams"
t.Run("when passed string with params then command with comma should be returned", func(t *testing.T) {
commandWithParams := commandWithParams(command, params)
require.Equal(t, command+","+params, commandWithParams)
})
t.Run("when passed command with no params then command should be returned", func(t *testing.T) {
commandWithParams := commandWithParams(command, "")
require.Equal(t, command, commandWithParams)
})
}
func Test_getParams(t *testing.T) {
command := "/mycommand"
params := "myParams"
t.Run("when passed string with params then command with comma should be returned", func(t *testing.T) {
commandParams := getParams(commandWithParams(command, params))
require.Equal(t, params, commandParams)
})
t.Run("when passed command with no params then empty string (representing empty params) should be returned", func(t *testing.T) {
commandParams := getParams(commandWithParams(command, ""))
require.Empty(t, commandParams)
})
}
func Test_jsonToArray(t *testing.T) {
key := "/ethdev/list"
t.Run("when got numeric array then string array should be returned", func(t *testing.T) {
firstValue := int64(0)
secondValue := int64(1)
jsonString := fmt.Sprintf(`{%q: [%d, %d]}`, key, firstValue, secondValue)
arr, err := jsonToArray([]byte(jsonString), key)
require.NoError(t, err)
require.Equal(t, strconv.FormatInt(firstValue, 10), arr[0])
require.Equal(t, strconv.FormatInt(secondValue, 10), arr[1])
})
t.Run("if non-json string is supplied as input then error should be returned", func(t *testing.T) {
_, err := jsonToArray([]byte("{notAJson}"), key)
require.Error(t, err)
})
t.Run("when empty string is supplied as input then error should be returned", func(t *testing.T) {
jsonString := ""
_, err := jsonToArray([]byte(jsonString), key)
require.Error(t, err)
require.Contains(t, err.Error(), "got empty object instead of json")
})
t.Run("when valid json with json-object is supplied as input then error should be returned", func(t *testing.T) {
jsonString := fmt.Sprintf(`{%q: {"testKey": "testValue"}}`, key)
_, err := jsonToArray([]byte(jsonString), key)
require.Error(t, err)
require.Contains(t, err.Error(), "failed to unmarshal json response")
})
}

View file

@ -0,0 +1,197 @@
// Code generated by mockery v2.46.3. DO NOT EDIT.
package mocks
import (
net "net"
time "time"
mock "github.com/stretchr/testify/mock"
)
// Conn is an autogenerated mock type for the Conn type
type Conn struct {
mock.Mock
}
// Close provides a mock function with given fields:
func (_m *Conn) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// LocalAddr provides a mock function with given fields:
func (_m *Conn) LocalAddr() net.Addr {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for LocalAddr")
}
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Addr)
}
}
return r0
}
// Read provides a mock function with given fields: b
func (_m *Conn) Read(b []byte) (int, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for Read")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
return rf(b)
}
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(b)
} else {
r0 = ret.Get(0).(int)
}
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(b)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoteAddr provides a mock function with given fields:
func (_m *Conn) RemoteAddr() net.Addr {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for RemoteAddr")
}
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Addr)
}
}
return r0
}
// SetDeadline provides a mock function with given fields: t
func (_m *Conn) SetDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetReadDeadline provides a mock function with given fields: t
func (_m *Conn) SetReadDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetReadDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetWriteDeadline provides a mock function with given fields: t
func (_m *Conn) SetWriteDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetWriteDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// Write provides a mock function with given fields: b
func (_m *Conn) Write(b []byte) (int, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for Write")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
return rf(b)
}
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(b)
} else {
r0 = ret.Get(0).(int)
}
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(b)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewConn creates a new instance of Conn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewConn(t interface {
mock.TestingT
Cleanup(func())
}) *Conn {
mock := &Conn{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -0,0 +1,58 @@
# Reads metrics from DPDK applications using v2 telemetry interface.
# This plugin ONLY supports Linux
[[inputs.dpdk]]
## Path to DPDK telemetry socket. This shall point to v2 version of DPDK
## telemetry interface.
# socket_path = "/var/run/dpdk/rte/dpdk_telemetry.v2"
## Duration that defines how long the connected socket client will wait for
## a response before terminating connection.
## This includes both writing to and reading from socket. Since it's local
## socket access to a fast packet processing application, the timeout should
## be sufficient for most users.
## Setting the value to 0 disables the timeout (not recommended)
# socket_access_timeout = "200ms"
## Enables telemetry data collection for selected device types.
## Adding "ethdev" enables collection of telemetry from DPDK NICs (stats, xstats, link_status, info).
## Adding "rawdev" enables collection of telemetry from DPDK Raw Devices (xstats).
# device_types = ["ethdev"]
## List of custom, application-specific telemetry commands to query
## The list of available commands depend on the application deployed.
## Applications can register their own commands via telemetry library API
## https://doc.dpdk.org/guides/prog_guide/telemetry_lib.html#registering-commands
## For L3 Forwarding with Power Management Sample Application this could be:
## additional_commands = ["/l3fwd-power/stats"]
# additional_commands = []
## List of plugin options.
## Supported options:
## - "in_memory" option enables reading for multiple sockets when a dpdk application is running with --in-memory option.
## When option is enabled plugin will try to find additional socket paths related to provided socket_path.
## Details: https://doc.dpdk.org/guides/howto/telemetry.html#connecting-to-different-dpdk-processes
# plugin_options = ["in_memory"]
## Specifies plugin behavior regarding unreachable socket (which might not have been initialized yet).
## Available choices:
## - error: Telegraf will return an error during the startup and gather phases if socket is unreachable
## - ignore: Telegraf will ignore error regarding unreachable socket on both startup and gather
# unreachable_socket_behavior = "error"
## List of metadata fields which will be added to every metric produced by the plugin.
## Supported options:
## - "pid" - exposes PID of DPDK process. Example: pid=2179660i
## - "version" - exposes version of DPDK. Example: version="DPDK 21.11.2"
# metadata_fields = ["pid", "version"]
## Allows turning off collecting data for individual "ethdev" commands.
## Remove "/ethdev/link_status" from list to gather link status metrics.
[inputs.dpdk.ethdev]
exclude_commands = ["/ethdev/link_status"]
## When running multiple instances of the plugin it's recommended to add a
## unique tag to each instance to identify metrics exposed by an instance
## of DPDK application. This is useful when multiple DPDK apps run on a
## single host.
## [inputs.dpdk.tags]
## dpdk_instance = "my-fwd-app"