Commit 80ff9291 authored by Dave Syer's avatar Dave Syer

Add redis properties as convenience in spring.metrics.export

The redis export and aggregate use case is a lot nicer with this
shared data between the two component types.

Also made MetricExportProperties itself a Trigger (so the default
delay etc. can be configured via spring.metrics.export.*).
parent e8e89f37
......@@ -16,30 +16,31 @@
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.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
*/
@ConfigurationProperties("spring.metrics.export")
public class MetricExportProperties {
public class MetricExportProperties extends Trigger {
/**
* Flag to disable all metric exports (assuming any MetricWriters are available).
*/
private boolean enabled = true;
private Export export = new Export();
private Map<String, SpecificTrigger> triggers = new LinkedHashMap<String, SpecificTrigger>();
private Map<String, Export> writers = new LinkedHashMap<String, Export>();
private Redis redis = new Redis();
/**
* Default values for trigger configuration for all writers. Can also be set by
......@@ -47,8 +48,8 @@ public class MetricExportProperties {
*
* @return the default trigger configuration
*/
public Export getDefault() {
return this.export;
public Trigger getDefault() {
return this;
}
/**
......@@ -58,30 +59,27 @@ public class MetricExportProperties {
*
* @return the writers
*/
public Map<String, Export> getWriters() {
return this.writers;
public Map<String, SpecificTrigger> getTriggers() {
return this.triggers;
}
public Redis getRedis() {
return redis;
}
public void setRedis(Redis redis) {
this.redis = redis;
}
@PostConstruct
public void setUpDefaults() {
Export defaults = null;
for (Entry<String, Export> entry : this.writers.entrySet()) {
Trigger defaults = this;
for (Entry<String, SpecificTrigger> entry : this.triggers.entrySet()) {
String key = entry.getKey();
Export value = entry.getValue();
SpecificTrigger 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);
......@@ -89,10 +87,7 @@ public class MetricExportProperties {
if (defaults.getDelayMillis() == null) {
defaults.setDelayMillis(5000);
}
for (Export value : this.writers.values()) {
if (value.isIgnoreTimestamps() == null) {
value.setIgnoreTimestamps(defaults.isIgnoreTimestamps());
}
for (Trigger value : this.triggers.values()) {
if (value.isSendLatest() == null) {
value.setSendLatest(defaults.isSendLatest());
}
......@@ -110,11 +105,92 @@ public class MetricExportProperties {
this.enabled = enabled;
}
public static class Export {
/**
* Find a matching trigger configuration.
* @param name the bean name to match
* @return a matching configuration if there is one
*/
public Trigger findTrigger(String name) {
for (SpecificTrigger value : this.triggers.values()) {
if (PatternMatchUtils.simpleMatch(value.getNames(), name)) {
return value;
}
}
return this;
}
/**
* Trigger for specific bean names.
*/
public static class SpecificTrigger extends Trigger {
/**
* Names (or patterns) for bean names that this configuration applies to.
*/
private String[] names;
public String[] getNames() {
return this.names;
}
public void setNames(String[] names) {
this.names = names;
}
}
public static class Redis {
/**
* Prefix for redis repository if active. Should be unique for this JVM, but most
* useful if it also has the form "x.y.a.b" where "x.y" is globally unique across
* all processes sharing the same repository, "a" is unique to this physical
* process and "b" is unique to this logical process (this application). If you
* set spring.application.name elsewhere, then the default will be in the right
* form.
*/
@Value("spring.metrics.${random.value:0000}.${spring.application.name:application}")
private String prefix = "spring.metrics";
/**
* Key for redis repository export (if active). Should be globally unique for a
* system sharing a redis repository.
*/
private String key = "keys.spring.metrics";
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getAggregatePrefix() {
String[] tokens = StringUtils.delimitedListToStringArray(this.prefix, ".");
if (tokens.length > 1) {
if (StringUtils.hasText(tokens[1])) {
// If the prefix has 2 or more non-trivial parts, use the first 1
return tokens[0];
}
}
return this.prefix;
}
}
}
class Trigger {
/**
* Delay in milliseconds between export ticks. Metrics are exported to external
* sources on a schedule with this delay.
......@@ -132,24 +208,10 @@ public class MetricExportProperties {
*/
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;
}
......@@ -182,14 +244,6 @@ public class MetricExportProperties {
this.delayMillis = delayMillis;
}
public Boolean isIgnoreTimestamps() {
return this.ignoreTimestamps;
}
public void setIgnoreTimestamps(boolean ignoreTimestamps) {
this.ignoreTimestamps = ignoreTimestamps;
}
public Boolean isSendLatest() {
return this.sendLatest;
}
......@@ -197,19 +251,5 @@ public class MetricExportProperties {
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 this.export;
}
}
......@@ -20,7 +20,6 @@ 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;
......@@ -56,7 +55,7 @@ public class MetricExporters implements SchedulingConfigurer {
for (Entry<String, MetricWriter> entry : this.writers.entrySet()) {
String name = entry.getKey();
Export trigger = this.export.findExport(name);
Trigger trigger = this.export.findTrigger(name);
if (trigger != null) {
......@@ -67,7 +66,6 @@ public class MetricExporters implements SchedulingConfigurer {
exporter.setIncludes(trigger.getIncludes());
exporter.setExcludes(trigger.getExcludes());
}
exporter.setIgnoreTimestamps(trigger.isIgnoreTimestamps());
exporter.setSendLatest(trigger.isSendLatest());
this.exporters.put(name, exporter);
......
......@@ -910,7 +910,7 @@ used by default if you are on Java 8 or if you are using Dropwizard metrics.
[[production-ready-metric-writers]]
=== Metric writers and aggregation
=== Metric writers, exporters and aggregation
Spring Boot provides a couple of implementations of a marker interface called `Exporter`
which can be used to copy metric readings from the in-memory buffers to a place where they
......@@ -919,11 +919,20 @@ can be analysed and displayed. Indeed, if you provide a `@Bean` that implements
metric updates every 5 seconds (configured via `spring.metrics.export.delayMillis`) via a
`@Scheduled` annotation in `MetricRepositoryAutoConfiguration`.
The default exporter is a `MetricCopyExporter` which tries to optimize itself by not
copying values that haven't changed since it was last called. The optimization can be
switched off using a flag (`spring.metrics.export.ignoreTimestamps`). Note also that the
`MetricRegistry` has no support for timestamps, so the optimization is not available if
you are using Dropwizard metrics (all metrics will be copied on every tick).
The default exporter is a `MetricCopyExporter` which tries to optimize
itself by not copying values that haven't changed since it was last
called (the optimization can be switched off using a flag
`spring.metrics.export.sendLatest`). Note also that the Dropwizard
`MetricRegistry` has no support for timestamps, so the optimization is
not available if you are using Dropwizard metrics (all metrics will be
copied on every tick).
The default values for the export trigger (`delayMillis`, `includes`,
`excludes`, `ignoreTimestamps` and `sendLatest`) can be set as
`spring.metrics.export.*`. Individual values for specific
`MetricWriters` can be set as
`spring.metrics.export.triggers.<name>.*` where `<name>` is a bean
name (or pattern for matching bean names).
......@@ -942,19 +951,20 @@ Example:
[source,java,indent=0]
----
@Value("metrics.mysystem.${random.value:0000}.${spring.application.name:application}")
private String prefix = "metrics.mysystem";
@Value("${metrics.key:keys.mysystem}")
private String key = "METRICSKEY";
@Bean
MetricWriter metricWriter() {
return new RedisMetricRepository(connectionFactory, prefix, key);
MetricWriter metricWriter(MetricExportProperties export) {
return new RedisMetricRepository(connectionFactory,
export.getRedis().getPrefix(), export.getRedis().getKey());
}
----
.application.properties
[source,properties]
----
spring.metrics.export.redis.prefix: metrics.mysystem.${random.value:0000}.${spring.application.name:application}
spring.metrics.export.redis.key: keys.mysystem
----
The prefix is constructed with the application name at the end, so it can easily be used
to identify a group of processes with the same logical name later.
......@@ -967,6 +977,11 @@ start with the master prefix (like `metrics.mysystem.*` in the example above). I
efficient to read all the keys from a "master" repository like that, but inefficient to
read a subset with a longer prefix (e.g. using one of the writing repositories).
NOTE: the example above uses `MetricExportProperties` to inject and
extract the key and prefix. This is provided to you as a convenience
by Spring Boot, and the defaults for that will be sensible, but there
is nothing to stop you using your own values as long as they follow
the recommendations.
[[production-ready-metric-writers-export-to-open-tdsb]]
......@@ -1064,6 +1079,9 @@ results to the "/metrics" endpoint. Example:
[source,java,indent=0]
----
@Autowired
private MetricExportProperties export;
@Bean
public PublicMetrics metricsAggregate() {
return new MetricReaderPublicMetrics(aggregates());
......@@ -1072,7 +1090,7 @@ results to the "/metrics" endpoint. Example:
@Bean
protected MetricReader repository(RedisConnectionFactory connectionFactory) {
RedisMetricRepository repository = new RedisMetricRepository(connectionFactory,
"mysystem", "myorg.keys");
this.export.getRedis().getAggregatePrefix(), this.export.getRedis().getKey());
return repository;
}
......@@ -1083,6 +1101,9 @@ results to the "/metrics" endpoint. Example:
}
----
NOTE: the example above uses `MetricExportProperties` to inject and
extract the key and prefix. This is provided to you as a convenience
by Spring Boot, and the defaults for that will be sensible.
[[production-ready-dropwizard-metrics]]
......
......@@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.metrics.aggregate.AggregateMetricReader;
import org.springframework.boot.actuate.metrics.export.MetricExportProperties;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
import org.springframework.context.annotation.Bean;
......@@ -33,7 +34,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
public class AggregateMetricsConfiguration {
@Autowired
private ExportProperties export;
private MetricExportProperties export;
@Autowired
private RedisConnectionFactory connectionFactory;
......@@ -45,7 +46,7 @@ public class AggregateMetricsConfiguration {
private MetricReader globalMetricsForAggregation() {
return new RedisMetricRepository(this.connectionFactory,
this.export.getAggregatePrefix(), this.export.getKey());
this.export.getRedis().getAggregatePrefix(), this.export.getRedis().getKey());
}
private MetricReader aggregatesMetricReader() {
......
/*
* 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 sample.metrics.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
@ConfigurationProperties("redis.metrics.export")
class ExportProperties {
private String prefix = "spring.metrics";
private String key = "keys.spring.metrics";
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getKey() {
return this.key;
}
public void setKey(String key) {
this.key = key;
}
public String getAggregatePrefix() {
String[] tokens = StringUtils.delimitedListToStringArray(this.prefix, ".");
if (tokens.length > 1) {
if (StringUtils.hasText(tokens[1])) {
// If the prefix has 2 or more non-trivial parts, use the first 1
return tokens[0];
}
}
return this.prefix;
}
}
\ No newline at end of file
......@@ -19,20 +19,19 @@ package sample.metrics.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.metrics.export.MetricExportProperties;
import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter;
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.jmx.export.MBeanExporter;
@SpringBootApplication
@EnableConfigurationProperties(ExportProperties.class)
public class SampleRedisExportApplication {
@Autowired
private ExportProperties export;
private MetricExportProperties export;
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleRedisExportApplication.class, args);
......@@ -41,8 +40,8 @@ public class SampleRedisExportApplication {
@Bean
public RedisMetricRepository redisMetricWriter(
RedisConnectionFactory connectionFactory) {
return new RedisMetricRepository(connectionFactory, this.export.getPrefix(),
this.export.getKey());
return new RedisMetricRepository(connectionFactory, this.export.getRedis().getPrefix(),
this.export.getRedis().getKey());
}
@Bean
......
service.name: Phil
redis.metrics.export.prefix: metrics.sample.${random.value:0000}.${spring.application.name:application}
redis.metrics.export.key: keys.metrics.sample
spring.metrics.export.redis.prefix: metrics.sample.${random.value:0000}.${spring.application.name:application}
spring.metrics.export.redis.key: keys.metrics.sample
spring.jmx.default-domain: org.springframework.boot
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment