2018-01-20 18:59:23 +00:00
package main
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"time"
"github.com/gosimple/slug"
log "github.com/sirupsen/logrus"
2019-05-25 11:26:43 +00:00
mondash "github.com/Luzifer/mondash/client"
"github.com/Luzifer/rconfig"
2018-01-20 18:59:23 +00:00
)
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" `
2019-05-25 11:26:43 +00:00
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)" `
2018-01-20 18:59:23 +00:00
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 {
2019-05-25 11:26:43 +00:00
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 ,
2018-01-20 18:59:23 +00:00
} ) ; 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 ]
}