Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
129
plugins/inputs/intel_baseband/README.md
Normal file
129
plugins/inputs/intel_baseband/README.md
Normal file
|
@ -0,0 +1,129 @@
|
|||
# Intel Baseband Accelerator Input Plugin
|
||||
|
||||
This plugin collects metrics from both dedicated and integrated Intel devices
|
||||
providing Wireless Baseband hardware acceleration. These devices play a key role
|
||||
in accelerating 5G and 4G Virtualized Radio Access Networks (vRAN) workloads,
|
||||
increasing the overall compute capacity of commercial, off-the-shelf platforms
|
||||
by integrating e.g.
|
||||
|
||||
- Forward Error Correction (FEC) processing,
|
||||
- 4G Turbo FEC processing,
|
||||
- 5G Low Density Parity Check (LDPC)
|
||||
- Fast Fourier Transform (FFT) block providing DFT/iDFT processing offload for
|
||||
the 5G Sounding Reference Signal (SRS)
|
||||
|
||||
⭐ Telegraf v1.27.0
|
||||
🏷️ hardware, network, system
|
||||
💻 linux
|
||||
|
||||
## Requirements
|
||||
|
||||
- supported Intel Baseband device installed and configured
|
||||
- Linux kernel 5.7+
|
||||
- [pf-bb-config](https://github.com/intel/pf-bb-config) (version >= v23.03)
|
||||
installed and running
|
||||
|
||||
This plugin supports the following hardware:
|
||||
|
||||
- Intel® vRAN Boost integrated accelerators:
|
||||
- 4th Gen Intel® Xeon® Scalable processor with Intel® vRAN Boost
|
||||
(also known as Sapphire Rapids Edge Enhanced / SPR-EE)
|
||||
- External expansion cards connected to the PCI bus:
|
||||
- Intel® vRAN Dedicated Accelerator ACC100 SoC (code named Mount Bryce)
|
||||
|
||||
For more information regarding system configuration, please follow DPDK
|
||||
installation guides:
|
||||
|
||||
- [Intel® vRAN Boost Poll Mode Driver (PMD)][VRB1]
|
||||
- [Intel® ACC100 5G/4G FEC Poll Mode Drivers][ACC100]
|
||||
|
||||
[VRB1]: https://doc.dpdk.org/guides/bbdevs/vrb1.html#installation
|
||||
[ACC100]: https://doc.dpdk.org/guides/bbdevs/acc100.html#installation
|
||||
|
||||
## 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
|
||||
# Intel Baseband Accelerator Input Plugin collects metrics from both dedicated and integrated
|
||||
# Intel devices that provide Wireless Baseband hardware acceleration.
|
||||
# This plugin ONLY supports Linux.
|
||||
[[inputs.intel_baseband]]
|
||||
## Path to socket exposed by pf-bb-config for CLI interaction (mandatory).
|
||||
## In version v23.03 of pf-bb-config the path is created according to the schema:
|
||||
## "/tmp/pf_bb_config.0000\:<b>\:<d>.<f>.sock" where 0000\:<b>\:<d>.<f> is the PCI device ID.
|
||||
socket_path = ""
|
||||
|
||||
## Path to log file exposed by pf-bb-config with telemetry to read (mandatory).
|
||||
## In version v23.03 of pf-bb-config the path is created according to the schema:
|
||||
## "/var/log/pf_bb_cfg_0000\:<b>\:<d>.<f>.log" where 0000\:<b>\:<d>.<f> is the PCI device ID.
|
||||
log_file_path = ""
|
||||
|
||||
## Specifies plugin behavior regarding unreachable socket (which might not have been initialized yet).
|
||||
## Available choices:
|
||||
## - error: Telegraf will return an error on startup if socket is unreachable
|
||||
## - ignore: Telegraf will ignore error regarding unreachable socket on both startup and gather
|
||||
# unreachable_socket_behavior = "error"
|
||||
|
||||
## Duration that defines how long the connected socket client will wait for
|
||||
## a response before terminating connection.
|
||||
## 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 = "1s"
|
||||
|
||||
## Duration that defines maximum time plugin will wait for pf-bb-config to write telemetry to the log file.
|
||||
## Timeout may differ depending on the environment.
|
||||
## Must be equal or larger than 50ms.
|
||||
# wait_for_telemetry_timeout = "1s"
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
Depending on version of Intel Baseband device and version of pf-bb-config,
|
||||
subset of following measurements may be exposed:
|
||||
|
||||
**The following tags and fields are supported by Intel Baseband plugin:**
|
||||
|
||||
| Tag | Description |
|
||||
|-------------|-------------------------------------------------------------|
|
||||
| `metric` | Type of metric : "code_blocks", "data_bytes", "per_engine". |
|
||||
| `operation` | Type of operation: "5GUL", "5GDL", "4GUL", "4GDL", "FFT". |
|
||||
| `vf` | Virtual Function number. |
|
||||
| `engine` | Engine number. |
|
||||
|
||||
| Metric name (field) | Description |
|
||||
|----------------------|-------------------------------------------------------------------|
|
||||
| `value` | Metric value for a given operation (non-negative integer, gauge). |
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
intel_baseband,host=ubuntu,metric=code_blocks,operation=5GUL,vf=0 value=54i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=code_blocks,operation=5GDL,vf=0 value=0i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=code_blocks,operation=FFT,vf=0 value=0i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=code_blocks,operation=5GUL,vf=1 value=0i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=code_blocks,operation=5GDL,vf=1 value=32i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=code_blocks,operation=FFT,vf=1 value=0i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=data_bytes,operation=5GUL,vf=0 value=18560i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=data_bytes,operation=5GDL,vf=0 value=0i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=data_bytes,operation=FFT,vf=0 value=0i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=data_bytes,operation=5GUL,vf=1 value=0i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=data_bytes,operation=5GDL,vf=1 value=86368i 1685695885000000000
|
||||
intel_baseband,host=ubuntu,metric=data_bytes,operation=FFT,vf=1 value=0i 1685695885000000000
|
||||
intel_baseband,engine=0,host=ubuntu,metric=per_engine,operation=5GUL value=72i 1685695885000000000
|
||||
intel_baseband,engine=1,host=ubuntu,metric=per_engine,operation=5GUL value=72i 1685695885000000000
|
||||
intel_baseband,engine=2,host=ubuntu,metric=per_engine,operation=5GUL value=72i 1685695885000000000
|
||||
intel_baseband,engine=3,host=ubuntu,metric=per_engine,operation=5GUL value=72i 1685695885000000000
|
||||
intel_baseband,engine=4,host=ubuntu,metric=per_engine,operation=5GUL value=72i 1685695885000000000
|
||||
intel_baseband,engine=0,host=ubuntu,metric=per_engine,operation=5GDL value=132i 1685695885000000000
|
||||
intel_baseband,engine=1,host=ubuntu,metric=per_engine,operation=5GDL value=130i 1685695885000000000
|
||||
intel_baseband,engine=0,host=ubuntu,metric=per_engine,operation=FFT value=0i 1685695885000000000
|
||||
```
|
227
plugins/inputs/intel_baseband/intel_baseband.go
Normal file
227
plugins/inputs/intel_baseband/intel_baseband.go
Normal file
|
@ -0,0 +1,227 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
const (
|
||||
// plugin name. Exposed with all metrics
|
||||
pluginName = "intel_baseband"
|
||||
|
||||
// VF Metrics
|
||||
vfCodeBlocks = "Code Blocks"
|
||||
vfDataBlock = "Data (Bytes)"
|
||||
|
||||
// Engine Metrics
|
||||
engineBlock = "Per Engine"
|
||||
|
||||
// Socket extensions
|
||||
socketExtension = ".sock"
|
||||
logFileExtension = ".log"
|
||||
|
||||
// UnreachableSocketBehavior Values
|
||||
unreachableSocketBehaviorError = "error"
|
||||
unreachableSocketBehaviorIgnore = "ignore"
|
||||
|
||||
defaultAccessSocketTimeout = config.Duration(time.Second)
|
||||
defaultWaitForTelemetryTimeout = config.Duration(time.Second)
|
||||
)
|
||||
|
||||
type Baseband struct {
|
||||
// required params
|
||||
SocketPath string `toml:"socket_path"`
|
||||
FileLogPath string `toml:"log_file_path"`
|
||||
|
||||
// optional params
|
||||
UnreachableSocketBehavior string `toml:"unreachable_socket_behavior"`
|
||||
SocketAccessTimeout config.Duration `toml:"socket_access_timeout"`
|
||||
WaitForTelemetryTimeout config.Duration `toml:"wait_for_telemetry_timeout"`
|
||||
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
logConn *logConnector
|
||||
sockConn *socketConnector
|
||||
}
|
||||
|
||||
func (*Baseband) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (b *Baseband) Init() error {
|
||||
if b.SocketAccessTimeout < 0 {
|
||||
return errors.New("socket_access_timeout should be positive number or equal to 0 (to disable timeouts)")
|
||||
}
|
||||
|
||||
waitForTelemetryDuration := time.Duration(b.WaitForTelemetryTimeout)
|
||||
if waitForTelemetryDuration < 50*time.Millisecond {
|
||||
return errors.New("wait_for_telemetry_timeout should be equal or larger than 50ms")
|
||||
}
|
||||
|
||||
// Filling default values
|
||||
// Check UnreachableSocketBehavior
|
||||
switch b.UnreachableSocketBehavior {
|
||||
case "":
|
||||
b.UnreachableSocketBehavior = unreachableSocketBehaviorError
|
||||
case unreachableSocketBehaviorError, unreachableSocketBehaviorIgnore:
|
||||
// Valid options, do nothing
|
||||
default:
|
||||
return fmt.Errorf("unknown choice for unreachable_socket_behavior: %q", b.UnreachableSocketBehavior)
|
||||
}
|
||||
|
||||
var err error
|
||||
// Validate Socket path
|
||||
if b.SocketPath, err = b.checkFilePath(b.SocketPath, socket); err != nil {
|
||||
return fmt.Errorf("socket_path: %w", err)
|
||||
}
|
||||
|
||||
// Validate log file path
|
||||
if b.FileLogPath, err = b.checkFilePath(b.FileLogPath, log); err != nil {
|
||||
return fmt.Errorf("log_file_path: %w", err)
|
||||
}
|
||||
|
||||
// Create Log Connector
|
||||
b.logConn = newLogConnector(b.FileLogPath, waitForTelemetryDuration)
|
||||
|
||||
// Create Socket Connector
|
||||
b.sockConn = newSocketConnector(b.SocketPath, time.Duration(b.SocketAccessTimeout))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Baseband) Gather(acc telegraf.Accumulator) error {
|
||||
err := b.sockConn.dumpTelemetryToLog()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the log
|
||||
err = b.logConn.readLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.logConn.readNumVFs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get the number of VFs: %w", err)
|
||||
}
|
||||
// b.numVFs less than 0 means that we are reading the file for the first time (or occurred discontinuity in file availability)
|
||||
if b.logConn.getNumVFs() <= 0 {
|
||||
return errors.New("error in accessing information about the amount of VF")
|
||||
}
|
||||
|
||||
// rawData eg: 12 0
|
||||
if err = b.gatherVFMetric(acc, vfCodeBlocks); err != nil {
|
||||
return fmt.Errorf("couldn't get %q metric: %w", vfCodeBlocks, err)
|
||||
}
|
||||
|
||||
// rawData eg: 12 0
|
||||
if err = b.gatherVFMetric(acc, vfDataBlock); err != nil {
|
||||
return fmt.Errorf("couldn't get %q metric: %w", vfDataBlock, err)
|
||||
}
|
||||
|
||||
// rawData eg: 12 0 0 0 0 0
|
||||
if err = b.gatherEngineMetric(acc, engineBlock); err != nil {
|
||||
return fmt.Errorf("couldn't get %q metric: %w", engineBlock, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Baseband) gatherVFMetric(acc telegraf.Accumulator, metricName string) error {
|
||||
metrics, err := b.logConn.getMetrics(metricName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error accessing information about the metric %q: %w", metricName, err)
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
if len(metric.data) != b.logConn.getNumVFs() {
|
||||
return fmt.Errorf("data is inconsistent, number of metrics in the file for %d VFs, the number of VFs read is %d",
|
||||
len(metric.data), b.logConn.numVFs)
|
||||
}
|
||||
|
||||
for i := range metric.data {
|
||||
value, err := logMetricDataToValue(metric.data[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"value": value,
|
||||
}
|
||||
tags := map[string]string{
|
||||
"operation": metric.operationName,
|
||||
"metric": metricNameToTagName(metricName),
|
||||
"vf": strconv.Itoa(i),
|
||||
}
|
||||
acc.AddGauge(pluginName, fields, tags)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Baseband) gatherEngineMetric(acc telegraf.Accumulator, metricName string) error {
|
||||
metrics, err := b.logConn.getMetrics(metricName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in accessing information about the metric %q: %w", metricName, err)
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
for i := range metric.data {
|
||||
value, err := logMetricDataToValue(metric.data[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"value": value,
|
||||
}
|
||||
tags := map[string]string{
|
||||
"operation": metric.operationName,
|
||||
"metric": metricNameToTagName(metricName),
|
||||
"engine": strconv.Itoa(i),
|
||||
}
|
||||
acc.AddGauge(pluginName, fields, tags)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate the provided path and return the clean version of it
|
||||
// if UnreachableSocketBehavior = error -> return error, otherwise ignore the error
|
||||
func (b *Baseband) checkFilePath(path string, fileType fileType) (resultPath string, err error) {
|
||||
if resultPath, err = validatePath(path, fileType); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = checkFile(path, fileType); err != nil {
|
||||
if b.UnreachableSocketBehavior == unreachableSocketBehaviorError {
|
||||
return "", err
|
||||
}
|
||||
b.Log.Warn(err)
|
||||
}
|
||||
return resultPath, nil
|
||||
}
|
||||
|
||||
func newBaseband() *Baseband {
|
||||
return &Baseband{
|
||||
SocketAccessTimeout: defaultAccessSocketTimeout,
|
||||
WaitForTelemetryTimeout: defaultWaitForTelemetryTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("intel_baseband", func() telegraf.Input {
|
||||
return newBaseband()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
//go:build !linux || !amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
type Baseband struct {
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
}
|
||||
|
||||
func (*Baseband) SampleConfig() string { return sampleConfig }
|
||||
|
||||
func (b *Baseband) Init() error {
|
||||
b.Log.Warn("Current platform is not supported")
|
||||
return nil
|
||||
}
|
||||
func (*Baseband) Gather(_ telegraf.Accumulator) error { return nil }
|
||||
|
||||
func init() {
|
||||
inputs.Add("intel_baseband", func() telegraf.Input {
|
||||
return &Baseband{}
|
||||
})
|
||||
}
|
179
plugins/inputs/intel_baseband/intel_baseband_test.go
Normal file
179
plugins/inputs/intel_baseband/intel_baseband_test.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
t.Run("with not specified path values Init should return an error", func(t *testing.T) {
|
||||
baseband := prepareBasebandEnvironment()
|
||||
require.NotNil(t, baseband)
|
||||
|
||||
err := baseband.Init()
|
||||
|
||||
// check default variables
|
||||
// check empty values
|
||||
require.Empty(t, baseband.SocketPath)
|
||||
require.Empty(t, baseband.FileLogPath)
|
||||
|
||||
// UnreachableSocketBehavior variable should be = unreachableSocketBehaviorError
|
||||
require.Equal(t, unreachableSocketBehaviorError, baseband.UnreachableSocketBehavior)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "path not specified")
|
||||
})
|
||||
|
||||
t.Run("with only SocketPath provided the plugin should return the error", func(t *testing.T) {
|
||||
baseband := prepareBasebandEnvironment()
|
||||
require.NotNil(t, baseband)
|
||||
|
||||
tempSocket := newTempSocket(t)
|
||||
defer tempSocket.Close()
|
||||
|
||||
baseband.SocketPath = tempSocket.pathToSocket
|
||||
err := baseband.Init()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "log_file_path")
|
||||
require.ErrorContains(t, err, "path not specified")
|
||||
})
|
||||
|
||||
t.Run("with SocketAccessTimeout less then 0 provided the plugin should return the error", func(t *testing.T) {
|
||||
baseband := prepareBasebandEnvironment()
|
||||
require.NotNil(t, baseband)
|
||||
|
||||
baseband.SocketAccessTimeout = -1
|
||||
err := baseband.Init()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "socket_access_timeout should be positive number or equal to 0")
|
||||
})
|
||||
|
||||
t.Run("with SocketPath and LogPath provided the plugin shouldn't return any errors", func(t *testing.T) {
|
||||
baseband := prepareBasebandEnvironment()
|
||||
require.NotNil(t, baseband)
|
||||
|
||||
tempSocket := newTempSocket(t)
|
||||
defer tempSocket.Close()
|
||||
|
||||
logTempFile := newTempLogFile(t)
|
||||
defer logTempFile.close()
|
||||
|
||||
baseband.SocketPath = tempSocket.pathToSocket
|
||||
baseband.FileLogPath = logTempFile.pathToFile
|
||||
err := baseband.Init()
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("with unknown option for UnreachableSocketBehavior plugin should return the error", func(t *testing.T) {
|
||||
baseband := prepareBasebandEnvironment()
|
||||
require.NotNil(t, baseband)
|
||||
|
||||
baseband.UnreachableSocketBehavior = "UnknownRandomString"
|
||||
err := baseband.Init()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "unreachable_socket_behavior")
|
||||
})
|
||||
|
||||
t.Run("with error option for UnreachableSocketBehavior plugin should return error", func(t *testing.T) {
|
||||
baseband := prepareBasebandEnvironment()
|
||||
require.NotNil(t, baseband)
|
||||
|
||||
baseband.UnreachableSocketBehavior = unreachableSocketBehaviorError
|
||||
baseband.SocketPath = "/some/random/path/test.sock"
|
||||
baseband.FileLogPath = "/some/random/path/test.log"
|
||||
|
||||
err := baseband.Init()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "socket_path")
|
||||
require.ErrorContains(t, err, "provided path does not exist")
|
||||
})
|
||||
|
||||
t.Run("with ignore option for UnreachableSocketBehavior plugin shouldn't return any errors", func(t *testing.T) {
|
||||
baseband := prepareBasebandEnvironment()
|
||||
require.NotNil(t, baseband)
|
||||
|
||||
baseband.UnreachableSocketBehavior = unreachableSocketBehaviorIgnore
|
||||
baseband.SocketPath = "/some/random/path/test.sock"
|
||||
baseband.FileLogPath = "/some/random/path/test.log"
|
||||
|
||||
err := baseband.Init()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// Test Socket
|
||||
type tempSocket struct {
|
||||
pathToSocket string
|
||||
socket net.Listener
|
||||
|
||||
dirPath string
|
||||
}
|
||||
|
||||
func (ts *tempSocket) Close() {
|
||||
var err error
|
||||
if err = ts.socket.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = os.RemoveAll(ts.dirPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newTempSocket(t *testing.T) *tempSocket {
|
||||
// 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 newTempSocket" finding.
|
||||
dirPath, err := os.MkdirTemp("", "test-socket")
|
||||
require.NoError(t, err)
|
||||
|
||||
pathToSocket := filepath.Join(dirPath, "test"+socketExtension)
|
||||
socket, err := net.Listen("unix", pathToSocket)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &tempSocket{
|
||||
dirPath: dirPath,
|
||||
pathToSocket: pathToSocket,
|
||||
socket: socket,
|
||||
}
|
||||
}
|
||||
|
||||
type tempLogFile struct {
|
||||
pathToFile string
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func (tlf *tempLogFile) close() {
|
||||
var err error
|
||||
if err = tlf.file.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = os.Remove(tlf.pathToFile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newTempLogFile(t *testing.T) *tempLogFile {
|
||||
file, err := os.CreateTemp(t.TempDir(), "*.log")
|
||||
require.NoError(t, err)
|
||||
|
||||
return &tempLogFile{
|
||||
pathToFile: file.Name(),
|
||||
file: file,
|
||||
}
|
||||
}
|
||||
|
||||
func prepareBasebandEnvironment() *Baseband {
|
||||
b := newBaseband()
|
||||
b.Log = testutil.Logger{Name: "BasebandPluginTest"}
|
||||
return b
|
||||
}
|
273
plugins/inputs/intel_baseband/log_connector.go
Normal file
273
plugins/inputs/intel_baseband/log_connector.go
Normal file
|
@ -0,0 +1,273 @@
|
|||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
infoLine = "INFO:"
|
||||
countersLine = "counters:"
|
||||
|
||||
deviceStatusStartPrefix = "Device Status::"
|
||||
deviceStatusEndPrefix = "VFs"
|
||||
|
||||
clearLogCmdText = "clear_log"
|
||||
)
|
||||
|
||||
var errFindingSubstring = errors.New("couldn't find the substring in the log file")
|
||||
|
||||
type logConnector struct {
|
||||
// path to log
|
||||
path string
|
||||
|
||||
// Num of VFs
|
||||
numVFs int
|
||||
|
||||
// Log file data
|
||||
lines []string
|
||||
|
||||
waitForTelemetryTimeout time.Duration
|
||||
lastModTime time.Time
|
||||
}
|
||||
|
||||
type logMetric struct {
|
||||
operationName string
|
||||
data []string
|
||||
}
|
||||
|
||||
// Try to read file and fill the .lines field.
|
||||
func (lc *logConnector) readLogFile() error {
|
||||
err := lc.checkLogFreshness()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.ReadFile(lc.path)
|
||||
if err != nil {
|
||||
lc.numVFs = -1
|
||||
return fmt.Errorf("couldn't read log file: %w", err)
|
||||
}
|
||||
|
||||
// Example content of the metric file is located in testdata/example.log
|
||||
// the minimum acceptable file content consists of three lines:
|
||||
// - one line for number of VFs
|
||||
// - two lines for operation (counters name and metrics value)
|
||||
lines := strings.Split(string(file), "\n")
|
||||
if len(lines) < 3 {
|
||||
return errors.New("log file is incomplete")
|
||||
}
|
||||
|
||||
lc.lines = lines
|
||||
return nil
|
||||
}
|
||||
|
||||
// function checks whether the data in the log file were updated by checking the last modification date and size
|
||||
func (lc *logConnector) checkLogFreshness() error {
|
||||
start := time.Now()
|
||||
|
||||
// initial wait for telemetry being written to file
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// check if it was written completely
|
||||
for {
|
||||
fileInfo, err := os.Stat(lc.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't stat log file: %w", err)
|
||||
}
|
||||
currModTime := fileInfo.ModTime()
|
||||
|
||||
// pf-bb-config first performs log clearing (which will write clear_log line to this log just before it will be cleared),
|
||||
// and then dumps the newest telemetry to this file.
|
||||
// This checks if:
|
||||
// - modification time has been changed
|
||||
// - file is not empty
|
||||
// - file doesn't contain clear_log command (it may appear for few milliseconds, just before file is cleared)
|
||||
if !lc.lastModTime.Equal(currModTime) && fileInfo.Size() != 0 && !lc.isClearLogContainedInFile() {
|
||||
// refreshing succeed
|
||||
lc.lastModTime = currModTime
|
||||
return nil
|
||||
}
|
||||
if time.Since(start) >= lc.waitForTelemetryTimeout {
|
||||
if fileInfo.Size() == 0 {
|
||||
return errors.New("log file is empty")
|
||||
}
|
||||
return errors.New("failed to refresh telemetry data")
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (lc *logConnector) isClearLogContainedInFile() bool {
|
||||
file, err := os.ReadFile(lc.path)
|
||||
if err != nil {
|
||||
// for now, error means that "clear_log" line is not contained in log
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Contains(string(file), clearLogCmdText)
|
||||
}
|
||||
|
||||
// Try to read file and return lines from it
|
||||
func (lc *logConnector) getLogLines() []string {
|
||||
return lc.lines
|
||||
}
|
||||
|
||||
// Try to read file and return lines from it
|
||||
func (lc *logConnector) getLogLinesNum() int {
|
||||
return len(lc.lines)
|
||||
}
|
||||
|
||||
// Return the number of VFs in the log file
|
||||
func (lc *logConnector) getNumVFs() int {
|
||||
return lc.numVFs
|
||||
}
|
||||
|
||||
// find a line which contains Device Status. Example = Thu Apr 13 13:28:40 2023:INFO:Device Status:: 2 VFs
|
||||
func (lc *logConnector) readNumVFs() error {
|
||||
for _, line := range lc.lines {
|
||||
if !strings.Contains(line, deviceStatusStartPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
numVFs, err := parseNumVFs(line)
|
||||
if err != nil {
|
||||
lc.numVFs = -1
|
||||
return err
|
||||
}
|
||||
lc.numVFs = numVFs
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("numVFs data wasn't found in the log file")
|
||||
}
|
||||
|
||||
// Find a line which contains a substring in the log file
|
||||
func (lc *logConnector) getSubstringLine(offsetLine int, substring string) (int, string, error) {
|
||||
if len(substring) == 0 {
|
||||
return 0, "", errors.New("substring is empty")
|
||||
}
|
||||
|
||||
for i := offsetLine; i < len(lc.lines); i++ {
|
||||
if !strings.Contains(lc.lines[i], substring) {
|
||||
continue
|
||||
}
|
||||
|
||||
return i, lc.lines[i], nil
|
||||
}
|
||||
return 0, "", fmt.Errorf("%q: %w", substring, errFindingSubstring)
|
||||
}
|
||||
|
||||
func (lc *logConnector) getMetrics(name string) (metrics []*logMetric, err error) {
|
||||
offset := 0
|
||||
for {
|
||||
currOffset, metric, err := lc.getMetric(offset, name)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errFindingSubstring) || len(metrics) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
metrics = append(metrics, metric)
|
||||
offset = currOffset
|
||||
}
|
||||
}
|
||||
|
||||
// Example of log file:
|
||||
// Thu May 18 08:45:15 2023:INFO:5GUL counters: Code Blocks
|
||||
// Thu May 18 08:45:15 2023:INFO:0 0
|
||||
// Input: offsetLine, metric name (Code Blocks)
|
||||
// Func will return: current offset after reading the metric (2), metric with operation name and data(5GUL, ["0", "0"]) and error
|
||||
func (lc *logConnector) getMetric(offsetLine int, name string) (int, *logMetric, error) {
|
||||
i, line, err := lc.getSubstringLine(offsetLine, name)
|
||||
if err != nil {
|
||||
return offsetLine, nil, err
|
||||
}
|
||||
|
||||
operationName := parseOperationName(line)
|
||||
if len(operationName) == 0 {
|
||||
return offsetLine, nil, errors.New("valid operation name wasn't found in log")
|
||||
}
|
||||
|
||||
if lc.getLogLinesNum() <= i+1 {
|
||||
return offsetLine, nil,
|
||||
fmt.Errorf("the content of the log file is incorrect, line which contains key word %q can't be the last one in log", countersLine)
|
||||
}
|
||||
|
||||
// infoData eg: Thu Apr 13 13:28:40 2023:INFO:12 0
|
||||
infoData := strings.Split(lc.lines[i+1], infoLine)
|
||||
if len(infoData) != 2 {
|
||||
// info data must be in format : some data + keyword "INFO:" + metrics
|
||||
return offsetLine, nil, fmt.Errorf("the content of the log file is incorrect, couldn't find %q separator", infoLine)
|
||||
}
|
||||
|
||||
dataRaw := strings.TrimSpace(infoData[1])
|
||||
if len(dataRaw) == 0 {
|
||||
return offsetLine, nil, errors.New("the content of the log file is incorrect, metric's data is incorrect")
|
||||
}
|
||||
|
||||
data := strings.Split(dataRaw, " ")
|
||||
for i := range data {
|
||||
if len(data[i]) == 0 {
|
||||
return offsetLine, nil, errors.New("the content of the log file is incorrect, metric's data is empty")
|
||||
}
|
||||
}
|
||||
return i + 2, &logMetric{operationName: operationName, data: data}, nil
|
||||
}
|
||||
|
||||
// Example value = Thu Apr 13 13:28:40 2023:INFO:Device Status:: 2 VFs
|
||||
func parseNumVFs(s string) (int, error) {
|
||||
i := strings.LastIndex(s, deviceStatusStartPrefix)
|
||||
if i == -1 {
|
||||
return 0, errors.New("couldn't find device status prefix in line")
|
||||
}
|
||||
|
||||
j := strings.Index(s[i:], deviceStatusEndPrefix)
|
||||
if j == -1 {
|
||||
return 0, errors.New("couldn't find device end prefix in line")
|
||||
}
|
||||
|
||||
startIndex := i + len(deviceStatusStartPrefix) + 1
|
||||
endIndex := i + j - 1
|
||||
if len(s) < startIndex || startIndex >= endIndex {
|
||||
return 0, errors.New("incorrect format of the line")
|
||||
}
|
||||
|
||||
return strconv.Atoi(s[startIndex:endIndex])
|
||||
}
|
||||
|
||||
// Parse Operation name
|
||||
// Example = Thu Apr 13 13:28:40 2023:INFO:5GUL counters: Code Blocks
|
||||
// Output: 5GUL
|
||||
func parseOperationName(s string) string {
|
||||
i := strings.Index(s, infoLine)
|
||||
if i >= 0 {
|
||||
j := strings.Index(s[i:], countersLine)
|
||||
startIndex := i + len(infoLine)
|
||||
endIndex := i + j - 1
|
||||
if j >= 0 && startIndex < endIndex {
|
||||
return s[startIndex:endIndex]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func newLogConnector(path string, waitForTelemetryTimeout time.Duration) *logConnector {
|
||||
lastModTime := time.Time{}
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err == nil {
|
||||
lastModTime = fileInfo.ModTime()
|
||||
}
|
||||
|
||||
return &logConnector{
|
||||
path: path,
|
||||
waitForTelemetryTimeout: waitForTelemetryTimeout,
|
||||
numVFs: -1,
|
||||
lastModTime: lastModTime,
|
||||
}
|
||||
}
|
257
plugins/inputs/intel_baseband/log_connector_test.go
Normal file
257
plugins/inputs/intel_baseband/log_connector_test.go
Normal file
|
@ -0,0 +1,257 @@
|
|||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReadLogFile(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
testLogPath string
|
||||
err error
|
||||
}{
|
||||
{"when file doesn't exist return the error", "testdata/logfiles/doesntexist", errors.New("no such file or directory")},
|
||||
{"when the file is empty return the error", "testdata/logfiles/empty.log", errors.New("log file is empty")},
|
||||
{"when the log file is correct, error should be nil", "testdata/logfiles/example.log", nil},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
logConnector := prepareLogConnMock()
|
||||
require.NotNil(t, logConnector)
|
||||
logConnector.path = tc.testLogPath
|
||||
|
||||
err := logConnector.readLogFile()
|
||||
if tc.err != nil {
|
||||
require.ErrorContains(t, err, tc.err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
data := logConnector.getLogLines()
|
||||
require.NotEmpty(t, data)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMetric(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input []string
|
||||
metricName string
|
||||
expectedOperation string
|
||||
expectedData []string
|
||||
err error
|
||||
}{
|
||||
{"with correct string no error should be returned",
|
||||
[]string{"Thu May 18 08:45:15 2023:INFO:5GUL counters: Code Blocks", "Thu May 18 08:45:15 2023:INFO:0 0"},
|
||||
vfCodeBlocks, "5GUL", []string{"0", "0"}, nil},
|
||||
|
||||
{"with correct string no error should be returned",
|
||||
[]string{"Thu May 18 08:45:15 2023:INFO:5GUL counters: Data (Bytes)", "Thu May 18 08:45:15 2023:INFO:0 0"},
|
||||
vfDataBlock, "5GUL", []string{"0", "0"}, nil},
|
||||
|
||||
{"with correct string no error should be returned",
|
||||
[]string{"Thu May 18 08:45:15 2023:INFO:5GUL counters: Per Engine", "Thu May 18 08:45:15 2023:INFO:0 0 3 0 50 0 200 0"},
|
||||
engineBlock, "5GUL", []string{"0", "0", "3", "0", "50", "0", "200", "0"}, nil},
|
||||
|
||||
{"when the incorrect number of lines provided, error should be returned",
|
||||
[]string{"Thu May 18 08:45:15 2023:INFO:5GUL counters: Per Engine"},
|
||||
engineBlock, "5GUL", []string{""}, errors.New("the content of the log file is incorrect")},
|
||||
|
||||
{"when the incorrect number of lines provided, error should be returned",
|
||||
[]string{"Thu May 18 08:45:15 2023:INFO:5GUL counters: Per Engine", ""},
|
||||
engineBlock, "5GUL", []string{""}, errors.New("the content of the log file is incorrect")},
|
||||
|
||||
{"when the incorrect line provided, error should be returned", []string{"Something different"},
|
||||
"", "5GUL", []string{""}, errors.New("substring is empty")},
|
||||
|
||||
{"when the incorrect line provided error should be returned", []string{"Device Status:: 1 VFs", "INFO:00counters:", "INFO:0 0"},
|
||||
"I", "", nil, errors.New("metric's data is empty")},
|
||||
|
||||
{"when the incorrect metric's line provided error should be returned", []string{"Device Status:: 1 VFs", "INFO:00counters:B", "INFO: "},
|
||||
"B", "", nil, errors.New("metric's data is incorrect")},
|
||||
|
||||
{"when the operation name wasn't found, error should be returned", []string{"Device Status:: 1 VFs", "", "INFO:countersCode Blocks"},
|
||||
"B", "", nil, errors.New("valid operation name wasn't found in log")},
|
||||
|
||||
{"when lines are empty, error should be returned", []string{""},
|
||||
"something", "5GUL", []string{""}, errors.New("couldn't find the substring")},
|
||||
|
||||
{"when lines are empty, error should be returned", nil,
|
||||
"something", "5GUL", []string{""}, errors.New("couldn't find the substring")},
|
||||
}
|
||||
|
||||
logConnector := prepareLogConnMock()
|
||||
require.NotNil(t, logConnector)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
logConnector.lines = tc.input
|
||||
offset, metric, err := logConnector.getMetric(0, tc.metricName)
|
||||
if tc.err != nil {
|
||||
require.ErrorContains(t, err, tc.err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, offset)
|
||||
require.Equal(t, tc.expectedOperation, metric.operationName)
|
||||
require.ElementsMatch(t, tc.expectedData, metric.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadAndGetMetrics(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
filePath string
|
||||
metricName string
|
||||
expectedOperations []string
|
||||
expectedData [][]string
|
||||
}{
|
||||
{"with correct values no error should be returned for Code Blocks",
|
||||
"testdata/logfiles/example.log",
|
||||
vfCodeBlocks, []string{"5GUL", "5GDL"}, [][]string{{"0", "0"}, {"1", "0"}}},
|
||||
{"with correct values no error should be returned for Data Blocks",
|
||||
"testdata/logfiles/example.log",
|
||||
vfDataBlock, []string{"5GUL", "5GDL"}, [][]string{{"0", "0"}, {"2699", "0"}}},
|
||||
{"with correct values no error should be returned for Per Engine Blocks",
|
||||
"testdata/logfiles/example.log",
|
||||
engineBlock, []string{"5GUL", "5GDL"}, [][]string{{"0", "0", "0", "0", "0", "0", "0", "0"}, {"1", "0", "0"}}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
logConnector := prepareLogConnMock()
|
||||
require.NotNil(t, logConnector)
|
||||
logConnector.path = tc.filePath
|
||||
err := logConnector.readLogFile()
|
||||
require.NoError(t, err)
|
||||
metrics, err := logConnector.getMetrics(tc.metricName)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, len(tc.expectedOperations))
|
||||
|
||||
for i := range metrics {
|
||||
require.Equal(t, tc.expectedOperations[i], metrics[i].operationName)
|
||||
require.ElementsMatch(t, tc.expectedData[i], metrics[i].data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMetrics(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input []string
|
||||
metricName string
|
||||
expectedOperations []string
|
||||
expectedData [][]string
|
||||
err error
|
||||
}{
|
||||
{"with correct values no error should be returned",
|
||||
[]string{"Thu May 18 08:45:15 2023:INFO:5GUL counters: Code Blocks", "Thu May 18 08:45:15 2023:INFO:0 0",
|
||||
"Thu May 18 08:45:15 2023:INFO:5GUL counters: XXXX XXXX", "Thu May 18 08:45:15 2023:INFO:0 1", "sdasadasdsa",
|
||||
"Thu May 18 08:45:15 2023:INFO:5GDL counters: Code Blocks", "Thu May 18 08:45:15 2023:INFO:1 1",
|
||||
"Thu May 18 08:45:15 2023:INFO:5GUL counters: XXXX XXXX", "Thu May 18 08:45:15 2023:INFO:0 1", "sdasadasdsa"},
|
||||
vfCodeBlocks, []string{"5GUL", "5GDL"}, [][]string{{"0", "0"}, {"1", "1"}}, nil},
|
||||
|
||||
{"when lines are empty, error should be returned", []string{""},
|
||||
"something", nil, nil, errors.New("couldn't find the substring in the log file")},
|
||||
|
||||
{"when lines are nil, error should be returned", nil,
|
||||
"something", nil, nil, errors.New("couldn't find the substring in the log file")},
|
||||
}
|
||||
|
||||
logConnector := prepareLogConnMock()
|
||||
require.NotNil(t, logConnector)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
logConnector.lines = tc.input
|
||||
metrics, err := logConnector.getMetrics(tc.metricName)
|
||||
if tc.err != nil {
|
||||
require.ErrorContains(t, err, tc.err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, metrics, len(tc.expectedOperations))
|
||||
|
||||
for i := range metrics {
|
||||
require.Equal(t, tc.expectedOperations[i], metrics[i].operationName)
|
||||
require.ElementsMatch(t, tc.expectedData[i], metrics[i].data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNumVFs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected int
|
||||
err error
|
||||
}{
|
||||
{"incorrect format of the line", []string{"Device Status::VFs"}, -1, errors.New("incorrect format of the line")},
|
||||
{"when the line is correct, no error should be returned", []string{"Device Status:: 0 VFs"}, 0, nil},
|
||||
{"when the line is correct, no error should be returned", []string{"Device Status:: 10 VFs"}, 10, nil},
|
||||
{"when the line is correct, no error should be returned", []string{"Device Status:: 5000 VFs"}, 5000, nil},
|
||||
{"when the value is not int, error should be returned", []string{"Device Status:: Nah VFs"}, -1, errors.New("invalid syntax")},
|
||||
{"when end prefix isn't found, error should be returned", []string{"Device Status:: Nah END"}, -1, errors.New("couldn't find device end prefix")},
|
||||
{"when the line is empty, error should be returned", []string{""}, -1, errors.New("numVFs data wasn't found")},
|
||||
{"when the line is empty, error should be returned", nil, -1, errors.New("numVFs data wasn't found")},
|
||||
}
|
||||
|
||||
logConnector := prepareLogConnMock()
|
||||
require.NotNil(t, logConnector)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
logConnector.lines = tc.input
|
||||
err := logConnector.readNumVFs()
|
||||
if tc.err != nil {
|
||||
require.ErrorContains(t, err, tc.err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
numVFs := logConnector.getNumVFs()
|
||||
require.Equal(t, tc.expected, numVFs)
|
||||
require.Equal(t, tc.expected, logConnector.numVFs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseOperationName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"Thu May 18 08:45:15 2023:INFO:5GUL counters: Code Blocks", "5GUL"},
|
||||
{"May 18 08:45:15 2023:INFO:5GUL counters: Per Engine", "5GUL"},
|
||||
{"023:INFO:3G counters: Per ", "3G"},
|
||||
{"Device Status:: Nah VFs", ""},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("expected "+tc.expected, func(t *testing.T) {
|
||||
operationName := parseOperationName(tc.input)
|
||||
require.Equal(t, tc.expected, operationName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func prepareLogConnMock() *logConnector {
|
||||
return &logConnector{
|
||||
path: "",
|
||||
numVFs: -1,
|
||||
lastModTime: time.Time{},
|
||||
}
|
||||
}
|
197
plugins/inputs/intel_baseband/mocks/conn.go
Normal file
197
plugins/inputs/intel_baseband/mocks/conn.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
// Code generated by mockery v2.46.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"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
|
||||
}
|
31
plugins/inputs/intel_baseband/sample.conf
Normal file
31
plugins/inputs/intel_baseband/sample.conf
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Intel Baseband Accelerator Input Plugin collects metrics from both dedicated and integrated
|
||||
# Intel devices that provide Wireless Baseband hardware acceleration.
|
||||
# This plugin ONLY supports Linux.
|
||||
[[inputs.intel_baseband]]
|
||||
## Path to socket exposed by pf-bb-config for CLI interaction (mandatory).
|
||||
## In version v23.03 of pf-bb-config the path is created according to the schema:
|
||||
## "/tmp/pf_bb_config.0000\:<b>\:<d>.<f>.sock" where 0000\:<b>\:<d>.<f> is the PCI device ID.
|
||||
socket_path = ""
|
||||
|
||||
## Path to log file exposed by pf-bb-config with telemetry to read (mandatory).
|
||||
## In version v23.03 of pf-bb-config the path is created according to the schema:
|
||||
## "/var/log/pf_bb_cfg_0000\:<b>\:<d>.<f>.log" where 0000\:<b>\:<d>.<f> is the PCI device ID.
|
||||
log_file_path = ""
|
||||
|
||||
## Specifies plugin behavior regarding unreachable socket (which might not have been initialized yet).
|
||||
## Available choices:
|
||||
## - error: Telegraf will return an error on startup if socket is unreachable
|
||||
## - ignore: Telegraf will ignore error regarding unreachable socket on both startup and gather
|
||||
# unreachable_socket_behavior = "error"
|
||||
|
||||
## Duration that defines how long the connected socket client will wait for
|
||||
## a response before terminating connection.
|
||||
## 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 = "1s"
|
||||
|
||||
## Duration that defines maximum time plugin will wait for pf-bb-config to write telemetry to the log file.
|
||||
## Timeout may differ depending on the environment.
|
||||
## Must be equal or larger than 50ms.
|
||||
# wait_for_telemetry_timeout = "1s"
|
102
plugins/inputs/intel_baseband/sock_connector.go
Normal file
102
plugins/inputs/intel_baseband/sock_connector.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Command code
|
||||
clearLogCmdID = 0x4
|
||||
deviceDataCmdID = 0x9
|
||||
)
|
||||
|
||||
type socketConnector struct {
|
||||
pathToSocket string
|
||||
accessTimeout time.Duration
|
||||
connection net.Conn
|
||||
}
|
||||
|
||||
func (sc *socketConnector) dumpTelemetryToLog() error {
|
||||
// clean the log to have only the latest metrics in the file
|
||||
err := sc.sendCommandToSocket(clearLogCmdID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send clear log command: %w", err)
|
||||
}
|
||||
|
||||
// fill the file with the latest metrics
|
||||
err = sc.sendCommandToSocket(deviceDataCmdID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send device data command: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *socketConnector) sendCommandToSocket(c byte) error {
|
||||
err := sc.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sc.close()
|
||||
err = sc.writeCommandToSocket(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *socketConnector) writeCommandToSocket(c byte) error {
|
||||
if sc.connection == nil {
|
||||
return errors.New("connection had not been established before")
|
||||
}
|
||||
var err error
|
||||
if sc.accessTimeout == 0 {
|
||||
err = sc.connection.SetWriteDeadline(time.Time{})
|
||||
} else {
|
||||
err = sc.connection.SetWriteDeadline(time.Now().Add(sc.accessTimeout))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set timeout for request: %w", err)
|
||||
}
|
||||
|
||||
_, err = sc.connection.Write([]byte{c, 0x00})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request to socket: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *socketConnector) connect() error {
|
||||
connection, err := net.Dial("unix", sc.pathToSocket)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to the socket: %w", err)
|
||||
}
|
||||
|
||||
sc.connection = connection
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *socketConnector) close() error {
|
||||
if sc.connection == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := sc.connection.Close()
|
||||
sc.connection = nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSocketConnector(pathToSocket string, accessTimeout time.Duration) *socketConnector {
|
||||
return &socketConnector{
|
||||
pathToSocket: pathToSocket,
|
||||
accessTimeout: accessTimeout,
|
||||
}
|
||||
}
|
77
plugins/inputs/intel_baseband/sock_connector_test.go
Normal file
77
plugins/inputs/intel_baseband/sock_connector_test.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/inputs/intel_baseband/mocks"
|
||||
)
|
||||
|
||||
func TestWriteCommandToSocket(t *testing.T) {
|
||||
t.Run("correct execution of the function", func(t *testing.T) {
|
||||
conn := &mocks.Conn{}
|
||||
conn.On("Write", mock.Anything).Return(2, nil)
|
||||
conn.On("SetWriteDeadline", mock.Anything).Return(nil)
|
||||
connector := socketConnector{connection: conn}
|
||||
|
||||
err := connector.writeCommandToSocket(0x00)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("without setting up a connection it should return an error", func(t *testing.T) {
|
||||
connector := socketConnector{}
|
||||
|
||||
err := connector.writeCommandToSocket(0x00)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "connection had not been established before")
|
||||
})
|
||||
|
||||
t.Run("handling timeout setting error", func(t *testing.T) {
|
||||
conn := &mocks.Conn{}
|
||||
conn.On("SetWriteDeadline", mock.Anything).Return(errors.New("deadline set error"))
|
||||
connector := socketConnector{connection: conn}
|
||||
|
||||
err := connector.writeCommandToSocket(0x00)
|
||||
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "failed to set timeout for request")
|
||||
require.ErrorContains(t, err, "deadline set error")
|
||||
defer conn.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("handling net.Write error", func(t *testing.T) {
|
||||
var unsupportedCommand byte = 0x99
|
||||
conn := &mocks.Conn{}
|
||||
conn.On("Write", []byte{unsupportedCommand, 0x00}).Return(0, errors.New("unsupported command"))
|
||||
conn.On("SetWriteDeadline", mock.Anything).Return(nil)
|
||||
connector := socketConnector{connection: conn}
|
||||
|
||||
err := connector.writeCommandToSocket(unsupportedCommand)
|
||||
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "failed to send request to socket")
|
||||
require.ErrorContains(t, err, "unsupported command")
|
||||
defer conn.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDumpTelemetryToLog(t *testing.T) {
|
||||
t.Run("with correct temporary socket should return only an error related to the inability to refresh telemetry", func(t *testing.T) {
|
||||
tempSocket := newTempSocket(t)
|
||||
defer tempSocket.Close()
|
||||
tempLogFile := newTempLogFile(t)
|
||||
defer tempLogFile.close()
|
||||
connector := newSocketConnector(tempSocket.pathToSocket, 5*time.Second)
|
||||
|
||||
err := connector.dumpTelemetryToLog()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
0
plugins/inputs/intel_baseband/testdata/logfiles/empty.log
vendored
Normal file
0
plugins/inputs/intel_baseband/testdata/logfiles/empty.log
vendored
Normal file
16
plugins/inputs/intel_baseband/testdata/logfiles/example.log
vendored
Normal file
16
plugins/inputs/intel_baseband/testdata/logfiles/example.log
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
Thu May 18 08:45:15 2023:INFO:device_data command received
|
||||
Thu May 18 08:45:15 2023:INFO:Device Status:: 2 VFs
|
||||
Thu May 18 08:45:15 2023:INFO:- VF 0 RTE_BBDEV_DEV_CONFIGURED
|
||||
Thu May 18 08:45:15 2023:INFO:- VF 1 RTE_BBDEV_DEV_CONFIGURED
|
||||
Thu May 18 08:45:15 2023:INFO:5GUL counters: Code Blocks
|
||||
Thu May 18 08:45:15 2023:INFO:0 0
|
||||
Thu May 18 08:45:15 2023:INFO:5GUL counters: Data (Bytes)
|
||||
Thu May 18 08:45:15 2023:INFO:0 0
|
||||
Thu May 18 08:45:15 2023:INFO:5GUL counters: Per Engine
|
||||
Thu May 18 08:45:15 2023:INFO:0 0 0 0 0 0 0 0
|
||||
Thu May 18 08:45:15 2023:INFO:5GDL counters: Code Blocks
|
||||
Thu May 18 08:45:15 2023:INFO:1 0
|
||||
Thu May 18 08:45:15 2023:INFO:5GDL counters: Data (Bytes)
|
||||
Thu May 18 08:45:15 2023:INFO:2699 0
|
||||
Thu May 18 08:45:15 2023:INFO:5GDL counters: Per Engine
|
||||
Thu May 18 08:45:15 2023:INFO:1 0 0
|
81
plugins/inputs/intel_baseband/utils.go
Normal file
81
plugins/inputs/intel_baseband/utils.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fileType int
|
||||
|
||||
const (
|
||||
log fileType = iota
|
||||
socket
|
||||
)
|
||||
|
||||
func validatePath(pathToRead string, ft fileType) (string, error) {
|
||||
if pathToRead == "" {
|
||||
return "", errors.New("required path not specified")
|
||||
}
|
||||
cleanPath := path.Clean(pathToRead)
|
||||
if (ft == log && path.Ext(cleanPath) != logFileExtension) || (ft == socket && path.Ext(cleanPath) != socketExtension) {
|
||||
return "", fmt.Errorf("wrong file extension: %q", cleanPath)
|
||||
}
|
||||
if !path.IsAbs(cleanPath) {
|
||||
return "", fmt.Errorf("path is not absolute %q", cleanPath)
|
||||
}
|
||||
return cleanPath, nil
|
||||
}
|
||||
|
||||
func checkFile(pathToFile string, fileType fileType) error {
|
||||
pathInfo, err := os.Lstat(pathToFile)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("provided path does not exist: %q", pathToFile)
|
||||
}
|
||||
if errors.Is(err, fs.ErrPermission) {
|
||||
return fmt.Errorf("user doesn't have enough privileges to file %q", pathToFile)
|
||||
}
|
||||
|
||||
return fmt.Errorf("couldn't get system information of file %q: %w", pathToFile, err)
|
||||
}
|
||||
|
||||
mode := pathInfo.Mode()
|
||||
switch fileType {
|
||||
case socket:
|
||||
if mode&os.ModeSocket != os.ModeSocket {
|
||||
return fmt.Errorf("provided path does not point to a socket file: %q", pathToFile)
|
||||
}
|
||||
case log:
|
||||
if !(mode.IsRegular()) {
|
||||
return fmt.Errorf("provided path does not point to a log file: %q", pathToFile)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace metric name to snake case
|
||||
// Example: Code Blocks -> code_blocks
|
||||
func metricNameToTagName(metricName string) string {
|
||||
cleanedStr := strings.Replace(strings.Replace(strings.Replace(metricName, "(", "", -1), ")", "", -1), " ", "_", -1)
|
||||
return strings.ToLower(cleanedStr)
|
||||
}
|
||||
|
||||
func logMetricDataToValue(data string) (int, error) {
|
||||
value, err := strconv.Atoi(data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if value < 0 {
|
||||
return 0, errors.New("metric can't be negative")
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
182
plugins/inputs/intel_baseband/utils_test.go
Normal file
182
plugins/inputs/intel_baseband/utils_test.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
//go:build linux && amd64
|
||||
|
||||
package intel_baseband
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMetricNameToTagName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
metricName string
|
||||
expectedTagName string
|
||||
}{
|
||||
{vfCodeBlocks, "code_blocks"},
|
||||
{vfDataBlock, "data_bytes"},
|
||||
{engineBlock, "per_engine"},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
t.Run("check the correct transformation metric name", func(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
tagName := metricNameToTagName(tc.metricName)
|
||||
require.Equal(t, tc.expectedTagName, tagName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidatePath(t *testing.T) {
|
||||
t.Run("with correct file extensions checkFile shouldn't return any errors", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
path string
|
||||
ft fileType
|
||||
expectedPath string
|
||||
}{
|
||||
{"/tmp/socket.sock", socket, "/tmp/socket.sock"},
|
||||
{"/foo/../tmp/socket.sock", socket, "/tmp/socket.sock"},
|
||||
{"/tmp/file.log", log, "/tmp/file.log"},
|
||||
{"/foo/../tmp/file.log", log, "/tmp/file.log"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
returnPath, err := validatePath(tc.path, tc.ft)
|
||||
require.Equal(t, tc.expectedPath, returnPath)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
t.Run("with empty path specified validate path should return an error", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
path string
|
||||
ft fileType
|
||||
expectedErrorContains string
|
||||
}{
|
||||
{"", socket, "required path not specified"},
|
||||
{"", log, "required path not specified"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
returnPath, err := validatePath(tc.path, tc.ft)
|
||||
require.Empty(t, returnPath)
|
||||
require.ErrorContains(t, err, tc.expectedErrorContains)
|
||||
}
|
||||
})
|
||||
t.Run("with wrong extension file validatePath should return an error", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
path string
|
||||
ft fileType
|
||||
expectedErrorContains string
|
||||
}{
|
||||
{"/tmp/socket.foo", socket, "wrong file extension"},
|
||||
{"/tmp/file.foo", log, "wrong file extension"},
|
||||
{"/tmp/socket.sock", log, "wrong file extension"},
|
||||
{"/tmp/file.log", socket, "wrong file extension"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
returnPath, err := validatePath(tc.path, tc.ft)
|
||||
require.Empty(t, returnPath)
|
||||
require.ErrorContains(t, err, tc.expectedErrorContains)
|
||||
}
|
||||
})
|
||||
t.Run("with not absolute path validatePath should return the error", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
path string
|
||||
ft fileType
|
||||
expectedErrorContains string
|
||||
}{
|
||||
{"foo/tmp/socket.sock", socket, "path is not absolute"},
|
||||
{"foo/tmp/file.log", log, "path is not absolute"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
returnPath, err := validatePath(tc.path, tc.ft)
|
||||
require.Empty(t, returnPath)
|
||||
require.ErrorContains(t, err, tc.expectedErrorContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckFile(t *testing.T) {
|
||||
t.Run("with correct file extensions checkFile shouldn't return any errors", func(t *testing.T) {
|
||||
tempSocket := newTempSocket(t)
|
||||
defer tempSocket.Close()
|
||||
|
||||
testCases := []struct {
|
||||
path string
|
||||
ft fileType
|
||||
}{
|
||||
{"testdata/logfiles/example.log", log},
|
||||
{tempSocket.pathToSocket, socket},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := checkFile(tc.path, tc.ft)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
t.Run("path does not point to the correct file type", func(t *testing.T) {
|
||||
tempSocket := newTempSocket(t)
|
||||
defer tempSocket.Close()
|
||||
|
||||
testCases := []struct {
|
||||
path string
|
||||
ft fileType
|
||||
expectedErrorContains string
|
||||
}{
|
||||
{"testdata/logfiles/example.log", socket, "provided path does not point to a socket file"},
|
||||
{tempSocket.pathToSocket, log, "provided path does not point to a log file:"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := checkFile(tc.path, tc.ft)
|
||||
require.ErrorContains(t, err, tc.expectedErrorContains)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with path to non existing file checkFile should return the error", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
path string
|
||||
ft fileType
|
||||
expectedErrorContains string
|
||||
}{
|
||||
{"/foo/example.log", log, "provided path does not exist"},
|
||||
{"/foo/example.sock", socket, "provided path does not exist"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := checkFile(tc.path, tc.ft)
|
||||
require.ErrorContains(t, err, tc.expectedErrorContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogMetricDataToValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
metricData string
|
||||
expectedValue int
|
||||
err error
|
||||
}{
|
||||
{"010", 10, nil},
|
||||
{"00", 0, nil},
|
||||
{"5", 5, nil},
|
||||
{"-010", 0, errors.New("metric can't be negative")},
|
||||
{"", 0, errors.New("invalid syntax")},
|
||||
{"0Nax10", 0, errors.New("invalid syntax")},
|
||||
}
|
||||
|
||||
t.Run("check correct returned values", func(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
value, err := logMetricDataToValue(tc.metricData)
|
||||
if tc.err != nil {
|
||||
require.ErrorContains(t, err, tc.err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedValue, value)
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue