improve the quality of starlark docs by executing them as tests (#8020)
This commit is contained in:
parent
bf0b376fc7
commit
bbc2aa660d
|
|
@ -9,7 +9,7 @@ Existing Python code is unlikely to work unmodified. The execution environment
|
||||||
is sandboxed, and it is not possible to do I/O operations such as reading from
|
is sandboxed, and it is not possible to do I/O operations such as reading from
|
||||||
files or sockets.
|
files or sockets.
|
||||||
|
|
||||||
The Starlark [specification][] has details about the syntax and available
|
The **[Starlark specification][]** has details about the syntax and available
|
||||||
functions.
|
functions.
|
||||||
|
|
||||||
Telegraf minimum version: Telegraf 1.15.0
|
Telegraf minimum version: Telegraf 1.15.0
|
||||||
|
|
@ -44,7 +44,7 @@ def apply(metric):
|
||||||
```
|
```
|
||||||
|
|
||||||
For a list of available types and functions that can be used in the code, see
|
For a list of available types and functions that can be used in the code, see
|
||||||
the Starlark [specification][].
|
the [Starlark specification][].
|
||||||
|
|
||||||
In addition to these, the following InfluxDB-specific
|
In addition to these, the following InfluxDB-specific
|
||||||
types and functions are exposed to the script.
|
types and functions are exposed to the script.
|
||||||
|
|
@ -149,14 +149,17 @@ Attempting to modify the global scope will fail with an error.
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
- [ratio](/plugins/processors/starlark/testdata/ratio.star)
|
- [ratio](/plugins/processors/starlark/testdata/ratio.star) - Compute the ratio of two integer fields
|
||||||
- [rename](/plugins/processors/starlark/testdata/rename.star)
|
- [rename](/plugins/processors/starlark/testdata/rename.star) - Rename tags or fields using a name mapping.
|
||||||
- [scale](/plugins/processors/starlark/testdata/scale.star)
|
- [scale](/plugins/processors/starlark/testdata/scale.star) - Multiply any field by a number
|
||||||
- [number logic](/plugins/processors/starlark/testdata/number_logic.star)
|
- [number logic](/plugins/processors/starlark/testdata/number_logic.star) - transform a numerical value to another numerical value
|
||||||
- [pivot](/plugins/processors/starlark/testdata/pivot.star)
|
- [pivot](/plugins/processors/starlark/testdata/pivot.star) - Pivots a key's value to be the key for another key.
|
||||||
|
- [value filter](plugins/processors/starlark/testdata/value_filter.star) - remove a metric based on a field value.
|
||||||
|
|
||||||
Open a [PR](https://github.com/influxdata/telegraf/compare) to add any other useful Starlark examples.
|
[All examples](/plugins/processors/starlark/testdata) are in the testdata folder.
|
||||||
|
|
||||||
[specification]: https://github.com/google/starlark-go/blob/master/doc/spec.md
|
Open a Pull Request to add any other useful Starlark examples.
|
||||||
|
|
||||||
|
[Starlark specification]: https://github.com/google/starlark-go/blob/master/doc/spec.md
|
||||||
[string]: https://github.com/google/starlark-go/blob/master/doc/spec.md#strings
|
[string]: https://github.com/google/starlark-go/blob/master/doc/spec.md#strings
|
||||||
[dict]: https://github.com/google/starlark-go/blob/master/doc/spec.md#dictionaries
|
[dict]: https://github.com/google/starlark-go/blob/master/doc/spec.md#dictionaries
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
package starlark
|
package starlark
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/parsers"
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
@ -2787,3 +2793,72 @@ def apply(metric):
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestAllScriptTestData(t *testing.T) {
|
||||||
|
// can be run from multiple folders
|
||||||
|
paths := []string{"testdata", "plugins/processors/starlark/testdata"}
|
||||||
|
for _, testdataPath := range paths {
|
||||||
|
filepath.Walk(testdataPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info == nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fn := path
|
||||||
|
t.Run(fn, func(t *testing.T) {
|
||||||
|
b, err := ioutil.ReadFile(fn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
lines := strings.Split(string(b), "\n")
|
||||||
|
inputMetrics := parseMetricsFrom(t, lines, "Example Input:")
|
||||||
|
outputMetrics := parseMetricsFrom(t, lines, "Example Output:")
|
||||||
|
plugin := &Starlark{
|
||||||
|
Script: fn,
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
acc := &testutil.Accumulator{}
|
||||||
|
|
||||||
|
err = plugin.Start(acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, m := range inputMetrics {
|
||||||
|
err = plugin.Add(m, acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = plugin.Stop()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutil.RequireMetricsEqual(t, outputMetrics, acc.GetTelegrafMetrics(), testutil.SortMetrics(), testutil.IgnoreTime())
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parser, _ = parsers.NewInfluxParser() // literally never returns errors.
|
||||||
|
|
||||||
|
// parses metric lines out of line protocol following a header, with a trailing blank line
|
||||||
|
func parseMetricsFrom(t *testing.T, lines []string, header string) (metrics []telegraf.Metric) {
|
||||||
|
require.NotZero(t, len(lines), "Expected some lines to parse from .star file, found none")
|
||||||
|
startIdx := -1
|
||||||
|
endIdx := len(lines)
|
||||||
|
for i := range lines {
|
||||||
|
if strings.TrimLeft(lines[i], "# ") == header {
|
||||||
|
startIdx = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotEqual(t, -1, startIdx, fmt.Sprintf("Header %q must exist in file", header))
|
||||||
|
for i := startIdx; i < len(lines); i++ {
|
||||||
|
line := strings.TrimLeft(lines[i], "# ")
|
||||||
|
if line == "" || line == "'''" {
|
||||||
|
endIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := startIdx; i < endIdx; i++ {
|
||||||
|
m, err := parser.ParseLine(strings.TrimLeft(lines[i], "# "))
|
||||||
|
require.NoError(t, err, fmt.Sprintf("Expected to be able to parse %q metric, but found error", header))
|
||||||
|
metrics = append(metrics, m)
|
||||||
|
}
|
||||||
|
return metrics
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
# Example: Set any 'status' field between 1 and 6 to a value of 0
|
# Example: Set any 'status' field between 1 and 6 to a value of 0
|
||||||
#
|
#
|
||||||
# Example Input:
|
# Example Input:
|
||||||
# lb, http_method=GET status=5 1465839830100400201
|
# lb,http_method=GET status=5i 1465839830100400201
|
||||||
|
#
|
||||||
# Example Output:
|
# Example Output:
|
||||||
# lb, http_method=GET status=0 1465839830100400201
|
# lb,http_method=GET status=0i 1465839830100400201
|
||||||
|
|
||||||
|
|
||||||
def apply(metric):
|
def apply(metric):
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,15 @@ Pivots a key's value to be the key for another key.
|
||||||
In this example it pivots the value of key `sensor`
|
In this example it pivots the value of key `sensor`
|
||||||
to be the key of the value in key `value`
|
to be the key of the value in key `value`
|
||||||
|
|
||||||
Input:
|
Example Input:
|
||||||
temperature sensor=001A0,value=111.48
|
temperature sensor="001A0",value=111.48
|
||||||
|
|
||||||
Output:
|
Example Output:
|
||||||
temperature 001A0=111.48
|
temperature 001A0=111.48
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def apply(metric):
|
def apply(metric):
|
||||||
metric.fields[str(metric.fields['sensor'])] = metric.fields['value']
|
metric.fields[str(metric.fields['sensor'])] = metric.fields['value']
|
||||||
metric.fields.pop('value',None)
|
metric.fields.pop('value',None)
|
||||||
metric.fields.pop('sensor',None)
|
metric.fields.pop('sensor',None)
|
||||||
return metric
|
return metric
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
# Example: A new field 'usage' from an existing fields 'used' and 'total'
|
# Example: A new field 'usage' from an existing fields 'used' and 'total'
|
||||||
#
|
#
|
||||||
# Example Input:
|
# Example Input:
|
||||||
# memory, host=hostname used=11038756864.4948,total=17179869184.1221 1597255082000000000
|
# memory,host=hostname used=11038756864.4948,total=17179869184.1221 1597255082000000000
|
||||||
|
#
|
||||||
# Example Output:
|
# Example Output:
|
||||||
# memory, host=hostname used=11038756864.4948,total=17179869184.1221,usage=64.254021647 1597255082000000000
|
# memory,host=hostname used=11038756864.4948,total=17179869184.1221,usage=64.25402164701573 1597255082000000000
|
||||||
|
|
||||||
def apply(metric):
|
def apply(metric):
|
||||||
used = float(metric.fields['used'])
|
used = float(metric.fields['used'])
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
# Rename any tags using the mapping in the renames dict.
|
# Rename any tags using the mapping in the renames dict.
|
||||||
#
|
#
|
||||||
# Example Input:
|
# Example Input:
|
||||||
# measurement, host=hostname lower=0,upper=100 1597255410000000000
|
# measurement,host=hostname lower=0,upper=100 1597255410000000000
|
||||||
|
#
|
||||||
# Example Output:
|
# Example Output:
|
||||||
# measurement, host=hostname min=0,max=100 1597255410000000000
|
# measurement,host=hostname min=0,max=100 1597255410000000000
|
||||||
|
|
||||||
renames = {
|
renames = {
|
||||||
'lower': 'min',
|
'lower': 'min',
|
||||||
|
|
@ -15,4 +16,8 @@ def apply(metric):
|
||||||
if k in renames:
|
if k in renames:
|
||||||
metric.tags[renames[k]] = v
|
metric.tags[renames[k]] = v
|
||||||
metric.tags.pop(k)
|
metric.tags.pop(k)
|
||||||
|
for k, v in metric.fields.items():
|
||||||
|
if k in renames:
|
||||||
|
metric.fields[renames[k]] = v
|
||||||
|
metric.fields.pop(k)
|
||||||
return metric
|
return metric
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# Example Input:
|
# Example Input:
|
||||||
# modbus,host=hostname Current=1.22,Energy=0,Frequency=60i,Power=0,Voltage=123.9000015258789 1554079521000000000
|
# modbus,host=hostname Current=1.22,Energy=0,Frequency=60i,Power=0,Voltage=123.9000015258789 1554079521000000000
|
||||||
|
#
|
||||||
# Example Output:
|
# Example Output:
|
||||||
# modbus,host=hostname Current=12.2,Energy=0,Frequency=60i,Power=0,Voltage=1239.000015258789 1554079521000000000
|
# modbus,host=hostname Current=12.2,Energy=0,Frequency=60i,Power=0,Voltage=1239.000015258789 1554079521000000000
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Filter metrics by value
|
||||||
|
'''
|
||||||
|
In this example we look at the `value` field of the metric.
|
||||||
|
If the value is zeor, we delete all the fields, effectively dropping the metric.
|
||||||
|
|
||||||
|
Example Input:
|
||||||
|
temperature sensor="001A0",value=111.48
|
||||||
|
temperature sensor="001B0",value=0.0
|
||||||
|
|
||||||
|
Example Output:
|
||||||
|
temperature sensor="001A0",value=111.48
|
||||||
|
'''
|
||||||
|
|
||||||
|
def apply(metric):
|
||||||
|
if metric.fields["value"] == 0.0:
|
||||||
|
# removing all fields deletes a metric
|
||||||
|
metric.fields.clear()
|
||||||
|
return metric
|
||||||
Loading…
Reference in New Issue