Enhance performance data for nagios parser (#4691)
This commit is contained in:
parent
d3ad591481
commit
1d76343422
|
@ -1,6 +1,8 @@
|
||||||
package nagios
|
package nagios
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -17,8 +19,10 @@ type NagiosParser struct {
|
||||||
|
|
||||||
// Got from Alignak
|
// Got from Alignak
|
||||||
// https://github.com/Alignak-monitoring/alignak/blob/develop/alignak/misc/perfdata.py
|
// https://github.com/Alignak-monitoring/alignak/blob/develop/alignak/misc/perfdata.py
|
||||||
var perfSplitRegExp, _ = regexp.Compile(`([^=]+=\S+)`)
|
var (
|
||||||
var nagiosRegExp, _ = regexp.Compile(`^([^=]+)=([\d\.\-\+eE]+)([\w\/%]*);?([\d\.\-\+eE:~@]+)?;?([\d\.\-\+eE:~@]+)?;?([\d\.\-\+eE]+)?;?([\d\.\-\+eE]+)?;?\s*`)
|
perfSplitRegExp = regexp.MustCompile(`([^=]+=\S+)`)
|
||||||
|
nagiosRegExp = regexp.MustCompile(`^([^=]+)=([\d\.\-\+eE]+)([\w\/%]*);?([\d\.\-\+eE:~@]+)?;?([\d\.\-\+eE:~@]+)?;?([\d\.\-\+eE]+)?;?([\d\.\-\+eE]+)?;?\s*`)
|
||||||
|
)
|
||||||
|
|
||||||
func (p *NagiosParser) ParseLine(line string) (telegraf.Metric, error) {
|
func (p *NagiosParser) ParseLine(line string) (telegraf.Metric, error) {
|
||||||
metrics, err := p.Parse([]byte(line))
|
metrics, err := p.Parse([]byte(line))
|
||||||
|
@ -29,88 +33,99 @@ func (p *NagiosParser) SetDefaultTags(tags map[string]string) {
|
||||||
p.DefaultTags = tags
|
p.DefaultTags = tags
|
||||||
}
|
}
|
||||||
|
|
||||||
//> rta,host=absol,unit=ms critical=6000,min=0,value=0.332,warning=4000 1456374625003628099
|
|
||||||
//> pl,host=absol,unit=% critical=90,min=0,value=0,warning=80 1456374625003693967
|
|
||||||
|
|
||||||
func (p *NagiosParser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
func (p *NagiosParser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
||||||
metrics := make([]telegraf.Metric, 0)
|
metrics := make([]telegraf.Metric, 0)
|
||||||
// Convert to string
|
lines := strings.Split(strings.TrimSpace(string(buf)), "\n")
|
||||||
out := string(buf)
|
|
||||||
// Prepare output for splitting
|
for _, line := range lines {
|
||||||
// Delete escaped pipes
|
data_splitted := strings.Split(line, "|")
|
||||||
out = strings.Replace(out, `\|`, "___PROTECT_PIPE___", -1)
|
|
||||||
// Split lines and get the first one
|
if len(data_splitted) != 2 {
|
||||||
lines := strings.Split(out, "\n")
|
// got human readable output only or bad line
|
||||||
// Split output and perfdatas
|
|
||||||
data_splitted := strings.Split(lines[0], "|")
|
|
||||||
if len(data_splitted) <= 1 {
|
|
||||||
// No pipe == no perf data
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// Get perfdatas
|
|
||||||
perfdatas := data_splitted[1]
|
|
||||||
// Add escaped pipes
|
|
||||||
perfdatas = strings.Replace(perfdatas, "___PROTECT_PIPE___", `\|`, -1)
|
|
||||||
// Split perfs
|
|
||||||
unParsedPerfs := perfSplitRegExp.FindAllSubmatch([]byte(perfdatas), -1)
|
|
||||||
// Iterate on all perfs
|
|
||||||
for _, unParsedPerfs := range unParsedPerfs {
|
|
||||||
// Get metrics
|
|
||||||
// Trim perf
|
|
||||||
trimedPerf := strings.Trim(string(unParsedPerfs[0]), " ")
|
|
||||||
// Parse perf
|
|
||||||
perf := nagiosRegExp.FindAllSubmatch([]byte(trimedPerf), -1)
|
|
||||||
// Bad string
|
|
||||||
if len(perf) == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(perf[0]) <= 2 {
|
m, err := parsePerfData(data_splitted[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("E! [parser.nagios] failed to parse performance data: %s\n", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if perf[0][1] == nil || perf[0][2] == nil {
|
metrics = append(metrics, m...)
|
||||||
|
}
|
||||||
|
return metrics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePerfData(perfdatas string) ([]telegraf.Metric, error) {
|
||||||
|
metrics := make([]telegraf.Metric, 0)
|
||||||
|
|
||||||
|
for _, unParsedPerf := range perfSplitRegExp.FindAllString(perfdatas, -1) {
|
||||||
|
trimedPerf := strings.TrimSpace(unParsedPerf)
|
||||||
|
perf := nagiosRegExp.FindStringSubmatch(trimedPerf)
|
||||||
|
|
||||||
|
// verify at least `'label'=value[UOM];` existed
|
||||||
|
if len(perf) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fieldName := string(perf[0][1])
|
if perf[1] == "" || perf[2] == "" {
|
||||||
tags := make(map[string]string)
|
continue
|
||||||
if perf[0][3] != nil {
|
}
|
||||||
str := string(perf[0][3])
|
|
||||||
|
fieldName := strings.Trim(perf[1], "'")
|
||||||
|
tags := map[string]string{"perfdata": fieldName}
|
||||||
|
if perf[3] != "" {
|
||||||
|
str := string(perf[3])
|
||||||
if str != "" {
|
if str != "" {
|
||||||
tags["unit"] = str
|
tags["unit"] = str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := make(map[string]interface{})
|
fields := make(map[string]interface{})
|
||||||
f, err := strconv.ParseFloat(string(perf[0][2]), 64)
|
if perf[2] == "U" {
|
||||||
|
return nil, errors.New("Value undetermined")
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(string(perf[2]), 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fields["value"] = f
|
fields["value"] = f
|
||||||
}
|
}
|
||||||
// TODO should we set empty field
|
if perf[4] != "" {
|
||||||
// if metric if there is no data ?
|
low, high, err := parseThreshold(perf[4])
|
||||||
if perf[0][4] != nil {
|
|
||||||
f, err := strconv.ParseFloat(string(perf[0][4]), 64)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fields["warning"] = f
|
if strings.Contains(perf[4], "@") {
|
||||||
|
fields["warning_le"] = low
|
||||||
|
fields["warning_ge"] = high
|
||||||
|
} else {
|
||||||
|
fields["warning_lt"] = low
|
||||||
|
fields["warning_gt"] = high
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if perf[0][5] != nil {
|
}
|
||||||
f, err := strconv.ParseFloat(string(perf[0][5]), 64)
|
if perf[5] != "" {
|
||||||
|
low, high, err := parseThreshold(perf[5])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fields["critical"] = f
|
if strings.Contains(perf[5], "@") {
|
||||||
|
fields["critical_le"] = low
|
||||||
|
fields["critical_ge"] = high
|
||||||
|
} else {
|
||||||
|
fields["critical_lt"] = low
|
||||||
|
fields["critical_gt"] = high
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if perf[0][6] != nil {
|
}
|
||||||
f, err := strconv.ParseFloat(string(perf[0][6]), 64)
|
if perf[6] != "" {
|
||||||
|
f, err := strconv.ParseFloat(perf[6], 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fields["min"] = f
|
fields["min"] = f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if perf[0][7] != nil {
|
if perf[7] != "" {
|
||||||
f, err := strconv.ParseFloat(string(perf[0][7]), 64)
|
f, err := strconv.ParseFloat(perf[7], 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fields["max"] = f
|
fields["max"] = f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create metric
|
// Create metric
|
||||||
metric, err := metric.New(fieldName, tags, fields, time.Now().UTC())
|
metric, err := metric.New("nagios", tags, fields, time.Now().UTC())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -120,3 +135,47 @@ func (p *NagiosParser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
||||||
|
|
||||||
return metrics, nil
|
return metrics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from math
|
||||||
|
const (
|
||||||
|
MaxFloat64 = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52
|
||||||
|
MinFloat64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52)
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrBadThresholdFormat = errors.New("Bad threshold format")
|
||||||
|
|
||||||
|
// Handles all cases from https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
|
||||||
|
func parseThreshold(threshold string) (min float64, max float64, err error) {
|
||||||
|
thresh := strings.Split(threshold, ":")
|
||||||
|
switch len(thresh) {
|
||||||
|
case 1:
|
||||||
|
max, err = strconv.ParseFloat(string(thresh[0]), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, ErrBadThresholdFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, max, nil
|
||||||
|
case 2:
|
||||||
|
if thresh[0] == "~" {
|
||||||
|
min = MinFloat64
|
||||||
|
} else {
|
||||||
|
min, err = strconv.ParseFloat(string(thresh[0]), 64)
|
||||||
|
if err != nil {
|
||||||
|
min = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if thresh[1] == "" {
|
||||||
|
max = MaxFloat64
|
||||||
|
} else {
|
||||||
|
max, err = strconv.ParseFloat(string(thresh[1]), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, ErrBadThresholdFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, 0, ErrBadThresholdFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ with three lines
|
||||||
`
|
`
|
||||||
const validOutput2 = "TCP OK - 0.008 second response time on port 80|time=0.008457s;;;0.000000;10.000000"
|
const validOutput2 = "TCP OK - 0.008 second response time on port 80|time=0.008457s;;;0.000000;10.000000"
|
||||||
const validOutput3 = "TCP OK - 0.008 second response time on port 80|time=0.008457"
|
const validOutput3 = "TCP OK - 0.008 second response time on port 80|time=0.008457"
|
||||||
|
const validOutput4 = "OK: Load average: 0.00, 0.01, 0.05 | 'load1'=0.00;~:4;@0:6;0; 'load5'=0.01;3;0:5;0; 'load15'=0.05;0:2;0:4;0;"
|
||||||
const invalidOutput3 = "PING OK - Packet loss = 0%, RTA = 0.30 ms"
|
const invalidOutput3 = "PING OK - Packet loss = 0%, RTA = 0.30 ms"
|
||||||
const invalidOutput4 = "PING OK - Packet loss = 0%, RTA = 0.30 ms| =3;;;; dgasdg =;;;; sff=;;;;"
|
const invalidOutput4 = "PING OK - Packet loss = 0%, RTA = 0.30 ms| =3;;;; dgasdg =;;;; sff=;;;;"
|
||||||
|
|
||||||
|
@ -24,50 +25,71 @@ func TestParseValidOutput(t *testing.T) {
|
||||||
// Output1
|
// Output1
|
||||||
metrics, err := parser.Parse([]byte(validOutput1))
|
metrics, err := parser.Parse([]byte(validOutput1))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, metrics, 2)
|
require.Len(t, metrics, 2)
|
||||||
// rta
|
// rta
|
||||||
assert.Equal(t, "rta", metrics[0].Name())
|
assert.Equal(t, "rta", metrics[0].Tags()["perfdata"])
|
||||||
assert.Equal(t, map[string]interface{}{
|
assert.Equal(t, map[string]interface{}{
|
||||||
"value": float64(0.298),
|
"value": float64(0.298),
|
||||||
"warning": float64(4000),
|
"warning_lt": float64(0),
|
||||||
"critical": float64(6000),
|
"warning_gt": float64(4000),
|
||||||
|
"critical_lt": float64(0),
|
||||||
|
"critical_gt": float64(6000),
|
||||||
"min": float64(0),
|
"min": float64(0),
|
||||||
}, metrics[0].Fields())
|
}, metrics[0].Fields())
|
||||||
assert.Equal(t, map[string]string{"unit": "ms"}, metrics[0].Tags())
|
assert.Equal(t, map[string]string{"unit": "ms", "perfdata": "rta"}, metrics[0].Tags())
|
||||||
// pl
|
// pl
|
||||||
assert.Equal(t, "pl", metrics[1].Name())
|
assert.Equal(t, "pl", metrics[1].Tags()["perfdata"])
|
||||||
assert.Equal(t, map[string]interface{}{
|
assert.Equal(t, map[string]interface{}{
|
||||||
"value": float64(0),
|
"value": float64(0),
|
||||||
"warning": float64(80),
|
"warning_lt": float64(0),
|
||||||
"critical": float64(90),
|
"warning_gt": float64(80),
|
||||||
|
"critical_lt": float64(0),
|
||||||
|
"critical_gt": float64(90),
|
||||||
"min": float64(0),
|
"min": float64(0),
|
||||||
"max": float64(100),
|
"max": float64(100),
|
||||||
}, metrics[1].Fields())
|
}, metrics[1].Fields())
|
||||||
assert.Equal(t, map[string]string{"unit": "%"}, metrics[1].Tags())
|
assert.Equal(t, map[string]string{"unit": "%", "perfdata": "pl"}, metrics[1].Tags())
|
||||||
|
|
||||||
// Output2
|
// Output2
|
||||||
metrics, err = parser.Parse([]byte(validOutput2))
|
metrics, err = parser.Parse([]byte(validOutput2))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, metrics, 1)
|
require.Len(t, metrics, 1)
|
||||||
// time
|
// time
|
||||||
assert.Equal(t, "time", metrics[0].Name())
|
assert.Equal(t, "time", metrics[0].Tags()["perfdata"])
|
||||||
assert.Equal(t, map[string]interface{}{
|
assert.Equal(t, map[string]interface{}{
|
||||||
"value": float64(0.008457),
|
"value": float64(0.008457),
|
||||||
"min": float64(0),
|
"min": float64(0),
|
||||||
"max": float64(10),
|
"max": float64(10),
|
||||||
}, metrics[0].Fields())
|
}, metrics[0].Fields())
|
||||||
assert.Equal(t, map[string]string{"unit": "s"}, metrics[0].Tags())
|
assert.Equal(t, map[string]string{"unit": "s", "perfdata": "time"}, metrics[0].Tags())
|
||||||
|
|
||||||
// Output3
|
// Output3
|
||||||
metrics, err = parser.Parse([]byte(validOutput3))
|
metrics, err = parser.Parse([]byte(validOutput3))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, metrics, 1)
|
require.Len(t, metrics, 1)
|
||||||
// time
|
// time
|
||||||
assert.Equal(t, "time", metrics[0].Name())
|
assert.Equal(t, "time", metrics[0].Tags()["perfdata"])
|
||||||
assert.Equal(t, map[string]interface{}{
|
assert.Equal(t, map[string]interface{}{
|
||||||
"value": float64(0.008457),
|
"value": float64(0.008457),
|
||||||
}, metrics[0].Fields())
|
}, metrics[0].Fields())
|
||||||
assert.Equal(t, map[string]string{}, metrics[0].Tags())
|
assert.Equal(t, map[string]string{"perfdata": "time"}, metrics[0].Tags())
|
||||||
|
|
||||||
|
// Output4
|
||||||
|
metrics, err = parser.Parse([]byte(validOutput4))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, metrics, 3)
|
||||||
|
// load
|
||||||
|
// const validOutput4 = "OK: Load average: 0.00, 0.01, 0.05 | 'load1'=0.00;0:4;0:6;0; 'load5'=0.01;0:3;0:5;0; 'load15'=0.05;0:2;0:4;0;"
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"value": float64(0.00),
|
||||||
|
"warning_lt": MinFloat64,
|
||||||
|
"warning_gt": float64(4),
|
||||||
|
"critical_le": float64(0),
|
||||||
|
"critical_ge": float64(6),
|
||||||
|
"min": float64(0),
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
|
||||||
|
assert.Equal(t, map[string]string{"perfdata": "load1"}, metrics[0].Tags())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseInvalidOutput(t *testing.T) {
|
func TestParseInvalidOutput(t *testing.T) {
|
||||||
|
@ -78,11 +100,64 @@ func TestParseInvalidOutput(t *testing.T) {
|
||||||
// invalidOutput3
|
// invalidOutput3
|
||||||
metrics, err := parser.Parse([]byte(invalidOutput3))
|
metrics, err := parser.Parse([]byte(invalidOutput3))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, metrics, 0)
|
require.Len(t, metrics, 0)
|
||||||
|
|
||||||
// invalidOutput4
|
// invalidOutput4
|
||||||
metrics, err = parser.Parse([]byte(invalidOutput4))
|
metrics, err = parser.Parse([]byte(invalidOutput4))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, metrics, 0)
|
require.Len(t, metrics, 0)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseThreshold(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
eMin float64
|
||||||
|
eMax float64
|
||||||
|
eErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "10",
|
||||||
|
eMin: 0,
|
||||||
|
eMax: 10,
|
||||||
|
eErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "10:",
|
||||||
|
eMin: 10,
|
||||||
|
eMax: MaxFloat64,
|
||||||
|
eErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "~:10",
|
||||||
|
eMin: MinFloat64,
|
||||||
|
eMax: 10,
|
||||||
|
eErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "10:20",
|
||||||
|
eMin: 10,
|
||||||
|
eMax: 20,
|
||||||
|
eErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "10:20",
|
||||||
|
eMin: 10,
|
||||||
|
eMax: 20,
|
||||||
|
eErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "10:20:30",
|
||||||
|
eMin: 0,
|
||||||
|
eMax: 0,
|
||||||
|
eErr: ErrBadThresholdFormat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range tests {
|
||||||
|
min, max, err := parseThreshold(tests[i].input)
|
||||||
|
require.Equal(t, tests[i].eMin, min)
|
||||||
|
require.Equal(t, tests[i].eMax, max)
|
||||||
|
require.Equal(t, tests[i].eErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue