Skip to content

Commit c0020a5

Browse files
authored
Merge pull request #290 from form3tech-oss/metrics_enrichments
Injection of static label/values to the F1 metrics
2 parents b125e2e + b4368ab commit c0020a5

File tree

9 files changed

+89
-32
lines changed

9 files changed

+89
-32
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.22.0
55
require (
66
github.com/guptarohit/asciigraph v0.7.2
77
github.com/mattn/go-isatty v0.0.20
8-
github.com/prometheus/client_golang v1.20.3
8+
github.com/prometheus/client_golang v1.20.4
99
github.com/prometheus/client_model v0.6.1
1010
github.com/prometheus/common v0.59.1
1111
github.com/sirupsen/logrus v1.9.3
@@ -24,6 +24,7 @@ require (
2424
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
2525
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2626
github.com/kr/text v0.2.0 // indirect
27+
github.com/kylelemons/godebug v1.1.0 // indirect
2728
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
2829
github.com/pmezard/go-difflib v1.0.0 // indirect
2930
github.com/prometheus/procfs v0.15.1 // indirect

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
2121
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
2222
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2323
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
24+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
25+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
2426
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
2527
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
2628
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
2729
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
2830
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2931
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
30-
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
31-
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
32+
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
33+
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
3234
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
3335
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
3436
github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0=

internal/metrics/metrics.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ const (
1212
metricSubsystem = "loadtest"
1313
)
1414

15-
const IterationMetricName = "form3_loadtest_iteration"
16-
1715
const (
1816
TestNameLabel = "test"
1917
StageLabel = "stage"
@@ -27,6 +25,7 @@ type Metrics struct {
2725
Iteration *prometheus.SummaryVec
2826
Registry *prometheus.Registry
2927
IterationMetricsEnabled bool
28+
staticMetricLabelValues []string
3029
}
3130

3231
//nolint:gochecknoglobals // removing the global Instance is a breaking change
@@ -35,7 +34,7 @@ var (
3534
once sync.Once
3635
)
3736

38-
func buildMetrics() *Metrics {
37+
func buildMetrics(staticMetrics map[string]string) *Metrics {
3938
percentileObjectives := map[float64]float64{
4039
0.5: 0.05, 0.75: 0.05, 0.9: 0.01, 0.95: 0.001, 0.99: 0.001, 0.9999: 0.00001, 1.0: 0.00001,
4140
}
@@ -47,37 +46,44 @@ func buildMetrics() *Metrics {
4746
Name: "setup",
4847
Help: "Duration of setup functions.",
4948
Objectives: percentileObjectives,
50-
}, []string{TestNameLabel, ResultLabel}),
49+
}, append([]string{TestNameLabel, ResultLabel}, getStaticMetricLabelKeys(staticMetrics)...)),
5150
Iteration: prometheus.NewSummaryVec(prometheus.SummaryOpts{
5251
Namespace: metricNamespace,
5352
Subsystem: metricSubsystem,
5453
Name: "iteration",
5554
Help: "Duration of iteration functions.",
5655
Objectives: percentileObjectives,
57-
}, []string{TestNameLabel, StageLabel, ResultLabel}),
56+
}, append([]string{TestNameLabel, StageLabel, ResultLabel}, getStaticMetricLabelKeys(staticMetrics)...)),
5857
}
5958
}
6059

