refactor: change the code's whole structure

This commit is contained in:
Emmanuel BENOîT 2024-07-19 14:29:51 +02:00
parent a659154937
commit 96637019c1
Signed by: Emmanuel BENOîT
SSH key fingerprint: SHA256:l7PFUUF5TCDsvYeQC9OnTNz08dFY7Fvf4Hv3neIqYpg
13 changed files with 295 additions and 52 deletions

197
pkg/perfdata/perfdata.go Normal file
View file

@ -0,0 +1,197 @@
// Package `perfdata` provides representations for a monitoring plugin's
// performance data.
package perfdata // import nocternity.net/go-monitoring/pkg/perfdata
import (
"fmt"
"regexp"
"strings"
)
// Units of measurement, which may be used to qualify the performance data.
type UnitOfMeasurement int
const (
UOM_NONE UnitOfMeasurement = iota
UOM_SECONDS
UOM_PERCENT
UOM_BYTES
UOM_KILOBYTES
UOM_MEGABYTES
UOM_GIGABYTES
UOM_TERABYTES
UOM_COUNTER
)
func (u UnitOfMeasurement) String() string {
return [...]string{"", "s", "%", "B", "KB", "MB", "GB", "TB", "c"}[u]
}
// Flags indicating which elements of performance data have been set.
type perfDataBits int
const (
PDAT_WARN perfDataBits = 1 << iota
PDAT_CRIT
PDAT_MIN
PDAT_MAX
)
// Regexps used to check values and ranges in performance data records.
var (
// Common value check regexp
vcRegexp = `^-?(0(\.\d*)?|[1-9]\d*(\.\d*)?|\.\d+)$`
// Compiled value check regexp
valueCheck = regexp.MustCompile(vcRegexp)
// Compiled range min value check
rangeMinCheck = regexp.MustCompile(vcRegexp + `|^~$`)
)
// Performance data range
type PerfDataRange struct {
start string
end string
inside bool
}
// Creates a performance data range from -inf to 0 and from the specified
// value to +inf.
func PDRMax(max string) *PerfDataRange {
if !valueCheck.MatchString(max) {
panic("invalid performance data range maximum value")
}
r := &PerfDataRange{}
r.start = "0"
r.end = max
return r
}
// Creates a performance data range from -inf to the specified minimal value
// and from the specified maximal value to +inf.
func PDRMinMax(min, max string) *PerfDataRange {
if !valueCheck.MatchString(max) {
panic("invalid performance data range maximum value")
}
if !rangeMinCheck.MatchString(min) {
panic("invalid performance data range minimum value")
}
r := &PerfDataRange{}
r.start = min
r.end = max
return r
}
// Inverts the range.
func (r *PerfDataRange) Inside() *PerfDataRange {
r.inside = true
return r
}
// Generates the range's string representation so it can be sent to the
// monitoring system.
func (r *PerfDataRange) String() string {
var start, inside string
if r.start == "" {
start = "~"
} else if r.start == "0" {
start = ""
} else {
start = r.start
}
if r.inside {
inside = "@"
} else {
inside = ""
}
return fmt.Sprintf("%s%s:%s", inside, start, r.end)
}
// Performance data, including a label, units, a value, warning/critical
// ranges and min/max boundaries.
type PerfData struct {
Label string
units UnitOfMeasurement
bits perfDataBits
value string
warn, crit PerfDataRange
min, max string
}
// Create performance data using the specified label and units.
func New(label string, units UnitOfMeasurement, value string) *PerfData {
if value != "" && !valueCheck.MatchString(value) {
panic("invalid value")
}
r := &PerfData{}
r.Label = label
r.units = units
if value == "" {
r.value = "U"
} else {
r.value = value
}
return r
}
// Set the warning range for the performance data record.
func (d *PerfData) SetWarn(r *PerfDataRange) {
d.warn = *r
d.bits = d.bits | PDAT_WARN
}
// Set the critical range for the performance data record.
func (d *PerfData) SetCrit(r *PerfDataRange) {
d.crit = *r
d.bits = d.bits | PDAT_CRIT
}
// Set the performance data's minimal value
func (d *PerfData) SetMin(min string) {
if !valueCheck.MatchString(min) {
panic("invalid value")
}
d.min = min
d.bits = d.bits | PDAT_MIN
}
// Set the performance data's maximal value.
func (d *PerfData) SetMax(max string) {
if !valueCheck.MatchString(max) {
panic("invalid value")
}
d.max = max
d.bits = d.bits | PDAT_MAX
}
// Converts performance data to a string which may be read by the monitoring
// system.
func (d *PerfData) String() string {
var sb strings.Builder
needsQuotes := strings.ContainsAny(d.Label, " '=\"")
if needsQuotes {
sb.WriteString("'")
}
sb.WriteString(strings.ReplaceAll(d.Label, "'", "''"))
if needsQuotes {
sb.WriteString("'")
}
sb.WriteString("=")
sb.WriteString(fmt.Sprintf("%s%s;", d.value, d.units.String()))
if d.bits&PDAT_WARN != 0 {
sb.WriteString(d.warn.String())
}
sb.WriteString(";")
if d.bits&PDAT_CRIT != 0 {
sb.WriteString(d.crit.String())
}
sb.WriteString(";")
if d.bits&PDAT_MIN != 0 {
sb.WriteString(d.min)
}
sb.WriteString(";")
if d.bits&PDAT_MAX != 0 {
sb.WriteString(d.max)
}
return sb.String()
}

