212 lines
4.6 KiB
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
|
|
}
|