61-
func NewInstance(registry *prometheus.Registry, iterationMetricsEnabled bool) *Metrics {
62-
i := buildMetrics()
60+
func NewInstance(registry *prometheus.Registry,
61+
iterationMetricsEnabled bool,
62+
staticMetrics map[string]string,
63+
) *Metrics {
64+
i := buildMetrics(staticMetrics)
6365
i.Registry = registry
6466

6567
i.Registry.MustRegister(
6668
i.Setup,
6769
i.Iteration,
6870
)
6971
i.IterationMetricsEnabled = iterationMetricsEnabled
70-
72+
i.staticMetricLabelValues = getStaticMetricLabelValues(staticMetrics)
7173
return i
7274
}
7375

7476
func Init(iterationMetricsEnabled bool) {
77+
InitWithStaticMetrics(iterationMetricsEnabled, nil)
78+
}
79+
80+
func InitWithStaticMetrics(iterationMetricsEnabled bool, staticMetrics map[string]string) {
7581
once.Do(func() {
7682
defaultRegistry, ok := prometheus.DefaultRegisterer.(*prometheus.Registry)
7783
if !ok {
7884
panic(errors.New("casting prometheus.DefaultRegisterer to Registry"))
7985
}
80-
m = NewInstance(defaultRegistry, iterationMetricsEnabled)
86+
m = NewInstance(defaultRegistry, iterationMetricsEnabled, staticMetrics)
8187
})
8288
}
8389

@@ -91,21 +97,38 @@ func (metrics *Metrics) Reset() {
9197
}
9298

9399
func (metrics *Metrics) RecordSetupResult(name string, result ResultType, nanoseconds int64) {
94-
metrics.Setup.WithLabelValues(name, result.String()).Observe(float64(nanoseconds))
100+
labels := append([]string{name, result.String()}, metrics.staticMetricLabelValues...)
101+
metrics.Setup.WithLabelValues(labels...).Observe(float64(nanoseconds))
95102
}
96103

97104
func (metrics *Metrics) RecordIterationResult(name string, result ResultType, nanoseconds int64) {
98105
if !metrics.IterationMetricsEnabled {
99106
return
100107
}
101-
102-
metrics.Iteration.WithLabelValues(name, IterationStage, result.String()).Observe(float64(nanoseconds))
108+
labels := append([]string{name, IterationStage, result.String()}, metrics.staticMetricLabelValues...)
109+
metrics.Iteration.WithLabelValues(labels...).Observe(float64(nanoseconds))
103110
}
104111

105112
func (metrics *Metrics) RecordIterationStage(name string, stage string, result ResultType, nanoseconds int64) {
106113
if !metrics.IterationMetricsEnabled {
107114
return
108115
}
116+
labels := append([]string{name, stage, result.String()}, metrics.staticMetricLabelValues...)
117+
metrics.Iteration.WithLabelValues(labels...).Observe(float64(nanoseconds))
118+
}
109119

110-
metrics.Iteration.WithLabelValues(name, stage, result.String()).Observe(float64(nanoseconds))
120+
func getStaticMetricLabelKeys(staticMetrics map[string]string) []string {
121+
data := make([]string, 0, len(staticMetrics))
122+
for k := range staticMetrics {
123+
data = append(data, k)
124+
}
125+
return data
126+
}
127+
128+
func getStaticMetricLabelValues(staticMetrics map[string]string) []string {
129+
data := make([]string, 0, len(staticMetrics))
130+
for _, v := range staticMetrics {
131+
data = append(data, v)
132+
}
133+
return data
111134
}

internal/metrics/metrics_test.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,39 @@ package metrics_test
33
import (
44
"testing"
55

6+
"github.com/prometheus/client_golang/prometheus"
7+
"github.com/prometheus/client_golang/prometheus/testutil"
68
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
710

811
"github.com/form3tech-oss/f1/v2/internal/metrics"
912
)
1013

1114
func TestMetrics_Init_IsSafe(t *testing.T) {
1215
t.Parallel()
13-
14-
metrics.Init(true)
15-
16-
// race detector assertion
16+
metrics.InitWithStaticMetrics(true, map[string]string{
17+
"product": "fps",
18+
"f1_id": "myid",
19+
}) // race detector assertion
1720
for range 10 {
1821
go func() {
19-
metrics.Init(false)
22+
metrics.InitWithStaticMetrics(true, map[string]string{
23+
"product": "fps",
24+
"f1_id": "myid",
25+
})
2026
}()
2127
}
22-
2328
assert.True(t, metrics.Instance().IterationMetricsEnabled)
29+
metrics.Instance().RecordIterationResult("test1", metrics.SuccessResult, 1)
30+
assert.Equal(t, 1, testutil.CollectAndCount(metrics.Instance().Iteration, "form3_loadtest_iteration"))
31+
o, err := metrics.Instance().Iteration.MetricVec.GetMetricWith(prometheus.Labels{
32+
metrics.TestNameLabel: "test1",
33+
metrics.StageLabel: metrics.IterationStage,
34+
metrics.ResultLabel: metrics.SuccessResult.String(),
35+
"product": "fps",
36+
"f1_id": "myid",
37+
})
38+
require.NoError(t, err)
39+
assert.Contains(t, o.Desc().String(), "product")
40+
assert.Contains(t, o.Desc().String(), "f1_id")
2441
}

internal/metrics/result.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package metrics
33
type ResultType string
44

55
const (
6-
SucessResult ResultType = "success"
6+
SuccessResult ResultType = "success"
77
FailedResult ResultType = "fail"
88
DroppedResult ResultType = "dropped"
99
UnknownResult ResultType = "unknown"
@@ -17,5 +17,5 @@ func Result(failed bool) ResultType {
1717
if failed {
1818
return FailedResult
1919
}
20-
return SucessResult
20+
return SuccessResult
2121
}

internal/progress/stats.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type Stats struct {
1616

1717
func (s *Stats) Record(result metrics.ResultType, nanoseconds int64) {
1818
switch result {
19-
case metrics.SucessResult:
19+
case metrics.SuccessResult:
2020
s.successfulIterationDurations.Record(nanoseconds)
2121
case metrics.FailedResult:
2222
s.failedIterationDurations.Record(nanoseconds)

internal/run/run_stage_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func NewRunTestStage(t *testing.T) (*RunTestStage, *RunTestStage, *RunTestStage)
118118
settings: envsettings.Get(),
119119
metricData: NewMetricData(),
120120
output: ui.NewDiscardOutput(),
121-
metrics: metrics.NewInstance(prometheus.NewRegistry(), true),
121+
metrics: metrics.NewInstance(prometheus.NewRegistry(), true, nil),
122122
stdout: syncWriter{writer: &bytes.Buffer{}},
123123
stderr: syncWriter{writer: &bytes.Buffer{}},
124124
waitForCompletionTimeout: 5 * time.Second,

pkg/f1/f1.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ const (
2727
// Represents an F1 CLI instance. Instantiate this struct to create an instance
2828
// of the F1 CLI and to register new test scenarios.
2929
type F1 struct {
30-
output *ui.Output
3130
scenarios *scenarios.Scenarios
3231
profiling *profiling
3332
settings envsettings.Settings
33+
options *f1Options
34+
}
35+
36+
type f1Options struct {
37+
output *ui.Output
38+
staticMetrics map[string]string
3439
}
3540

3641
// New instantiates a new instance of an F1 CLI.
@@ -41,7 +46,9 @@ func New() *F1 {
4146
scenarios: scenarios.New(),
4247
profiling: &profiling{},
4348
settings: settings,
44-
output: ui.NewDefaultOutput(settings.Log.SlogLevel(), settings.Log.IsFormatJSON()),
49+
options: &f1Options{
50+
output: ui.NewDefaultOutput(settings.Log.SlogLevel(), settings.Log.IsFormatJSON()),
51+
},
4552
}
4653
}
4754

@@ -52,7 +59,13 @@ func New() *F1 {
5259
//
5360
// The logger will be used for non-interactive output, file logs or when `--verbose` is specified.
5461
func (f *F1) WithLogger(logger *slog.Logger) *F1 {
55-
f.output = ui.NewDefaultOutputWithLogger(logger)
62+
f.options.output = ui.NewDefaultOutputWithLogger(logger)
63+
return f
64+
}
65+
66+
// WithStaticMetrics registers additional labels with fixed values to the f1 metrics
67+
func (f *F1) WithStaticMetrics(labels map[string]string) *F1 {
68+
f.options.staticMetrics = labels
5669
return f
5770
}
5871

@@ -107,7 +120,7 @@ func newSignalContext(stopCh <-chan struct{}) context.Context {
107120
}
108121

109122
func (f *F1) execute(args []string) error {
110-
rootCmd, err := buildRootCmd(f.scenarios, f.settings, f.profiling, f.output)
123+
rootCmd, err := buildRootCmd(f.scenarios, f.settings, f.profiling, f.options.output, f.options.staticMetrics)
111124
if err != nil {
112125
return fmt.Errorf("building root command: %w", err)
113126
}
@@ -138,7 +151,7 @@ func (f *F1) execute(args []string) error {
138151
// function.
139152
func (f *F1) Execute() {
140153
if err := f.execute(nil); err != nil {
141-
f.output.Display(ui.ErrorMessage{Message: "f1 failed", Error: err})
154+
f.options.output.Display(ui.ErrorMessage{Message: "f1 failed", Error: err})
142155
os.Exit(1)
143156
}
144157
}

pkg/f1/root_cmd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func buildRootCmd(
2626
settings envsettings.Settings,
2727
p *profiling,
2828
output *ui.Output,
29+
staticMetrics map[string]string,
2930
) (*cobra.Command, error) {
3031
rootCmd := &cobra.Command{
3132
Use: getCmdName(),
@@ -43,7 +44,7 @@ func buildRootCmd(
4344
return nil, fmt.Errorf("marking flag as filename: %w", err)
4445
}
4546

46-
metrics.Init(settings.PrometheusEnabled())
47+
metrics.InitWithStaticMetrics(settings.PrometheusEnabled(), staticMetrics)
4748
metricsInstance := metrics.Instance()
4849

4950
builders := trigger.GetBuilders(output)

0 commit comments

Comments
 (0)