Skip to content

Commit

Permalink
Merge pull request #156 from raynigon/feature/make-metrics-configurable
Browse files Browse the repository at this point in the history
Allow Metrics to be disabled if not needed
  • Loading branch information
raynigon committed Nov 8, 2022
2 parents 432706d + 06d13b9 commit 5f5d7f1
Show file tree
Hide file tree
Showing 20 changed files with 471 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
import com.raynigon.ecs.logging.async.executor.DefaultMdcForkJoinPool;
import com.raynigon.ecs.logging.async.model.MdcRunnable;
import com.raynigon.ecs.logging.async.scheduler.MdcScheduledExecutorService;
import com.raynigon.ecs.logging.async.service.AsyncMetricsService;
import com.raynigon.ecs.logging.async.service.MicrometerMetricsService;
import com.raynigon.ecs.logging.async.service.NoOpMetricsService;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -51,4 +58,17 @@ public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
executor.initialize();
return executor;
}

@Bean
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnProperty(name = "raynigon.logging.async.metrics.enabled", havingValue = "true")
public AsyncMetricsService micrometerAsyncMetricsService(MeterRegistry meterRegistry){
return new MicrometerMetricsService(meterRegistry);
}

@Bean
@ConditionalOnMissingBean(AsyncMetricsService.class)
public AsyncMetricsService fallbackAsyncMetricsService(){
return new NoOpMetricsService();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.raynigon.ecs.logging.async.service;

import com.raynigon.ecs.logging.async.service.helper.TimerWrapper;

public interface AsyncMetricsService {

TimerWrapper createQueueTimer(Class<?> source);

TimerWrapper createExecutionTimer(Class<?> source);
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.raynigon.ecs.logging.async.service;

import com.raynigon.ecs.logging.async.executor.MdcForkJoinPool;
import com.raynigon.ecs.logging.async.service.helper.SampleWrapper;
import com.raynigon.ecs.logging.async.service.helper.TimerWrapper;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,24 +19,22 @@
@RequiredArgsConstructor
public class DefaultAsyncService implements AsyncService {

private static final String QUEUE_TIMER_NAME = "raynigon.async.service.queue.duration";
private static final String EXECUTION_TIMER_NAME = "raynigon.async.service.execution.duration";


private final MdcForkJoinPool forkJoinPool;

private final MeterRegistry meterRegistry;
private final AsyncMetricsService metrics;

private static final Logger log = LoggerFactory.getLogger(DefaultAsyncService.class);

@Override
public <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
String sourceName = formatSource(supplier.getClass());
Timer queueTimer = meterRegistry.timer(QUEUE_TIMER_NAME, "source", sourceName);
Timer execTimer = meterRegistry.timer(EXECUTION_TIMER_NAME, "source", sourceName);
Timer.Sample sample = Timer.start();
TimerWrapper queueTimer = metrics.createQueueTimer(supplier.getClass());
TimerWrapper execTimer = metrics.createExecutionTimer(supplier.getClass());
SampleWrapper sample = queueTimer.start();
Supplier<U> wrapped = () -> {
long nanoseconds = sample.stop(queueTimer);
log.trace("The supplier {} took {} ms in Queue", sourceName, nanoseconds / 1000000.0);
long nanoseconds = queueTimer.stop(sample);
log.trace("The supplier {} took {} ms in Queue", supplier.getClass(), nanoseconds / 1000000.0);
return execTimer.record(supplier);
};
log.trace("Add supplier {} to ForkJoinPool", supplier);
Expand All @@ -43,19 +43,15 @@ public <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {

@Override
public <V> ForkJoinTask<V> submit(Callable<V> callable) {
String sourceName = formatSource(callable.getClass());
Timer queueTimer = meterRegistry.timer(QUEUE_TIMER_NAME, "source", sourceName);
Timer execTimer = meterRegistry.timer(EXECUTION_TIMER_NAME, "source", sourceName);
Timer.Sample sample = Timer.start();
TimerWrapper queueTimer = metrics.createQueueTimer(callable.getClass());
TimerWrapper execTimer = metrics.createExecutionTimer(callable.getClass());
SampleWrapper sample = queueTimer.start();
Callable<V> wrapped = () -> {
long nanoseconds = sample.stop(queueTimer);
log.trace("The callable {} took {} ms in Queue", sourceName, nanoseconds / 1000000.0);
long nanoseconds = queueTimer.stop(sample);
log.trace("The callable {} took {} ms in Queue", callable.getClass(), nanoseconds / 1000000.0);
return execTimer.recordCallable(callable);
};
return forkJoinPool.submit(wrapped);
}

private String formatSource(Class<?> source){
return source.getName().split("/")[0];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.raynigon.ecs.logging.async.service;

import com.raynigon.ecs.logging.async.service.helper.MicrometerTimer;
import com.raynigon.ecs.logging.async.service.helper.TimerWrapper;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class MicrometerMetricsService implements AsyncMetricsService {


private static final String QUEUE_TIMER_NAME = "raynigon.async.service.queue.duration";
private static final String EXECUTION_TIMER_NAME = "raynigon.async.service.execution.duration";

private final MeterRegistry meterRegistry;

@Override
public TimerWrapper createQueueTimer(Class<?> source) {
Timer timer = meterRegistry.timer(QUEUE_TIMER_NAME, "source", formatSource(source));
return wrap(timer);
}

@Override
public TimerWrapper createExecutionTimer(Class<?> source) {
Timer timer = meterRegistry.timer(EXECUTION_TIMER_NAME, "source", formatSource(source));
return wrap(timer);
}

private TimerWrapper wrap(Timer timer) {
return new MicrometerTimer(timer);
}

private String formatSource(Class<?> source) {
return source.getName().split("/")[0];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.raynigon.ecs.logging.async.service;

import com.raynigon.ecs.logging.async.service.helper.NoOpTimer;
import com.raynigon.ecs.logging.async.service.helper.TimerWrapper;
import org.springframework.stereotype.Service;

public class NoOpMetricsService implements AsyncMetricsService {
@Override
public TimerWrapper createQueueTimer(Class<?> source) {
return new NoOpTimer();
}

@Override
public TimerWrapper createExecutionTimer(Class<?> source) {
return new NoOpTimer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.raynigon.ecs.logging.async.service.helper;

import io.micrometer.core.instrument.Timer;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class MicrometerSample implements SampleWrapper {

private final Timer timer;
private final Timer.Sample sample;

public long stop() {
return sample.stop(timer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.raynigon.ecs.logging.async.service.helper;

import io.micrometer.core.instrument.Timer;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

import java.util.concurrent.Callable;
import java.util.function.Supplier;

@RequiredArgsConstructor
public class MicrometerTimer implements TimerWrapper{

private final Timer timer;

@Override
@SneakyThrows
public <V> V recordCallable(Callable<V> callable) {
return timer.recordCallable(callable);
}

@Override
public <U> U record(Supplier<U> supplier) {
return timer.record(supplier);
}

@Override
public SampleWrapper start() {
return wrap(timer, Timer.start());
}

@Override
public long stop(SampleWrapper wrapper) {
MicrometerSample sample = (MicrometerSample) wrapper;
return sample.stop();
}

private SampleWrapper wrap(Timer timer, Timer.Sample sample) {
return new MicrometerSample(timer, sample);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.raynigon.ecs.logging.async.service.helper;

public class NoOpSample implements SampleWrapper {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.raynigon.ecs.logging.async.service.helper;

import lombok.SneakyThrows;

import java.util.concurrent.Callable;
import java.util.function.Supplier;

public class NoOpTimer implements TimerWrapper {
@Override
@SneakyThrows
public <V> V recordCallable(Callable<V> callable) {
return callable.call();
}

@Override
public <U> U record(Supplier<U> supplier) {
return supplier.get();
}

@Override
public SampleWrapper start() {
return new NoOpSample();
}

@Override
public long stop(SampleWrapper wrapper) {
return -1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.raynigon.ecs.logging.async.service.helper;

public interface SampleWrapper {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.raynigon.ecs.logging.async.service.helper;

import java.util.concurrent.Callable;
import java.util.function.Supplier;

public interface TimerWrapper {
<V> V recordCallable(Callable<V> callable);

<U> U record(Supplier<U> supplier);

SampleWrapper start();

long stop(SampleWrapper wrapper);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.raynigon.ecs.logging.async

import com.raynigon.ecs.logging.async.helper.MeterRegistryProvider
import com.raynigon.ecs.logging.async.service.AsyncMetricsService
import com.raynigon.ecs.logging.async.service.NoOpMetricsService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification

@EnableAutoConfiguration
@ContextConfiguration(classes = [MdcExecutorConfiguration, MeterRegistryProvider])
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = [])
class FallbackMetricsServiceITSpec extends Specification {

@Autowired
AsyncMetricsService metricsService

def "test"(){
expect:
metricsService instanceof NoOpMetricsService
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.raynigon.ecs.logging.async

import com.raynigon.ecs.logging.async.helper.MeterRegistryProvider
import com.raynigon.ecs.logging.async.service.AsyncMetricsService
import com.raynigon.ecs.logging.async.service.MicrometerMetricsService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification

@EnableAutoConfiguration
@ContextConfiguration(classes = [MdcExecutorConfiguration, MeterRegistryProvider])
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["raynigon.logging.async.metrics.enabled=true"])
class MicrometerMetricsServiceITSpec extends Specification {

@Autowired
AsyncMetricsService metricsService

def "test"() {
expect:
metricsService instanceof MicrometerMetricsService
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ class DefaultAsyncServiceSpec extends Specification {

SimpleMeterRegistry meterRegistry = new SimpleMeterRegistry()

AsyncMetricsService metricsService = new MicrometerMetricsService(meterRegistry)

@Subject
AsyncService service = new DefaultAsyncService(pool, meterRegistry)
AsyncService service = new DefaultAsyncService(pool, metricsService)

def 'supplyAsync gets executed'() {
given:
Expand All @@ -35,8 +37,8 @@ class DefaultAsyncServiceSpec extends Specification {
1 * pool.execute(_ as Runnable) >> { Runnable r -> r.run() }

and:
meterRegistry.find(DefaultAsyncService.QUEUE_TIMER_NAME).timer().count() == 1
meterRegistry.find(DefaultAsyncService.EXECUTION_TIMER_NAME).timer().count() == 1
meterRegistry.find(MicrometerMetricsService.QUEUE_TIMER_NAME).timer().count() == 1
meterRegistry.find(MicrometerMetricsService.EXECUTION_TIMER_NAME).timer().count() == 1
}

def 'submit gets executed'() {
Expand All @@ -62,7 +64,7 @@ class DefaultAsyncServiceSpec extends Specification {
}

and:
meterRegistry.find(DefaultAsyncService.QUEUE_TIMER_NAME).timer().count() == 1
meterRegistry.find(DefaultAsyncService.EXECUTION_TIMER_NAME).timer().count() == 1
meterRegistry.find(MicrometerMetricsService.QUEUE_TIMER_NAME).timer().count() == 1
meterRegistry.find(MicrometerMetricsService.EXECUTION_TIMER_NAME).timer().count() == 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.raynigon.ecs.logging.async.service

import io.micrometer.core.instrument.simple.SimpleMeterRegistry
import spock.lang.Specification
import spock.lang.Subject

class MicrometerMetricsServiceSpec extends Specification {

SimpleMeterRegistry meterRegistry = new SimpleMeterRegistry()

@Subject
MicrometerMetricsService service = new MicrometerMetricsService(meterRegistry)

def "create queue timer"() {
when:
service.createQueueTimer(String.class).record({ null })

then:
meterRegistry.find(MicrometerMetricsService.QUEUE_TIMER_NAME).timer().count() == 1
meterRegistry.find(MicrometerMetricsService.EXECUTION_TIMER_NAME).timer() == null
}

def "create execution timer"() {
when:
service.createExecutionTimer(String.class).record({ null })

then:
meterRegistry.find(MicrometerMetricsService.QUEUE_TIMER_NAME).timer() == null
meterRegistry.find(MicrometerMetricsService.EXECUTION_TIMER_NAME).timer().count() == 1
}
}
Loading

0 comments on commit 5f5d7f1

Please sign in to comment.