Skip to content

Commit 1db2017

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 ffaa9bc commit 1db2017

File tree

6 files changed

+155
-3
lines changed

6 files changed

+155
-3
lines changed

pkg/controller/operators/catalog/operator.go

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

135+
operatorV1alpha1Resolver := resolver.NewOperatorsV1alpha1Resolver(lister, crClient, opClient.KubernetesInterface())
136+
successMetricsEmitter := metrics.RegisterDependencyResolutionSuccess
137+
failureMetricsEmitter := metrics.RegisterDependencyResolutionFailure
135138
// Allocate the new instance of an Operator.
136139
op := &Operator{
137140
Operator: queueOperator,
@@ -142,7 +145,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
142145
client: crClient,
143146
lister: lister,
144147
namespace: operatorNamespace,
145-
resolver: resolver.NewOperatorsV1alpha1Resolver(lister, crClient, opClient.KubernetesInterface()),
148+
resolver: resolver.NewInstrumentedResolver(operatorV1alpha1Resolver, successMetricsEmitter, failureMetricsEmitter),
146149
catsrcQueueSet: queueinformer.NewEmptyResourceQueueSet(),
147150
subQueueSet: queueinformer.NewEmptyResourceQueueSet(),
148151
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: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 TestInstrumentedResolverFailure(t *testing.T) {
38+
result := []time.Duration{}
39+
40+
changeToFailure := func(num time.Duration) {
41+
result = append(result, failure)
42+
}
43+
44+
changeToSuccess := func(num time.Duration) {
45+
result = append(result, success)
46+
}
47+
48+
instrumentedResolver := NewInstrumentedResolver(newFakeResolverWithError(), changeToSuccess, changeToFailure)
49+
instrumentedResolver.ResolveSteps("", nil)
50+
require.Equal(t, len(result), 1) // check that only one call was made to a change function
51+
require.Equal(t, result[0], failure) // check that the call was made to changeToFailure function
52+
}
53+
54+
func TestInstrumentedResolverSuccess(t *testing.T) {
55+
result := []time.Duration{}
56+
57+
changeToFailure := func(num time.Duration) {
58+
result = append(result, failure)
59+
}
60+
61+
changeToSuccess := func(num time.Duration) {
62+
result = append(result, success)
63+
}
64+
65+
instrumentedResolver := NewInstrumentedResolver(newFakeResolverWithoutError(), changeToSuccess, changeToFailure)
66+
instrumentedResolver.ResolveSteps("", nil)
67+
require.Equal(t, len(result), 1) // check that only one call was made to a change function
68+
require.Equal(t, result[0], success) // check that the call was made to changeToSuccess function
69+
}

pkg/metrics/metrics.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package metrics
22

33
import (
4+
"time"
5+
46
"github.com/prometheus/client_golang/prometheus"
57
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
68
"k8s.io/apimachinery/pkg/labels"
@@ -18,6 +20,9 @@ const (
1820
PHASE_LABEL = "phase"
1921
REASON_LABEL = "reason"
2022
PACKAGE_LABEL = "package"
23+
Outcome = "outcome"
24+
Succeeded = "succeeded"
25+
Failed = "failed"
2126
)
2227

2328
type MetricsProvider interface {
@@ -59,7 +64,7 @@ func (m *metricsInstallPlan) HandleMetrics() error {
5964
}
6065

6166
type metricsSubscription struct {
62-
lister v1alpha1.SubscriptionLister
67+
lister v1alpha1.SubscriptionLister
6368
}
6469

6570
func NewMetricsSubscription(lister v1alpha1.SubscriptionLister) MetricsProvider {
@@ -167,6 +172,15 @@ var (
167172
[]string{NAMESPACE_LABEL, NAME_LABEL, VERSION_LABEL, PHASE_LABEL, REASON_LABEL},
168173
)
169174

175+
dependencyResolutionSummary = prometheus.NewSummaryVec(
176+
prometheus.SummaryOpts{
177+
Name: "olm_resolution_duration_seconds",
178+
Help: "The duration of a dependency resolution attempt",
179+
Objectives: map[float64]float64{0.95: 0.05, 0.9: 0.01, 0.99: 0.001},
180+
},
181+
[]string{Outcome},
182+
)
183+
170184
// subscriptionSyncCounters keeps a record of the promethues counters emitted by
171185
// Subscription objects. The key of a record is the Subscription name, while the value
172186
// is struct containing label values used in the counter
@@ -191,6 +205,7 @@ func RegisterCatalog() {
191205
prometheus.MustRegister(subscriptionCount)
192206
prometheus.MustRegister(catalogSourceCount)
193207
prometheus.MustRegister(SubscriptionSyncCount)
208+
prometheus.MustRegister(dependencyResolutionSummary)
194209
}
195210

196211
func CounterForSubscription(name, installedCSV, channelName, packageName string) prometheus.Counter {
@@ -268,3 +283,11 @@ func UpdateSubsSyncCounterStorage(sub *olmv1alpha1.Subscription) {
268283
subscriptionSyncCounters[sub.GetName()] = counterValues
269284
}
270285
}
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
@@ -80,6 +80,15 @@ func WithValue(v float64) MetricPredicate {
8080
}
8181
}
8282

83+
func WithValueGreaterThan(v float64) MetricPredicate {
84+
return MetricPredicate{
85+
name: fmt.Sprintf("WithValueGreaterThan(%g)", v),
86+
f: func(m Metric) bool {
87+
return m.Value > v
88+
},
89+
}
90+
}
91+
8392
type LikeMetricMatcher struct {
8493
Predicates []MetricPredicate
8594
}

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
WithPackage(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("outcome", "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)