gomonop/pkg/perfdata/range.go

212 lines
4.6 KiB
Go

package perfdata // import nocternity.net/gomonop/pkg/perfdata
import (
"fmt"
"strconv"
"strings"
)
// Performance data range.
type Range struct {
start string
end string
inside bool
}
// Creates a performance data range from -inf to 0 and from the specified
// value to +inf.
func RangeMax(max string) *Range {
if !valueCheck.MatchString(max) {
panic("invalid performance data range maximum value")
}
pdRange := &Range{}
pdRange.start = "0"
pdRange.end = max
return pdRange
}
// Creates a performance data range from -inf to the specified minimal value
// and from the specified maximal value to +inf.
func RangeMinMax(min, max string) *Range {
if !valueCheck.MatchString(max) {
panic("invalid performance data range maximum value")
}
if !rangeMinCheck.MatchString(min) {
panic("invalid performance data range minimum value")
}
pdRange := &Range{}
if min == "~" {
min = ""
}
pdRange.start = min
pdRange.end = max
return pdRange
}
// Inverts the range.
func (r *Range) Inside() *Range {
r.inside = true
return r
}
// Generates the range's string representation so it can be sent to the
// monitoring system.
func (r *Range) String() string {
var start, inside string
switch r.start {
case "":
start = "~"
case "0":
start = ""
default:
start = r.start
}
if r.inside {
inside = "@"
}
return inside + start + ":" + r.end
}
// Contains checks whether a numeric value is within the range.
func (r *Range) Contains(value float64) bool {
var inStart, inEnd bool
if r.start != "" {
startValue, err := strconv.ParseFloat(r.start, 64)
if err != nil {
panic(fmt.Sprintf("invalid performance data range start value: %v", err))
}
inStart = value < startValue
}
if r.end != "" {
endValue, err := strconv.ParseFloat(r.end, 64)
if err != nil {
panic(fmt.Sprintf("invalid performance data range end value: %v", err))
}
inEnd = value > endValue
}
return (inStart || inEnd) != r.inside
}
// A state of the range parser.
type rangeParserState int
const (
rpsInit rangeParserState = iota // Initial state
rpsExpectStart // Expect the start of the range
rpsInStart // Reading the start of the range
rpsExpectColon // Expect the colon that separates the start and end
rpsExpectEnd // Expect the end of the range
rpsInEnd // Reading the end of the range
)
// An error emitted by the range parser.
type rangeParserError struct {
input string
position int
message string
}
// parseError creates a new range parser error.
func parseError(input string, position int, message string) *rangeParserError {
return &rangeParserError{
input: input,
position: position,
message: message,
}
}
// Error implements the error interface for the range parser error type.
func (rpe *rangeParserError) Error() string {
return fmt.Sprintf("in `%s' at position %d: %s", rpe.input, rpe.position, rpe.message)
}
// Try to parse a string into a performance data range.
func ParseRange(input string) (*Range, error) {
runes := []rune(input)
index := 0
state := rpsInit
parsed := Range{}
startOfStart := 0
startOfEnd := 0
strBuilder := strings.Builder{}
for index < len(runes) {
curRune := runes[index]
switch state {
case rpsInit:
if curRune == '@' {
parsed.inside = true
index++
}
state = rpsExpectStart
case rpsExpectStart:
switch curRune {
case ':':
parsed.start = "0"
state = rpsExpectEnd
case '~':
state = rpsExpectColon
default:
strBuilder.WriteRune(curRune)
startOfStart = index
state = rpsInStart
}
index++
case rpsInStart:
switch curRune {
case ':':
parsed.start = strBuilder.String()
if !valueCheck.MatchString(parsed.start) {
return nil, parseError(input, startOfStart, "invalid start value")
}
strBuilder.Reset()
state = rpsExpectEnd
default:
strBuilder.WriteRune(curRune)
}
index++
case rpsExpectColon:
switch curRune {
case ':':
state = rpsExpectEnd
index++
default:
return nil, parseError(input, index, "expected ':'")
}
case rpsExpectEnd:
startOfEnd = index
state = rpsInEnd
case rpsInEnd:
strBuilder.WriteRune(curRune)
index++
}
}
if state == rpsInStart {
// The range was a single value, so that's the upper bound.
parsed.start = "0"
state = rpsInEnd
}
if state != rpsInEnd {
return nil, parseError(input, index, "unexpected end of input")
}
parsed.end = strBuilder.String()
if !valueCheck.MatchString(parsed.end) {
return nil, parseError(input, startOfEnd, "invalid end value")
}
return &parsed, nil
}