diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index a548a74fd2..9cbce39f0a 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -19,6 +19,7 @@
11
11
11
+ 1.7.3
@@ -94,5 +95,10 @@
3.20.2
test
+
+ io.micrometer
+ micrometer-core
+ ${micrometer-core.version}
+
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Metrics.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Metrics.java
new file mode 100644
index 0000000000..784c4988d7
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Metrics.java
@@ -0,0 +1,117 @@
+package io.javaoperatorsdk.operator;
+
+import io.fabric8.kubernetes.client.CustomResource;
+import io.javaoperatorsdk.operator.api.Context;
+import io.javaoperatorsdk.operator.api.ResourceController;
+import io.javaoperatorsdk.operator.api.UpdateControl;
+import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
+import io.micrometer.core.instrument.*;
+import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
+import io.micrometer.core.instrument.distribution.pause.PauseDetector;
+import io.micrometer.core.instrument.noop.*;
+import java.util.concurrent.TimeUnit;
+import java.util.function.ToDoubleFunction;
+import java.util.function.ToLongFunction;
+
+public class Metrics {
+ public static final Metrics NOOP = new Metrics(new NoopMeterRegistry(Clock.SYSTEM));
+ private final MeterRegistry registry;
+
+ public Metrics(MeterRegistry registry) {
+ this.registry = registry;
+ }
+
+ public UpdateControl timeControllerCreateOrUpdate(
+ ResourceController controller,
+ ControllerConfiguration configuration,
+ R resource,
+ Context context) {
+ // todo: https://github.com/java-operator-sdk/java-operator-sdk/issues/496 would simplify
+ // things
+ final var name = configuration.getName();
+ final var timer =
+ registry.timer("operator.sdk.controllers.execution.createOrUpdate", "controller", name);
+ try {
+ final var result = timer.record(() -> controller.createOrUpdateResource(resource, context));
+ String successType = "cr";
+ if (result.isUpdateStatusSubResource()) {
+ successType = "status";
+ }
+ if (result.isUpdateCustomResourceAndStatusSubResource()) {
+ successType = "both";
+ }
+ registry.counter(
+ "operator.sdk.controllers.execution.success", "controller", name, "type", successType);
+ return result;
+ } catch (Exception e) {
+ registry.counter(
+ "operator.sdk.controllers.execution.failure",
+ "controller",
+ name,
+ "exception",
+ e.getClass().getSimpleName());
+ throw e;
+ }
+ }
+
+ private static class NoopMeterRegistry extends MeterRegistry {
+ protected NoopMeterRegistry(Clock clock) {
+ super(clock);
+ }
+
+ @Override
+ protected Gauge newGauge(Meter.Id id, T t, ToDoubleFunction toDoubleFunction) {
+ return new NoopGauge(id);
+ }
+
+ @Override
+ protected Counter newCounter(Meter.Id id) {
+ return new NoopCounter(id);
+ }
+
+ @Override
+ protected Timer newTimer(
+ Meter.Id id,
+ DistributionStatisticConfig distributionStatisticConfig,
+ PauseDetector pauseDetector) {
+ return new NoopTimer(id);
+ }
+
+ @Override
+ protected DistributionSummary newDistributionSummary(
+ Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, double v) {
+ return new NoopDistributionSummary(id);
+ }
+
+ @Override
+ protected Meter newMeter(Meter.Id id, Meter.Type type, Iterable iterable) {
+ return new NoopMeter(id);
+ }
+
+ @Override
+ protected FunctionTimer newFunctionTimer(
+ Meter.Id id,
+ T t,
+ ToLongFunction toLongFunction,
+ ToDoubleFunction toDoubleFunction,
+ TimeUnit timeUnit) {
+ return new NoopFunctionTimer(id);
+ }
+
+ @Override
+ protected FunctionCounter newFunctionCounter(
+ Meter.Id id, T t, ToDoubleFunction toDoubleFunction) {
+ return new NoopFunctionCounter(id);
+ }
+
+ @Override
+ protected TimeUnit getBaseTimeUnit() {
+ return TimeUnit.SECONDS;
+ }
+
+ @Override
+ protected DistributionStatisticConfig defaultHistogramConfig() {
+ return DistributionStatisticConfig.NONE;
+ }
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
index bda70ca7e6..7feb3d5ee7 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java
@@ -25,18 +25,25 @@ public class Operator implements AutoCloseable {
private final Object lock;
private final List controllers;
private volatile boolean started;
+ private final Metrics metrics;
- public Operator(KubernetesClient k8sClient, ConfigurationService configurationService) {
+ public Operator(
+ KubernetesClient k8sClient, ConfigurationService configurationService, Metrics metrics) {
this.k8sClient = k8sClient;
this.configurationService = configurationService;
this.closeables = new ArrayList<>();
this.lock = new Object();
this.controllers = new ArrayList<>();
this.started = false;
+ this.metrics = metrics;
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
}
+ public Operator(KubernetesClient k8sClient, ConfigurationService configurationService) {
+ this(k8sClient, configurationService, Metrics.NOOP);
+ }
+
public KubernetesClient getKubernetesClient() {
return k8sClient;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
index 4862889e60..13419c11d2 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.CustomResource;
+import io.javaoperatorsdk.operator.Metrics;
import io.javaoperatorsdk.operator.api.ResourceController;
import java.util.Set;
@@ -91,4 +92,8 @@ default ObjectMapper getObjectMapper() {
default int getTerminationTimeoutSeconds() {
return DEFAULT_TERMINATION_TIMEOUT_SECONDS;
}
+
+ default Metrics getMetrics() {
+ return Metrics.NOOP;
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java
index e44891096c..e355b39cb7 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/EventDispatcher.java
@@ -122,7 +122,11 @@ private PostExecutionControl handleCreateOrUpdate(
getName(resource),
getVersion(resource),
executionScope);
- UpdateControl updateControl = controller.createOrUpdateResource(resource, context);
+ UpdateControl updateControl =
+ configuration
+ .getConfigurationService()
+ .getMetrics()
+ .timeControllerCreateOrUpdate(controller, configuration, resource, context);
R updatedCustomResource = null;
if (updateControl.isUpdateCustomResourceAndStatusSubResource()) {
updatedCustomResource = updateCustomResource(updateControl.getCustomResource());
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/EventDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/EventDispatcherTest.java
index 1d00ebfcac..37bd426c4f 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/EventDispatcherTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/EventDispatcherTest.java
@@ -15,12 +15,14 @@
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.Watcher;
+import io.javaoperatorsdk.operator.Metrics;
import io.javaoperatorsdk.operator.TestUtils;
import io.javaoperatorsdk.operator.api.Context;
import io.javaoperatorsdk.operator.api.DeleteControl;
import io.javaoperatorsdk.operator.api.ResourceController;
import io.javaoperatorsdk.operator.api.RetryInfo;
import io.javaoperatorsdk.operator.api.UpdateControl;
+import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEvent;
@@ -40,6 +42,7 @@ class EventDispatcherTest {
private final ResourceController controller = mock(ResourceController.class);
private ControllerConfiguration configuration =
mock(ControllerConfiguration.class);
+ private final ConfigurationService configService = mock(ConfigurationService.class);
private final EventDispatcher.CustomResourceFacade customResourceFacade =
mock(EventDispatcher.CustomResourceFacade.class);
@@ -51,6 +54,9 @@ void setup() {
when(configuration.getFinalizer()).thenReturn(DEFAULT_FINALIZER);
when(configuration.useFinalizer()).thenCallRealMethod();
+ when(configuration.getName()).thenReturn("EventDispatcherTestController");
+ when(configService.getMetrics()).thenReturn(Metrics.NOOP);
+ when(configuration.getConfigurationService()).thenReturn(configService);
when(controller.createOrUpdateResource(eq(testCustomResource), any()))
.thenReturn(UpdateControl.updateCustomResource(testCustomResource));
when(controller.deleteResource(eq(testCustomResource), any()))
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java
index f69317b646..e88498b3fc 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java
@@ -61,7 +61,7 @@ public void initialize(KubernetesClient k8sClient, ResourceController controller
namespaces.create(
new NamespaceBuilder().withNewMetadata().withName(TEST_NAMESPACE).endMetadata().build());
}
- operator = new Operator(k8sClient, configurationService);
+ operator = new Operator(k8sClient, configurationService, Metrics.NOOP);
final var overriddenConfig =
ControllerConfigurationOverrider.override(config).settingNamespace(TEST_NAMESPACE);
if (retry != null) {