feat: add mock input plugin (#9782)

This commit is contained in:
Joshua Powers 2021-12-23 13:34:29 -07:00 committed by GitHub
parent f38290f454
commit 2220fa2c2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 312 additions and 0 deletions

View File

@ -111,6 +111,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/memcached"
_ "github.com/influxdata/telegraf/plugins/inputs/mesos"
_ "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/mongodb"
_ "github.com/influxdata/telegraf/plugins/inputs/monit"

View File

@ -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
```

162
plugins/inputs/mock/mock.go Normal file
View File

@ -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{}
})
}

View File

@ -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")
}