Skip to content

Commit 7ba3ea9

Browse files
laxmikantbpandharemetacosm
authored andcommitted
feat: micrometer Integration for generating metrics
Fixes #64
1 parent f5b30da commit 7ba3ea9

File tree

10 files changed

+270
-13
lines changed

10 files changed

+270
-13
lines changed

Diff for: operator-framework-core/pom.xml

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
<description>Core framework for implementing Kubernetes operators</description>
1616
<packaging>jar</packaging>
1717

18+
<properties>
19+
<java.version>11</java.version>
20+
<maven.compiler.source>11</maven.compiler.source>
21+
<maven.compiler.target>11</maven.compiler.target>
22+
<micrometer-core.version>1.7.3</micrometer-core.version>
23+
</properties>
24+
1825
<build>
1926
<plugins>
2027
<plugin>
@@ -92,5 +99,10 @@
9299
<artifactId>log4j-core</artifactId>
93100
<scope>test</scope>
94101
</dependency>
102+
<dependency>
103+
<groupId>io.micrometer</groupId>
104+
<artifactId>micrometer-core</artifactId>
105+
<version>${micrometer-core.version}</version>
106+
</dependency>
95107
</dependencies>
96108
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.javaoperatorsdk.operator.api.Context;
5+
import io.javaoperatorsdk.operator.api.DeleteControl;
6+
import io.javaoperatorsdk.operator.api.ResourceController;
7+
import io.javaoperatorsdk.operator.api.UpdateControl;
8+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
9+
import io.micrometer.core.instrument.*;
10+
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
11+
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
12+
import io.micrometer.core.instrument.noop.*;
13+
import java.util.concurrent.TimeUnit;
14+
import java.util.function.ToDoubleFunction;
15+
import java.util.function.ToLongFunction;
16+
17+
public class Metrics {
18+
public static final Metrics NOOP = new Metrics(new NoopMeterRegistry(Clock.SYSTEM));
19+
private final MeterRegistry registry;
20+
21+
public Metrics(MeterRegistry registry) {
22+
this.registry = registry;
23+
}
24+
25+
public <R extends CustomResource> UpdateControl<R> timeControllerCreateOrUpdate(
26+
ResourceController<R> controller,
27+
ControllerConfiguration<R> configuration,
28+
R resource,
29+
Context<R> context) {
30+
final var name = configuration.getName();
31+
final var timer =
32+
Timer.builder("operator.sdk.controllers.execution.createorupdate")
33+
.tags("controller", name)
34+
.publishPercentiles(0.3, 0.5, 0.95)
35+
.publishPercentileHistogram()
36+
.register(registry);
37+
try {
38+
final var result = timer.record(() -> controller.createOrUpdateResource(resource, context));
39+
String successType = "cr";
40+
if (result.isUpdateStatusSubResource()) {
41+
successType = "status";
42+
}
43+
if (result.isUpdateCustomResourceAndStatusSubResource()) {
44+
successType = "both";
45+
}
46+
registry
47+
.counter(
48+
"operator.sdk.controllers.execution.success", "controller", name, "type", successType)
49+
.increment();
50+
return result;
51+
} catch (Exception e) {
52+
registry
53+
.counter(
54+
"operator.sdk.controllers.execution.failure",
55+
"controller",
56+
name,
57+
"exception",
58+
e.getClass().getSimpleName())
59+
.increment();
60+
throw e;
61+
}
62+
}
63+
64+
public DeleteControl timeControllerDelete(
65+
ResourceController controller,
66+
ControllerConfiguration configuration,
67+
CustomResource resource,
68+
Context context) {
69+
final var name = configuration.getName();
70+
final var timer =
71+
Timer.builder("operator.sdk.controllers.execution.delete")
72+
.tags("controller", name)
73+
.publishPercentiles(0.3, 0.5, 0.95)
74+
.publishPercentileHistogram()
75+
.register(registry);
76+
try {
77+
final var result = timer.record(() -> controller.deleteResource(resource, context));
78+
String successType = "notDelete";
79+
if (result == DeleteControl.DEFAULT_DELETE) {
80+
successType = "delete";
81+
}
82+
registry
83+
.counter(
84+
"operator.sdk.controllers.execution.success", "controller", name, "type", successType)
85+
.increment();
86+
return result;
87+
} catch (Exception e) {
88+
registry
89+
.counter(
90+
"operator.sdk.controllers.execution.failure",
91+
"controller",
92+
name,
93+
"exception",
94+
e.getClass().getSimpleName())
95+
.increment();
96+
throw e;
97+
}
98+
}
99+
100+
public void timeControllerRetry() {
101+
102+
registry
103+
.counter(
104+
"operator.sdk.retry.on.exception", "retry", "retryCounter", "type",
105+
"retryException")
106+
.increment();
107+
108+
}
109+
110+
public void timeControllerEvents() {
111+
112+
registry
113+
.counter(
114+
"operator.sdk.total.events.received", "events", "totalEvents", "type",
115+
"eventsReceived")
116+
.increment();
117+
118+
}
119+
120+
public static class NoopMeterRegistry extends MeterRegistry {
121+
public NoopMeterRegistry(Clock clock) {
122+
super(clock);
123+
}
124+
125+
@Override
126+
protected <T> Gauge newGauge(Meter.Id id, T t, ToDoubleFunction<T> toDoubleFunction) {
127+
return new NoopGauge(id);
128+
}
129+
130+
@Override
131+
protected Counter newCounter(Meter.Id id) {
132+
return new NoopCounter(id);
133+
}
134+
135+
@Override
136+
protected Timer newTimer(
137+
Meter.Id id,
138+
DistributionStatisticConfig distributionStatisticConfig,
139+
PauseDetector pauseDetector) {
140+
return new NoopTimer(id);
141+
}
142+
143+
@Override
144+
protected DistributionSummary newDistributionSummary(
145+
Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, double v) {
146+
return new NoopDistributionSummary(id);
147+
}
148+
149+
@Override
150+
protected Meter newMeter(Meter.Id id, Meter.Type type, Iterable<Measurement> iterable) {
151+
return new NoopMeter(id);
152+
}
153+
154+
@Override
155+
protected <T> FunctionTimer newFunctionTimer(
156+
Meter.Id id,
157+
T t,
158+
ToLongFunction<T> toLongFunction,
159+
ToDoubleFunction<T> toDoubleFunction,
160+
TimeUnit timeUnit) {
161+
return new NoopFunctionTimer(id);
162+
}
163+
164+
@Override
165+
protected <T> FunctionCounter newFunctionCounter(
166+
Meter.Id id, T t, ToDoubleFunction<T> toDoubleFunction) {
167+
return new NoopFunctionCounter(id);
168+
}
169+
170+
@Override
171+
protected TimeUnit getBaseTimeUnit() {
172+
return TimeUnit.SECONDS;
173+
}
174+
175+
@Override
176+
protected DistributionStatisticConfig defaultHistogramConfig() {
177+
return DistributionStatisticConfig.NONE;
178+
}
179+
}
180+
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,28 @@ public class Operator implements AutoCloseable {
2525
private final Object lock;
2626
private final List<ControllerRef> controllers;
2727
private volatile boolean started;
28+
private final Metrics metrics;
2829

29-
public Operator(KubernetesClient k8sClient, ConfigurationService configurationService) {
30+
public Operator(
31+
KubernetesClient k8sClient, ConfigurationService configurationService, Metrics metrics) {
3032
this.k8sClient = k8sClient;
3133
this.configurationService = configurationService;
3234
this.closeables = new ArrayList<>();
3335
this.lock = new Object();
3436
this.controllers = new ArrayList<>();
3537
this.started = false;
38+
this.metrics = metrics;
3639
}
3740

3841
/** Adds a shutdown hook that automatically calls {@link #close()} when the app shuts down. */
3942
public void installShutdownHook() {
4043
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
4144
}
4245

46+
public Operator(KubernetesClient k8sClient, ConfigurationService configurationService) {
47+
this(k8sClient, configurationService, Metrics.NOOP);
48+
}
49+
4350
public KubernetesClient getKubernetesClient() {
4451
return k8sClient;
4552
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import io.fabric8.kubernetes.client.Config;
55
import io.fabric8.kubernetes.client.CustomResource;
6+
import io.javaoperatorsdk.operator.Metrics;
67
import io.javaoperatorsdk.operator.api.ResourceController;
78
import java.util.Set;
89

@@ -93,4 +94,8 @@ default ObjectMapper getObjectMapper() {
9394
default int getTerminationTimeoutSeconds() {
9495
return DEFAULT_TERMINATION_TIMEOUT_SECONDS;
9596
}
97+
98+
default Metrics getMetrics() {
99+
return Metrics.NOOP;
100+
}
96101
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import io.fabric8.kubernetes.client.CustomResource;
88
import io.fabric8.kubernetes.client.dsl.MixedOperation;
9+
import io.javaoperatorsdk.operator.Metrics;
910
import io.javaoperatorsdk.operator.api.ResourceController;
1011
import io.javaoperatorsdk.operator.api.RetryInfo;
1112
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
@@ -16,6 +17,7 @@
1617
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
1718
import io.javaoperatorsdk.operator.processing.retry.Retry;
1819
import io.javaoperatorsdk.operator.processing.retry.RetryExecution;
20+
import io.micrometer.core.instrument.Clock;
1921
import java.util.HashMap;
2022
import java.util.HashSet;
2123
import java.util.Map;
@@ -46,6 +48,7 @@ public class DefaultEventHandler implements EventHandler {
4648
private final int terminationTimeout;
4749
private final ReentrantLock lock = new ReentrantLock();
4850
private DefaultEventSourceManager eventSourceManager;
51+
private ControllerConfiguration configuration;
4952

5053
public DefaultEventHandler(
5154
ResourceController controller, ControllerConfiguration configuration, MixedOperation client) {
@@ -54,28 +57,28 @@ public DefaultEventHandler(
5457
configuration.getName(),
5558
GenericRetry.fromConfiguration(configuration.getRetryConfiguration()),
5659
configuration.getConfigurationService().concurrentReconciliationThreads(),
57-
configuration.getConfigurationService().getTerminationTimeoutSeconds());
60+
configuration.getConfigurationService().getTerminationTimeoutSeconds(), configuration);
5861
}
5962

6063
DefaultEventHandler(
6164
EventDispatcher eventDispatcher,
6265
String relatedControllerName,
6366
Retry retry,
64-
int concurrentReconciliationThreads) {
67+
int concurrentReconciliationThreads, ControllerConfiguration configuration) {
6568
this(
6669
eventDispatcher,
6770
relatedControllerName,
6871
retry,
6972
concurrentReconciliationThreads,
70-
ConfigurationService.DEFAULT_TERMINATION_TIMEOUT_SECONDS);
73+
ConfigurationService.DEFAULT_TERMINATION_TIMEOUT_SECONDS, configuration);
7174
}
7275

7376
private DefaultEventHandler(
7477
EventDispatcher eventDispatcher,
7578
String relatedControllerName,
7679
Retry retry,
7780
int concurrentReconciliationThreads,
78-
int terminationTimeout) {
81+
int terminationTimeout, ControllerConfiguration configuration) {
7982
this.eventDispatcher = eventDispatcher;
8083
this.retry = retry;
8184
this.controllerName = relatedControllerName;
@@ -85,6 +88,7 @@ private DefaultEventHandler(
8588
new ScheduledThreadPoolExecutor(
8689
concurrentReconciliationThreads,
8790
runnable -> new Thread(runnable, "EventHandler-" + relatedControllerName));
91+
this.configuration = configuration;
8892
}
8993

9094
@Override
@@ -113,6 +117,10 @@ public void handleEvent(Event event) {
113117
final Predicate<CustomResource> selector = event.getCustomResourcesSelector();
114118
for (String uid : eventSourceManager.getLatestResourceUids(selector)) {
115119
eventBuffer.addEvent(uid, event);
120+
configuration
121+
.getConfigurationService()
122+
.getMetrics()
123+
.timeControllerEvents();
116124
executeBufferedEvents(uid);
117125
}
118126
} finally {
@@ -162,6 +170,10 @@ void eventProcessingFinished(
162170

163171
if (retry != null && postExecutionControl.exceptionDuringExecution()) {
164172
handleRetryOnException(executionScope);
173+
configuration
174+
.getConfigurationService()
175+
.getMetrics()
176+
.timeControllerRetry();
165177
return;
166178
}
167179

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@ private PostExecutionControl handleCreateOrUpdate(
123123
getName(resource),
124124
getVersion(resource),
125125
executionScope);
126-
UpdateControl<R> updateControl = controller.createOrUpdateResource(resource, context);
126+
127+
UpdateControl<R> updateControl =
128+
configuration
129+
.getConfigurationService()
130+
.getMetrics()
131+
.timeControllerCreateOrUpdate(controller, configuration, resource, context);
127132
R updatedCustomResource = null;
128133
if (updateControl.isUpdateCustomResourceAndStatusSubResource()) {
129134
updatedCustomResource = updateCustomResource(updateControl.getCustomResource());
@@ -153,8 +158,12 @@ private PostExecutionControl handleDelete(R resource, Context<R> context) {
153158
"Executing delete for resource: {} with version: {}",
154159
getName(resource),
155160
getVersion(resource));
156-
// todo: this is be executed in a try-catch statement, in case this fails
157-
DeleteControl deleteControl = controller.deleteResource(resource, context);
161+
162+
DeleteControl deleteControl =
163+
configuration
164+
.getConfigurationService()
165+
.getMetrics()
166+
.timeControllerDelete(controller, configuration, resource, context);
158167
final var useFinalizer = configuration.useFinalizer();
159168
if (useFinalizer) {
160169
if (deleteControl == DeleteControl.DEFAULT_DELETE

0 commit comments

Comments
 (0)