Skip to content

Commit 47e52d4

Browse files
committed
[metrics] Dependency resolution metrics
This PR introduces two histogram metrics to help in analysing the time taken by dependency resolution requests.
1 parent afd2348 commit 47e52d4

File tree

6 files changed

+147
-2
lines changed

6 files changed

+147
-2
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
128128
// Create an OperatorLister
129129
lister := operatorlister.NewLister()
130130

131+
operatorV1alpha1Resolver := resolver.NewOperatorsV1alpha1Resolver(lister, crClient, opClient.KubernetesInterface())
132+
successMetricsEmitter := metrics.RegisterDependencyResolutionSuccess
133+
failureMetricsEmitter := metrics.RegisterDependencyResolutionFailure
131134
// Allocate the new instance of an Operator.
132135
op := &Operator{
133136
Operator: queueOperator,
@@ -138,7 +141,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
138141
client: crClient,
139142
lister: lister,
140143
namespace: operatorNamespace,
141-
resolver: resolver.NewOperatorsV1alpha1Resolver(lister, crClient, opClient.KubernetesInterface()),
144+
resolver: resolver.NewInstrumentedResolver(operatorV1alpha1Resolver, successMetricsEmitter, failureMetricsEmitter),
142145
catsrcQueueSet: queueinformer.NewEmptyResourceQueueSet(),
143146
subQueueSet: queueinformer.NewEmptyResourceQueueSet(),
144147
ipQueueSet: queueinformer.NewEmptyResourceQueueSet(),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package resolver
2+
3+
import (
4+
"time"
5+
6+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
7+
)
8+
9+
type InstrumentedResolver struct {
10+
operatorsV1alpha1Resolver Resolver
11+
successMetricsEmitter func(time.Duration)
12+
failureMetricsEmitter func(time.Duration)
13+
}
14+
15+
var _ Resolver = &OperatorsV1alpha1Resolver{}
16+
17+
func NewInstrumentedResolver(resolver Resolver, successMetricsEmitter, failureMetricsEmitter func(time.Duration)) *InstrumentedResolver {
18+
return &InstrumentedResolver{
19+
operatorsV1alpha1Resolver: resolver,
20+
successMetricsEmitter: successMetricsEmitter,
21+
failureMetricsEmitter: failureMetricsEmitter,
22+
}
23+
}
24+
25+
func (ir *InstrumentedResolver) ResolveSteps(namespace string, sourceQuerier SourceQuerier) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) {
26+
start := time.Now()
27+
steps, lookups, subs, err := ir.operatorsV1alpha1Resolver.ResolveSteps(namespace, sourceQuerier)
28+
if err != nil {
29+
ir.failureMetricsEmitter(time.Now().Sub(start))
30+
} else {
31+
ir.successMetricsEmitter(time.Now().Sub(start))
32+
}
33+
return steps, lookups, subs, err
34+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package resolver
2+
3+
import (
4+
"errors"
5+
"testing"
6+
"time"
7+
8+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
const (
13+
failure = time.Duration(0)
14+
success = time.Duration(1)
15+
reset = time.Duration(99999)
16+
)
17+
18+
type fakeResolverWithError struct{}
19+
type fakeResolverWithoutError struct{}
20+
21+
func (r *fakeResolverWithError) ResolveSteps(namespace string, sourceQuerier SourceQuerier) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) {
22+
return nil, nil, nil, errors.New("Fake error")
23+
}
24+
25+
func (r *fakeResolverWithoutError) ResolveSteps(namespace string, sourceQuerier SourceQuerier) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) {
26+
return nil, nil, nil, nil
27+
}
28+
29+
func newFakeResolverWithError() *fakeResolverWithError {
30+
return &fakeResolverWithError{}
31+
}
32+
33+
func newFakeResolverWithoutError() *fakeResolverWithoutError {
34+
return &fakeResolverWithoutError{}
35+
}
36+
37+
func TestInstrumentedResolver(t *testing.T) {
38+
39+
var successResult time.Duration
40+
var failureResult time.Duration
41+
42+
changeToFailure := func(num time.Duration) {
43+
failureResult = failure
44+
}
45+
46+
changeToSuccess := func(num time.Duration) {
47+
successResult = success
48+
}
49+
50+
failureResult = reset
51+
successResult = reset
52+
instrumentedResolver := NewInstrumentedResolver(newFakeResolverWithError(), changeToSuccess, changeToFailure)
53+
instrumentedResolver.ResolveSteps("", nil)
54+
require.Equal(t, failureResult, failure)
55+
require.Equal(t, successResult, reset)
56+
57+
failureResult = reset
58+
successResult = reset
59+
instrumentedResolver = NewInstrumentedResolver(newFakeResolverWithoutError(), changeToSuccess, changeToFailure)
60+
instrumentedResolver.ResolveSteps("", nil)
61+
require.Equal(t, successResult, success)
62+
require.Equal(t, failureResult, reset)
63+
}

