init push
This commit is contained in:
74
internal/shared/pkg/metricparse/metricparse.go
Normal file
74
internal/shared/pkg/metricparse/metricparse.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Package metricparse extracts a numeric value from the free-text engine/metric strings
|
||||
// used across FM reports (e.g. "GPC/N1 63.2"). The same parser MUST be used both when
|
||||
// aggregating totals and when deriving a single report's "today" contribution, otherwise
|
||||
// prev = total - today would not reconcile.
|
||||
package metricparse
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// numberToken matches a signed decimal number (integer or fractional).
|
||||
var numberToken = regexp.MustCompile(`[-+]?\d*\.?\d+`)
|
||||
|
||||
// Parse returns ParseString(*v), or 0 when v is nil.
|
||||
func Parse(v *string) float64 {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
return ParseString(*v)
|
||||
}
|
||||
|
||||
// ParseString pulls the numeric VALUE out of a metric label string:
|
||||
//
|
||||
// "GPC/N1 63.2" -> 63.2 "PTC/N2 61.8" -> 61.8 "63.2" -> 63.2
|
||||
// "63,2" -> 63.2 " -5 " -> -5 "N1" -> 0 (label only)
|
||||
//
|
||||
// It ignores digits fused to a label letter (the "1" in "N1", the "2" in "N2") by skipping
|
||||
// any number immediately preceded by a letter, and prefers a token that carries a decimal
|
||||
// point when several stand-alone numbers are present.
|
||||
func ParseString(s string) float64 {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
// Normalize a decimal comma ("63,2") to a dot when it is the sole separator.
|
||||
if !strings.Contains(s, ".") {
|
||||
if i := strings.IndexByte(s, ','); i > 0 && i+1 < len(s) &&
|
||||
isASCIIDigit(s[i-1]) && isASCIIDigit(s[i+1]) {
|
||||
s = s[:i] + "." + s[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
locs := numberToken.FindAllStringIndex(s, -1)
|
||||
candidates := make([]string, 0, len(locs))
|
||||
for _, loc := range locs {
|
||||
// A value is never glued to a preceding letter; that only happens for labels
|
||||
// like N1/N2. Real values start the string or follow a space/slash/colon/etc.
|
||||
if loc[0] > 0 && unicode.IsLetter(rune(s[loc[0]-1])) {
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates, s[loc[0]:loc[1]])
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
chosen := candidates[0]
|
||||
for _, c := range candidates {
|
||||
if strings.Contains(c, ".") {
|
||||
chosen = c
|
||||
break
|
||||
}
|
||||
}
|
||||
n, err := strconv.ParseFloat(chosen, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func isASCIIDigit(b byte) bool { return b >= '0' && b <= '9' }
|
||||
Reference in New Issue
Block a user