Skip to content

Commit e723525

Browse files
metacosmcsviri
andcommitted
feat: add metadata support for Metrics (#1323)
Right now, the only metadata that's provided is the group/version/kind information associated with the resource being processed. Fixes #1322. Fixes #1324. Co-authored-by: csviri <[email protected]>
1 parent 2c089c8 commit e723525

File tree

10 files changed

+308
-42
lines changed

10 files changed

+308
-42
lines changed

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

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
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;
88

99
import io.javaoperatorsdk.operator.OperatorException;
1010
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
11+
import io.javaoperatorsdk.operator.api.reconciler.Constants;
1112
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
13+
import io.javaoperatorsdk.operator.processing.GroupVersionKind;
1214
import io.javaoperatorsdk.operator.processing.event.Event;
1315
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1416
import io.micrometer.core.instrument.MeterRegistry;
@@ -27,9 +29,24 @@ public MicrometerMetrics(MeterRegistry registry) {
2729
public <T> T timeControllerExecution(ControllerExecution<T> execution) {
2830
final var name = execution.controllerName();
2931
final var execName = PREFIX + "controllers.execution." + execution.name();
32+
final var resourceID = execution.resourceID();
33+
final var metadata = execution.metadata();
34+
final var tags = new ArrayList<String>(metadata.size() + 4);
35+
tags.addAll(List.of(
36+
"controller", name,
37+
"resource.name", resourceID.getName(),
38+
"resource.namespace", resourceID.getNamespace().orElse(""),
39+
"resource.scope", resourceID.getNamespace().isPresent() ? "namespace" : "cluster"));
40+
final var gvk = (GroupVersionKind) metadata.get(Constants.RESOURCE_GVK_KEY);
41+
if (gvk != null) {
42+
tags.addAll(List.of(
43+
"resource.group", gvk.group,
44+
"resource.version", gvk.version,
45+
"resource.kind", gvk.kind));
46+
}
3047
final var timer =
3148
Timer.builder(execName)
32-
.tags("controller", name)
49+
.tags(tags.toArray(new String[0]))
3350
.publishPercentiles(0.3, 0.5, 0.95)
3451
.publishPercentileHistogram()
3552
.register(registry);
@@ -55,55 +72,70 @@ public <T> T timeControllerExecution(ControllerExecution<T> execution) {
5572
}
5673
}
5774

58-
public void receivedEvent(Event event) {
59-
incrementCounter(event.getRelatedCustomResourceID(), "events.received", "event",
60-
event.getClass().getSimpleName());
75+
public void receivedEvent(Event event, Map<String, Object> metadata) {
76+
incrementCounter(event.getRelatedCustomResourceID(), "events.received",
77+
metadata,
78+
"event", event.getClass().getSimpleName());
6179
}
6280

6381
@Override
64-
public void cleanupDoneFor(ResourceID resourceID) {
65-
incrementCounter(resourceID, "events.delete");
82+
public void cleanupDoneFor(ResourceID resourceID, Map<String, Object> metadata) {
83+
incrementCounter(resourceID, "events.delete", metadata);
6684
}
6785

6886
@Override
69-
public void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfoNullable) {
87+
public void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfoNullable,
88+
Map<String, Object> metadata) {
7089
Optional<RetryInfo> retryInfo = Optional.ofNullable(retryInfoNullable);
7190
incrementCounter(resourceID, RECONCILIATIONS + "started",
91+
metadata,
7292
RECONCILIATIONS + "retries.number",
7393
"" + retryInfo.map(RetryInfo::getAttemptCount).orElse(0),
7494
RECONCILIATIONS + "retries.last",
7595
"" + retryInfo.map(RetryInfo::isLastAttempt).orElse(true));
7696
}
7797

7898
@Override
79-
public void finishedReconciliation(ResourceID resourceID) {
80-
incrementCounter(resourceID, RECONCILIATIONS + "success");
99+
public void finishedReconciliation(ResourceID resourceID, Map<String, Object> metadata) {
100+
incrementCounter(resourceID, RECONCILIATIONS + "success", metadata);
81101
}
82102

83-
public void failedReconciliation(ResourceID resourceID, Exception exception) {
103+
public void failedReconciliation(ResourceID resourceID, Exception exception,
104+
Map<String, Object> metadata) {
84105
var cause = exception.getCause();
85106
if (cause == null) {
86107
cause = exception;
87108
} else if (cause instanceof RuntimeException) {
88109
cause = cause.getCause() != null ? cause.getCause() : cause;
89110
}
90-
incrementCounter(resourceID, RECONCILIATIONS + "failed", "exception",
111+
incrementCounter(resourceID, RECONCILIATIONS + "failed", metadata, "exception",
91112
cause.getClass().getSimpleName());
92113
}
93114

94115
public <T extends Map<?, ?>> T monitorSizeOf(T map, String name) {
95116
return registry.gaugeMapSize(PREFIX + name + ".size", Collections.emptyList(), map);
96117
}
97118

98-
private void incrementCounter(ResourceID id, String counterName, String... additionalTags) {
99-
var tags = List.of(
119+
private void incrementCounter(ResourceID id, String counterName, Map<String, Object> metadata,
120+
String... additionalTags) {
121+
final var additionalTagsNb =
122+
additionalTags != null && additionalTags.length > 0 ? additionalTags.length : 0;
123+
final var metadataNb = metadata != null ? metadata.size() : 0;
124+
final var tags = new ArrayList<String>(6 + additionalTagsNb + metadataNb);
125+
tags.addAll(List.of(
100126
"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);
127+
"namespace", id.getNamespace().orElse(""),
128+
"scope", id.getNamespace().isPresent() ? "namespace" : "cluster"));
129+
if (additionalTagsNb > 0) {
105130
tags.addAll(List.of(additionalTags));
106131
}
132+
if (metadataNb > 0) {
133+
final var gvk = (GroupVersionKind) metadata.get(Constants.RESOURCE_GVK_KEY);
134+
tags.addAll(List.of(
135+
"group", gvk.group,
136+
"version", gvk.version,
137+
"kind", gvk.kind));
138+
}
107139
registry.counter(PREFIX + counterName, tags.toArray(new String[0])).increment();
108140
}
109141
}

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

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

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

6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
import io.javaoperatorsdk.operator.api.reconciler.Context;
58
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
69
import io.javaoperatorsdk.operator.processing.event.Event;
710
import io.javaoperatorsdk.operator.processing.event.ResourceID;
811

12+
/**
13+
* An interface that metrics providers can implement and that the SDK will call at different times
14+
* of its execution cycle.
15+
*/
916
public interface Metrics {
17+
18+
/**
19+
* The default Metrics provider: a no-operation implementation.
20+
*/
1021
Metrics NOOP = new Metrics() {};
1122

12-
default void receivedEvent(Event event) {}
23+
/**
24+
* Called when an event has been accepted by the SDK from an event source, which would result in
25+
* potentially triggering the associated Reconciler.
26+
*
27+
* @param event the event
28+
* @param metadata metadata associated with the resource being processed
29+
*/
30+
default void receivedEvent(Event event, Map<String, Object> metadata) {}
1331

14-
default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo) {}
32+
/**
33+
*
34+
* @deprecated Use (and implement) {@link #receivedEvent(Event, Map)} instead
35+
*/
36+
@Deprecated
37+
default void receivedEvent(Event event) {
38+
receivedEvent(event, Collections.emptyMap());
39+
}
1540

16-
default void failedReconciliation(ResourceID resourceID, Exception exception) {}
41+
/**
42+
*
43+
* @deprecated Use (and implement) {@link #reconcileCustomResource(ResourceID, RetryInfo, Map)}
44+
* instead
45+
*/
46+
@Deprecated
47+
default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo) {
48+
reconcileCustomResource(resourceID, retryInfo, Collections.emptyMap());
49+
}
1750

18-
default void cleanupDoneFor(ResourceID resourceID) {}
51+
/**
52+
* Called right before a resource is dispatched to the ExecutorService for reconciliation.
53+
*
54+
* @param resourceID the {@link ResourceID} associated with the resource
55+
* @param retryInfo the current retry state information for the reconciliation request
56+
* @param metadata metadata associated with the resource being processed
57+
*/
58+
default void reconcileCustomResource(ResourceID resourceID, RetryInfo retryInfo,
59+
Map<String, Object> metadata) {}
1960

20-
default void finishedReconciliation(ResourceID resourceID) {}
61+
/**
62+
*
63+
* @deprecated Use (and implement) {@link #failedReconciliation(ResourceID, Exception, Map)}
64+
* instead
65+
*/
66+
@Deprecated
67+
default void failedReconciliation(ResourceID resourceID, Exception exception) {
68+
failedReconciliation(resourceID, exception, Collections.emptyMap());
69+
}
70+
71+
/**
72+
* Called when a precedent reconciliation for the resource associated with the specified
73+
* {@link ResourceID} resulted in the provided exception, resulting in a retry of the
74+
* reconciliation.
75+
*
76+
* @param resourceID the {@link ResourceID} associated with the resource being processed
77+
* @param exception the exception that caused the failed reconciliation resulting in a retry
78+
* @param metadata metadata associated with the resource being processed
79+
*/
80+
default void failedReconciliation(ResourceID resourceID, Exception exception,
81+
Map<String, Object> metadata) {}
82+
83+
/**
84+
*
85+
* @deprecated Use (and implement) {@link #cleanupDoneFor(ResourceID, Map)} instead
86+
*/
87+
@Deprecated
88+
default void cleanupDoneFor(ResourceID resourceID) {
89+
cleanupDoneFor(resourceID, Collections.emptyMap());
90+
}
2191

92+
/**
93+
* Called when the resource associated with the specified {@link ResourceID} has been successfully
94+
* deleted and the clean-up performed by the associated reconciler is finished.
95+
*
96+
* @param resourceID the {@link ResourceID} associated with the resource being processed
97+
* @param metadata metadata associated with the resource being processed
98+
*/
99+
default void cleanupDoneFor(ResourceID resourceID, Map<String, Object> metadata) {}
22100

101+
/**
102+
*
103+
* @deprecated Use (and implement) {@link #finishedReconciliation(ResourceID, Map)} instead
104+
*/
105+
@Deprecated
106+
default void finishedReconciliation(ResourceID resourceID) {
107+
finishedReconciliation(resourceID, Collections.emptyMap());
108+
}
109+
110+
/**
111+
* Called when the
112+
* {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler#reconcile(HasMetadata, Context)}
113+
* method of the Reconciler associated with the resource associated with the specified
114+
* {@link ResourceID} has sucessfully finished.
115+
*
116+
* @param resourceID the {@link ResourceID} associated with the resource being processed
117+
* @param metadata metadata associated with the resource being processed
118+
*/
119+
default void finishedReconciliation(ResourceID resourceID, Map<String, Object> metadata) {}
120+
121+
/**
122+
* Encapsulates the information about a controller execution i.e. a call to either
123+
* {@link io.javaoperatorsdk.operator.api.reconciler.Reconciler#reconcile(HasMetadata, Context)}
124+
* or {@link io.javaoperatorsdk.operator.api.reconciler.Cleaner#cleanup(HasMetadata, Context)}.
125+
* Note that instances are automatically created for you by the SDK and passed to your Metrics
126+
* implementation at the appropriate time to the
127+
* {@link #timeControllerExecution(ControllerExecution)} method.
128+
*
129+
* @param <T> the outcome type associated with the controller execution. Currently, one of
130+
* {@link io.javaoperatorsdk.operator.api.reconciler.UpdateControl} or
131+
* {@link io.javaoperatorsdk.operator.api.reconciler.DeleteControl}
132+
*/
23133
interface ControllerExecution<T> {
134+
135+
/**
136+
* Retrieves the name of type of reconciliation being performed: either {@code reconcile} or
137+
* {@code cleanup}.
138+
*
139+
* @return the name of type of reconciliation being performed
140+
*/
24141
String name();
25142

143+
/**
144+
* Retrieves the name of the controller executing the reconciliation.
145+
*
146+
* @return the associated controller name
147+
*/
26148
String controllerName();
27149

150+
/**
151+
* Retrieves the name of the successful result when the reconciliation ended positively.
152+
* Possible values comes from the different outcomes provided by
153+
* {@link io.javaoperatorsdk.operator.api.reconciler.UpdateControl} or
154+
* {@link io.javaoperatorsdk.operator.api.reconciler.DeleteControl}.
155+
*
156+
* @param result the reconciliation result
157+
* @return a name associated with the specified outcome
158+
*/
28159
String successTypeName(T result);
29160

161+
/**
162+
* Retrieves the {@link ResourceID} of the resource associated with the controller execution
163+
* being considered
164+
*
165+
* @return the {@link ResourceID} of the resource being reconciled
166+
*/
167+
ResourceID resourceID();
168+
169+
/**
170+
* Retrieves metadata associated with the current reconciliation, typically additional
171+
* information (such as kind) about the resource being reconciled
172+
*
173+
* @return metadata associated with the current reconciliation
174+
*/
175+
Map<String, Object> metadata();
176+
177+
/**
178+
* Performs the controller execution.
179+
*
180+
* @return the result of the controller execution
181+
* @throws Exception if an error occurred during the controller's execution
182+
*/
30183
T execute() throws Exception;
31184
}
32185

186+
/**
187+
* Times the execution of the controller operation encapsulated by the provided
188+
* {@link ControllerExecution}.
189+
*
190+
* @param execution the controller operation to be timed
191+
* @return the result of the controller's execution if successful
192+
* @param <T> the type of the outcome/result of the controller's execution
193+
* @throws Exception if an error occurred during the controller's execution, usually this should
194+
* just be a pass-through of whatever the controller returned
195+
*/
33196
default <T> T timeControllerExecution(ControllerExecution<T> execution) throws Exception {
34197
return execution.execute();
35198
}
36199

200+
/**
201+
* Monitors the size of the specified map. This currently isn't used directly by the SDK but could
202+
* be used by operators to monitor some of their structures, such as cache size.
203+
*
204+
* @param map the Map which size is to be monitored
205+
* @param name the name of the provided Map to be used in metrics data
206+
* @return the Map that was passed in so the registration can be done as part of an assignment
207+
* statement.
208+
* @param <T> the type of the Map being monitored
209+
*/
210+
@SuppressWarnings("unused")
37211
default <T extends Map<?, ?>> T monitorSizeOf(T map, String name) {
38212
return map;
39213
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ public final class Constants {
1919
public static final long NO_RECONCILIATION_MAX_INTERVAL = -1L;
2020
public static final String SAME_AS_CONTROLLER = "JOSDK_SAME_AS_CONTROLLER";
2121

22+
public static final String RESOURCE_GVK_KEY = "josdk.resource.gvk";
23+
2224
private Constants() {}
2325
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DefaultContext.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public Optional<RetryInfo> getRetryInfo() {
3232
}
3333

3434
@Override
35-
@SuppressWarnings("unchecked")
3635
public <T> Set<T> getSecondaryResources(Class<T> expectedType) {
3736
return controller.getEventSourceManager().getEventSourcesFor(expectedType).stream()
3837
.map(es -> es.getSecondaryResources(primaryResource))

0 commit comments

Comments
 (0)