Skip to content

Commit ae1c020

Browse files
committed
feat: now possible to only output non-resource related metrics
Fixes #1812.
1 parent 9c8ed19 commit ae1c020

File tree

2 files changed

+137
-78
lines changed

2 files changed

+137
-78
lines changed

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

+135-77
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public class MicrometerMetrics implements Metrics {
3030
private static final String RECONCILIATIONS = "reconciliations.";
3131
private static final String RECONCILIATIONS_EXECUTIONS = PREFIX + RECONCILIATIONS + "executions.";
3232
private static final String RECONCILIATIONS_QUEUE_SIZE = PREFIX + RECONCILIATIONS + "queue.size.";
33+
private final boolean collectPerResourceMetrics;
3334
private final MeterRegistry registry;
3435
private final Map<String, AtomicInteger> gauges = new ConcurrentHashMap<>();
35-
private final Map<ResourceID, Set<Meter.Id>> metersPerResource = new ConcurrentHashMap<>();
3636
private final Cleaner cleaner;
3737

3838
/**
@@ -42,43 +42,30 @@ public class MicrometerMetrics implements Metrics {
4242
* @param registry the {@link MeterRegistry} instance to use for metrics recording
4343
*/
4444
public MicrometerMetrics(MeterRegistry registry) {
45-
this(registry, 0);
45+
this(registry, Cleaner.NOOP);
4646
}
4747

48-
/**
49-
* Creates a micrometer-based Metrics implementation that delays cleaning up {@link Meter}s
50-
* associated with deleted resources by the specified amount of seconds, using a single thread for
51-
* that process.
52-
*
53-
* @param registry the {@link MeterRegistry} instance to use for metrics recording
54-
* @param cleanUpDelayInSeconds the number of seconds to wait before meters are removed for
55-
* deleted resources
56-
*/
57-
public MicrometerMetrics(MeterRegistry registry, int cleanUpDelayInSeconds) {
58-
this(registry, cleanUpDelayInSeconds, 1);
48+
@SuppressWarnings("unused")
49+
public static MicrometerMetrics withoutPerResourceMetrics(MeterRegistry registry) {
50+
return new MicrometerMetrics(registry);
51+
}
52+
53+
public static MicrometerMetricsBuilder newMicrometerMetrics(MeterRegistry registry) {
54+
return new MicrometerMetricsBuilder(registry);
5955
}
6056

6157
/**
62-
* Creates a micrometer-based Metrics implementation that delays cleaning up {@link Meter}s
63-
* associated with deleted resources by the specified amount of seconds, using the specified
64-
* (maximally) number of threads for that process.
58+
* Creates a micrometer-based Metrics implementation that cleans up {@link Meter}s associated with
59+
* deleted resources as specified by the (possibly {@code null}) provided {@link Cleaner}
60+
* instance.
6561
*
6662
* @param registry the {@link MeterRegistry} instance to use for metrics recording
67-
* @param cleanUpDelayInSeconds the number of seconds to wait before meters are removed for
68-
* deleted resources
69-
* @param cleaningThreadsNumber the number of threads to use for the cleaning process
63+
* @param cleaner the {@link Cleaner} to use
7064
*/
71-
public MicrometerMetrics(MeterRegistry registry, int cleanUpDelayInSeconds,
72-
int cleaningThreadsNumber) {
65+
private MicrometerMetrics(MeterRegistry registry, Cleaner cleaner) {
7366
this.registry = registry;
74-
if (cleanUpDelayInSeconds < 0) {
75-
cleaner = new NoDelayCleaner();
76-
} else {
77-
cleaningThreadsNumber =
78-
cleaningThreadsNumber <= 0 ? Runtime.getRuntime().availableProcessors()
79-
: cleaningThreadsNumber;
80-
cleaner = new DelayedCleaner(cleanUpDelayInSeconds, cleaningThreadsNumber);
81-
}
67+
this.cleaner = cleaner;
68+
this.collectPerResourceMetrics = Cleaner.NOOP != cleaner;
8269
}
8370

8471
@Override
@@ -153,45 +140,53 @@ private static String getScope(ResourceID resourceID) {
153140

154141
@Override
155142
public void receivedEvent(Event event, Map<String, Object> metadata) {
156-
final String[] tags;
157-
if (event instanceof ResourceEvent) {
158-
tags = new String[] {"event", event.getClass().getSimpleName(), "action",
159-
((ResourceEvent) event).getAction().toString()};
160-
} else {
161-
tags = new String[] {"event", event.getClass().getSimpleName()};
143+
if (collectPerResourceMetrics) {
144+
final String[] tags;
145+
if (event instanceof ResourceEvent) {
146+
tags = new String[] {"event", event.getClass().getSimpleName(), "action",
147+
((ResourceEvent) event).getAction().toString()};
148+
} else {
149+
tags = new String[] {"event", event.getClass().getSimpleName()};
150+
}
151+
152+
incrementCounter(event.getRelatedCustomResourceID(), "events.received",
153+
metadata,
154+
tags);
162155
}
163-
164-
incrementCounter(event.getRelatedCustomResourceID(), "events.received",
165-
metadata,
166-
tags);
167156
}
168157

169158
@Override
170159
public void cleanupDoneFor(ResourceID resourceID, Map<String, Object> metadata) {
171-
incrementCounter(resourceID, "events.delete", metadata);
160+
if (collectPerResourceMetrics) {
161+
incrementCounter(resourceID, "events.delete", metadata);
172162

173-
cleaner.removeMetersFor(resourceID);
163+
cleaner.removeMetersFor(resourceID);
164+
}
174165
}
175166

176167
@Override
177168
public void reconcileCustomResource(HasMetadata resource, RetryInfo retryInfoNullable,
178169
Map<String, Object> metadata) {
179-
Optional<RetryInfo> retryInfo = Optional.ofNullable(retryInfoNullable);
180-
incrementCounter(ResourceID.fromResource(resource), RECONCILIATIONS + "started",
181-
metadata,
182-
RECONCILIATIONS + "retries.number",
183-
String.valueOf(retryInfo.map(RetryInfo::getAttemptCount).orElse(0)),
184-
RECONCILIATIONS + "retries.last",
185-
String.valueOf(retryInfo.map(RetryInfo::isLastAttempt).orElse(true)));
186-
187-
var controllerQueueSize =
188-
gauges.get(RECONCILIATIONS_QUEUE_SIZE + metadata.get(CONTROLLER_NAME));
189-
controllerQueueSize.incrementAndGet();
170+
if (collectPerResourceMetrics) {
171+
Optional<RetryInfo> retryInfo = Optional.ofNullable(retryInfoNullable);
172+
incrementCounter(ResourceID.fromResource(resource), RECONCILIATIONS + "started",
173+
metadata,
174+
RECONCILIATIONS + "retries.number",
175+
String.valueOf(retryInfo.map(RetryInfo::getAttemptCount).orElse(0)),
176+
RECONCILIATIONS + "retries.last",
177+
String.valueOf(retryInfo.map(RetryInfo::isLastAttempt).orElse(true)));
178+
179+
var controllerQueueSize =
180+
gauges.get(RECONCILIATIONS_QUEUE_SIZE + metadata.get(CONTROLLER_NAME));
181+
controllerQueueSize.incrementAndGet();
182+
}
190183
}
191184

192185
@Override
193186
public void finishedReconciliation(HasMetadata resource, Map<String, Object> metadata) {
194-
incrementCounter(ResourceID.fromResource(resource), RECONCILIATIONS + "success", metadata);
187+
if (collectPerResourceMetrics) {
188+
incrementCounter(ResourceID.fromResource(resource), RECONCILIATIONS + "success", metadata);
189+
}
195190
}
196191

197192
@Override
@@ -215,15 +210,17 @@ public void reconciliationExecutionFinished(HasMetadata resource, Map<String, Ob
215210
@Override
216211
public void failedReconciliation(HasMetadata resource, Exception exception,
217212
Map<String, Object> metadata) {
218-
var cause = exception.getCause();
219-
if (cause == null) {
220-
cause = exception;
221-
} else if (cause instanceof RuntimeException) {
222-
cause = cause.getCause() != null ? cause.getCause() : cause;
213+
if (collectPerResourceMetrics) {
214+
var cause = exception.getCause();
215+
if (cause == null) {
216+
cause = exception;
217+
} else if (cause instanceof RuntimeException) {
218+
cause = cause.getCause() != null ? cause.getCause() : cause;
219+
}
220+
incrementCounter(ResourceID.fromResource(resource), RECONCILIATIONS + "failed", metadata,
221+
"exception",
222+
cause.getClass().getSimpleName());
223223
}
224-
incrementCounter(ResourceID.fromResource(resource), RECONCILIATIONS + "failed", metadata,
225-
"exception",
226-
cause.getClass().getSimpleName());
227224
}
228225

229226
@Override
@@ -258,48 +255,109 @@ private void incrementCounter(ResourceID id, String counterName, Map<String, Obj
258255
"kind", gvk.kind));
259256
}
260257
final var counter = registry.counter(PREFIX + counterName, tags.toArray(new String[0]));
261-
metersPerResource.computeIfAbsent(id, resourceID -> new HashSet<>()).add(counter.getId());
258+
cleaner.recordAssociation(id, counter);
262259
counter.increment();
263260
}
264261

265262
protected Set<Meter.Id> recordedMeterIdsFor(ResourceID resourceID) {
266-
return metersPerResource.get(resourceID);
263+
return cleaner.recordedMeterIdsFor(resourceID);
267264
}
268265

269-
private interface Cleaner {
270-
void removeMetersFor(ResourceID resourceID);
266+
public static class MicrometerMetricsBuilder {
267+
private final MeterRegistry registry;
268+
private int cleaningThreadsNumber;
269+
private int cleanUpDelayInSeconds;
270+
271+
private MicrometerMetricsBuilder(MeterRegistry registry) {
272+
this.registry = registry;
273+
}
274+
275+
public MicrometerMetricsBuilder withCleaningThreadNumber(int cleaningThreadsNumber) {
276+
this.cleaningThreadsNumber = cleaningThreadsNumber;
277+
return this;
278+
}
279+
280+
/**
281+
* @param cleanUpDelayInSeconds the number of seconds to wait before meters are removed for
282+
* deleted resources
283+
*/
284+
public MicrometerMetricsBuilder withCleanUpDelayInSeconds(int cleanUpDelayInSeconds) {
285+
this.cleanUpDelayInSeconds = cleanUpDelayInSeconds;
286+
return this;
287+
}
288+
289+
public MicrometerMetrics build() {
290+
MicrometerMetrics.Cleaner cleaner;
291+
if (cleanUpDelayInSeconds < 0) {
292+
cleaner = new MicrometerMetrics.DefaultCleaner(registry);
293+
} else {
294+
cleaningThreadsNumber =
295+
cleaningThreadsNumber <= 0 ? Runtime.getRuntime().availableProcessors()
296+
: cleaningThreadsNumber;
297+
cleaner = new DelayedCleaner(registry, cleanUpDelayInSeconds, cleaningThreadsNumber);
298+
}
299+
300+
return new MicrometerMetrics(registry, cleaner);
301+
}
271302
}
272303

273-
private void removeMetersFor(ResourceID resourceID) {
274-
// remove each meter
275-
final var toClean = metersPerResource.get(resourceID);
276-
if (toClean != null) {
277-
toClean.forEach(registry::remove);
304+
private interface Cleaner {
305+
Cleaner NOOP = new Cleaner() {};
306+
307+
default void removeMetersFor(ResourceID resourceID) {}
308+
309+
default void recordAssociation(ResourceID resourceID, Meter meter) {}
310+
311+
default Set<Meter.Id> recordedMeterIdsFor(ResourceID resourceID) {
312+
return Collections.emptySet();
278313
}
279-
// then clean-up local recording of associations
280-
metersPerResource.remove(resourceID);
281314
}
282315

283-
private class NoDelayCleaner implements Cleaner {
316+
private static class DefaultCleaner implements Cleaner {
317+
private final Map<ResourceID, Set<Meter.Id>> metersPerResource = new ConcurrentHashMap<>();
318+
private final MeterRegistry registry;
319+
320+
private DefaultCleaner(MeterRegistry registry) {
321+
this.registry = registry;
322+
}
323+
284324
@Override
285325
public void removeMetersFor(ResourceID resourceID) {
286-
MicrometerMetrics.this.removeMetersFor(resourceID);
326+
// remove each meter
327+
final var toClean = metersPerResource.get(resourceID);
328+
if (toClean != null) {
329+
toClean.forEach(registry::remove);
330+
}
331+
// then clean-up local recording of associations
332+
metersPerResource.remove(resourceID);
333+
}
334+
335+
@Override
336+
public void recordAssociation(ResourceID resourceID, Meter meter) {
337+
metersPerResource.computeIfAbsent(resourceID, id -> new HashSet<>()).add(meter.getId());
338+
}
339+
340+
@Override
341+
public Set<Meter.Id> recordedMeterIdsFor(ResourceID resourceID) {
342+
return metersPerResource.get(resourceID);
287343
}
288344
}
289345

290-
private class DelayedCleaner implements Cleaner {
346+
private static class DelayedCleaner extends MicrometerMetrics.DefaultCleaner {
291347
private final ScheduledExecutorService metersCleaner;
292348
private final int cleanUpDelayInSeconds;
293349

294-
private DelayedCleaner(int cleanUpDelayInSeconds, int cleaningThreadsNumber) {
350+
private DelayedCleaner(MeterRegistry registry, int cleanUpDelayInSeconds,
351+
int cleaningThreadsNumber) {
352+
super(registry);
295353
this.cleanUpDelayInSeconds = cleanUpDelayInSeconds;
296354
this.metersCleaner = Executors.newScheduledThreadPool(cleaningThreadsNumber);
297355
}
298356

299357
@Override
300358
public void removeMetersFor(ResourceID resourceID) {
301359
// schedule deletion of meters associated with ResourceID
302-
metersCleaner.schedule(() -> MicrometerMetrics.this.removeMetersFor(resourceID),
360+
metersCleaner.schedule(() -> super.removeMetersFor(resourceID),
303361
cleanUpDelayInSeconds, TimeUnit.SECONDS);
304362
}
305363
}

micrometer-support/src/test/java/io/javaoperatorsdk/operator/monitoring/micrometer/MetricsCleaningOnDeleteIT.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public class MetricsCleaningOnDeleteIT {
2929

3030
private static final TestSimpleMeterRegistry registry = new TestSimpleMeterRegistry();
3131
private static final int testDelay = 1;
32-
private static final MicrometerMetrics metrics = new MicrometerMetrics(registry, testDelay, 2);
32+
private static final MicrometerMetrics metrics = MicrometerMetrics.newMicrometerMetrics(registry)
33+
.withCleanUpDelayInSeconds(testDelay).withCleaningThreadNumber(2).build();
3334
private static final String testResourceName = "cleaning-metrics-cr";
3435

3536
@BeforeAll

0 commit comments

Comments
 (0)