Skip to content

Commit

Permalink
Add samples and tweak metrics reader/writers till they work
Browse files Browse the repository at this point in the history
  • Loading branch information
dsyer authored and wilkinsona committed May 13, 2015
1 parent 60a4943 commit 3fda744
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ static class DefaultMetricsExporterConfiguration {
@Autowired(required = false)
private List<MetricWriter> writers;

@Autowired
private MetricsProperties metrics;

@Autowired(required = false)
@Qualifier("actuatorMetricRepository")
private MetricWriter actuatorMetricRepository;
Expand All @@ -188,13 +191,20 @@ public MetricCopyExporter metricWritersMetricExporter(MetricReader reader) {
&& writers.contains(this.actuatorMetricRepository)) {
writers.remove(this.actuatorMetricRepository);
}
return new MetricCopyExporter(reader, new CompositeMetricWriter(writers)) {
MetricCopyExporter exporter = new MetricCopyExporter(reader,
new CompositeMetricWriter(writers)) {
@Scheduled(fixedDelayString = "${spring.metrics.export.delayMillis:5000}")
@Override
public void export() {
super.export();
}
};
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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,31 @@ public static class Export {
private boolean enabled = true;

/**
* Flag to switch off an optimization based on not exporting unchanged metric
* values.
* Flag to switch off any available optimizations based on not exporting unchanged
* metric values.
*/
private boolean ignoreTimestamps = false;

private String[] includes;

private String[] excludes;

public String[] getIncludes() {
return this.includes;
}

public void setIncludes(String[] includes) {
this.includes = includes;
}

public String[] getExcludes() {
return this.excludes;
}

public void setExcludes(String[] excludes) {
this.excludes = excludes;
}

public boolean isEnabled() {
return this.enabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private void update(InMemoryMetricRepository result, String key, Metric<?> metri
if (aggregate == null) {
aggregate = new Metric<Number>(name, metric.getValue(), metric.getTimestamp());
}
else if (key.startsWith("counter")) {
else if (key.contains("counter.")) {
// accumulate all values
aggregate = new Metric<Number>(name, metric.increment(
aggregate.getValue().intValue()).getValue(), metric.getTimestamp());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@ public MetricCopyExporter(MetricReader reader, MetricWriter writer) {
}

public void setIncludes(String... includes) {
this.includes = includes;
if (includes != null) {
this.includes = includes;
}
}

public void setExcludes(String... excludes) {
this.excludes = excludes;
if (excludes != null) {
this.excludes = excludes;
}
}

public MetricCopyExporter(MetricReader reader, MetricWriter writer, String prefix) {
Expand All @@ -61,7 +65,8 @@ public MetricCopyExporter(MetricReader reader, MetricWriter writer, String prefi

@Override
protected Iterable<Metric<?>> next(String group) {
if (this.includes.length == 0 && this.excludes.length == 0) {
if ((this.includes != null || this.includes.length == 0)
&& (this.excludes != null || this.excludes.length == 0)) {
return this.reader.findAll();
}
return new Iterable<Metric<?>>() {
Expand Down Expand Up @@ -108,7 +113,8 @@ private Metric<?> findNext() {
boolean matched = false;
while (this.iterator.hasNext() && !matched) {
metric = this.iterator.next();
if (MetricCopyExporter.this.includes.length == 0) {
if (MetricCopyExporter.this.includes == null
|| MetricCopyExporter.this.includes.length == 0) {
matched = true;
}
else {
Expand All @@ -119,10 +125,12 @@ private Metric<?> findNext() {
}
}
}
for (String pattern : MetricCopyExporter.this.excludes) {
if (PatternMatchUtils.simpleMatch(pattern, metric.getName())) {
matched = false;
break;
if (MetricCopyExporter.this.excludes != null) {
for (String pattern : MetricCopyExporter.this.excludes) {
if (PatternMatchUtils.simpleMatch(pattern, metric.getName())) {
matched = false;
break;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,22 @@

/**
* A naming strategy that just passes through the metric name, together with tags from a
* set of static values. Open TSDB requires at least one tag, so one is always added for
* you: the {@value #PREFIX_KEY} key is added with a unique value "spring.X" where X is an
* object hash code ID for this (the naming stategy). In most cases this will be unique
* enough to allow aggregation of the underlying metrics in Open TSDB, but normally it is
* best to provide your own tags, including a prefix if you know one (overwriting the
* default).
* set of static values. Open TSDB requires at least one tag, so tags are always added for
* you: the {@value #DOMAIN_KEY} key is added with a value "spring", and the
* {@value #PROCESS_KEY} key is added with a value equal to the object hash of "this" (the
* naming strategy). The "domain" value is a system identifier - it would be common to all
* processes in the same distributed system. In most cases this will be unique enough to
* allow aggregation of the underlying metrics in Open TSDB, but normally it is best to
* provide your own tags, including a prefix and process identifier if you know one
* (overwriting the default).
*
* @author Dave Syer
*/
public class DefaultOpenTsdbNamingStrategy implements OpenTsdbNamingStrategy {

public static final String PREFIX_KEY = "prefix";
public static final String DOMAIN_KEY = "domain";

public static final String PROCESS_KEY = "process";

/**
* Tags to apply to every metric. Open TSDB requires at least one tag, so a "prefix"
Expand All @@ -46,8 +50,8 @@ public class DefaultOpenTsdbNamingStrategy implements OpenTsdbNamingStrategy {
private Map<String, OpenTsdbName> cache = new HashMap<String, OpenTsdbName>();

public DefaultOpenTsdbNamingStrategy() {
this.tags.put(PREFIX_KEY,
"spring." + ObjectUtils.getIdentityHexString(this));
this.tags.put(DOMAIN_KEY, "org.springframework.metrics");
this.tags.put(PROCESS_KEY, ObjectUtils.getIdentityHexString(this));
}

public void setTags(Map<String, String> staticTags) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
* A {@link MetricRepository} implementation for a redis backend. Metric values are stored
Expand Down Expand Up @@ -92,6 +93,8 @@ public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
public RedisMetricRepository(RedisConnectionFactory redisConnectionFactory,
String prefix, String key) {
Assert.notNull(redisConnectionFactory, "RedisConnectionFactory must not be null");
Assert.state(StringUtils.hasText(prefix), "Prefix must be non-empty");
Assert.state(StringUtils.hasText(key), "Key must be non-empty");
this.redisOperations = RedisUtils.stringTemplate(redisConnectionFactory);
if (!prefix.endsWith(".")) {
prefix = prefix + ".";
Expand Down
43 changes: 29 additions & 14 deletions spring-boot-docs/src/main/asciidoc/production-ready-features.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -943,10 +943,10 @@ Example:
[source,java,indent=0]
----
@Value("${spring.application.name:application}.${random.value:0000}")
private String prefix = "metrics";
@Value("metrics.mysystem.${random.value:0000}.${spring.application.name:application}")
private String prefix = "metrics.mysystem";
@Value("${metrics.key:METRICSKEY}")
@Value("${metrics.key:keys.mysystem}")
private String key = "METRICSKEY";
@Bean
Expand All @@ -955,35 +955,50 @@ MetricWriter metricWriter() {
}
----

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.

NOTE: it's important to set both the key and the prefix. The key is used for all
repository operations, and can be shared by multiple repositories. If multiple
repositories share a key (like in the case where you need to aggregate across them), then
you normally have a read-only "master" repository that has a short, but identifiable,
prefix (like "metrics.mysystem"), and many write-only repositories with prefixes that
start with the master prefix (like `metrics.mysystem.*` in the example above). It is
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).



[[production-ready-metric-writers-export-to-open-tdsb]]
==== Example: Export to Open TSDB

If you provide a `@Bean` of type `OpenTsdbHttpMetricWriter` the metrics are exported to
http://opentsdb.net/[Open TSDB] for aggregation. The `OpenTsdbHttpMetricWriter` has a
http://opentsdb.net/[Open TSDB] for aggregation. The `OpenTsdbHttpMetricWriter` has a
`url` property that you need to set to the Open TSDB "/put" endpoint, e.g.
`http://localhost:4242/api/put`). It also has a `namingStrategy` that you can customize
or configure to make the metrics match the data structure you need on the server. By
default it just passes through the metric name as an Open TSDB metric name and adds a tag
"prefix" with value "spring.X" where "X" is the object hash of the default naming
strategy. Thus, after running the application and generating some metrics (e.g. by pinging
the home page) you can inspect the metrics in the TDB UI (http://localhost:4242 by
default). Example:
`http://localhost:4242/api/put`). It also has a `namingStrategy` that you can customize or
configure to make the metrics match the data structure you need on the server. By default
it just passes through the metric name as an Open TSDB metric name and adds a tag "domain"
with value "org.springframework.metrics" and another tag "process" with value equals to
the object hash of the naming strategy. Thus, after running the application and generating
some metrics (e.g. by pinging the home page) you can inspect the metrics in the TDB UI
(http://localhost:4242 by default). Example:

[source,indent=0]
----
curl localhost:4242/api/query?start=1h-ago&m=max:counter.status.200.root
[
{
"metric": "counter.status.200.root",
"tags": {
"prefix": "spring.b968a76"
"domain": "org.springframework.metrics",
"process": "b968a76"
},
"aggregateTags": [],
"dps": {
"1430492872": 2,
"1430492875": 6
}
}
}
]
----

Expand Down Expand Up @@ -1057,7 +1072,7 @@ results to the "/metrics" endpoint. Example:
@Bean
protected MetricReader repository(RedisConnectionFactory connectionFactory) {
RedisMetricRepository repository = new RedisMetricRepository(connectionFactory,
"metrics", "METRICSKEY");
"mysystem", "myorg.keys");
return repository;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
service.name: Phil
service.name: Phil
metrics.names.tags.process: ${spring.application.name:application}:${random.value:0000}
22 changes: 22 additions & 0 deletions spring-boot-samples/spring-boot-sample-metrics-redis/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,25 @@ the result in Redis, e.g.
2) "4"
----

There is also an `AggregateMetricReader` with public metrics in the application context,
and you can see the result in the "/metrics" (metrics with names in "aggregate.*").
The way the Redis repository was set up (with a random key in the metric names) makes the
aggregates work across restarts of the same application, or across a scaled up application
running in multiple processes. E.g.
[source,indent=0]
----
$ curl localhost:8080/metrics
{
...
"aggregate.application.counter.status.200.metrics": 12,
"aggregate.application.counter.status.200.root": 29,
"aggregate.application.gauge.response.metrics": 43,
"aggregate.application.gauge.response.root": 5,
"counter.status.200.root": 2,
"counter.status.200.metrics": 1,
"gauge.response.metrics": 43,
"gauge.response.root": 5
}
----
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.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.reader.MetricReader;
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;

/**
* @author Dave Syer
*/
@Configuration
public class AggregateMetricsConfiguration {

@Autowired
private ExportProperties export;

@Autowired
private RedisConnectionFactory connectionFactory;

@Bean
public PublicMetrics metricsAggregate() {
return new MetricReaderPublicMetrics(aggregatesMetricReader());
}

private MetricReader globalMetricsForAggregation() {
return new RedisMetricRepository(this.connectionFactory,
this.export.getAggregatePrefix(), this.export.getKey());
}

private MetricReader aggregatesMetricReader() {
AggregateMetricReader repository = new AggregateMetricReader(
globalMetricsForAggregation());
return repository;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package sample.metrics.redis;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;

@ConfigurationProperties("metrics.export")
class ExportProperties {
Expand All @@ -40,4 +41,15 @@ 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;
}

}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
service.name: Phil
metrics.export.prefix: ${spring.application.name:application}:${random.value:0000}
metrics.export.prefix: metrics.sample.${random.value:0000}.${spring.application.name:application}
metrics.export.key: keys.metrics.sample

0 comments on commit 3fda744

Please sign in to comment.