From 2f2750e713e71095bf022f4e5c29cba2a0aa9ed7 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 11 May 2015 11:52:10 +0100 Subject: [PATCH] Extract metric writers into a MetricExporters composite Adds a separate exporter per MetricWriter and allows individual configuration of exporters for fine-grained control of schedules and patterns etc. --- .../MetricRepositoryAutoConfiguration.java | 44 +++--- .../export/MetricExportProperties.java | 144 ++++++++++++++++-- .../metrics/export/MetricExporters.java | 88 +++++++++++ ...etricRepositoryAutoConfigurationTests.java | 7 +- 4 files changed, 243 insertions(+), 40 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java index c2308e7a9d6..2cb20dad7e6 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java @@ -16,8 +16,9 @@ package org.springframework.boot.actuate.autoconfigure; -import java.util.ArrayList; -import java.util.List; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -30,15 +31,15 @@ import org.springframework.boot.actuate.metrics.buffer.CounterBuffers; import org.springframework.boot.actuate.metrics.buffer.GaugeBuffers; import org.springframework.boot.actuate.metrics.export.Exporter; import org.springframework.boot.actuate.metrics.export.MetricCopyExporter; +import org.springframework.boot.actuate.metrics.export.MetricExportProperties; +import org.springframework.boot.actuate.metrics.export.MetricExporters; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; import org.springframework.boot.actuate.metrics.repository.MetricRepository; -import org.springframework.boot.actuate.metrics.writer.CompositeMetricWriter; import org.springframework.boot.actuate.metrics.writer.DefaultCounterService; import org.springframework.boot.actuate.metrics.writer.DefaultGaugeService; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnJava; import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion; import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range; @@ -50,7 +51,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.messaging.MessageChannel; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; import com.codahale.metrics.MetricRegistry; @@ -91,7 +91,7 @@ import com.codahale.metrics.MetricRegistry; * @author Dave Syer */ @Configuration -@EnableConfigurationProperties(MetricsProperties.class) +@EnableConfigurationProperties(MetricExportProperties.class) public class MetricRepositoryAutoConfiguration { @Configuration @@ -173,10 +173,10 @@ public class MetricRepositoryAutoConfiguration { static class DefaultMetricsExporterConfiguration { @Autowired(required = false) - private List writers; + private Map writers = Collections.emptyMap(); @Autowired - private MetricsProperties metrics; + private MetricExportProperties metrics; @Autowired(required = false) @Qualifier("actuatorMetricRepository") @@ -184,27 +184,19 @@ public class MetricRepositoryAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnBean(MetricWriter.class) - public MetricCopyExporter metricWritersMetricExporter(MetricReader reader) { - List writers = new ArrayList(this.writers); + public MetricExporters metricWritersMetricExporter(MetricReader reader) { + Map writers = new HashMap( + this.writers); if (this.actuatorMetricRepository != null - && writers.contains(this.actuatorMetricRepository)) { - writers.remove(this.actuatorMetricRepository); - } - MetricCopyExporter exporter = new MetricCopyExporter(reader, - new CompositeMetricWriter(writers)) { - @Scheduled(fixedDelayString = "${spring.metrics.export.delayMillis:5000}") - @Override - public void export() { - super.export(); + && writers.containsValue(this.actuatorMetricRepository)) { + for (String name : this.writers.keySet()) { + if (writers.get(name).equals(this.actuatorMetricRepository)) { + writers.remove(name); + } } - }; - if (this.metrics.getExport().getIncludes() != null - || this.metrics.getExport().getExcludes() != null) { - exporter.setIncludes(this.metrics.getExport().getIncludes()); - exporter.setExcludes(this.metrics.getExport().getExcludes()); } - return exporter; + MetricExporters exporters = new MetricExporters(reader, writers, this.metrics); + return exporters; } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java index 20f2be9200a..0faad713eda 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java @@ -14,28 +14,112 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure; +package org.springframework.boot.actuate.metrics.export; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.PostConstruct; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.PatternMatchUtils; /** * @author Dave Syer */ -@ConfigurationProperties("spring.metrics") -public class MetricsProperties { +@ConfigurationProperties("spring.metrics.export") +public class MetricExportProperties { + + /** + * Flag to disable all metric exports (assuming any MetricWriters are available). + */ + private boolean enabled = true; private Export export = new Export(); - public Export getExport() { + private Map writers = new LinkedHashMap(); + + /** + * Default values for trigger configuration for all writers. Can also be set by + * including a writer with name="*". + * + * @return the default trigger configuration + */ + public Export getDefault() { return this.export; } + /** + * Configuration for triggers on individual named writers. Each value can individually + * specify a name pattern explicitly, or else the map key will be used if the name is + * not set. + * + * @return the writers + */ + public Map getWriters() { + return this.writers; + } + + @PostConstruct + public void setDefaults() { + Export defaults = null; + for (Entry entry : this.writers.entrySet()) { + String key = entry.getKey(); + Export value = entry.getValue(); + if (value.getNames() == null || value.getNames().length == 0) { + value.setNames(new String[] { key }); + } + if (Arrays.asList(value.getNames()).contains("*")) { + defaults = value; + } + } + if (defaults == null) { + this.export.setNames(new String[] { "*" }); + this.writers.put("*", this.export); + defaults = this.export; + } + if (defaults.isIgnoreTimestamps() == null) { + defaults.setIgnoreTimestamps(false); + } + if (defaults.isSendLatest() == null) { + defaults.setSendLatest(true); + } + if (defaults.getDelayMillis() == null) { + defaults.setDelayMillis(5000); + } + for (Export value : this.writers.values()) { + if (value.isIgnoreTimestamps() == null) { + value.setIgnoreTimestamps(false); + } + if (value.isSendLatest() == null) { + value.setSendLatest(true); + } + if (value.getDelayMillis() == null) { + value.setDelayMillis(5000); + } + } + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public static class Export { + /** + * Names (or patterns) for bean names that this configuration applies to. + */ + private String[] names; /** * Delay in milliseconds between export ticks. Metrics are exported to external * sources on a schedule with this delay. */ - private long delayMillis; + private Long delayMillis; /** * Flag to enable metric export (assuming a MetricWriter is available). @@ -46,12 +130,26 @@ public class MetricsProperties { * Flag to switch off any available optimizations based on not exporting unchanged * metric values. */ - private boolean ignoreTimestamps = false; + private Boolean sendLatest; + + /** + * Flag to ignore timestamps completely. If true, send all metrics all the time, + * including ones that haven't changed since startup. + */ + private Boolean ignoreTimestamps; private String[] includes; private String[] excludes; + public String[] getNames() { + return this.names; + } + + public void setNames(String[] names) { + this.names = names; + } + public String[] getIncludes() { return this.includes; } @@ -60,14 +158,14 @@ public class MetricsProperties { this.includes = includes; } - public String[] getExcludes() { - return this.excludes; - } - public void setExcludes(String[] excludes) { this.excludes = excludes; } + public String[] getExcludes() { + return this.excludes; + } + public boolean isEnabled() { return this.enabled; } @@ -76,7 +174,7 @@ public class MetricsProperties { this.enabled = enabled; } - public long getDelayMillis() { + public Long getDelayMillis() { return this.delayMillis; } @@ -84,12 +182,34 @@ public class MetricsProperties { this.delayMillis = delayMillis; } - public boolean isIgnoreTimestamps() { + public Boolean isIgnoreTimestamps() { return this.ignoreTimestamps; } public void setIgnoreTimestamps(boolean ignoreTimestamps) { this.ignoreTimestamps = ignoreTimestamps; } + + public Boolean isSendLatest() { + return this.sendLatest; + } + + public void setSendLatest(boolean sendLatest) { + this.sendLatest = sendLatest; + } + } + + /** + * Find a matching trigger configuration. + * @param name the bean name to match + * @return a matching configuration if there is one + */ + public Export findExport(String name) { + for (Export value : this.writers.values()) { + if (PatternMatchUtils.simpleMatch(value.getNames(), name)) { + return value; + } + } + return null; } } 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 new file mode 100644 index 00000000000..bd2b36928eb --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2015 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 org.springframework.boot.actuate.metrics.export; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.boot.actuate.metrics.export.MetricExportProperties.Export; +import org.springframework.boot.actuate.metrics.reader.MetricReader; +import org.springframework.boot.actuate.metrics.writer.MetricWriter; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.IntervalTask; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +/** + * @author Dave Syer + */ +public class MetricExporters implements SchedulingConfigurer { + + private MetricExportProperties export; + + private Map writers; + + private Map exporters = new HashMap(); + + private MetricReader reader; + + public MetricExporters(MetricReader reader, Map writers, + MetricExportProperties export) { + this.reader = reader; + this.export = export; + this.writers = writers; + } + + public Map getExporters() { + return this.exporters; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + + for (Entry entry : this.writers.entrySet()) { + String name = entry.getKey(); + Export trigger = this.export.findExport(name); + + if (trigger != null) { + + MetricWriter writer = entry.getValue(); + final MetricCopyExporter exporter = new MetricCopyExporter(this.reader, + writer); + if (trigger.getIncludes() != null || trigger.getExcludes() != null) { + exporter.setIncludes(trigger.getIncludes()); + exporter.setExcludes(trigger.getExcludes()); + } + exporter.setIgnoreTimestamps(trigger.isIgnoreTimestamps()); + exporter.setSendLatest(trigger.isSendLatest()); + + this.exporters.put(name, exporter); + + taskRegistrar.addFixedDelayTask(new IntervalTask(new Runnable() { + @Override + public void run() { + exporter.export(); + } + }, trigger.getDelayMillis(), trigger.getDelayMillis())); + + } + + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java index 7e366b4dbe9..6c3f348db00 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfigurationTests.java @@ -27,6 +27,7 @@ import org.springframework.boot.actuate.metrics.buffer.BufferCounterService; import org.springframework.boot.actuate.metrics.buffer.BufferGaugeService; import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices; import org.springframework.boot.actuate.metrics.export.MetricCopyExporter; +import org.springframework.boot.actuate.metrics.export.MetricExporters; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.reader.PrefixMetricReader; import org.springframework.boot.actuate.metrics.writer.MetricWriter; @@ -86,7 +87,7 @@ public class MetricRepositoryAutoConfigurationTests { MessageChannelConfiguration.class, MetricsChannelAutoConfiguration.class, MetricRepositoryAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); - MetricCopyExporter exporter = this.context.getBean(MetricCopyExporter.class); + MetricExporters exporter = this.context.getBean(MetricExporters.class); assertNotNull(exporter); } @@ -98,7 +99,9 @@ public class MetricRepositoryAutoConfigurationTests { GaugeService gaugeService = this.context.getBean(GaugeService.class); assertNotNull(gaugeService); gaugeService.submit("foo", 2.7); - MetricCopyExporter exporter = this.context.getBean(MetricCopyExporter.class); + MetricExporters exporters = this.context.getBean(MetricExporters.class); + MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters().get( + "writer"); exporter.setIgnoreTimestamps(true); exporter.export(); MetricWriter writer = this.context.getBean("writer", MetricWriter.class);