diff --git a/promutils/labeled/counter.go b/promutils/labeled/counter.go index ca193d1c..e1ef4ebb 100644 --- a/promutils/labeled/counter.go +++ b/promutils/labeled/counter.go @@ -8,7 +8,7 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -// Represents a counter labeled with values from the context. See labeled.SetMetricsKeys for information about to +// Represents a counter labeled with values from the context. See labeled.SetMetricsKeys for information about how to // configure that. type Counter struct { *prometheus.CounterVec @@ -18,7 +18,7 @@ type Counter struct { } // Inc increments the counter by 1. Use Add to increment it by arbitrary non-negative values. The data point will be -// labeled with values from context. See labeled.SetMetricsKeys for information about to configure that. +// labeled with values from context. See labeled.SetMetricsKeys for information about how to configure that. func (c Counter) Inc(ctx context.Context) { counter, err := c.CounterVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, c.additionalLabels...)...)) if err != nil { @@ -32,7 +32,7 @@ func (c Counter) Inc(ctx context.Context) { } // Add adds the given value to the counter. It panics if the value is < 0.. The data point will be labeled with values -// from context. See labeled.SetMetricsKeys for information about to configure that. +// from context. See labeled.SetMetricsKeys for information about how to configure that. func (c Counter) Add(ctx context.Context, v float64) { counter, err := c.CounterVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, c.additionalLabels...)...)) if err != nil { diff --git a/promutils/labeled/gauge.go b/promutils/labeled/gauge.go index 6e08f8e9..5a056cd6 100644 --- a/promutils/labeled/gauge.go +++ b/promutils/labeled/gauge.go @@ -17,7 +17,7 @@ type Gauge struct { } // Inc increments the gauge by 1. Use Add to increment by arbitrary values. The data point will be -// labeled with values from context. See labeled.SetMetricsKeys for information about to configure that. +// labeled with values from context. See labeled.SetMetricsKeys for information about how to configure that. func (g Gauge) Inc(ctx context.Context) { gauge, err := g.GaugeVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, g.additionalLabels...)...)) if err != nil { @@ -31,7 +31,7 @@ func (g Gauge) Inc(ctx context.Context) { } // Add adds the given value to the Gauge. (The value can be negative, resulting in a decrease of the Gauge.) -// The data point will be labeled with values from context. See labeled.SetMetricsKeys for information about to configure that. +// The data point will be labeled with values from context. See labeled.SetMetricsKeys for information about how to configure that. func (g Gauge) Add(ctx context.Context, v float64) { gauge, err := g.GaugeVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, g.additionalLabels...)...)) if err != nil { @@ -45,7 +45,7 @@ func (g Gauge) Add(ctx context.Context, v float64) { } // Set sets the Gauge to an arbitrary value. -// The data point will be labeled with values from context. See labeled.SetMetricsKeys for information about to configure that. +// The data point will be labeled with values from context. See labeled.SetMetricsKeys for information about how to configure that. func (g Gauge) Set(ctx context.Context, v float64) { gauge, err := g.GaugeVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, g.additionalLabels...)...)) if err != nil { @@ -59,7 +59,7 @@ func (g Gauge) Set(ctx context.Context, v float64) { } // Dec decrements the level by 1. Use Sub to decrement by arbitrary values. The data point will be -// labeled with values from context. See labeled.SetMetricsKeys for information about to configure that. +// labeled with values from context. See labeled.SetMetricsKeys for information about how to configure that. func (g Gauge) Dec(ctx context.Context) { gauge, err := g.GaugeVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, g.additionalLabels...)...)) if err != nil { @@ -73,7 +73,7 @@ func (g Gauge) Dec(ctx context.Context) { } // Sub adds the given value to the Gauge. The value can be negative, resulting in an increase of the Gauge. -// The data point will be labeled with values from context. See labeled.SetMetricsKeys for information about to configure that. +// The data point will be labeled with values from context. See labeled.SetMetricsKeys for information about how to configure that. func (g Gauge) Sub(ctx context.Context, v float64) { gauge, err := g.GaugeVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, g.additionalLabels...)...)) if err != nil { @@ -101,7 +101,7 @@ func (g Gauge) SetToCurrentTime(ctx context.Context) { // Creates a new labeled gauge. Label keys must be set before instantiating. If the unlabeled option is given, // this object will also instantiate and emit another gauge with the given name with an _unlabeled suffix. -// See labeled.SetMetricsKeys for information about to configure that. +// See labeled.SetMetricsKeys for information about how to configure that. func NewGauge(name, description string, scope promutils.Scope, opts ...MetricOption) Gauge { if len(metricKeys) == 0 { panic(ErrNeverSet) diff --git a/promutils/labeled/stopwatch.go b/promutils/labeled/stopwatch.go index faccb462..9d5a4ccd 100644 --- a/promutils/labeled/stopwatch.go +++ b/promutils/labeled/stopwatch.go @@ -46,7 +46,7 @@ func (c StopWatch) Start(ctx context.Context) Timer { } // Observes specified duration between the start and end time. The data point will be labeled with values from context. -// See labeled.SetMetricsKeys for information about to configure that. +// See labeled.SetMetricsKeys for information about how to configure that. func (c StopWatch) Observe(ctx context.Context, start, end time.Time) { w, err := c.StopWatchVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, c.additionalLabels...)...)) if err != nil { @@ -69,7 +69,7 @@ func (c StopWatch) Time(ctx context.Context, f func()) { } // Creates a new labeled stopwatch. Label keys must be set before instantiating a counter. See labeled.SetMetricsKeys -// for information about to configure that. +// for information about how to configure that. func NewStopWatch(name, description string, scale time.Duration, scope promutils.Scope, opts ...MetricOption) StopWatch { if len(metricKeys) == 0 { panic(ErrNeverSet) diff --git a/promutils/labeled/summary.go b/promutils/labeled/summary.go new file mode 100644 index 00000000..ea28269e --- /dev/null +++ b/promutils/labeled/summary.go @@ -0,0 +1,56 @@ +package labeled + +import ( + "context" + + "github.com/flyteorg/flytestdlib/contextutils" + "github.com/flyteorg/flytestdlib/promutils" + "github.com/prometheus/client_golang/prometheus" +) + +// Summary represents a summary labeled with values from the context. See labeled.SetMetricsKeys for information about +// how to configure that. +type Summary struct { + *prometheus.SummaryVec + + prometheus.Summary + additionalLabels []contextutils.Key +} + +// Observe adds a single observation to the summary. +func (s Summary) Observe(ctx context.Context, v float64) { + summary, err := s.SummaryVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, s.additionalLabels...)...)) + if err != nil { + panic(err.Error()) + } + summary.Observe(v) + + if s.Summary != nil { + s.Summary.Observe(v) + } +} + +// NewSummary creates a new labeled summary. Label keys must be set before instantiating. If the unlabeled option is +// given, this object will also instantiate and emit another summary with the given name with an _unlabeled suffix. +// See labeled.SetMetricsKeys for information about how to configure that. +func NewSummary(name, description string, scope promutils.Scope, opts ...MetricOption) Summary { + if len(metricKeys) == 0 { + panic(ErrNeverSet) + } + + s := Summary{} + for _, opt := range opts { + if _, emitUnlabeledMetric := opt.(EmitUnlabeledMetricOption); emitUnlabeledMetric { + s.Summary = scope.MustNewSummary(GetUnlabeledMetricName(name), description) + } else if additionalLabels, casted := opt.(AdditionalLabelsOption); casted { + s.SummaryVec = scope.MustNewSummaryVec(name, description, append(metricStringKeys, additionalLabels.Labels...)...) + s.additionalLabels = contextutils.MetricKeysFromStrings(additionalLabels.Labels) + } + } + + if s.SummaryVec == nil { + s.SummaryVec = scope.MustNewSummaryVec(name, description, metricStringKeys...) + } + + return s +} diff --git a/promutils/labeled/summary_test.go b/promutils/labeled/summary_test.go new file mode 100644 index 00000000..011ff911 --- /dev/null +++ b/promutils/labeled/summary_test.go @@ -0,0 +1,127 @@ +package labeled + +import ( + "context" + "strings" + "testing" + + "github.com/flyteorg/flytestdlib/contextutils" + "github.com/flyteorg/flytestdlib/promutils" + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" +) + +func TestLabeledSummary(t *testing.T) { + UnsetMetricKeys() + assert.NotPanics(t, func() { + SetMetricKeys(contextutils.ProjectKey, contextutils.DomainKey, contextutils.WorkflowIDKey, contextutils.TaskIDKey, contextutils.LaunchPlanIDKey) + }) + + t.Run("labeled", func(t *testing.T) { + scope := promutils.NewScope("testscope_summary") + ctx := context.Background() + ctx = contextutils.WithProjectDomain(ctx, "flyte", "dev") + s := NewSummary("unittest", "some desc", scope) + assert.NotNil(t, s) + + s.Observe(ctx, 10) + + const header = ` + # HELP testscope_summary:unittest some desc + # TYPE testscope_summary:unittest summary + ` + var expected = ` + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.5"} 10 + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.9"} 10 + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.99"} 10 + testscope_summary:unittest_sum{domain="dev",lp="",project="flyte",task="",wf=""} 10 + testscope_summary:unittest_count{domain="dev",lp="",project="flyte",task="",wf=""} 1 + ` + err := testutil.CollectAndCompare(s.SummaryVec, strings.NewReader(header+expected)) + assert.NoError(t, err) + + s.Observe(ctx, 100) + s.Observe(ctx, 1000) + + expected = ` + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.5"} 100 + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.9"} 1000 + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.99"} 1000 + testscope_summary:unittest_sum{domain="dev",lp="",project="flyte",task="",wf=""} 1110 + testscope_summary:unittest_count{domain="dev",lp="",project="flyte",task="",wf=""} 3 + ` + err = testutil.CollectAndCompare(s.SummaryVec, strings.NewReader(header+expected)) + assert.NoError(t, err) + + s.Observe(contextutils.WithProjectDomain(ctx, "flyte", "prod"), 10) + + expected = ` + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.5"} 100 + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.9"} 1000 + testscope_summary:unittest{domain="dev",lp="",project="flyte",task="",wf="",quantile="0.99"} 1000 + testscope_summary:unittest_sum{domain="dev",lp="",project="flyte",task="",wf=""} 1110 + testscope_summary:unittest_count{domain="dev",lp="",project="flyte",task="",wf=""} 3 + testscope_summary:unittest{domain="prod",lp="",project="flyte",task="",wf="",quantile="0.5"} 10 + testscope_summary:unittest{domain="prod",lp="",project="flyte",task="",wf="",quantile="0.9"} 10 + testscope_summary:unittest{domain="prod",lp="",project="flyte",task="",wf="",quantile="0.99"} 10 + testscope_summary:unittest_sum{domain="prod",lp="",project="flyte",task="",wf=""} 10 + testscope_summary:unittest_count{domain="prod",lp="",project="flyte",task="",wf=""} 1 + ` + err = testutil.CollectAndCompare(s.SummaryVec, strings.NewReader(header+expected)) + assert.NoError(t, err) + }) + + t.Run("extra labels", func(t *testing.T) { + scope := promutils.NewScope("testscope_summary") + ctx := context.Background() + s := NewSummary("unittest2", "some desc", scope, AdditionalLabelsOption{Labels: []string{"method"}}) + assert.NotNil(t, s) + + methodKey := contextutils.Key("method") + s.Observe(context.WithValue(ctx, methodKey, "GET"), 10) + s.Observe(context.WithValue(ctx, methodKey, "POST"), 100) + + const header = ` + # HELP testscope_summary:unittest2 some desc + # TYPE testscope_summary:unittest2 summary + ` + var expected = ` + testscope_summary:unittest2{domain="",lp="",method="GET",project="",task="",wf="",quantile="0.5"} 10 + testscope_summary:unittest2{domain="",lp="",method="GET",project="",task="",wf="",quantile="0.9"} 10 + testscope_summary:unittest2{domain="",lp="",method="GET",project="",task="",wf="",quantile="0.99"} 10 + testscope_summary:unittest2_sum{domain="",lp="",method="GET",project="",task="",wf=""} 10 + testscope_summary:unittest2_count{domain="",lp="",method="GET",project="",task="",wf=""} 1 + testscope_summary:unittest2{domain="",lp="",method="POST",project="",task="",wf="",quantile="0.5"} 100 + testscope_summary:unittest2{domain="",lp="",method="POST",project="",task="",wf="",quantile="0.9"} 100 + testscope_summary:unittest2{domain="",lp="",method="POST",project="",task="",wf="",quantile="0.99"} 100 + testscope_summary:unittest2_sum{domain="",lp="",method="POST",project="",task="",wf=""} 100 + testscope_summary:unittest2_count{domain="",lp="",method="POST",project="",task="",wf=""} 1 + ` + err := testutil.CollectAndCompare(s.SummaryVec, strings.NewReader(header+expected)) + assert.NoError(t, err) + }) + + t.Run("unlabeled", func(t *testing.T) { + scope := promutils.NewScope("testscope_summary") + ctx := context.Background() + ctx = contextutils.WithProjectDomain(ctx, "flyte", "dev") + s := NewSummary("unittest3", "some desc", scope, EmitUnlabeledMetric) + assert.NotNil(t, s) + + s.Observe(ctx, 10) + + const header = ` + # HELP testscope_summary:unittest3_unlabeled some desc + # TYPE testscope_summary:unittest3_unlabeled summary + ` + var expected = ` + testscope_summary:unittest3_unlabeled{quantile="0.5"} 10 + testscope_summary:unittest3_unlabeled{quantile="0.9"} 10 + testscope_summary:unittest3_unlabeled{quantile="0.99"} 10 + testscope_summary:unittest3_unlabeled_sum 10 + testscope_summary:unittest3_unlabeled_count 1 + ` + err := testutil.CollectAndCompare(s.Summary, strings.NewReader(header+expected)) + assert.NoError(t, err) + }) +}