Skip to content

feat: native open telemetry metrics #2882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,6 @@

public class MicrometerMetrics implements Metrics {

private static final String PREFIX = "operator.sdk.";
private static final String RECONCILIATIONS = "reconciliations.";
private static final String RECONCILIATIONS_FAILED = RECONCILIATIONS + "failed";
private static final String RECONCILIATIONS_SUCCESS = RECONCILIATIONS + "success";
private static final String RECONCILIATIONS_RETRIES_LAST = RECONCILIATIONS + "retries.last";
private static final String RECONCILIATIONS_RETRIES_NUMBER = RECONCILIATIONS + "retries.number";
private static final String RECONCILIATIONS_STARTED = RECONCILIATIONS + "started";
private static final String RECONCILIATIONS_EXECUTIONS = PREFIX + RECONCILIATIONS + "executions.";
private static final String RECONCILIATIONS_QUEUE_SIZE = PREFIX + RECONCILIATIONS + "queue.size.";
private static final String NAME = "name";
private static final String NAMESPACE = "namespace";
private static final String GROUP = "group";
private static final String VERSION = "version";
private static final String KIND = "kind";
private static final String SCOPE = "scope";
private static final String METADATA_PREFIX = "resource.";
private static final String CONTROLLERS_EXECUTION = "controllers.execution.";
private static final String CONTROLLER = "controller";
private static final String SUCCESS_SUFFIX = ".success";
private static final String FAILURE_SUFFIX = ".failure";
private static final String TYPE = "type";
private static final String EXCEPTION = "exception";
private static final String EVENT = "event";
private static final String ACTION = "action";
private static final String EVENTS_RECEIVED = "events.received";
private static final String EVENTS_DELETE = "events.delete";
private static final String CLUSTER = "cluster";
private static final String SIZE_SUFFIX = ".size";
private final boolean collectPerResourceMetrics;
private final MeterRegistry registry;
private final Map<String, AtomicInteger> gauges = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -167,13 +139,13 @@ public <T> T timeControllerExecution(ControllerExecution<T> execution) {

@Override
public void receivedEvent(Event event, Map<String, Object> metadata) {
if (event instanceof ResourceEvent) {
if (event instanceof ResourceEvent resourceEvent) {
incrementCounter(
event.getRelatedCustomResourceID(),
EVENTS_RECEIVED,
metadata,
Tag.of(EVENT, event.getClass().getSimpleName()),
Tag.of(ACTION, ((ResourceEvent) event).getAction().toString()));
Tag.of(ACTION, resourceEvent.getAction().toString()));
} else {
incrementCounter(
event.getRelatedCustomResourceID(),
Expand Down
75 changes: 75 additions & 0 deletions open-telemetry-support/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.1.2-SNAPSHOT</version>
</parent>

<artifactId>open-telemetry-support</artifactId>
<name>Operator SDK - Open Telemetry Metrics Support</name>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>${opentelemetry.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-core</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-httpclient-vertx</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package io.javaoperatorsdk.operator.monitoring.opentelemetry;

import java.util.HashMap;
import java.util.Map;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.processing.GroupVersionKind;
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;

public class OpenTelemetryMetrics implements Metrics {

private Meter meter;
private final boolean collectPerResourceMetrics;

public OpenTelemetryMetrics(Meter meter, boolean collectPerResourceMetrics) {
this.meter = meter;
this.collectPerResourceMetrics = collectPerResourceMetrics;
}

@Override
public void controllerRegistered(Controller<? extends HasMetadata> controller) {}

@Override
public void receivedEvent(Event event, Map<String, Object> metadata) {
if (event instanceof ResourceEvent) {

} else {

}
}

@Override
public void reconcileCustomResource(
HasMetadata resource, RetryInfo retryInfo, Map<String, Object> metadata) {}

@Override
public void failedReconciliation(
HasMetadata resource, Exception exception, Map<String, Object> metadata) {}

@Override
public void reconciliationExecutionStarted(HasMetadata resource, Map<String, Object> metadata) {}

@Override
public void reconciliationExecutionFinished(HasMetadata resource, Map<String, Object> metadata) {}

@Override
public void cleanupDoneFor(ResourceID resourceID, Map<String, Object> metadata) {}

@Override
public void finishedReconciliation(HasMetadata resource, Map<String, Object> metadata) {}

@Override
public <T> T timeControllerExecution(ControllerExecution<T> execution) throws Exception {
return null;
}

@Override
public <T extends Map<?, ?>> T monitorSizeOf(T map, String name) {
return null;
}

private void incrementCounter(
ResourceID id,
String counterName,
Map<String, Object> metadata,
Map<String, String> additionalTags) {

meter.counterBuilder("").buildWithCallback(m -> {}).close();

Map<String, String> tags = new HashMap<>(additionalTags);
addMetadataTags(id, metadata, tags, false);

var counter = meter.counterBuilder(PREFIX + counterName).build();
var attributes = Attributes.builder();
additionalTags.forEach(attributes::put);
counter.add(1, attributes.build());
}

private void addMetadataTags(
ResourceID resourceID,
Map<String, Object> metadata,
Map<String, String> tags,
boolean prefixed) {
if (collectPerResourceMetrics) {
addTag(NAME, resourceID.getName(), tags, prefixed);
addTagOmittingOnEmptyValue(NAMESPACE, resourceID.getNamespace().orElse(null), tags, prefixed);
}
addTag(SCOPE, getScope(resourceID), tags, prefixed);
final var gvk = (GroupVersionKind) metadata.get(Constants.RESOURCE_GVK_KEY);
if (gvk != null) {
addGVKTags(gvk, tags, prefixed);
}
}

private static void addTag(
String name, String value, Map<String, String> tags, boolean prefixed) {
tags.put(getPrefixedMetadataTag(name, prefixed), value);
}

private static void addGVKTags(GroupVersionKind gvk, Map<String, String> tags, boolean prefixed) {
addTagOmittingOnEmptyValue(GROUP, gvk.getGroup(), tags, prefixed);
addTag(VERSION, gvk.getVersion(), tags, prefixed);
addTag(KIND, gvk.getKind(), tags, prefixed);
}

private static String getPrefixedMetadataTag(String tagName, boolean prefixed) {
return prefixed ? METADATA_PREFIX + tagName : tagName;
}

private static void addTagOmittingOnEmptyValue(
String name, String value, Map<String, String> tags, boolean prefixed) {
if (value != null && !value.isBlank()) {
addTag(name, value, tags, prefixed);
}
}

private static String getScope(ResourceID resourceID) {
return resourceID.getNamespace().isPresent() ? NAMESPACE : CLUSTER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@
*/
public interface Metrics {

String PREFIX = "operator.sdk.";
String RECONCILIATIONS = "reconciliations.";
String RECONCILIATIONS_FAILED = RECONCILIATIONS + "failed";
String RECONCILIATIONS_SUCCESS = RECONCILIATIONS + "success";
String RECONCILIATIONS_RETRIES_LAST = RECONCILIATIONS + "retries.last";
String RECONCILIATIONS_RETRIES_NUMBER = RECONCILIATIONS + "retries.number";
String RECONCILIATIONS_STARTED = RECONCILIATIONS + "started";
String RECONCILIATIONS_EXECUTIONS = PREFIX + RECONCILIATIONS + "executions.";
String RECONCILIATIONS_QUEUE_SIZE = PREFIX + RECONCILIATIONS + "queue.size.";
String NAME = "name";
String NAMESPACE = "namespace";
String GROUP = "group";
String VERSION = "version";
String KIND = "kind";
String SCOPE = "scope";
String METADATA_PREFIX = "resource.";
String CONTROLLERS_EXECUTION = "controllers.execution.";
String CONTROLLER = "controller";
String SUCCESS_SUFFIX = ".success";
String FAILURE_SUFFIX = ".failure";
String TYPE = "type";
String EXCEPTION = "exception";
String EVENT = "event";
String ACTION = "action";
String EVENTS_RECEIVED = "events.received";
String EVENTS_DELETE = "events.delete";
String CLUSTER = "cluster";
String SIZE_SUFFIX = ".size";

/** The default Metrics provider: a no-operation implementation. */
Metrics NOOP = new Metrics() {};

Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<module>operator-framework-junit5</module>
<module>operator-framework</module>
<module>micrometer-support</module>
<module>open-telemetry-support</module>
<module>sample-operators</module>
<module>caffeine-bounded-cache-support</module>
<module>bootstrapper-maven-plugin</module>
Expand Down Expand Up @@ -68,6 +69,7 @@
<mustache.version>0.9.14</mustache.version>
<commons.io.version>2.20.0</commons.io.version>
<java.diff.version>4.16</java.diff.version>
<opentelemetry.version>1.52.0</opentelemetry.version>

<fmt-maven-plugin.version>2.11</fmt-maven-plugin.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
Expand Down