Skip to content

Commit

Permalink
feat: add configurable namespace label to prometheus metrics (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
damiankaminski-form3 authored Nov 9, 2023
1 parent 2fd182e commit 8ee2967
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 3 deletions.
6 changes: 6 additions & 0 deletions internal/run/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (

var fakePrometheus FakePrometheus

const fakePrometheusNamespace = "test-namespace"

func TestMain(m *testing.M) {

var err error
Expand All @@ -23,6 +25,10 @@ func TestMain(m *testing.M) {
if err != nil {
log.Fatal(err)
}
err = os.Setenv("PROMETHEUS_NAMESPACE", fakePrometheusNamespace)
if err != nil {
log.Fatal(err)
}

fakePrometheus.StartServer()

Expand Down
32 changes: 32 additions & 0 deletions internal/run/prom_push_gw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"sync/atomic"

Expand All @@ -24,12 +25,43 @@ type FakePrometheus struct {
}

func (f *FakePrometheus) ServeHTTP(response http.ResponseWriter, request *http.Request) {
parseGroupedLabels := func() []*io_prometheus_client.LabelPair {
// labels added via push.Grouping are passed through URI
// example: /metrics/job/f1-f94b1fd3-1a08-4829-896e-792397ccdbfd/namespace/test-namespace/abc/cde

var labels []*io_prometheus_client.LabelPair
parts := strings.Split(request.RequestURI, "/")[4:]

var labelName string
for i, part := range parts {
if i%2 == 0 {
labelName = part
continue
}

name := labelName
value := part
labels = append(labels, &io_prometheus_client.LabelPair{
Name: &name,
Value: &value,
})
}

return labels
}

if request != nil && request.Body != nil {
defer errorh.SafeClose(request.Body)
metricFamily := &io_prometheus_client.MetricFamily{}
expfmt.NewDecoder(request.Body, expfmt.ResponseFormat(request.Header)).Decode(metricFamily)
mf, ok := f.metricFamilies.Load(*metricFamily.Name)
if !ok {
if metricFamily.Metric != nil {
groupedLabels := parseGroupedLabels()
for _, m := range metricFamily.Metric {
m.Label = append(m.Label, groupedLabels...)
}
}
f.metricFamilies.Store(*metricFamily.Name, metricFamily)
} else {
value, ok := mf.(*io_prometheus_client.MetricFamily)
Expand Down
16 changes: 15 additions & 1 deletion internal/run/run_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

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

test := TestParam{
Expand Down Expand Up @@ -585,6 +585,20 @@ func TestSetupMetricsAreRecorded(t *testing.T) {
there_is_a_metric_called("form3_loadtest_setup")
}

func TestNamespaceLabel(t *testing.T) {
given, when, then := NewRunTestStage(t)

given.
a_rate_of("10/s").and().
a_scenario_where_each_iteration_takes(1 * time.Millisecond)

when.i_execute_the_run_command()

then.
metrics_are_pushed_to_prometheus().and().
all_exported_metrics_contain_label("namespace", fakePrometheusNamespace)
}

func TestFailureCounts(t *testing.T) {
given, when, then := NewRunTestStage(t)

Expand Down
31 changes: 29 additions & 2 deletions internal/run/run_stage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ package run_test
import (
"fmt"
"math"
"strings"
"sync"
"sync/atomic"
"syscall"
"testing"
"time"

"github.com/form3tech-oss/f1/v2/internal/run"
"github.com/form3tech-oss/f1/v2/pkg/f1"

"github.com/form3tech-oss/f1/v2/internal/trigger/ramp"
"github.com/form3tech-oss/f1/v2/pkg/f1"
"github.com/prometheus/common/expfmt"

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

Expand Down Expand Up @@ -553,6 +554,32 @@ func (s *RunTestStage) the_iteration_metric_has_n_results(n int, result string)
return s
}

func (s *RunTestStage) all_exported_metrics_contain_label(labelName string, labelValue string) *RunTestStage {
metricNames := fakePrometheus.GetMetricNames()

for _, name := range metricNames {
metricFamily := fakePrometheus.GetMetricFamily(name)
s.require.NotNil(metricFamily)

for _, metric := range metricFamily.Metric {
match := false
for _, label := range metric.Label {
nameMatch := label.GetName() == labelName
valueMatch := label.GetValue() == labelValue
match = match || (nameMatch && valueMatch)
}

if !match {
openMetrics := strings.Builder{}
_, _ = expfmt.MetricFamilyToOpenMetrics(&openMetrics, metricFamily)
s.require.FailNowf("Label is missing", "Metric %q do not have label %q with value %q:\n%s",
metricFamily.GetName(), labelName, labelValue, openMetrics.String())
}
}
}
return s
}

func getMetricByResult(metricFamily *io_prometheus_client.MetricFamily, result string) *io_prometheus_client.Metric {
for _, metric := range metricFamily.Metric {
for _, label := range metric.Label {
Expand Down
5 changes: 5 additions & 0 deletions internal/run/test_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func NewRun(options options.RunOptions, t *api.Trigger) (*Run, error) {
prometheusUrl := os.Getenv("PROMETHEUS_PUSH_GATEWAY")
if prometheusUrl != "" {
run.pusher = push.New(prometheusUrl, "f1-"+options.Scenario).Gatherer(prometheus.DefaultGatherer)

prometheusNamespace := os.Getenv("PROMETHEUS_NAMESPACE")
if prometheusNamespace != "" {
run.pusher = run.pusher.Grouping("namespace", prometheusNamespace)
}
}
if run.Options.RegisterLogHookFunc == nil {
run.Options.RegisterLogHookFunc = logging.NoneRegisterLogHookFunc
Expand Down

0 comments on commit 8ee2967

Please sign in to comment.