Files
graphql-monitoring-proxy/monitoring/helpers.go
T
lukaszraczylo 2e40ee0c62 Update the helpers to sort labels alpabetically.
It will help to avoid the flaky tests and duplicated metrics.

As a bonus - added tests and benchmarks for monitoring package.
2024-06-11 19:57:18 +01:00

130 lines
3.0 KiB
Go

package libpack_monitoring
import (
"fmt"
"os"
"sort"
"strings"
"unicode"
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
)
func (ms *MetricsSetup) get_metrics_name(name string, labels map[string]string) (complete_name string) {
const unknownPodName = "unknown"
var sb strings.Builder
// Prepare default labels without initializing a new map
podName := unknownPodName
if hn, err := os.Hostname(); err == nil {
podName = hn
}
if labels == nil {
labels = map[string]string{
"microservice": libpack_config.PKG_NAME,
"pod": podName,
}
} else {
if _, exists := labels["microservice"]; !exists {
labels["microservice"] = libpack_config.PKG_NAME
}
if _, exists := labels["pod"]; !exists {
labels["pod"] = podName
}
}
// Prefix handling
if ms.metrics_prefix != "" {
sb.WriteString(ms.metrics_prefix)
sb.WriteString("_")
}
sb.WriteString(name)
// Append labels if any
if len(labels) > 0 {
sb.WriteString("{")
keys := make([]string, 0, len(labels))
for k := range labels {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(k)
sb.WriteString("=\"")
sb.WriteString(labels[k])
sb.WriteString("\"")
}
sb.WriteString("}")
}
return sb.String()
}
// validate_metrics_name validates the name of the metric to adhere to the Prometheus naming conventions
// https://prometheus.io/docs/practices/naming/
func validate_metrics_name(name string) error {
var sb strings.Builder // Use strings.Builder for efficient string concatenation
// Track if the last character was an underscore to avoid duplicate underscores
lastWasUnderscore := false
for _, r := range name {
// Convert spaces to underscores and skip non-alphanumeric characters except underscores
if r == ' ' || (unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_') {
if r == ' ' || r == '_' {
if lastWasUnderscore {
continue // Skip if the previous character was also an underscore
}
r = '_' // Convert spaces to underscores
lastWasUnderscore = true
} else {
lastWasUnderscore = false
}
sb.WriteRune(r) // Add valid characters to the builder
}
}
// Trim leading and trailing underscores
name_new := strings.Trim(sb.String(), "_")
// Check if the processed name matches the original input
if name_new != name {
return fmt.Errorf("Invalid metric name: %s, expected %s", name, name_new)
}
return nil
}
func compile_metrics_with_labels(name string, labels map[string]string) string {
var totalLength int
totalLength += len(name)
for k, v := range labels {
totalLength += len(k) + len(v) + 2
}
var sb strings.Builder
sb.Grow(totalLength + 1)
sb.WriteString(name)
// Collect keys and sort them
keys := make([]string, 0, len(labels))
for k := range labels {
keys = append(keys, k)
}
sort.Strings(keys)
// Append sorted key-value pairs to the builder
for _, k := range keys {
sb.WriteString("_")
sb.WriteString(k)
sb.WriteString("_")
sb.WriteString(labels[k])
}
return sb.String()
}