View file

@ -0,0 +1,108 @@
package perfdata // import nocternity.net/go-monitoring/pkg/perfdata
import (
"fmt"
"testing"
)
func assert(t *testing.T, check bool, msg string) {
if !check {
t.Errorf(msg)
}
}
func assertPanic(t *testing.T, f func(), msg string) {
defer func() {
if r := recover(); r == nil {
t.Errorf(msg)
}
}()
f()
}
func TestValueCheckValid(t *testing.T) {
validValues := []string{
"0", "0.", "0.952", "1", "123", "123.", "123.45", ".1",
"-0", "-0.", "-0.952", "-1", "-123", "-123.", "-123.45", "-.1",
}
for _, value := range validValues {
if !valueCheck.MatchString(value) {
t.Errorf("'%s' is a valid value string", value)
}
}
}
func TestValueCheckInvalid(t *testing.T) {
invalidValues := []string{".", "-.", "a", " ", ""}
for _, value := range invalidValues {
if valueCheck.MatchString(value) {
t.Errorf("'%s' is an invalid value string", value)
}
}
}
func TestPdrMaxInvalid(t *testing.T) {
assertPanic(
t, func() { PDRMax("") },
"Created PerfDataRange with invalid max value",
)
}
func TestPdrMax(t *testing.T) {
value := "123"
pdr := PDRMax(value)
assert(t, pdr.start == "0", "Min value should be '0'")
assert(t, pdr.end == value, "Max value not copied to PerfDataRange")
assert(t, !pdr.inside, "Inside flag should not be set")
}
func TestPdrMinMaxInvalid(t *testing.T) {
assertPanic(
t, func() { PDRMinMax("", "123") },
"Created PerfDataRange with invalid min value",
)
assertPanic(
t, func() { PDRMinMax("123", "") },
"Created PerfDataRange with invalid max value",
)
}
func TestPdrMinMax(t *testing.T) {
min, max := "123", "456"
pdr := PDRMinMax(min, max)
assert(t, pdr.start == min, "Min value not copied to PerfDataRange")
assert(t, pdr.end == max, "Max value not copied to PerfDataRange")
assert(t, !pdr.inside, "Inside flag should not be set")
}
func TestPdrInside(t *testing.T) {
pdr := &PerfDataRange{}
pdr = pdr.Inside()
assert(t, pdr.inside, "Inside flag should be set")
pdr = pdr.Inside()
assert(t, pdr.inside, "Inside flag should still be set")
}
func TestPdrString(t *testing.T) {
type Test struct {
pdr PerfDataRange
out string
}
tests := []Test{
{pdr: PerfDataRange{start: "Y", end: "X"}, out: "Y:X"},
{pdr: PerfDataRange{end: "X"}, out: "~:X"},
{pdr: PerfDataRange{start: "0", end: "X"}, out: ":X"},
{pdr: PerfDataRange{inside: true, start: "Y", end: "X"}, out: "@Y:X"},
}
for _, test := range tests {
result := test.pdr.String()
assert(
t,
result == test.out,
fmt.Sprintf("Expected '%s', got '%s'", test.out, result),
)
}
}

