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.
This commit is contained in:
2024-06-11 19:57:18 +01:00
parent df9f43718a
commit 2e40ee0c62
3 changed files with 246 additions and 20 deletions
+59 -20
View File
@@ -3,6 +3,7 @@ package libpack_monitoring
import (
"fmt"
"os"
"sort"
"strings"
"unicode"
@@ -10,37 +11,53 @@ import (
)
func (ms *MetricsSetup) get_metrics_name(name string, labels map[string]string) (complete_name string) {
if labels == nil {
labels = make(map[string]string)
}
// Adding default labels
labels["microservice"] = libpack_config.PKG_NAME
if podName, err := os.Hostname(); err == nil {
labels["pod"] = podName
} else {
labels["pod"] = "unknown"
}
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("{")
first := true
for k, v := range labels {
if !first {
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(v)
sb.WriteString(labels[k])
sb.WriteString("\"")
first = false
}
sb.WriteString("}")
}
@@ -82,9 +99,31 @@ func validate_metrics_name(name string) error {
}
func compile_metrics_with_labels(name string, labels map[string]string) string {
metric_name := name
var totalLength int
totalLength += len(name)
for k, v := range labels {
metric_name += "_" + k + "_" + v
totalLength += len(k) + len(v) + 2
}
return metric_name
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()
}
+44
View File
@@ -0,0 +1,44 @@
package libpack_monitoring
import (
"testing"
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
)
func BenchmarkGetMetricsName(b *testing.B) {
// Setup environment
libpack_config.PKG_NAME = "test_service"
ms := &MetricsSetup{metrics_prefix: "test_prefix"}
labels := map[string]string{
"env": "production",
"region": "us-west-2",
}
// Run the benchmark
for n := 0; n < b.N; n++ {
ms.get_metrics_name("request_count", labels)
}
}
func BenchmarkCompileMetricsWithLabels(b *testing.B) {
labels := map[string]string{
"env": "production",
"region": "us-west-2",
"app": "api-server",
}
for n := 0; n < b.N; n++ {
compile_metrics_with_labels("request_count", labels)
}
}
func BenchmarkValidateMetricsName(b *testing.B) {
input := "valid metric name with special chars @#! and underscores__"
for n := 0; n < b.N; n++ {
validate_metrics_name(input)
}
}
+143
View File
@@ -0,0 +1,143 @@
package libpack_monitoring
import (
"os"
"testing"
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
"github.com/stretchr/testify/assert"
)
func TestGetMetricsName(t *testing.T) {
ms := &MetricsSetup{metrics_prefix: "prefix"}
libpack_config.PKG_NAME = "example_microservice"
tests := []struct {
name string
metricName string
labels map[string]string
expectedOutput string
}{
{
name: "No labels",
metricName: "test_metric",
labels: nil,
expectedOutput: "prefix_test_metric{microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
{
name: "With labels",
metricName: "test_metric",
labels: map[string]string{
"label1": "value1",
"label2": "value2",
},
expectedOutput: "prefix_test_metric{label1=\"value1\",label2=\"value2\",microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
{
name: "Alphabetical order labels",
metricName: "test_metric",
labels: map[string]string{
"label2": "value2",
"label1": "value1",
},
expectedOutput: "prefix_test_metric{label1=\"value1\",label2=\"value2\",microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
{
name: "Empty metric name",
metricName: "",
labels: nil,
expectedOutput: "prefix_{microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
{
name: "Empty labels map",
metricName: "test_metric",
labels: map[string]string{},
expectedOutput: "prefix_test_metric{microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
{
name: "Single label",
metricName: "test_metric",
labels: map[string]string{
"label1": "value1",
},
expectedOutput: "prefix_test_metric{label1=\"value1\",microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
{
name: "Multiple labels with special characters",
metricName: "test_metric",
labels: map[string]string{
"label-2": "value-2",
"label_1": "value_1",
},
expectedOutput: "prefix_test_metric{label-2=\"value-2\",label_1=\"value_1\",microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
{
name: "Prefix only",
metricName: "",
labels: map[string]string{
"label1": "value1",
},
expectedOutput: "prefix_{label1=\"value1\",microservice=\"example_microservice\",pod=\"" + getPodName() + "\"}",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ms.get_metrics_name(tt.metricName, tt.labels)
assert.Equal(t, tt.expectedOutput, result)
})
}
}
func TestCompileMetricsWithLabels(t *testing.T) {
tests := []struct {
name string
labels map[string]string
want string
}{
{"request_count", map[string]string{"env": "production", "region": "us-west-2"}, "request_count_env_production_region_us-west-2"},
{"metric_name", map[string]string{}, "metric_name"},
{"metric_name", nil, "metric_name"},
{"metric_name", map[string]string{"key1": "value1"}, "metric_name_key1_value1"},
{"metric_name", map[string]string{"k": "v", "key2": "value2"}, "metric_name_k_v_key2_value2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := compile_metrics_with_labels(tt.name, tt.labels); got != tt.want {
t.Errorf("compile_metrics_with_labels() = %v, want %v", got, tt.want)
}
})
}
}
func TestValidateMetricsName(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"Valid name", "valid_metric_name", false},
{"Name with spaces", "valid metric name", true},
{"Name with special chars", "valid@metric#name!", true},
{"Name with leading underscore", "_valid_metric_name", true},
{"Name with trailing underscore", "valid_metric_name_", true},
{"Name with consecutive underscores", "valid__metric__name", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validate_metrics_name(tt.input); (err != nil) != tt.wantErr {
t.Errorf("validate_metrics_name() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func getPodName() string {
podName, err := os.Hostname()
if err != nil {
return "unknown"
}
return podName
}