2016-02-25 04:32:22 +00:00
|
|
|
package nagios
|
|
|
|
|
|
|
|
import (
|
2019-03-25 23:24:42 +00:00
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2018-09-18 16:08:46 +00:00
|
|
|
"errors"
|
2019-03-25 23:24:42 +00:00
|
|
|
"fmt"
|
2018-09-18 16:08:46 +00:00
|
|
|
"log"
|
2019-03-25 23:24:42 +00:00
|
|
|
"os/exec"
|
2016-02-25 04:32:22 +00:00
|
|
|
"regexp"
|
2018-03-28 00:30:51 +00:00
|
|
|
"strconv"
|
2016-02-25 04:32:22 +00:00
|
|
|
"strings"
|
2019-03-25 23:24:42 +00:00
|
|
|
"syscall"
|
2016-02-25 04:32:22 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/influxdata/telegraf"
|
2016-11-22 12:51:57 +00:00
|
|
|
"github.com/influxdata/telegraf/metric"
|
2016-02-25 04:32:22 +00:00
|
|
|
)
|
|
|
|
|
2019-03-25 23:24:42 +00:00
|
|
|
// getExitCode get the exit code from an error value which is the result
|
|
|
|
// of running a command through exec package api.
|
|
|
|
func getExitCode(err error) (int, error) {
|
|
|
|
if err == nil {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ee, ok := err.(*exec.ExitError)
|
|
|
|
if !ok {
|
|
|
|
// If it is not an *exec.ExitError, then it must be
|
|
|
|
// an io error, but docs do not say anything about the
|
|
|
|
// exit code in this case.
|
|
|
|
return 0, errors.New("expected *exec.ExitError")
|
|
|
|
}
|
|
|
|
|
|
|
|
ws, ok := ee.Sys().(syscall.WaitStatus)
|
|
|
|
if !ok {
|
|
|
|
return 0, errors.New("expected syscall.WaitStatus")
|
|
|
|
}
|
|
|
|
|
|
|
|
return ws.ExitStatus(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TryAddState attempts to add a state derived from the runErr.
|
|
|
|
// If any error occurs, it is guaranteed to be returned along with
|
|
|
|
// the initial metric slice.
|
|
|
|
func TryAddState(runErr error, metrics []telegraf.Metric) ([]telegraf.Metric, error) {
|
|
|
|
state, err := getExitCode(runErr)
|
|
|
|
if err != nil {
|
|
|
|
return metrics, fmt.Errorf("exec: get exit code: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range metrics {
|
|
|
|
if m.Name() == "nagios_state" {
|
|
|
|
m.AddField("state", state)
|
|
|
|
return metrics, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var ts time.Time
|
|
|
|
if len(metrics) != 0 {
|
|
|
|
ts = metrics[0].Time()
|
|
|
|
} else {
|
|
|
|
ts = time.Now().UTC()
|
|
|
|
}
|
|
|
|
f := map[string]interface{}{
|
|
|
|
"state": state,
|
|
|
|
}
|
|
|
|
m, err := metric.New("nagios_state", nil, f, ts)
|
|
|
|
if err != nil {
|
|
|
|
return metrics, err
|
|
|
|
}
|
|
|
|
metrics = append(metrics, m)
|
|
|
|
return metrics, nil
|
|
|
|
}
|
|
|
|
|
2016-02-25 04:32:22 +00:00
|
|
|
type NagiosParser struct {
|
|
|
|
MetricName string
|
|
|
|
DefaultTags map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Got from Alignak
|
|
|
|
// https://github.com/Alignak-monitoring/alignak/blob/develop/alignak/misc/perfdata.py
|
2018-09-18 16:08:46 +00:00
|
|
|
var (
|
|
|
|
perfSplitRegExp = regexp.MustCompile(`([^=]+=\S+)`)
|
|
|
|
nagiosRegExp = regexp.MustCompile(`^([^=]+)=([\d\.\-\+eE]+)([\w\/%]*);?([\d\.\-\+eE:~@]+)?;?([\d\.\-\+eE:~@]+)?;?([\d\.\-\+eE]+)?;?([\d\.\-\+eE]+)?;?\s*`)
|
|
|
|
)
|
2016-02-25 04:32:22 +00:00
|
|
|
|
|
|
|
func (p *NagiosParser) ParseLine(line string) (telegraf.Metric, error) {
|
|
|
|
metrics, err := p.Parse([]byte(line))
|
|
|
|
return metrics[0], err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *NagiosParser) SetDefaultTags(tags map[string]string) {
|
|
|
|
p.DefaultTags = tags
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *NagiosParser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
2019-03-25 23:24:42 +00:00
|
|
|
ts := time.Now().UTC()
|
|
|
|
|
|
|
|
s := bufio.NewScanner(bytes.NewReader(buf))
|
|
|
|
|
|
|
|
var msg bytes.Buffer
|
|
|
|
var longmsg bytes.Buffer
|
|
|
|
|
2016-02-25 04:32:22 +00:00
|
|
|
metrics := make([]telegraf.Metric, 0)
|
2018-09-18 16:08:46 +00:00
|
|
|
|
2019-03-25 23:24:42 +00:00
|
|
|
// Scan the first line.
|
|
|
|
if !s.Scan() && s.Err() != nil {
|
|
|
|
return nil, s.Err()
|
|
|
|
}
|
|
|
|
parts := bytes.Split(s.Bytes(), []byte{'|'})
|
|
|
|
switch len(parts) {
|
|
|
|
case 2:
|
|
|
|
ms, err := parsePerfData(string(parts[1]), ts)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("E! [parser.nagios] failed to parse performance data: %s\n", err.Error())
|
|
|
|
}
|
|
|
|
metrics = append(metrics, ms...)
|
|
|
|
fallthrough
|
|
|
|
case 1:
|
|
|
|
msg.Write(bytes.TrimSpace(parts[0]))
|
|
|
|
default:
|
|
|
|
return nil, errors.New("illegal output format")
|
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
|
2019-03-25 23:24:42 +00:00
|
|
|
// Read long output.
|
|
|
|
for s.Scan() {
|
|
|
|
if bytes.Contains(s.Bytes(), []byte{'|'}) {
|
|
|
|
parts := bytes.Split(s.Bytes(), []byte{'|'})
|
|
|
|
if longmsg.Len() != 0 {
|
|
|
|
longmsg.WriteByte('\n')
|
|
|
|
}
|
|
|
|
longmsg.Write(bytes.TrimSpace(parts[0]))
|
|
|
|
|
|
|
|
ms, err := parsePerfData(string(parts[1]), ts)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("E! [parser.nagios] failed to parse performance data: %s\n", err.Error())
|
|
|
|
}
|
|
|
|
metrics = append(metrics, ms...)
|
|
|
|
break
|
2016-02-25 04:32:22 +00:00
|
|
|
}
|
2019-03-25 23:24:42 +00:00
|
|
|
if longmsg.Len() != 0 {
|
|
|
|
longmsg.WriteByte('\n')
|
|
|
|
}
|
|
|
|
longmsg.Write(bytes.TrimSpace((s.Bytes())))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse extra performance data.
|
|
|
|
for s.Scan() {
|
|
|
|
ms, err := parsePerfData(s.Text(), ts)
|
2018-09-18 16:08:46 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("E! [parser.nagios] failed to parse performance data: %s\n", err.Error())
|
|
|
|
}
|
2019-03-25 23:24:42 +00:00
|
|
|
metrics = append(metrics, ms...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.Err() != nil {
|
|
|
|
log.Printf("D! [parser.nagios] unexpected io error: %s\n", s.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create nagios state.
|
|
|
|
fields := map[string]interface{}{
|
|
|
|
"service_output": msg.String(),
|
|
|
|
}
|
|
|
|
if longmsg.Len() != 0 {
|
|
|
|
fields["long_service_output"] = longmsg.String()
|
2018-09-18 16:08:46 +00:00
|
|
|
}
|
2019-03-25 23:24:42 +00:00
|
|
|
|
|
|
|
m, err := metric.New("nagios_state", nil, fields, ts)
|
|
|
|
if err == nil {
|
|
|
|
metrics = append(metrics, m)
|
|
|
|
} else {
|
|
|
|
log.Printf("E! [parser.nagios] failed to add nagios_state: %s\n", err)
|
|
|
|
}
|
|
|
|
|
2018-09-18 16:08:46 +00:00
|
|
|
return metrics, nil
|
|
|
|
}
|
|
|
|
|
2019-03-25 23:24:42 +00:00
|
|
|
func parsePerfData(perfdatas string, timestamp time.Time) ([]telegraf.Metric, error) {
|
2018-09-18 16:08:46 +00:00
|
|
|
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 {
|
2016-02-25 04:32:22 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
if perf[1] == "" || perf[2] == "" {
|
2016-02-25 04:32:22 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
|
|
|
|
fieldName := strings.Trim(perf[1], "'")
|
|
|
|
tags := map[string]string{"perfdata": fieldName}
|
|
|
|
if perf[3] != "" {
|
|
|
|
str := string(perf[3])
|
2018-03-28 00:30:51 +00:00
|
|
|
if str != "" {
|
|
|
|
tags["unit"] = str
|
|
|
|
}
|
2016-02-25 04:32:22 +00:00
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
|
2016-02-25 04:32:22 +00:00
|
|
|
fields := make(map[string]interface{})
|
2018-09-18 16:08:46 +00:00
|
|
|
if perf[2] == "U" {
|
|
|
|
return nil, errors.New("Value undetermined")
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := strconv.ParseFloat(string(perf[2]), 64)
|
2018-03-28 00:30:51 +00:00
|
|
|
if err == nil {
|
|
|
|
fields["value"] = f
|
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
if perf[4] != "" {
|
|
|
|
low, high, err := parseThreshold(perf[4])
|
2018-03-28 00:30:51 +00:00
|
|
|
if err == nil {
|
2018-09-18 16:08:46 +00:00
|
|
|
if strings.Contains(perf[4], "@") {
|
|
|
|
fields["warning_le"] = low
|
|
|
|
fields["warning_ge"] = high
|
|
|
|
} else {
|
|
|
|
fields["warning_lt"] = low
|
|
|
|
fields["warning_gt"] = high
|
|
|
|
}
|
2018-03-28 00:30:51 +00:00
|
|
|
}
|
2016-02-25 04:32:22 +00:00
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
if perf[5] != "" {
|
|
|
|
low, high, err := parseThreshold(perf[5])
|
2018-03-28 00:30:51 +00:00
|
|
|
if err == nil {
|
2018-09-18 16:08:46 +00:00
|
|
|
if strings.Contains(perf[5], "@") {
|
|
|
|
fields["critical_le"] = low
|
|
|
|
fields["critical_ge"] = high
|
|
|
|
} else {
|
|
|
|
fields["critical_lt"] = low
|
|
|
|
fields["critical_gt"] = high
|
|
|
|
}
|
2018-03-28 00:30:51 +00:00
|
|
|
}
|
2016-02-25 04:32:22 +00:00
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
if perf[6] != "" {
|
|
|
|
f, err := strconv.ParseFloat(perf[6], 64)
|
2018-03-28 00:30:51 +00:00
|
|
|
if err == nil {
|
|
|
|
fields["min"] = f
|
|
|
|
}
|
2016-02-25 04:32:22 +00:00
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
if perf[7] != "" {
|
|
|
|
f, err := strconv.ParseFloat(perf[7], 64)
|
2018-03-28 00:30:51 +00:00
|
|
|
if err == nil {
|
|
|
|
fields["max"] = f
|
|
|
|
}
|
2016-02-25 04:32:22 +00:00
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
|
2016-02-25 04:32:22 +00:00
|
|
|
// Create metric
|
2019-03-25 23:24:42 +00:00
|
|
|
metric, err := metric.New("nagios", tags, fields, timestamp)
|
2016-02-25 04:32:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Add Metric
|
|
|
|
metrics = append(metrics, metric)
|
|
|
|
}
|
|
|
|
|
|
|
|
return metrics, nil
|
|
|
|
}
|
2018-09-18 16:08:46 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|