116
pkg/plugin/plugin.go Normal file
View file

@ -0,0 +1,116 @@
// Package plugin implements a helper that can be used to implement a Nagios,
// Centreon, Icinga... service monitoring plugin.
package plugin // import nocternity.net/go-monitoring/pkg/perfdata
import (
"container/list"
"fmt"
"os"
"strings"
"nocternity.net/go-monitoring/pkg/perfdata"
)
// Status represents the return status of the monitoring plugin. The
// corresponding integer value will be used as the program's exit code,
// to be interpreted by the monitoring system.
type Status int
// Plugin exit statuses.
const (
OK Status = iota
WARNING
CRITICAL
UNKNOWN
)
// String representations of the plugin statuses.
func (s Status) String() string {
return [...]string{"OK", "WARNING", "ERROR", "UNKNOWN"}[s]
}
// Plugin represents the monitoring plugin's state, including its name,
// return status and message, additional lines of text, and performance
// data to be encoded in the output.
type Plugin struct {
name string
status Status
message string
extraText *list.List
perfData map[string]*perfdata.PerfData
}
// New creates the plugin with `name` as its name and an unknown status.
func New(name string) *Plugin {
p := new(Plugin)
p.name = name
p.status = UNKNOWN
p.message = "no status set"
p.perfData = make(map[string]*perfdata.PerfData)
return p
}
// SetState sets the plugin's output code to `status` and its message to
// the specified `message`.
func (p *Plugin) SetState(status Status, message string) {
p.status = status
p.message = message
}
// AddLine adds the specified string to the extra output text buffer.
func (p *Plugin) AddLine(format string, data ...interface{}) {
if p.extraText == nil {
p.extraText = list.New()
}
p.extraText.PushBack(fmt.Sprintf(format, data...))
}
// AddLines add the specified `lines` to the output text.
func (p *Plugin) AddLines(lines []string) {
for _, line := range lines {
p.AddLine("%s", line)
}
}
// AddPerfData adds performance data described by the "pd" argument to the
// output's performance data. If two performance data records are added for
// the same label, the program panics.
func (p *Plugin) AddPerfData(pd *perfdata.PerfData) {
_, exists := p.perfData[pd.Label]
if exists {
panic(fmt.Sprintf("duplicate performance data %s", pd.Label))
}
p.perfData[pd.Label] = pd
}
// Done generates the plugin's text output from its name, status, text data
// and performance data, before exiting with the code corresponding to the
// status.
func (p *Plugin) Done() {
var sb strings.Builder
sb.WriteString(p.name)
sb.WriteString(" ")
sb.WriteString(p.status.String())
sb.WriteString(": ")
sb.WriteString(p.message)
if len(p.perfData) > 0 {
sb.WriteString(" | ")
needSep := false
for k := range p.perfData {
if needSep {
sb.WriteString(", ")
} else {
needSep = true
}
sb.WriteString(p.perfData[k].String())
}
}
if p.extraText != nil {
for em := p.extraText.Front(); em != nil; em = em.Next() {
sb.WriteString("\n")
sb.WriteString(em.Value.(string))
}
}
fmt.Println(sb.String())
os.Exit(int(p.status))
}

9
pkg/program/program.go Normal file
View file

@ -0,0 +1,9 @@
package program // import nocternity.net/go-monitoring/pkg/program
type Program interface {
CheckArguments() bool
RunCheck()
Done()
}
type ProgramBuilder func() Program

27
pkg/version/version.go Normal file
View file

@ -0,0 +1,27 @@
package version // import nocternity.net/go-monitoring/pkg/version
import "fmt"
var (
version = ""
commit = "unknown"
status = ""
target = "unknown/unknown"
)
func Version() string {
var schar, ver string
if status == "dirty" {
schar = "*"
} else {
schar = ""
}
if version == "" {
ver = "development version"
} else {
ver = version
}
return fmt.Sprintf("%s (%s%s) %s", ver, commit, schar, target)
}