Skip to content

Commit 2894e53

Browse files
metacosmcsviri
authored andcommitted
feat: add metadata support for Metrics
Right now, the only metadata that's provided is the group/version/kind information associated with the resource being processed. Fixes #1322.
1 parent 7e9513c commit 2894e53

File tree

6 files changed

+125
-34
lines changed

6 files changed

+125
-34
lines changed

micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.javaoperatorsdk.operator.monitoring.micrometer;
22

3+
import java.util.ArrayList;
34
import java.util.Collections;
4-
import java.util.LinkedList;
55
import java.util.List;
66
import java.util.Map;
77
import java.util.Optional;
@@ -56,54 +56,80 @@ public <T> T timeControllerExecution(ControllerExecution<T> execution) {
5656
}
5757

5858
public void receivedEvent(Event event) {
59-
incrementCounter(event.getRelatedCustomResourceID(), "events.received", "event",
60-
event.getClass().getSimpleName());
59+
incrementCounter(event.getRelatedCustomResourceID(), "events.received",
60+
Collections.emptyMap(),
61+
"event", event.getClass().getSimpleName());
6162
}
6263

6364
@Override
64-
public void cleanupDoneFor(ResourceID resourceID) {
65-
incrementCounter(resourceID, "events.delete");
65+
public void cleanupDoneFor(ResourceID resourceID, Map<String, Object> metadata) {
66+
incrementCounter(resourceID, "events.delete", metadata);
6667
}
6768

6869
@Override
69-
public void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfoNullable) {
70+
public void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfoNullable,
71+
Map<String, Object> metadata) {
7072
Optional<RetryInfo> retryInfo = Optional.ofNullable(retryInfoNullable);
7173
incrementCounter(resourceID, RECONCILIATIONS + "started",
74+
metadata,
7275
RECONCILIATIONS + "retries.number",
7376
"" + retryInfo.map(RetryInfo::getAttemptCount).orElse(0),
7477
RECONCILIATIONS + "retries.last",
7578
"" + retryInfo.map(RetryInfo::isLastAttempt).orElse(true));
7679
}
7780

