This repository has been archived by the owner on Oct 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add labeled Prometheus summary (#112)
- Loading branch information
1 parent
7a05197
commit df5879c
Showing
5 changed files
with
194 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} |