pkg/metrics/metrics.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package metrics
22

33
import (
44
"context"
5+
"time"
56

67
"github.com/prometheus/client_golang/prometheus"
78
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -21,6 +22,9 @@ const (
2122
PHASE_LABEL = "phase"
2223
REASON_LABEL = "reason"
2324
PACKAGE_LABEL = "package"
25+
RESOLUTION = "resolution"
26+
SUCCEEDED = "succeeded"
27+
FAILED = "failed"
2428
)
2529

2630
type MetricsProvider interface {
@@ -170,6 +174,15 @@ var (
170174
[]string{NAMESPACE_LABEL, NAME_LABEL, VERSION_LABEL, PHASE_LABEL, REASON_LABEL},
171175
)
172176

177+
dependencyResolutionSummary = prometheus.NewSummaryVec(
178+
prometheus.SummaryOpts{
179+
Name: "olm_resolution_duration_seconds",
180+
Help: "The duration for a dependency to get resolved",
181+
Objectives: map[float64]float64{0.95: 0.05, 0.9: 0.01, 0.99: 0.001},
182+
},
183+
[]string{RESOLUTION},
184+
)
185+
173186
// subscriptionSyncCounters keeps a record of the promethues counters emitted by
174187
// Subscription objects. The key of a record is the Subscription name, while the value
175188
// is struct containing label values used in the counter
@@ -194,6 +207,7 @@ func RegisterCatalog() {
194207
prometheus.MustRegister(subscriptionCount)
195208
prometheus.MustRegister(catalogSourceCount)
196209
prometheus.MustRegister(SubscriptionSyncCount)
210+
prometheus.MustRegister(dependencyResolutionSummary)
197211
}
198212

199213
func CounterForSubscription(name, installedCSV, channelName, packageName string) prometheus.Counter {
@@ -269,3 +283,11 @@ func UpdateSubsSyncCounterStorage(sub *olmv1alpha1.Subscription) {
269283
counterValues.channel = sub.Spec.Channel
270284
}
271285
}
286+
287+
func RegisterDependencyResolutionSuccess(duration time.Duration) {
288+
dependencyResolutionSummary.WithLabelValues(SUCCEEDED).Observe(duration.Seconds())
289+
}
290+
291+
func RegisterDependencyResolutionFailure(duration time.Duration) {
292+
dependencyResolutionSummary.WithLabelValues(FAILED).Observe(duration.Seconds())
293+
}

test/e2e/like_metric_matcher_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ func WithValue(v float64) MetricPredicate {
5656
}
5757
}
5858

59+
func WithValueGreaterThan(v float64) MetricPredicate {
60+
return MetricPredicate{
61+
name: fmt.Sprintf("WithValueGreaterThan(%g)", v),
62+
f: func(m Metric) bool {
63+
return m.Value > v
64+
},
65+
}
66+
}
67+
5968
type LikeMetricMatcher struct {
6069
Predicates []MetricPredicate
6170
}

test/e2e/metrics_e2e_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ var _ = Describe("Metrics are generated for OLM managed resources", func() {
109109
})
110110
})
111111

112-
Context("Subscription Metric", func() {
112+
Context("Metrics emitted by objects during operator installation", func() {
113113
var (
114114
subscriptionCleanup cleanupFunc
115115
subscription *v1alpha1.Subscription
@@ -138,6 +138,20 @@ var _ = Describe("Metrics are generated for OLM managed resources", func() {
138138
WithLabel("package", testPackageName),
139139
)))
140140
})
141+
142+
It("generates dependency_resolution metric", func() {
143+
144+
// Verify metrics have been emitted for dependency resolution
145+
Eventually(func() bool {
146+
return Eventually(func() []Metric {
147+
return getMetricsFromPod(c, getPodWithLabel(c, "app=catalog-operator"), "8081")
148+
}).Should(ContainElement(LikeMetric(
149+
WithFamily("olm_resolution_duration_seconds"),
150+
WithLabel("resolution", "failed"),
151+
WithValueGreaterThan(0),
152+
)))
153+
})
154+
})
141155
})
142156

143157
When("A subscription object is updated after emitting metrics", func() {

0 commit comments

Comments
 (0)