/*
* Copyright 2014 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
*
* http://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 de.codecentric.batch.metrics;
/**
* Interface for business metrics.
*
* All metric names get the prefix 'batch.{jobName}.{jobExecutionId}.' in addition to other prefixes.
* For example, when using the counter methods (increment, decrement, reset) the complete prefix will be
* 'counter.batch.{jobName}.{jobExecutionId}.'. When written to the
* Step-ExecutionContext, the complete prefix is omitted.
*
* There are two types of methods: non-transactional methods and transactional methods. The methods that
* don't include 'NonTransactional' in their name are by default transaction-aware. That means that
* their execution is delayed after a successful commit of the current transaction. If there's no
* current transaction, the method is executed immediately. The non-transactional methods are
* executed directly, no matter if there's a transaction or not.
*
* How do you use this component in a batch environment?
*
* You may inject this component into any batch artifact, a reader, processor, writer, a listener and so on.
* Counter and gauges are held in memory as long as the job runs and will be written to the Step-ExecutionContext
* once the step finishes, successful or not. They are written to the log file as well.
* If the job execution is a restart of a previously failed run, the counters are the sum of both executions.
* Gauges are the ones from the last run.
*
* The tricky part is to decide whether the transactional or the non-transactional method should be used for
* correct counting. This blog post series might help for better understanding how transactions in Spring Batch
* work: https://blog.codecentric.de/en/2012/03/transactions-in-spring-batch-part-1-the-basics/.
*
* The thing is that Spring Batch's transactional behaviour is smart, but gets in your way if you do the metrics
* in a naive way. When using skip functionality, for example, it may happen that a process - method of a
* ItemProcessor gets executed several times for one successful processed item, because the item was together
* in a chunk with a bad item, and the chunk was rolled back. If your counting doesn't get rolled back as well,
* the metric is wrong. That's why it's so essential to have transactional metrics in Spring Batch.
*
* In general you should always use the transactional methods, because when there's no transaction they behave
* like the non-transactional methods, and when there's a transaction they are only executed when the transaction
* is successful. However, there are exceptions. With a job configured the default way you have a cache for the
* read items. When there's a rollback of a chunk, the ItemReader is not re-executed. So for correct countings
* you have to use the non-transactional methods in the ItemReader and the ItemReadListener. If you set
* reader-transactional-queue to true for your job, the cache is not used and you have to use the transactional
* methods in ItemReader and ItemReadListener as well. If you set processor-transactional to false for your job
* there will be a cache for the processed items as well, so you have to use the non-transactional methods in the
* ItemProcessor and the ItemProcessListener. And, of course, if you are counting errors in the onError-methods
* of ItemListeners, you have to use the non-transactional methods because a rollback is going to happen afterwards.
* If you do a filter in an ItemProcessor you have to use the non-transactional methods, because filtered items
* are removed from the item cache as well and will never be reprocessed. When using the AsyncItemProcessor there
* is no filtering possible at all, because a Future-Object is always returned from the ItemProcessor.
*
* @author Tobias Flohre
*/
public interface BatchMetrics {
/**
* Increment the specified counter by 1. Transaction-aware.
*
* @param metricName the name of the counter
*/
void increment(String metricName);
/**
* Increment the specified counter by the given value. Transaction-aware.
*
* @param metricName the name of the counter
* @param value the amount to increment by
*/
void increment(String metricName, Long value);
/**
* Decrement the specified counter by 1. Transaction-aware.
*
* @param metricName the name of the counter
*/
void decrement(String metricName);
/**
* Decrement the specified counter by the given value. Transaction-aware.
*
* @param metricName the name of the counter
* @param value the amount to decrement by
*/
void decrement(String metricName, Long value);
/**
* Reset the specified counter. Transaction-aware.
*
* @param metricName the name of the counter
*/
void reset(String metricName);
/**
* Set the specified gauge value. Transaction-aware.
*
* @param metricName the name of the gauge to set
* @param value the value of the gauge
*/
void submit(String metricName, double value);
/**
* Increment the specified counter by 1.
*
* @param metricName the name of the counter
*/
void incrementNonTransactional(String metricName);
/**
* Increment the specified counter by the given value.
*
* @param metricName the name of the counter
* @param value the amount to increment by
*/
void incrementNonTransactional(String metricName, Long value);
/**
* Decrement the specified counter by 1.
*
* @param metricName the name of the counter
*/
void decrementNonTransactional(String metricName);
/**
* Decrement the specified counter by the given value.
*
* @param metricName the name of the counter
* @param value the amount to decrement by
*/
void decrementNonTransactional(String metricName, Long value);
/**
* Reset the specified counter.
*
* @param metricName the name of the counter
*/
void resetNonTransactional(String metricName);
/**
* Set the specified gauge value
*
* @param metricName the name of the gauge to set
* @param value the value of the gauge
*/
void submitNonTransactional(String metricName, double value);
}