Initial support for batch metrics with Micrometer

Implements BATCH-2774
This commit is contained in:
Mahmoud Ben Hassine
2019-02-07 16:14:30 +01:00
parent 145bacc34a
commit 57c8d70ff3
11 changed files with 462 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2013 the original author or authors.
* Copyright 2006-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.io.Serializable;
* they can be applied at a chunk boundary.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
*
*/
@SuppressWarnings("serial")
@@ -43,11 +44,14 @@ public class StepContribution implements Serializable {
private ExitStatus exitStatus = ExitStatus.EXECUTING;
private volatile StepExecution stepExecution;
/**
* @param execution {@link StepExecution} the stepExecution used to initialize
* {@code skipCount}.
*/
public StepContribution(StepExecution execution) {
this.stepExecution = execution;
this.parentSkipCount = execution.getSkipCount();
}
@@ -191,6 +195,14 @@ public class StepContribution implements Serializable {
return processSkipCount;
}
/**
* Public getter for the parent step execution of this contribution.
* @return parent step execution of this contribution
*/
public StepExecution getStepExecution() {
return stepExecution;
}
/*
* (non-Javadoc)
*

View File

@@ -19,6 +19,9 @@ package org.springframework.batch.core.job;
import java.util.Collection;
import java.util.Date;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
@@ -36,6 +39,7 @@ import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.launch.support.ExitCodeMapper;
import org.springframework.batch.core.listener.CompositeJobExecutionListener;
import org.springframework.batch.core.metrics.BatchMetrics;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
@@ -297,7 +301,9 @@ InitializingBean {
}
JobSynchronizationManager.register(execution);
LongTaskTimer longTaskTimer = BatchMetrics.createLongTaskTimer("job.active", "Active jobs");
LongTaskTimer.Sample longTaskTimerSample = longTaskTimer.start();
Timer.Sample timerSample = BatchMetrics.createTimerSample();
try {
jobParametersValidator.validate(execution.getJobParameters());
@@ -353,6 +359,11 @@ InitializingBean {
execution.setExitStatus(exitStatus.and(newExitStatus));
}
timerSample.stop(BatchMetrics.createTimer("job", "Job duration",
Tag.of("name", execution.getJobInstance().getJobName()),
Tag.of("status", execution.getExitStatus().getExitCode())
));
longTaskTimerSample.stop();
execution.setEndTime(new Date());
try {

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.metrics;
import java.util.Arrays;
import java.util.function.Supplier;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
/**
* Main entry point to interact with Micrometer's {@link Metrics#globalRegistry}.
* Provides common metrics such as {@link Timer}, {@link Counter} and {@link Gauge}.
*
* Only intended for internal use.
*
* @author Mahmoud Ben Hassine
* @since 4.2
*/
public final class BatchMetrics {
private static final String METRICS_PREFIX = "spring.batch.";
public static final String STATUS_SUCCESS = "SUCCESS";
public static final String STATUS_FAILURE = "FAILURE";
private BatchMetrics() {}
/**
* Create a {@link Timer}.
*
* @param name of the timer. Will be prefixed with {@link BatchMetrics#METRICS_PREFIX}.
* @param description of the timer
* @param tags of the timer
* @return a new timer instance
*/
public static Timer createTimer(String name, String description, Tag... tags) {
return Timer.builder(METRICS_PREFIX + name)
.description(description)
.tags(Arrays.asList(tags))
.register(Metrics.globalRegistry);
}
/**
* Create a new {@link Timer.Sample}.
*
* @return a new timer sample instance
*/
public static Timer.Sample createTimerSample() {
return Timer.start(Metrics.globalRegistry);
}
/**
* Create a new {@link LongTaskTimer}.
*
* @param name of the long task timer. Will be prefixed with {@link BatchMetrics#METRICS_PREFIX}.
* @param description of the long task timer.
* @param tags of the timer
* @return a new long task timer instance
*/
public static LongTaskTimer createLongTaskTimer(String name, String description, Tag... tags) {
return LongTaskTimer.builder(METRICS_PREFIX + name)
.description(description)
.tags(Arrays.asList(tags))
.register(Metrics.globalRegistry);
}
/**
* Create a new {@link Counter}.
*
* @param name of the counter. Will be prefixed with {@link BatchMetrics#METRICS_PREFIX}.
* @param description of the counter
* @param tags of the counter
* @return a new counter instance
*/
public static Counter createCounter(String name, String description, Tag... tags) {
return Counter.builder(METRICS_PREFIX + name)
.description(description)
.tags(Arrays.asList(tags))
.register(Metrics.globalRegistry);
}
/**
* Create a new {@link Gauge}.
*
* @param name of the gauge. Will be prefixed with {@link BatchMetrics#METRICS_PREFIX}.
* @param description of the gauge
* @param supplier A supplier that yields a value for the gauge.
* @param tags of the gauge
* @return a new gauge instance
*/
public static Gauge createGauge(String name, String description, Supplier<Number> supplier, Tag... tags) {
return Gauge.builder(METRICS_PREFIX + name, supplier)
.description(description)
.tags(Arrays.asList(tags))
.register(Metrics.globalRegistry);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2018 the original author or authors.
* Copyright 2006-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@ package org.springframework.batch.core.step;
import java.util.Date;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
@@ -30,6 +32,7 @@ import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.launch.support.ExitCodeMapper;
import org.springframework.batch.core.listener.CompositeStepExecutionListener;
import org.springframework.batch.core.metrics.BatchMetrics;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.item.ExecutionContext;
@@ -188,6 +191,7 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
}
stepExecution.setStartTime(new Date());
stepExecution.setStatus(BatchStatus.STARTED);
Timer.Sample sample = BatchMetrics.createTimerSample();
getJobRepository().update(stepExecution);
// Start with a default value that will be trumped by anything
@@ -256,6 +260,11 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw
+ "This job is now in an unknown state and should not be restarted.", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e);
}
sample.stop(BatchMetrics.createTimer("step", "Step duration",
Tag.of("job.name", stepExecution.getJobExecution().getJobInstance().getJobName()),
Tag.of("name", stepExecution.getStepName()),
Tag.of("status", stepExecution.getExitStatus().getExitCode())
));
stepExecution.setEndTime(new Date());
stepExecution.setExitStatus(exitStatus);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2018 the original author or authors.
* Copyright 2006-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,14 @@ package org.springframework.batch.core.step.item;
import java.util.List;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.listener.MulticasterBatchListener;
import org.springframework.batch.core.metrics.BatchMetrics;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.InitializingBean;
@@ -283,6 +288,8 @@ public class SimpleChunkProcessor<I, O> implements ChunkProcessor<I>, Initializi
* @throws Exception if there is a problem
*/
protected void write(StepContribution contribution, Chunk<I> inputs, Chunk<O> outputs) throws Exception {
Timer.Sample sample = BatchMetrics.createTimerSample();
String status = BatchMetrics.STATUS_SUCCESS;
try {
doWrite(outputs.getItems());
}
@@ -292,8 +299,12 @@ public class SimpleChunkProcessor<I, O> implements ChunkProcessor<I>, Initializi
* here, so prevent any more processing of these inputs.
*/
inputs.clear();
status = BatchMetrics.STATUS_FAILURE;
throw e;
}
finally {
stopTimer(sample, contribution.getStepExecution(), "chunk.write", status, "Chunk writing");
}
contribution.incrementWriteCount(outputs.size());
}
@@ -302,6 +313,8 @@ public class SimpleChunkProcessor<I, O> implements ChunkProcessor<I>, Initializi
for (Chunk<I>.ChunkIterator iterator = inputs.iterator(); iterator.hasNext();) {
final I item = iterator.next();
O output;
Timer.Sample sample = BatchMetrics.createTimerSample();
String status = BatchMetrics.STATUS_SUCCESS;
try {
output = doProcess(item);
}
@@ -311,8 +324,12 @@ public class SimpleChunkProcessor<I, O> implements ChunkProcessor<I>, Initializi
* here, so prevent any more processing of these inputs.
*/
inputs.clear();
status = BatchMetrics.STATUS_FAILURE;
throw e;
}
finally {
stopTimer(sample, contribution.getStepExecution(), "item.process", status, "Item processing");
}
if (output != null) {
outputs.add(output);
}
@@ -323,4 +340,12 @@ public class SimpleChunkProcessor<I, O> implements ChunkProcessor<I>, Initializi
return outputs;
}
private void stopTimer(Timer.Sample sample, StepExecution stepExecution, String metricName, String status, String description) {
sample.stop(BatchMetrics.createTimer(metricName, description + " duration",
Tag.of("job.name", stepExecution.getJobExecution().getJobInstance().getJobName()),
Tag.of("step.name", stepExecution.getStepName()),
Tag.of("status", status)
));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2018 the original author or authors.
* Copyright 2006-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,16 @@ package org.springframework.batch.core.step.item;
import java.util.List;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.listener.MulticasterBatchListener;
import org.springframework.batch.core.metrics.BatchMetrics;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
@@ -115,14 +120,20 @@ public class SimpleChunkProvider<I> implements ChunkProvider<I> {
@Override
public RepeatStatus doInIteration(final RepeatContext context) throws Exception {
I item = null;
Timer.Sample sample = Timer.start(Metrics.globalRegistry);
String status = BatchMetrics.STATUS_SUCCESS;
try {
item = read(contribution, inputs);
}
catch (SkipOverflowException e) {
// read() tells us about an excess of skips by throwing an
// exception
status = BatchMetrics.STATUS_FAILURE;
return RepeatStatus.FINISHED;
}
finally {
stopTimer(sample, contribution.getStepExecution(), status);
}
if (item == null) {
inputs.setEnd();
return RepeatStatus.FINISHED;
@@ -138,6 +149,14 @@ public class SimpleChunkProvider<I> implements ChunkProvider<I> {
}
private void stopTimer(Timer.Sample sample, StepExecution stepExecution, String status) {
sample.stop(BatchMetrics.createTimer("item.read", "Item reading duration",
Tag.of("job.name", stepExecution.getJobExecution().getJobInstance().getJobName()),
Tag.of("step.name", stepExecution.getStepName()),
Tag.of("status", status)
));
}
@Override
public void postProcess(StepContribution contribution, Chunk<I> chunk) {
// do nothing