Skip to content

Commit

Permalink
Merge pull request #1018 from vladimir-bukhtoyarov/968
Browse files Browse the repository at this point in the history
Make reporters more user friendly for managed environments like GAE or JEE
  • Loading branch information
arteam committed Jan 5, 2017
2 parents 29e5d6c + 5222fe7 commit 3c369cd
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.PrintStream;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -33,6 +34,8 @@ public static class Builder {
private TimeUnit rateUnit;
private TimeUnit durationUnit;
private MetricFilter filter;
private ScheduledExecutorService executor;
private boolean shutdownExecutorOnStop;

private Builder(MetricRegistry registry) {
this.registry = registry;
Expand All @@ -43,6 +46,34 @@ private Builder(MetricRegistry registry) {
this.rateUnit = TimeUnit.SECONDS;
this.durationUnit = TimeUnit.MILLISECONDS;
this.filter = MetricFilter.ALL;
this.executor = null;
this.shutdownExecutorOnStop = true;
}

/**
* Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
* Default value is true.
* Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
*
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
* @return {@code this}
*/
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
this.shutdownExecutorOnStop = shutdownExecutorOnStop;
return this;
}

/**
* Specifies the executor to use while scheduling reporting of metrics.
* Default value is null.
* Null value leads to executor will be auto created on start.
*
* @param executor the executor to use while scheduling reporting of metrics.
* @return {@code this}
*/
public Builder scheduleOn(ScheduledExecutorService executor) {
this.executor = executor;
return this;
}

/**
Expand Down Expand Up @@ -135,7 +166,9 @@ public ConsoleReporter build() {
timeZone,
rateUnit,
durationUnit,
filter);
filter,
executor,
shutdownExecutorOnStop);
}
}

Expand All @@ -153,8 +186,10 @@ private ConsoleReporter(MetricRegistry registry,
TimeZone timeZone,
TimeUnit rateUnit,
TimeUnit durationUnit,
MetricFilter filter) {
super(registry, "console-reporter", filter, rateUnit, durationUnit);
MetricFilter filter,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop) {
super(registry, "console-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
this.output = output;
this.locale = locale;
this.clock = clock;
Expand Down
42 changes: 39 additions & 3 deletions metrics-core/src/main/java/com/codahale/metrics/CsvReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -35,6 +36,8 @@ public static class Builder {
private TimeUnit durationUnit;
private Clock clock;
private MetricFilter filter;
private ScheduledExecutorService executor;
private boolean shutdownExecutorOnStop;

private Builder(MetricRegistry registry) {
this.registry = registry;
Expand All @@ -43,6 +46,34 @@ private Builder(MetricRegistry registry) {
this.durationUnit = TimeUnit.MILLISECONDS;
this.clock = Clock.defaultClock();
this.filter = MetricFilter.ALL;
this.executor = null;
this.shutdownExecutorOnStop = true;
}

/**
* Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
* Default value is true.
* Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
*
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
* @return {@code this}
*/
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
this.shutdownExecutorOnStop = shutdownExecutorOnStop;
return this;
}

/**
* Specifies the executor to use while scheduling reporting of metrics.
* Default value is null.
* Null value leads to executor will be auto created on start.
*
* @param executor the executor to use while scheduling reporting of metrics.
* @return {@code this}
*/
public Builder scheduleOn(ScheduledExecutorService executor) {
this.executor = executor;
return this;
}

/**
Expand Down Expand Up @@ -114,7 +145,10 @@ public CsvReporter build(File directory) {
rateUnit,
durationUnit,
clock,
filter);
filter,
executor,
shutdownExecutorOnStop
);
}
}

Expand All @@ -131,8 +165,10 @@ private CsvReporter(MetricRegistry registry,
TimeUnit rateUnit,
TimeUnit durationUnit,
Clock clock,
MetricFilter filter) {
super(registry, "csv-reporter", filter, rateUnit, durationUnit);
MetricFilter filter,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop) {
super(registry, "csv-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
this.directory = directory;
this.locale = locale;
this.clock = clock;
Expand Down
109 changes: 86 additions & 23 deletions metrics-core/src/main/java/com/codahale/metrics/ScheduledReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import java.io.Closeable;
import java.util.Locale;
import java.util.SortedMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
Expand Down Expand Up @@ -54,6 +51,8 @@ public Thread newThread(Runnable r) {

private final MetricRegistry registry;
private final ScheduledExecutorService executor;
private final boolean shutdownExecutorOnStop;
private ScheduledFuture<?> scheduledFuture;
private final MetricFilter filter;
private final double durationFactor;
private final String durationUnit;
Expand All @@ -75,10 +74,9 @@ protected ScheduledReporter(MetricRegistry registry,
MetricFilter filter,
TimeUnit rateUnit,
TimeUnit durationUnit) {
this(registry, name, filter, rateUnit, durationUnit,
Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(name + '-' + FACTORY_ID.incrementAndGet())));
this(registry, name, filter, rateUnit, durationUnit, createDefaultExecutor(name));
}

/**
* Creates a new {@link ScheduledReporter} instance.
*
Expand All @@ -94,9 +92,30 @@ protected ScheduledReporter(MetricRegistry registry,
TimeUnit rateUnit,
TimeUnit durationUnit,
ScheduledExecutorService executor) {
this(registry, name, filter, rateUnit, durationUnit, executor, true);
}

/**
* Creates a new {@link ScheduledReporter} instance.
*
* @param registry the {@link com.codahale.metrics.MetricRegistry} containing the metrics this
* reporter will report
* @param name the reporter's name
* @param filter the filter for which metrics to report
* @param executor the executor to use while scheduling reporting of metrics.
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
*/
protected ScheduledReporter(MetricRegistry registry,
String name,
MetricFilter filter,
TimeUnit rateUnit,
TimeUnit durationUnit,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop) {
this.registry = registry;
this.filter = filter;
this.executor = executor;
this.executor = executor == null? createDefaultExecutor(name) : executor;
this.shutdownExecutorOnStop = shutdownExecutorOnStop;
this.rateFactor = rateUnit.toSeconds(1);
this.rateUnit = calculateRateUnit(rateUnit);
this.durationFactor = 1.0 / durationUnit.toNanos(1);
Expand All @@ -109,8 +128,11 @@ protected ScheduledReporter(MetricRegistry registry,
* @param period the amount of time between polls
* @param unit the unit for {@code period}
*/
public void start(long period, TimeUnit unit) {
executor.scheduleAtFixedRate(new Runnable() {
synchronized public void start(long period, TimeUnit unit) {
if (this.scheduledFuture != null) {
throw new IllegalArgumentException("Reporter already started");
}
this.scheduledFuture = executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Expand All @@ -123,26 +145,58 @@ public void run() {
}

/**
* Stops the reporter and shuts down its thread of execution.
* Stops the reporter and if shutdownExecutorOnStop is true then shuts down its thread of execution.
*
* Uses the shutdown pattern from http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
*/
public void stop() {
executor.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
executor.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (shutdownExecutorOnStop) {
executor.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
System.err.println(getClass().getSimpleName() + ": ScheduledExecutorService did not terminate");
executor.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
System.err.println(getClass().getSimpleName() + ": ScheduledExecutorService did not terminate");
}
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
executor.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
} else {
// The external manager(like JEE container) responsible for lifecycle of executor
synchronized (this) {
if (this.scheduledFuture == null) {
// was never started
return;
}
if (this.scheduledFuture.isCancelled()) {
// already cancelled
return;
}
// just cancel the scheduledFuture and exit
this.scheduledFuture.cancel(false);
try {
// Wait a while for existing tasks to terminate
scheduledFuture.get(1, TimeUnit.SECONDS);
} catch (ExecutionException e) {
// well, we should get this error when future is cancelled normally, just ignore it
} catch (InterruptedException e) {
// The thread was interrupted while waiting future to complete
// Preserve interrupt status
Thread.currentThread().interrupt();
if (!this.scheduledFuture.isDone()) {
LOG.warn("The reporting schedulingFuture is not cancelled yet");
}
} catch (TimeoutException e) {
// The last reporting cycle is still in progress, nothing wrong, just add log record
LOG.warn("The reporting schedulingFuture is not cancelled yet");
}
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
executor.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}

Expand Down Expand Up @@ -198,8 +252,17 @@ protected double convertRate(double rate) {
return rate * rateFactor;
}

protected boolean isShutdownExecutorOnStop() {
return shutdownExecutorOnStop;
}

private String calculateRateUnit(TimeUnit unit) {
final String s = unit.toString().toLowerCase(Locale.US);
return s.substring(0, s.length() - 1);
}

private static ScheduledExecutorService createDefaultExecutor(String name) {
return Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(name + '-' + FACTORY_ID.incrementAndGet()));
}

}
39 changes: 36 additions & 3 deletions metrics-core/src/main/java/com/codahale/metrics/Slf4jReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -41,6 +42,8 @@ public static class Builder {
private TimeUnit rateUnit;
private TimeUnit durationUnit;
private MetricFilter filter;
private ScheduledExecutorService executor;
private boolean shutdownExecutorOnStop;

private Builder(MetricRegistry registry) {
this.registry = registry;
Expand All @@ -51,6 +54,34 @@ private Builder(MetricRegistry registry) {
this.durationUnit = TimeUnit.MILLISECONDS;
this.filter = MetricFilter.ALL;
this.loggingLevel = LoggingLevel.INFO;
this.executor = null;
this.shutdownExecutorOnStop = true;
}

/**
* Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
* Default value is true.
* Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
*
* @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
* @return {@code this}
*/
public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
this.shutdownExecutorOnStop = shutdownExecutorOnStop;
return this;
}

/**
* Specifies the executor to use while scheduling reporting of metrics.
* Default value is null.
* Null value leads to executor will be auto created on start.
*
* @param executor the executor to use while scheduling reporting of metrics.
* @return {@code this}
*/
public Builder scheduleOn(ScheduledExecutorService executor) {
this.executor = executor;
return this;
}

/**
Expand Down Expand Up @@ -155,7 +186,7 @@ public Slf4jReporter build() {
loggerProxy = new DebugLoggerProxy(logger);
break;
}
return new Slf4jReporter(registry, loggerProxy, marker, prefix, rateUnit, durationUnit, filter);
return new Slf4jReporter(registry, loggerProxy, marker, prefix, rateUnit, durationUnit, filter, executor, shutdownExecutorOnStop);
}
}

Expand All @@ -169,8 +200,10 @@ private Slf4jReporter(MetricRegistry registry,
String prefix,
TimeUnit rateUnit,
TimeUnit durationUnit,
MetricFilter filter) {
super(registry, "logger-reporter", filter, rateUnit, durationUnit);
MetricFilter filter,
ScheduledExecutorService executor,
boolean shutdownExecutorOnStop) {
super(registry, "logger-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
this.loggerProxy = loggerProxy;
this.marker = marker;
this.prefix = prefix;
Expand Down
Loading

0 comments on commit 3c369cd

Please sign in to comment.