Skip to content

Commit 8ee2967

Browse files
feat: add configurable namespace label to prometheus metrics (#145)
1 parent 2fd182e commit 8ee2967

File tree

5 files changed

+87
-3
lines changed

5 files changed

+87
-3
lines changed

internal/run/main_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212

1313
var fakePrometheus FakePrometheus
1414

15+
const fakePrometheusNamespace = "test-namespace"
16+
1517
func TestMain(m *testing.M) {
1618

1719
var err error
@@ -23,6 +25,10 @@ func TestMain(m *testing.M) {
2325
if err != nil {
2426
log.Fatal(err)
2527
}
28+
err = os.Setenv("PROMETHEUS_NAMESPACE", fakePrometheusNamespace)
29+
if err != nil {
30+
log.Fatal(err)
31+
}
2632

2733
fakePrometheus.StartServer()
2834

internal/run/prom_push_gw_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"net/http"
7+
"strings"
78
"sync"
89
"sync/atomic"
910

@@ -24,12 +25,43 @@ type FakePrometheus struct {
2425
}
2526

2627
func (f *FakePrometheus) ServeHTTP(response http.ResponseWriter, request *http.Request) {
28+
parseGroupedLabels := func() []*io_prometheus_client.LabelPair {
29+
// labels added via push.Grouping are passed through URI
30+
// example: /metrics/job/f1-f94b1fd3-1a08-4829-896e-792397ccdbfd/namespace/test-namespace/abc/cde
31+
32+
var labels []*io_prometheus_client.LabelPair
33+
parts := strings.Split(request.RequestURI, "/")[4:]
34+
35+
var labelName string
36+
for i, part := range parts {
37+
if i%2 == 0 {
38+
labelName = part
39+
continue
40+
}
41+
42+
name := labelName
43+
value := part
44+
labels = append(labels, &io_prometheus_client.LabelPair{
45+
Name: &name,
46+
Value: &value,
47+
})
48+
}
49+
50+
return labels
51+
}
52+
2753
if request != nil && request.Body != nil {
2854
defer errorh.SafeClose(request.Body)
2955
metricFamily := &io_prometheus_client.MetricFamily{}
3056
expfmt.NewDecoder(request.Body, expfmt.ResponseFormat(request.Header)).Decode(metricFamily)
3157
mf, ok := f.metricFamilies.Load(*metricFamily.Name)
3258
if !ok {
59+
if metricFamily.Metric != nil {
60+
groupedLabels := parseGroupedLabels()
61+
for _, m := range metricFamily.Metric {
62+
m.Label = append(m.Label, groupedLabels...)
63+
}
64+
}
3365
f.metricFamilies.Store(*metricFamily.Name, metricFamily)
3466
} else {
3567
value, ok := mf.(*io_prometheus_client.MetricFamily)

internal/run/run_cmd_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77

88
// TestSimpleFlow is equivalent to a single run from TestParameterised. It's useful for debugging individual test runs
99
func TestSimpleFlow(t *testing.T) {
10-
//t.Skip("Duplicate of Parameterised test. Useful for manual testing when adding new tests or debugging, so leaving in place")
10+
// t.Skip("Duplicate of Parameterised test. Useful for manual testing when adding new tests or debugging, so leaving in place")
1111
given, when, then := NewRunTestStage(t)
1212

1313
test := TestParam{
@@ -585,6 +585,20 @@ func TestSetupMetricsAreRecorded(t *testing.T) {
585585
there_is_a_metric_called("form3_loadtest_setup")
586586
}
587587

588+
func TestNamespaceLabel(t *testing.T) {
589+
given, when, then := NewRunTestStage(t)
590+
591+
given.
592+
a_rate_of("10/s").and().
593+
a_scenario_where_each_iteration_takes(1 * time.Millisecond)
594+
595+
when.i_execute_the_run_command()
596+
597+
then.
598+
metrics_are_pushed_to_prometheus().and().
599+
all_exported_metrics_contain_label("namespace", fakePrometheusNamespace)
600+
}
601+
588602
func TestFailureCounts(t *testing.T) {
589603
given, when, then := NewRunTestStage(t)
590604

internal/run/run_stage_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ package run_test
33
import (
44
"fmt"
55
"math"
6+
"strings"
67
"sync"
78
"sync/atomic"
89
"syscall"
910
"testing"
1011
"time"
1112

1213
"github.com/form3tech-oss/f1/v2/internal/run"
13-
"github.com/form3tech-oss/f1/v2/pkg/f1"
14-
1514
"github.com/form3tech-oss/f1/v2/internal/trigger/ramp"
15+
"github.com/form3tech-oss/f1/v2/pkg/f1"
16+
"github.com/prometheus/common/expfmt"
1617

1718
"github.com/form3tech-oss/f1/v2/internal/trigger/file"
1819

@@ -553,6 +554,32 @@ func (s *RunTestStage) the_iteration_metric_has_n_results(n int, result string)
553554
return s
554555
}
555556

557+
func (s *RunTestStage) all_exported_metrics_contain_label(labelName string, labelValue string) *RunTestStage {
558+
metricNames := fakePrometheus.GetMetricNames()
559+
560+
for _, name := range metricNames {
561+
metricFamily := fakePrometheus.GetMetricFamily(name)
562+
s.require.NotNil(metricFamily)
563+
564+
for _, metric := range metricFamily.Metric {
565+
match := false
566+
for _, label := range metric.Label {
567+
nameMatch := label.GetName() == labelName
568+
valueMatch := label.GetValue() == labelValue
569+
match = match || (nameMatch && valueMatch)
570+
}
571+
572+
if !match {
573+
openMetrics := strings.Builder{}
574+
_, _ = expfmt.MetricFamilyToOpenMetrics(&openMetrics, metricFamily)
575+
s.require.FailNowf("Label is missing", "Metric %q do not have label %q with value %q:\n%s",
576+
metricFamily.GetName(), labelName, labelValue, openMetrics.String())
577+
}
578+
}
579+
}
580+
return s
581+
}
582+
556583
func getMetricByResult(metricFamily *io_prometheus_client.MetricFamily, result string) *io_prometheus_client.Metric {
557584
for _, metric := range metricFamily.Metric {
558585
for _, label := range metric.Label {

internal/run/test_runner.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ func NewRun(options options.RunOptions, t *api.Trigger) (*Run, error) {
4343
prometheusUrl := os.Getenv("PROMETHEUS_PUSH_GATEWAY")
4444
if prometheusUrl != "" {
4545
run.pusher = push.New(prometheusUrl, "f1-"+options.Scenario).Gatherer(prometheus.DefaultGatherer)
46+
47+
prometheusNamespace := os.Getenv("PROMETHEUS_NAMESPACE")
48+
if prometheusNamespace != "" {
49+
run.pusher = run.pusher.Grouping("namespace", prometheusNamespace)
50+
}
4651
}
4752
if run.Options.RegisterLogHookFunc == nil {
4853
run.Options.RegisterLogHookFunc = logging.NoneRegisterLogHookFunc

0 commit comments

Comments
 (0)