erigon-pulse/erigon-lib/metrics/parsing.go
2024-01-12 13:07:49 +00:00

111 lines
2.1 KiB
Go

package metrics
import (
"fmt"
"regexp"
"strings"
"github.com/prometheus/client_golang/prometheus"
)
func parseMetric(s string) (string, prometheus.Labels, error) {
if len(s) == 0 {
return "", nil, fmt.Errorf("metric cannot be empty")
}
ident, rest, ok := strings.Cut(s, "{")
if !ok {
if err := validateIdent(s); err != nil {
return "", nil, err
}
return s, nil, nil
}
if err := validateIdent(ident); err != nil {
return "", nil, err
}
if len(rest) == 0 || rest[len(rest)-1] != '}' {
return "", nil, fmt.Errorf("missing closing curly brace at the end of %q", ident)
}
tags, err := parseTags(rest[:len(rest)-1])
if err != nil {
return "", nil, err
}
return ident, tags, nil
}
func parseTags(s string) (prometheus.Labels, error) {
if len(s) == 0 {
return nil, nil
}
var labels prometheus.Labels
for {
n := strings.IndexByte(s, '=')
if n < 0 {
return nil, fmt.Errorf("missing `=` after %q", s)
}
ident := s[:n]
s = s[n+1:]
if err := validateIdent(ident); err != nil {
return nil, err
}
if len(s) == 0 || s[0] != '"' {
return nil, fmt.Errorf("missing starting `\"` for %q value; tail=%q", ident, s)
}
s = s[1:]
value := ""
for {
n = strings.IndexByte(s, '"')
if n < 0 {
return nil, fmt.Errorf("missing trailing `\"` for %q value; tail=%q", ident, s)
}
m := n
for m > 0 && s[m-1] == '\\' {
m--
}
if (n-m)%2 == 1 {
value = value + s[:n]
s = s[n+1:]
continue
}
value = value + s[:n]
if labels == nil {
labels = prometheus.Labels{}
}
labels[ident] = value
s = s[n+1:]
if len(s) == 0 {
return labels, nil
}
if !strings.HasPrefix(s, ",") {
return nil, fmt.Errorf("missing `,` after %q value; tail=%q", ident, s)
}
s = skipSpace(s[1:])
break
}
}
}
func skipSpace(s string) string {
for len(s) > 0 && s[0] == ' ' {
s = s[1:]
}
return s
}
func validateIdent(s string) error {
if !identRegexp.MatchString(s) {
return fmt.Errorf("invalid identifier %q", s)
}
return nil
}
var identRegexp = regexp.MustCompile("^[a-zA-Z_:.][a-zA-Z0-9_:.]*$")