1
0
mirror of https://github.com/Luzifer/mondash.git synced 2024-09-19 17:02:58 +00:00
mondash/cmd/mondash-nagios/main.go
Knut Ahlers b2d529faf8
Support different staleness status
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2019-05-25 13:26:43 +02:00

171 lines
4.8 KiB
Go

package main
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"time"
"github.com/gosimple/slug"
log "github.com/sirupsen/logrus"
mondash "github.com/Luzifer/mondash/client"
"github.com/Luzifer/rconfig"
)
var (
cfg = struct {
MondashHost string `flag:"host,h" env:"HOST" default:"https://mondash.org" description:"Change to use an on-premise instance of MonDash"`
MondashBoard string `flag:"board,b" env:"BOARD" default:"" description:"ID of the board to submit the metric to" validate:"nonzero"`
MondashToken string `flag:"token,t" env:"TOKEN" default:"" description:"Token associated with the specified board" validate:"nonzero"`
MetricID string `flag:"metric-id,m" env:"METRIC_ID" default:"" description:"ID of the metric, if not specified a generated ID from the title will be used"`
MetricTitle string `flag:"metric-title" env:"METRIC_TITLE" default:"" description:"Title of the metric, if not specified the command line will be used"`
Freshness time.Duration `flag:"freshness" env:"FRESHNESS" default:"1h" description:"Freshness of the metric (will turn to unknown if no new result was submitted)"`
StaleStatus string `flag:"stale-status" default:"Unknown" description:"Status to set when metric is stale (One of Unknown, OK, Warning, Critical)"`
Timeout time.Duration `flag:"timeout" env:"TIMEOUT" default:"1m" description:"Timeout for the script command to be killed"`
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
}{}
statusMapping = map[int]mondash.Status{
0: mondash.StatusOK,
1: mondash.StatusWarning,
2: mondash.StatusCritical,
3: mondash.StatusUnknown,
}
version = "dev"
)
func init() {
if err := rconfig.ParseAndValidate(&cfg); err != nil {
log.Fatalf("Unable to parse commandline options: %s", err)
}
if cfg.VersionAndExit {
fmt.Printf("mondash-nagios %s\n", version)
os.Exit(0)
}
}
func main() {
command := rconfig.Args()[1:]
if len(command) == 0 {
log.Fatal("Please specify a command to execute")
}
if cfg.MetricTitle == "" {
cfg.MetricTitle = strings.Join(command, " ")
}
if cfg.MetricID == "" {
cfg.MetricID = slug.Make(cfg.MetricTitle)
}
var (
ctx = context.Background()
cancel context.CancelFunc
)
if cfg.Timeout > 0 {
ctx, cancel = context.WithTimeout(context.Background(), cfg.Timeout)
defer cancel()
}
outputBuffer := new(bytes.Buffer)
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Stdout = outputBuffer
cmd.Stderr = os.Stderr
exitCode := 0
if err := cmd.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
ws := exitError.Sys().(syscall.WaitStatus)
exitCode = ws.ExitStatus()
} else {
log.Warn("Could not get exit code for failed program, using UNKNOWN for reporting")
exitCode = 3 // Unknown
}
}
if _, ok := statusMapping[exitCode]; !ok {
log.WithFields(log.Fields{"exit-code": exitCode}).Warn("Undefined exit code, using UNKNOWN for reporting")
exitCode = 3 // Unknown
}
output, value := parseStdout(outputBuffer.String())
if output == "" {
output = fmt.Sprintf("exit %d", exitCode)
}
client := mondash.New(cfg.MondashBoard, cfg.MondashToken).WithHost(cfg.MondashHost)
if err := client.PostMetric(&mondash.PostMetricInput{
MetricID: cfg.MetricID,
Title: cfg.MetricTitle,
Description: output,
Status: statusMapping[exitCode],
Value: value,
Freshness: int64(cfg.Freshness / time.Second),
IgnoreMAD: true,
HideMAD: true,
StalenessStatus: cfg.StaleStatus,
}); err != nil {
log.WithError(err).Fatal("Could not submit metric")
}
}
func parseStdout(stdout string) (string, float64) {
// Drop everything after first line
stdout = strings.SplitN(stdout, "\n", 2)[0]
// Split output from perf data
parts := strings.SplitN(stdout, "|", 2)
// Return in case there is no perf data
if len(parts) == 1 {
return parts[0], 0
}
// Save the output
output := strings.TrimSpace(parts[0])
var (
perfData = map[string]float64{}
fallback string
)
// Parse perf data included in the output
perfParts := strings.Split(strings.TrimSpace(parts[1]), ", ")
for i, part := range perfParts {
tmp := strings.SplitN(part, "=", 2)
if len(tmp) != 2 {
continue
}
name := tmp[0]
value, err := strconv.ParseFloat(tmp[1], 64)
if err != nil {
log.WithFields(log.Fields{"perf-part": part}).WithError(err).Error("Unable to parse perf data")
continue
}
// In case there is a data part "value" use this one
if name == "value" {
return output, value
}
// Store first seen metric as "fallback"
if i == 0 {
fallback = name
}
perfData[name] = value
}
// Return output with first seen metric
return output, perfData[fallback]
}