From e78a2359bec7748c28f3b0ead0642005943d5b62 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 18 Aug 2021 16:18:26 +0200 Subject: [PATCH 1/3] feat: initial PoC for micrometer integration --- operator-framework-core/pom.xml | 6 + .../io/javaoperatorsdk/operator/Metrics.java | 111 ++++++++++++++++++ .../io/javaoperatorsdk/operator/Operator.java | 9 +- .../api/config/ConfigurationService.java | 5 + .../operator/processing/EventDispatcher.java | 6 +- .../operator/IntegrationTestSupport.java | 5 +- 6 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Metrics.java 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..152d5ce20a --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Metrics.java @@ -0,0 +1,111 @@ +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", 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", name, successType); + return result; + } catch (Exception e) { + registry.counter( + "operator.sdk.controllers.execution.failure", name, 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/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java index f69317b646..7a511e9718 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java @@ -22,6 +22,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.concurrent.TimeUnit; + +import io.micrometer.core.instrument.composite.CompositeMeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +64,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) { From 88fd95d2df30857b5de35bf6e0d0a4249bdbcbe3 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 18 Aug 2021 16:36:22 +0200 Subject: [PATCH 2/3] fix: tags must be key/value pairs --- .../java/io/javaoperatorsdk/operator/Metrics.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 index 152d5ce20a..784c4988d7 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Metrics.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Metrics.java @@ -29,7 +29,8 @@ public UpdateControl timeControllerCreateOrUpdate( // 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", name); + 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"; @@ -39,11 +40,16 @@ public UpdateControl timeControllerCreateOrUpdate( if (result.isUpdateCustomResourceAndStatusSubResource()) { successType = "both"; } - registry.counter("operator.sdk.controllers.execution.success", name, successType); + registry.counter( + "operator.sdk.controllers.execution.success", "controller", name, "type", successType); return result; } catch (Exception e) { registry.counter( - "operator.sdk.controllers.execution.failure", name, e.getClass().getSimpleName()); + "operator.sdk.controllers.execution.failure", + "controller", + name, + "exception", + e.getClass().getSimpleName()); throw e; } } From db4e80cbb8ccf81331be25f4bd7f3abc338bb9ed Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 18 Aug 2021 16:36:54 +0200 Subject: [PATCH 3/3] fix: add proper mock expectations --- .../operator/processing/EventDispatcherTest.java | 6 ++++++ .../io/javaoperatorsdk/operator/IntegrationTestSupport.java | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) 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 7a511e9718..e88498b3fc 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestSupport.java @@ -22,9 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.concurrent.TimeUnit; - -import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory;