mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-23 06:47:26 +00:00
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:
+59
-20
@@ -3,6 +3,7 @@ package libpack_monitoring
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@@ -10,37 +11,53 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (ms *MetricsSetup) get_metrics_name(name string, labels map[string]string) (complete_name string) {
|
func (ms *MetricsSetup) get_metrics_name(name string, labels map[string]string) (complete_name string) {
|
||||||
if labels == nil {
|
const unknownPodName = "unknown"
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
var sb strings.Builder
|
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 != "" {
|
if ms.metrics_prefix != "" {
|
||||||
sb.WriteString(ms.metrics_prefix)
|
sb.WriteString(ms.metrics_prefix)
|
||||||
sb.WriteString("_")
|
sb.WriteString("_")
|
||||||
}
|
}
|
||||||
sb.WriteString(name)
|
sb.WriteString(name)
|
||||||
|
|
||||||
|
// Append labels if any
|
||||||
if len(labels) > 0 {
|
if len(labels) > 0 {
|
||||||
sb.WriteString("{")
|
sb.WriteString("{")
|
||||||
first := true
|
|
||||||
for k, v := range labels {
|
keys := make([]string, 0, len(labels))
|
||||||
if !first {
|
for k := range labels {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for i, k := range keys {
|
||||||
|
if i > 0 {
|
||||||
sb.WriteString(",")
|
sb.WriteString(",")
|
||||||
}
|
}
|
||||||
sb.WriteString(k)
|
sb.WriteString(k)
|
||||||
sb.WriteString("=\"")
|
sb.WriteString("=\"")
|
||||||
sb.WriteString(v)
|
sb.WriteString(labels[k])
|
||||||
sb.WriteString("\"")
|
sb.WriteString("\"")
|
||||||
first = false
|
|
||||||
}
|
}
|
||||||
sb.WriteString("}")
|
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 {
|
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 {
|
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user