7881
@Override
79-
public void finishedReconciliation(ResourceID resourceID) {
80-
incrementCounter(resourceID, RECONCILIATIONS + "success");
82+
public void finishedReconciliation(ResourceID resourceID, Map<String, Object> metadata) {
83+
incrementCounter(resourceID, RECONCILIATIONS + "success", metadata);
8184
}
8285

83-
public void failedReconciliation(ResourceID resourceID, Exception exception) {
86+
public void failedReconciliation(ResourceID resourceID, Exception exception,
87+
Map<String, Object> metadata) {
8488
var cause = exception.getCause();
8589
if (cause == null) {
8690
cause = exception;
8791
} else if (cause instanceof RuntimeException) {
8892
cause = cause.getCause() != null ? cause.getCause() : cause;
8993
}
90-
incrementCounter(resourceID, RECONCILIATIONS + "failed", "exception",
94+
incrementCounter(resourceID, RECONCILIATIONS + "failed", metadata, "exception",
9195
cause.getClass().getSimpleName());
9296
}
9397

9498
public <T extends Map<?, ?>> T monitorSizeOf(T map, String name) {
9599
return registry.gaugeMapSize(PREFIX + name + ".size", Collections.emptyList(), map);
96100
}
97101

98-
private void incrementCounter(ResourceID id, String counterName, String... additionalTags) {
99-
var tags = List.of(
102+
private void incrementCounter(ResourceID id, String counterName, Map<String, Object> metadata,
103+
String... additionalTags) {
104+
final var additionalTagsNb =
105+
additionalTags != null && additionalTags.length > 0 ? additionalTags.length : 0;
106+
final var metadataNb = metadata != null ? metadata.size() : 0;
107+
final var tags = new ArrayList<String>(6 + additionalTagsNb + metadataNb);
108+
tags.addAll(List.of(
100109
"name", id.getName(),
101-
"name", id.getName(), "namespace", id.getNamespace().orElse(""),
102-
"scope", id.getNamespace().isPresent() ? "namespace" : "cluster");
103-
if (additionalTags != null && additionalTags.length > 0) {
104-
tags = new LinkedList<>(tags);
110+
"namespace", id.getNamespace().orElse(""),
111+
"scope", id.getNamespace().isPresent() ? "namespace" : "cluster"));
112+
if (additionalTagsNb > 0) {
105113
tags.addAll(List.of(additionalTags));
106114
}
115+
if (metadataNb > 0) {
116+
addReservedMetadataToTags(metadata, tags, "group", Metrics.RESOURCE_GROUP_KEY);
117+
addReservedMetadataToTags(metadata, tags, "version", Metrics.RESOURCE_VERSION_KEY);
118+
addReservedMetadataToTags(metadata, tags, "kind", Metrics.RESOURCE_KIND_KEY);
119+
metadata.forEach((k, v) -> {
120+
tags.add(k);
121+
tags.add(v.toString());
122+
});
123+
}
107124
registry.counter(PREFIX + counterName, tags.toArray(new String[0])).increment();
108125
}
126+
127+
private static void addReservedMetadataToTags(Map<String, Object> metadata, List<String> tags,
128+
String tagKey, String reservedKey) {
129+
if (metadata.containsKey(reservedKey)) {
130+
tags.add(tagKey);
131+
tags.add(metadata.get(reservedKey).toString());
132+
metadata.remove(reservedKey);
133+
}
134+
}
109135
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/monitoring/Metrics.java

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.javaoperatorsdk.operator.api.monitoring;
22

3+
import java.util.Collections;
34
import java.util.Map;
45

56
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
@@ -9,15 +10,59 @@
910
public interface Metrics {
1011
Metrics NOOP = new Metrics() {};
1112

13+
String RESOURCE_GROUP_KEY = "josdk.resource.group";
14+
String RESOURCE_VERSION_KEY = "josdk.resource.version";
15+
String RESOURCE_KIND_KEY = "josdk.resource.kind";
16+
1217
default void receivedEvent(Event event) {}
1318

14-
default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo) {}
19+
/**
20+
*
21+
* @deprecated Use (and implement) {@link #reconcileCustomResource(ResourceID, RetryInfo, Map)}
22+
* instead
23+
*/
24+
@Deprecated
25+
default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo) {
26+
reconcileCustomResource(resourceID, retryInfo, Collections.emptyMap());
27+
}
28+
29+
default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo,
30+
Map<String, Object> metadata) {}
31+
32+
/**
33+
*
34+
* @deprecated Use (and implement) {@link #failedReconciliation(ResourceID, Exception, Map)}
35+
* instead
36+
*/
37+
@Deprecated
38+
default void failedReconciliation(ResourceID resourceID, Exception exception) {
39+
failedReconciliation(resourceID, exception, Collections.emptyMap());
40+
}
1541

16-
default void failedReconciliation(ResourceID resourceID, Exception exception) {}
42+
default void failedReconciliation(ResourceID resourceID, Exception exception,
43+
Map<String, Object> metadata) {}
1744

18-
default void cleanupDoneFor(ResourceID resourceID) {}
45+
/**
46+
*
47+
* @deprecated Use (and implement) {@link #cleanupDoneFor(ResourceID, Map)} instead
48+
*/
49+
@Deprecated
50+
default void cleanupDoneFor(ResourceID resourceID) {
51+
cleanupDoneFor(resourceID, Collections.emptyMap());
52+
}
53+
54+
default void cleanupDoneFor(ResourceID resourceID, Map<String, Object> metadata) {}
55+
56+
/**
57+
*
58+
* @deprecated Use (and implement) {@link #finishedReconciliation(ResourceID, Map)} instead
59+
*/
60+
@Deprecated
61+
default void finishedReconciliation(ResourceID resourceID) {
62+
finishedReconciliation(resourceID, Collections.emptyMap());
63+
}
1964

20-
default void finishedReconciliation(ResourceID resourceID) {}
65+
default void finishedReconciliation(ResourceID resourceID, Map<String, Object> metadata) {}
2166

2267

2368
interface ControllerExecution<T> {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ private void handleMarkedEventForResource(ResourceState state) {
132132
private void submitReconciliationExecution(ResourceState state) {
133133
try {
134134
boolean controllerUnderExecution = isControllerUnderExecution(state);
135-
Optional<R> latest = cache.get(state.getId());
136-
latest.ifPresent(MDCUtils::addResourceInfo);
137-
if (!controllerUnderExecution && latest.isPresent()) {
135+
Optional<R> maybeLatest = cache.get(state.getId());
136+
maybeLatest.ifPresent(MDCUtils::addResourceInfo);
137+
if (!controllerUnderExecution && maybeLatest.isPresent()) {
138138
var rateLimit = state.getRateLimit();
139139
if (rateLimit == null) {
140140
rateLimit = rateLimiter.initState();
@@ -146,19 +146,21 @@ private void submitReconciliationExecution(ResourceState state) {
146146
return;
147147
}
148148
state.setUnderProcessing(true);
149+
final var latest = maybeLatest.get();
150+
state.addMetadata(latest);
149151
final var retryInfo = state.getRetry();
150-
ExecutionScope<R> executionScope = new ExecutionScope<>(latest.get(), retryInfo);
152+
ExecutionScope<R> executionScope = new ExecutionScope<>(latest, retryInfo);
151153
state.unMarkEventReceived();
152-
metrics.reconcileCustomResource(state.getId(), retryInfo);
154+
metrics.reconcileCustomResource(state.getId(), retryInfo, state.getMetadata());
153155
log.debug("Executing events for custom resource. Scope: {}", executionScope);
154156
executor.execute(new ControllerExecution(executionScope));
155157
} else {
156158
log.debug(
157159
"Skipping executing controller for resource id: {}. Controller in execution: {}. Latest Resource present: {}",
158160
state,
159161
controllerUnderExecution,
160-
latest.isPresent());
161-
if (latest.isEmpty()) {
162+
maybeLatest.isPresent());
163+
if (maybeLatest.isEmpty()) {
162164
log.debug("no custom resource found in cache for ResourceID: {}", state);
163165
}
164166
}
@@ -240,7 +242,7 @@ synchronized void eventProcessingFinished(
240242
return;
241243
}
242244
cleanupOnSuccessfulExecution(executionScope);
243-
metrics.finishedReconciliation(resourceID);
245+
metrics.finishedReconciliation(resourceID, state.getMetadata());
244246
if (state.deleteEventPresent()) {
245247
cleanupForDeletedEvent(executionScope.getResourceID());
246248
} else if (postExecutionControl.isFinalizerRemoved()) {
@@ -307,7 +309,7 @@ private void handleRetryOnException(
307309
"Scheduling timer event for retry with delay:{} for resource: {}",
308310
delay,
309311
resourceID);
310-
metrics.failedReconciliation(resourceID, exception);
312+
metrics.failedReconciliation(resourceID, exception, state.getMetadata());
311313
retryEventSource().scheduleOnce(resourceID, delay);
312314
},
313315
() -> log.error("Exhausted retries for {}", executionScope));
@@ -334,8 +336,8 @@ private ResourceState getOrInitRetryExecution(ExecutionScope<R> executionScope)
334336

335337
private void cleanupForDeletedEvent(ResourceID resourceID) {
336338
log.debug("Cleaning up for delete event for: {}", resourceID);
337-
resourceStateManager.remove(resourceID);
338-
metrics.cleanupDoneFor(resourceID);
339+
final var state = resourceStateManager.remove(resourceID);
340+
metrics.cleanupDoneFor(resourceID, state.getMetadata());
339341
}
340342

341343
private boolean isControllerUnderExecution(ResourceState state) {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceState.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package io.javaoperatorsdk.operator.processing.event;
22

3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
38
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter.RateLimitState;
49
import io.javaoperatorsdk.operator.processing.retry.RetryExecution;
510

@@ -33,6 +38,8 @@ private enum EventingState {
3338
private EventingState eventing;
3439
private RateLimitState rateLimit;
3540

41+
private final Map<String, Object> metadata = new HashMap<>();
42+
3643
public ResourceState(ResourceID id) {
3744
this.id = id;
3845
eventing = EventingState.NO_EVENT_PRESENT;
@@ -108,4 +115,15 @@ public void unMarkEventReceived() {
108115
throw new IllegalStateException("Cannot unmark delete event.");
109116
}
110117
}
118+
119+
public Map<String, Object> getMetadata() {
120+
return metadata;
121+
}
122+
123+
public void addMetadata(HasMetadata resource) {
124+
final Class<? extends HasMetadata> resourceClass = resource.getClass();
125+
metadata.put(Metrics.RESOURCE_GROUP_KEY, HasMetadata.getGroup(resourceClass));
126+
metadata.put(Metrics.RESOURCE_KIND_KEY, resource.getKind());
127+
metadata.put(Metrics.RESOURCE_VERSION_KEY, HasMetadata.getVersion(resourceClass));
128+
}
111129
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public ResourceState getOrCreate(ResourceID resourceID) {
1616
return states.computeIfAbsent(resourceID, ResourceState::new);
1717
}
1818

19-
public void remove(ResourceID resourceID) {
20-
states.remove(resourceID);
19+
public ResourceState remove(ResourceID resourceID) {
20+
return states.remove(resourceID);
2121
}
2222

2323
public boolean contains(ResourceID resourceID) {

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ void startProcessedMarkedEventReceivedBefore() {
270270
eventProcessor.start();
271271

272272
verify(reconciliationDispatcherMock, timeout(100).times(1)).handleExecution(any());
273-
verify(metricsMock, times(1)).reconcileCustomResource(any(), isNull());
273+
verify(metricsMock, times(1)).reconcileCustomResource(any(), isNull(), any());
274274
}
275275

276276
@Test

0 commit comments

Comments
 (0)