feat: add mock input plugin (#9782)
This commit is contained in:
parent
f38290f454
commit
2220fa2c2d
|
|
@ -111,6 +111,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/memcached"
|
_ "github.com/influxdata/telegraf/plugins/inputs/memcached"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/mesos"
|
_ "github.com/influxdata/telegraf/plugins/inputs/mesos"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/minecraft"
|
_ "github.com/influxdata/telegraf/plugins/inputs/minecraft"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/mock"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/modbus"
|
_ "github.com/influxdata/telegraf/plugins/inputs/modbus"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/mongodb"
|
_ "github.com/influxdata/telegraf/plugins/inputs/mongodb"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/monit"
|
_ "github.com/influxdata/telegraf/plugins/inputs/monit"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Mock Data
|
||||||
|
|
||||||
|
The mock input plugin generates random data based on a selection of different
|
||||||
|
algorithms. For example, it can produce random data between a set of values,
|
||||||
|
fake stock data, sine waves, and step-wise values.
|
||||||
|
|
||||||
|
Additionally, users can set the measurement name and tags used to whatever is
|
||||||
|
required to mock their situation.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The mock plugin only requires that:
|
||||||
|
|
||||||
|
1) Metric name is set
|
||||||
|
2) One of the below data field algorithms is defined
|
||||||
|
|
||||||
|
Below is a sample config to generate one of each of the four types:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[inputs.mock]]
|
||||||
|
## Set the metric name to use for reporting
|
||||||
|
metric_name = "mock"
|
||||||
|
|
||||||
|
## Optional string key-value pairs of tags to add to all metrics
|
||||||
|
# [inputs.mock.tags]
|
||||||
|
# "key" = "value"
|
||||||
|
|
||||||
|
## One or more mock data fields *must* be defined.
|
||||||
|
##
|
||||||
|
## [[inputs.mock.random]]
|
||||||
|
## name = "rand"
|
||||||
|
## min = 1.0
|
||||||
|
## max = 6.0
|
||||||
|
## [[inputs.mock.sine_wave]]
|
||||||
|
## name = "wave"
|
||||||
|
## amplitude = 1.0
|
||||||
|
## period = 0.5
|
||||||
|
## [[inputs.mock.step]]
|
||||||
|
## name = "plus_one"
|
||||||
|
## start = 0.0
|
||||||
|
## step = 1.0
|
||||||
|
## [[inputs.mock.stock]]
|
||||||
|
## name = "abc"
|
||||||
|
## price = 50.00
|
||||||
|
## volatility = 0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Algorithms
|
||||||
|
|
||||||
|
The available algorithms for generating mock data include:
|
||||||
|
|
||||||
|
* Random Float - generate a random float, inclusive of min and max
|
||||||
|
* Sine Wave - produce a sine wave with a certain amplitude and period
|
||||||
|
* Step - always add the step value, negative values accepted
|
||||||
|
* Stock - generate fake, stock-like price values based on a volatility variable
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
The following example shows all available algorithms configured with an
|
||||||
|
additional two tags as well:
|
||||||
|
|
||||||
|
```s
|
||||||
|
mock_sensors,building=5A,site=FTC random=4.875966794516125,abc=50,wave=0,plus_one=0 1632170840000000000
|
||||||
|
mock_sensors,building=5A,site=FTC random=5.738651873834452,abc=45.095549448434774,wave=5.877852522924732,plus_one=1 1632170850000000000
|
||||||
|
mock_sensors,building=5A,site=FTC random=1.0429328917205203,abc=51.928560083072924,wave=9.510565162951535,plus_one=2 1632170860000000000
|
||||||
|
mock_sensors,building=5A,site=FTC random=5.290188595384418,abc=44.41090520217027,wave=9.510565162951536,plus_one=3 1632170870000000000
|
||||||
|
mock_sensors,building=5A,site=FTC random=2.0724967227069135,abc=47.212167806890314,wave=5.877852522924733,plus_one=4 1632170880000000000
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mock struct {
|
||||||
|
counter int64
|
||||||
|
|
||||||
|
MetricName string `toml:"metric_name"`
|
||||||
|
Tags map[string]string `toml:"tags"`
|
||||||
|
|
||||||
|
Random []*random `toml:"random"`
|
||||||
|
Step []*step `toml:"step"`
|
||||||
|
Stock []*stock `toml:"stock"`
|
||||||
|
SineWave []*sineWave `toml:"sine_wave"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type random struct {
|
||||||
|
Name string `toml:"name"`
|
||||||
|
Min float64 `toml:"min"`
|
||||||
|
Max float64 `toml:"max"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type sineWave struct {
|
||||||
|
Name string `toml:"name"`
|
||||||
|
Amplitude float64 `toml:"amplitude"`
|
||||||
|
Period float64 `toml:"period"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type step struct {
|
||||||
|
latest float64
|
||||||
|
|
||||||
|
Name string `toml:"name"`
|
||||||
|
Start float64 `toml:"min"`
|
||||||
|
Step float64 `toml:"max"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type stock struct {
|
||||||
|
latest float64
|
||||||
|
|
||||||
|
Name string `toml:"name"`
|
||||||
|
Price float64 `toml:"price"`
|
||||||
|
Volatility float64 `toml:"volatility"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleConfig = `
|
||||||
|
## Set the metric name to use for reporting
|
||||||
|
metric_name = "mock"
|
||||||
|
|
||||||
|
## Optional string key-value pairs of tags to add to all metrics
|
||||||
|
# [inputs.mock.tags]
|
||||||
|
# "key" = "value"
|
||||||
|
|
||||||
|
## One or more mock data fields *must* be defined.
|
||||||
|
##
|
||||||
|
## [[inputs.mock.random]]
|
||||||
|
## name = "rand"
|
||||||
|
## min = 1.0
|
||||||
|
## max = 6.0
|
||||||
|
## [[inputs.mock.sine_wave]]
|
||||||
|
## name = "wave"
|
||||||
|
## amplitude = 1.0
|
||||||
|
## period = 0.5
|
||||||
|
## [[inputs.mock.step]]
|
||||||
|
## name = "plus_one"
|
||||||
|
## start = 0.0
|
||||||
|
## step = 1.0
|
||||||
|
## [[inputs.mock.stock]]
|
||||||
|
## name = "abc"
|
||||||
|
## price = 50.00
|
||||||
|
## volatility = 0.2
|
||||||
|
`
|
||||||
|
|
||||||
|
func (m *Mock) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mock) Description() string {
|
||||||
|
return "Generate metrics for test and demonstration purposes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mock) Init() error {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mock) Gather(acc telegraf.Accumulator) error {
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
m.generateRandomFloat64(fields)
|
||||||
|
m.generateStockPrice(fields)
|
||||||
|
m.generateSineWave(fields)
|
||||||
|
m.generateStep(fields)
|
||||||
|
|
||||||
|
tags := make(map[string]string)
|
||||||
|
for key, value := range m.Tags {
|
||||||
|
tags[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.AddFields(m.MetricName, fields, tags)
|
||||||
|
|
||||||
|
m.counter++
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate random value between min and max, inclusivly
|
||||||
|
func (m *Mock) generateRandomFloat64(fields map[string]interface{}) {
|
||||||
|
for _, random := range m.Random {
|
||||||
|
fields[random.Name] = random.Min + rand.Float64()*(random.Max-random.Min)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sine waves
|
||||||
|
func (m *Mock) generateSineWave(fields map[string]interface{}) {
|
||||||
|
for _, field := range m.SineWave {
|
||||||
|
fields[field.Name] = math.Sin((float64(m.counter) * field.Period * math.Pi)) * field.Amplitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin at start value and then add step value every tick
|
||||||
|
func (m *Mock) generateStep(fields map[string]interface{}) {
|
||||||
|
for _, step := range m.Step {
|
||||||
|
if m.counter == 0 {
|
||||||
|
step.latest = step.Start
|
||||||
|
} else {
|
||||||
|
step.latest += step.Step
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[step.Name] = step.latest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin at start price and then generate random value
|
||||||
|
func (m *Mock) generateStockPrice(fields map[string]interface{}) {
|
||||||
|
for _, stock := range m.Stock {
|
||||||
|
if stock.latest == 0.0 {
|
||||||
|
stock.latest = stock.Price
|
||||||
|
} else {
|
||||||
|
noise := 2 * (rand.Float64() - 0.5)
|
||||||
|
stock.latest = stock.latest + (stock.latest * stock.Volatility * noise)
|
||||||
|
|
||||||
|
// avoid going below zero
|
||||||
|
if stock.latest < 1.0 {
|
||||||
|
stock.latest = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[stock.Name] = stock.latest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("mock", func() telegraf.Input {
|
||||||
|
return &Mock{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGather(t *testing.T) {
|
||||||
|
testRandom := &random{
|
||||||
|
Name: "random",
|
||||||
|
Min: 1.0,
|
||||||
|
Max: 6.0,
|
||||||
|
}
|
||||||
|
testSineWave := &sineWave{
|
||||||
|
Name: "sine",
|
||||||
|
Amplitude: 1.0,
|
||||||
|
Period: 0.5,
|
||||||
|
}
|
||||||
|
testStep := &step{
|
||||||
|
Name: "step",
|
||||||
|
Start: 0.0,
|
||||||
|
Step: 1.0,
|
||||||
|
}
|
||||||
|
testStock := &stock{
|
||||||
|
Name: "abc",
|
||||||
|
Price: 50.00,
|
||||||
|
Volatility: 0.2,
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"buildling": "tbd",
|
||||||
|
"site": "nowhere",
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &Mock{
|
||||||
|
MetricName: "test",
|
||||||
|
Tags: tags,
|
||||||
|
|
||||||
|
Random: []*random{testRandom},
|
||||||
|
SineWave: []*sineWave{testSineWave},
|
||||||
|
Step: []*step{testStep},
|
||||||
|
Stock: []*stock{testStock},
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
require.NoError(t, m.Gather(&acc))
|
||||||
|
|
||||||
|
require.Len(t, acc.Metrics, 1)
|
||||||
|
|
||||||
|
metric := acc.Metrics[0]
|
||||||
|
require.Equal(t, "test", metric.Measurement)
|
||||||
|
require.Equal(t, tags, metric.Tags)
|
||||||
|
for k, v := range metric.Fields {
|
||||||
|
switch k {
|
||||||
|
case "abc":
|
||||||
|
require.Equal(t, 50.0, v)
|
||||||
|
case "random":
|
||||||
|
require.GreaterOrEqual(t, 6.0, v)
|
||||||
|
require.LessOrEqual(t, 1.0, v)
|
||||||
|
case "sine":
|
||||||
|
require.Equal(t, 0.0, v)
|
||||||
|
case "step":
|
||||||
|
require.Equal(t, 0.0, v)
|
||||||
|
default:
|
||||||
|
require.Failf(t, "unexpected field %q", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatherEmpty(t *testing.T) {
|
||||||
|
m := &Mock{
|
||||||
|
MetricName: "test_empty",
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
require.NoError(t, m.Gather(&acc))
|
||||||
|
|
||||||
|
acc.AssertDoesNotContainMeasurement(t, "test_empty")
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue