From b9db4742ac5ebbe52b41a5435c0ca1375c2aab43 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 22 Apr 2016 09:09:05 +0100 Subject: [PATCH] Add metric flusher to export remaining metrics on shutdown Before this change the app context closes and metrics that have not yet been exported ccan be orphaned. The design of this feature is simple: use Closeable where possible, so that it will be called automatically by Spring on shutdown. Fixes gh-5771 --- .../export/AbstractMetricExporter.java | 12 +++++++++++- .../metrics/export/MetricExporters.java | 19 ++++++++++++++++++- .../MetricExportAutoConfigurationTests.java | 15 +++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/AbstractMetricExporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/AbstractMetricExporter.java index 6bc35191fa8..05a7782f7d4 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/AbstractMetricExporter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/AbstractMetricExporter.java @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.metrics.export; +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -36,7 +39,7 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @since 1.3.0 */ -public abstract class AbstractMetricExporter implements Exporter { +public abstract class AbstractMetricExporter implements Exporter, Closeable, Flushable { private static final Log logger = LogFactory.getLog(AbstractMetricExporter.class); @@ -143,6 +146,13 @@ public abstract class AbstractMetricExporter implements Exporter { } } + @Override + public void close() throws IOException { + export(); + flushQuietly(); + } + + @Override public void flush() { } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java index ceb2095a1b5..204c7e48a15 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java @@ -16,9 +16,13 @@ package org.springframework.boot.actuate.metrics.export; +import java.io.Closeable; +import java.io.IOException; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.writer.GaugeWriter; @@ -32,7 +36,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; * @author Dave Syer * @since 1.3.0 */ -public class MetricExporters implements SchedulingConfigurer { +public class MetricExporters implements SchedulingConfigurer, Closeable { private MetricReader reader; @@ -42,6 +46,8 @@ public class MetricExporters implements SchedulingConfigurer { private final Map exporters = new HashMap(); + private final Set closeables = new HashSet(); + public MetricExporters(MetricExportProperties properties) { this.properties = properties; } @@ -78,6 +84,7 @@ public class MetricExporters implements SchedulingConfigurer { if (trigger != null) { MetricCopyExporter exporter = getExporter(writer, trigger); this.exporters.put(name, exporter); + this.closeables.add(name); ExportRunner runner = new ExportRunner(exporter); IntervalTask task = new IntervalTask(runner, trigger.getDelayMillis(), trigger.getDelayMillis()); @@ -99,6 +106,16 @@ public class MetricExporters implements SchedulingConfigurer { return this.exporters; } + @Override + public void close() throws IOException { + for (String name : this.closeables) { + Exporter exporter = this.exporters.get(name); + if (exporter instanceof Closeable) { + ((Closeable) exporter).close(); + } + } + } + private static class ExportRunner implements Runnable { private final Exporter exporter; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java index 170258fd6b9..08a5e440ce6 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricExportAutoConfigurationTests.java @@ -68,6 +68,21 @@ public class MetricExportAutoConfigurationTests { } } + @Test + public void metricsFlushAutomatically() throws Exception { + this.context = new AnnotationConfigApplicationContext(WriterConfig.class, + MetricRepositoryAutoConfiguration.class, + MetricExportAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + GaugeService gaugeService = this.context.getBean(GaugeService.class); + assertNotNull(gaugeService); + gaugeService.submit("foo", 2.7); + MetricExporters flusher = this.context.getBean(MetricExporters.class); + flusher.close(); // this will be called by Spring on shutdown + MetricWriter writer = this.context.getBean("writer", MetricWriter.class); + Mockito.verify(writer, Mockito.atLeastOnce()).set(Matchers.any(Metric.class)); + } + @Test public void defaultExporterWhenMessageChannelAvailable() throws Exception { this.context = new AnnotationConfigApplicationContext(