From 2be6b3e419c2801f5545ef9cadef295888f7e78e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 13 Jun 2014 14:26:22 +0200 Subject: [PATCH] Flexible registration of additional PublicMetrics This commit permits the use of several PublicMetrics instances by default. Previously, only one PublicMetrics service could be specified and a user configuration would remove all the defaulting. VanillaPublicMetrics now takes a collection of PublicMetrics and invokes them in sequence to build the final collection of metrics. The system-related metrics have been moved to SystemPublicMetrics and are registered by default. Also updated the documentation to mention this feature and how it could be fully overridden. Fixes gh-1094 --- .../EndpointAutoConfiguration.java | 21 ++- .../boot/actuate/endpoint/PublicMetrics.java | 1 + .../actuate/endpoint/SystemPublicMetrics.java | 136 ++++++++++++++++++ .../endpoint/VanillaPublicMetrics.java | 110 ++------------ .../EndpointAutoConfigurationTests.java | 77 +++++++--- .../endpoint/SystemPublicMetricsTests.java | 61 ++++++++ .../endpoint/VanillaPublicMetricsTests.java | 48 ++++--- .../asciidoc/production-ready-features.adoc | 24 +++- 8 files changed, 334 insertions(+), 144 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SystemPublicMetrics.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SystemPublicMetricsTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index 53b24e203b..afc1f5b8d2 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -35,6 +36,7 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; +import org.springframework.boot.actuate.endpoint.SystemPublicMetrics; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.endpoint.VanillaPublicMetrics; import org.springframework.boot.actuate.health.HealthAggregator; @@ -68,6 +70,7 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; * @author Phillip Webb * @author Greg Turnquist * @author Christian Dupuis + * @author Stephane Nicoll */ @Configuration public class EndpointAutoConfiguration { @@ -85,7 +88,7 @@ public class EndpointAutoConfiguration { private MetricReader metricRepository = new InMemoryMetricRepository(); @Autowired(required = false) - private PublicMetrics metrics; + private Collection allMetrics; @Autowired(required = false) private TraceRepository traceRepository = new InMemoryTraceRepository(); @@ -126,10 +129,8 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean public MetricsEndpoint metricsEndpoint() { - if (this.metrics == null) { - this.metrics = new VanillaPublicMetrics(this.metricRepository); - } - return new MetricsEndpoint(this.metrics); + PublicMetrics metrics = new VanillaPublicMetrics(this.metricRepository, this.allMetrics); + return new MetricsEndpoint(metrics); } @Bean @@ -178,6 +179,16 @@ public class EndpointAutoConfiguration { return endpoint; } + @Configuration + protected static class CorePublicMetrics { + + @Bean + SystemPublicMetrics systemPublicMetrics() { + return new SystemPublicMetrics(); + } + + } + @Configuration protected static class InfoPropertiesConfiguration { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/PublicMetrics.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/PublicMetrics.java index dbc0e3a7e3..5b21667a35 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/PublicMetrics.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/PublicMetrics.java @@ -25,6 +25,7 @@ import org.springframework.boot.actuate.metrics.Metric; * * @author Dave Syer * @see VanillaPublicMetrics + * @see SystemPublicMetrics */ public interface PublicMetrics { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SystemPublicMetrics.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SystemPublicMetrics.java new file mode 100644 index 0000000000..565bf1e789 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/SystemPublicMetrics.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2014 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.endpoint; + +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; +import java.lang.management.ThreadMXBean; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.util.StringUtils; + +/** + * A {@link PublicMetrics} implementation that provides various + * system-related metrics. + * + * @author Dave Syer + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class SystemPublicMetrics implements PublicMetrics { + + private long timestamp; + + public SystemPublicMetrics() { + this.timestamp = System.currentTimeMillis(); + } + + @Override + public Collection> metrics() { + Collection> result = new LinkedHashSet>(); + + addBasicMetrics(result); + addHeapMetrics(result); + addThreadMetrics(result); + addClassLoadingMetrics(result); + addGarbageCollectionMetrics(result); + + return result; + } + + + /** + * Add basic system metrics. + */ + protected void addBasicMetrics(Collection> result) { + result.add(new Metric("mem", + Runtime.getRuntime().totalMemory() / 1024)); + result.add(new Metric("mem.free", Runtime.getRuntime() + .freeMemory() / 1024)); + result.add(new Metric("processors", Runtime.getRuntime() + .availableProcessors())); + // Add JVM up time in ms + result.add(new Metric("uptime", ManagementFactory + .getRuntimeMXBean().getUptime())); + result.add(new Metric("instance.uptime", System.currentTimeMillis() + - this.timestamp)); + } + + /** + * Add JVM heap metrics. + */ + protected void addHeapMetrics(Collection> result) { + MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean() + .getHeapMemoryUsage(); + result.add(new Metric("heap.committed", memoryUsage.getCommitted() / 1024)); + result.add(new Metric("heap.init", memoryUsage.getInit() / 1024)); + result.add(new Metric("heap.used", memoryUsage.getUsed() / 1024)); + result.add(new Metric("heap", memoryUsage.getMax() / 1024)); + } + + /** + * Add thread metrics. + */ + protected void addThreadMetrics(Collection> result) { + ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); + result.add(new Metric("threads.peak", (long) threadMxBean + .getPeakThreadCount())); + result.add(new Metric("threads.daemon", (long) threadMxBean + .getDaemonThreadCount())); + result.add(new Metric("threads", (long) threadMxBean.getThreadCount())); + } + + /** + * Add class loading metrics. + */ + protected void addClassLoadingMetrics(Collection> result) { + ClassLoadingMXBean classLoadingMxBean = ManagementFactory.getClassLoadingMXBean(); + result.add(new Metric("classes", (long) classLoadingMxBean + .getLoadedClassCount())); + result.add(new Metric("classes.loaded", classLoadingMxBean + .getTotalLoadedClassCount())); + result.add(new Metric("classes.unloaded", classLoadingMxBean + .getUnloadedClassCount())); + } + + /** + * Add garbage collection metrics. + */ + protected void addGarbageCollectionMetrics(Collection> result) { + List garbageCollectorMxBeans = ManagementFactory + .getGarbageCollectorMXBeans(); + for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) { + String name = beautifyGcName(garbageCollectorMXBean.getName()); + result.add(new Metric("gc." + name + ".count", garbageCollectorMXBean.getCollectionCount())); + result.add(new Metric("gc." + name + ".time", garbageCollectorMXBean.getCollectionTime())); + } + } + + /** + * Turn GC names like 'PS Scavenge' or 'PS MarkSweep' into something that is more + * metrics friendly. + */ + private String beautifyGcName(String name) { + return StringUtils.replace(name, " ", "_").toLowerCase(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetrics.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetrics.java index b570a34fe7..090f382d86 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetrics.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetrics.java @@ -16,36 +16,37 @@ package org.springframework.boot.actuate.endpoint; -import java.lang.management.ClassLoadingMXBean; -import java.lang.management.GarbageCollectorMXBean; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryUsage; -import java.lang.management.ThreadMXBean; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; -import java.util.List; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Default implementation of {@link PublicMetrics} that exposes all metrics from a - * {@link MetricReader} along with memory information. + * {@link MetricReader} along with a collection of configurable {@link PublicMetrics} + * instances. * * @author Dave Syer * @author Christian Dupuis + * @author Stephane Nicoll */ public class VanillaPublicMetrics implements PublicMetrics { private final MetricReader reader; - private long timestamp; + private final Collection publicMetrics; - public VanillaPublicMetrics(MetricReader reader) { + public VanillaPublicMetrics(MetricReader reader, Collection publicMetrics) { Assert.notNull(reader, "MetricReader must not be null"); + Assert.notNull(publicMetrics, "PublicMetrics must not be null"); this.reader = reader; - this.timestamp = System.currentTimeMillis(); + this.publicMetrics = publicMetrics; + } + + public VanillaPublicMetrics(MetricReader reader) { + this(reader, Collections.emptyList()); } @Override @@ -54,92 +55,11 @@ public class VanillaPublicMetrics implements PublicMetrics { for (Metric metric : this.reader.findAll()) { result.add(metric); } - - addMetrics(result); - addHeapMetrics(result); - addThreadMetrics(result); - addClassLoadingMetrics(result); - addGarbageCollecitonMetrics(result); + for (PublicMetrics publicMetric : publicMetrics) { + result.addAll(publicMetric.metrics()); + } return result; } - /** - * Add basic system metrics. - */ - protected void addMetrics(Collection> result) { - result.add(new Metric("mem", - new Long(Runtime.getRuntime().totalMemory()) / 1024)); - result.add(new Metric("mem.free", new Long(Runtime.getRuntime() - .freeMemory()) / 1024)); - result.add(new Metric("processors", Runtime.getRuntime() - .availableProcessors())); - // Add JVM uptime in ms - result.add(new Metric("uptime", new Long(ManagementFactory - .getRuntimeMXBean().getUptime()))); - result.add(new Metric("instance.uptime", System.currentTimeMillis() - - this.timestamp)); - } - - /** - * Add JVM heap metrics. - */ - protected void addHeapMetrics(Collection> result) { - MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean() - .getHeapMemoryUsage(); - result.add(new Metric("heap.committed", memoryUsage.getCommitted() / 1024)); - result.add(new Metric("heap.init", memoryUsage.getInit() / 1024)); - result.add(new Metric("heap.used", memoryUsage.getUsed() / 1024)); - result.add(new Metric("heap", memoryUsage.getMax() / 1024)); - } - - /** - * Add thread metrics. - */ - protected void addThreadMetrics(Collection> result) { - ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); - result.add(new Metric("threads.peak", new Long(threadMxBean - .getPeakThreadCount()))); - result.add(new Metric("threads.daemon", new Long(threadMxBean - .getDaemonThreadCount()))); - result.add(new Metric("threads", new Long(threadMxBean.getThreadCount()))); - } - - /** - * Add class loading metrics. - */ - protected void addClassLoadingMetrics(Collection> result) { - ClassLoadingMXBean classLoadingMxBean = ManagementFactory.getClassLoadingMXBean(); - result.add(new Metric("classes", new Long(classLoadingMxBean - .getLoadedClassCount()))); - result.add(new Metric("classes.loaded", new Long(classLoadingMxBean - .getTotalLoadedClassCount()))); - result.add(new Metric("classes.unloaded", new Long(classLoadingMxBean - .getUnloadedClassCount()))); - } - - /** - * Add garbage collection metrics. - */ - protected void addGarbageCollecitonMetrics(Collection> result) { - List garbageCollectorMxBeans = ManagementFactory - .getGarbageCollectorMXBeans(); - for (int i = 0; i < garbageCollectorMxBeans.size(); i++) { - GarbageCollectorMXBean garbageCollectorMXBean = garbageCollectorMxBeans - .get(i); - String name = beautifyGcName(garbageCollectorMXBean.getName()); - result.add(new Metric("gc." + name + ".count", new Long( - garbageCollectorMXBean.getCollectionCount()))); - result.add(new Metric("gc." + name + ".time", new Long( - garbageCollectorMXBean.getCollectionTime()))); - } - } - - /** - * Turn GC names like 'PS Scavenge' or 'PS MarkSweep' into something that is more - * metrics friendly. - */ - private String beautifyGcName(String name) { - return StringUtils.replace(name, " ", "_").toLowerCase(); - } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index a17d0a7d2f..d30973f0c8 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.autoconfigure; import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint; import org.springframework.boot.actuate.endpoint.BeansEndpoint; @@ -26,20 +25,28 @@ import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.InfoEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; +import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + /** * Tests for {@link EndpointAutoConfiguration}. * @@ -47,18 +54,12 @@ import static org.junit.Assert.assertTrue; * @author Phillip Webb * @author Greg Turnquist * @author Christian Dupuis + * @author Stephane Nicoll */ public class EndpointAutoConfigurationTests { private AnnotationConfigApplicationContext context; - @Before - public void setup() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EndpointAutoConfiguration.class); - this.context.refresh(); - } - @After public void close() { if (this.context != null) { @@ -68,6 +69,7 @@ public class EndpointAutoConfigurationTests { @Test public void endpoints() throws Exception { + load(EndpointAutoConfiguration.class); assertNotNull(this.context.getBean(BeansEndpoint.class)); assertNotNull(this.context.getBean(DumpEndpoint.class)); assertNotNull(this.context.getBean(EnvironmentEndpoint.class)); @@ -81,10 +83,8 @@ public class EndpointAutoConfigurationTests { @Test public void healthEndpoint() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EmbeddedDataSourceConfiguration.class, + load(EmbeddedDataSourceConfiguration.class, EndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); - this.context.refresh(); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); assertNotNull(bean); Health result = bean.invoke(); @@ -94,10 +94,8 @@ public class EndpointAutoConfigurationTests { @Test public void healthEndpointWithDefaultHealthIndicator() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EndpointAutoConfiguration.class, + load(EndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class); - this.context.refresh(); HealthEndpoint bean = this.context.getBean(HealthEndpoint.class); assertNotNull(bean); Health result = bean.invoke(); @@ -105,11 +103,33 @@ public class EndpointAutoConfigurationTests { } @Test - public void autoconfigurationAuditEndpoints() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(EndpointAutoConfiguration.class, + public void metricEndpointsHasSystemMetricsByDefault() { + load(EndpointAutoConfiguration.class); + MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); + Map metrics = endpoint.invoke(); + assertTrue(metrics.containsKey("mem")); + assertTrue(metrics.containsKey("heap.used")); + } + + @Test + public void metricEndpointCustomPublicMetrics() { + load(CustomPublicMetricsConfig.class, EndpointAutoConfiguration.class); + MetricsEndpoint endpoint = this.context.getBean(MetricsEndpoint.class); + Map metrics = endpoint.invoke(); + + // Custom metrics + assertTrue(metrics.containsKey("foo")); + + // System metrics still available + assertTrue(metrics.containsKey("mem")); + assertTrue(metrics.containsKey("heap.used")); + + } + + @Test + public void autoConfigurationAuditEndpoints() { + load(EndpointAutoConfiguration.class, ConditionEvaluationReport.class); - this.context.refresh(); assertNotNull(this.context.getBean(AutoConfigurationReportEndpoint.class)); } @@ -136,4 +156,25 @@ public class EndpointAutoConfigurationTests { assertNotNull(endpoint); assertNull(endpoint.invoke().get("git")); } + + private void load(Class... config) { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(config); + this.context.refresh(); + } + + + @Configuration + static class CustomPublicMetricsConfig { + + @Bean + PublicMetrics customPublicMetrics() { + return new PublicMetrics() { + @Override + public Collection> metrics() { + return Collections.>singleton(new Metric("foo", 1)); + } + }; + } + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SystemPublicMetricsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SystemPublicMetricsTests.java new file mode 100644 index 0000000000..948ef79c77 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/SystemPublicMetricsTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2014 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.endpoint; + +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.boot.actuate.metrics.Metric; + +/** + * Tests for {@link SystemPublicMetrics} + * + * @author Stephane Nicoll + */ +public class SystemPublicMetricsTests { + + @Test + public void testSystemMetrics() throws Exception { + SystemPublicMetrics publicMetrics = new SystemPublicMetrics(); + Map> results = new HashMap>(); + for (Metric metric : publicMetrics.metrics()) { + results.put(metric.getName(), metric); + } + assertTrue(results.containsKey("mem")); + assertTrue(results.containsKey("mem.free")); + assertTrue(results.containsKey("processors")); + assertTrue(results.containsKey("uptime")); + + assertTrue(results.containsKey("heap.committed")); + assertTrue(results.containsKey("heap.init")); + assertTrue(results.containsKey("heap.used")); + assertTrue(results.containsKey("heap")); + + assertTrue(results.containsKey("threads.peak")); + assertTrue(results.containsKey("threads.daemon")); + assertTrue(results.containsKey("threads")); + + assertTrue(results.containsKey("classes.loaded")); + assertTrue(results.containsKey("classes.unloaded")); + assertTrue(results.containsKey("classes")); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java index 90ed0e8e39..ab6335414b 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/VanillaPublicMetricsTests.java @@ -16,8 +16,12 @@ package org.springframework.boot.actuate.endpoint; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import org.junit.Test; @@ -25,14 +29,14 @@ import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Tests for {@link VanillaPublicMetrics}. * * @author Phillip Webb * @author Christian Dupuis + * @author Stephane Nicoll */ public class VanillaPublicMetricsTests { @@ -45,36 +49,38 @@ public class VanillaPublicMetricsTests { for (Metric metric : publicMetrics.metrics()) { results.put(metric.getName(), metric); } - assertTrue(results.containsKey("mem")); - assertTrue(results.containsKey("mem.free")); assertThat(results.get("a").getValue().doubleValue(), equalTo(0.5)); } @Test - public void testSystemMetrics() throws Exception { + public void testAdditionalMetrics() throws Exception { InMemoryMetricRepository repository = new InMemoryMetricRepository(); - repository.set(new Metric("a", 0.5, new Date())); - VanillaPublicMetrics publicMetrics = new VanillaPublicMetrics(repository); + Collection allMetrics = new ArrayList(); + allMetrics.add(new ImmutablePublicMetrics(new Metric("first", 2L))); + allMetrics.add(new ImmutablePublicMetrics(new Metric("second", 4L))); + + VanillaPublicMetrics publicMetrics = new VanillaPublicMetrics(repository, allMetrics); Map> results = new HashMap>(); for (Metric metric : publicMetrics.metrics()) { results.put(metric.getName(), metric); } - assertTrue(results.containsKey("mem")); - assertTrue(results.containsKey("mem.free")); - assertTrue(results.containsKey("processors")); - assertTrue(results.containsKey("uptime")); + assertTrue(results.containsKey("first")); + assertTrue(results.containsKey("second")); + assertEquals(2, results.size()); + } - assertTrue(results.containsKey("heap.committed")); - assertTrue(results.containsKey("heap.init")); - assertTrue(results.containsKey("heap.used")); - assertTrue(results.containsKey("heap")); - assertTrue(results.containsKey("threads.peak")); - assertTrue(results.containsKey("threads.daemon")); - assertTrue(results.containsKey("threads")); + private static class ImmutablePublicMetrics implements PublicMetrics { + private final Collection> metrics; - assertTrue(results.containsKey("classes.loaded")); - assertTrue(results.containsKey("classes.unloaded")); - assertTrue(results.containsKey("classes")); + private ImmutablePublicMetrics(Metric metrics) { + this.metrics = new LinkedHashSet>(); + this.metrics.addAll(Arrays.asList(metrics)); + } + + @Override + public Collection> metrics() { + return metrics; + } } } diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 53eade7c51..3b2c041929 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -583,8 +583,14 @@ documentation]. == Metrics Spring Boot Actuator includes a metrics service with ``gauge'' and ``counter'' support. A ``gauge'' records a single value; and a ``counter'' records a delta (an increment or -decrement). Metrics for all HTTP requests are automatically recorded, so if you hit the -`metrics` endpoint should should see a response similar to this: +decrement). Spring Boot Actuator also provides a +{sc-spring-boot-actuator}/endpoint/PublicMetrics.{sc-ext}[`PublicMetrics`] interface that +you can implement to expose metrics that you cannot record via one of those two mechanisms. Look +at {sc-spring-boot-actuator}/endpoint/SystemPublicMetrics.{sc-ext}[`SystemPublicMetrics`] +for an example. + +Metrics for all HTTP requests are automatically recorded, so if you hit the +`metrics` endpoint you should see a response similar to this: [source,json,indent=0] ---- @@ -612,9 +618,10 @@ decrement). Metrics for all HTTP requests are automatically recorded, so if you ---- Here we can see basic `memory`, `heap`, `class loading`, `processor` and `thread pool` -information along with some HTTP metrics. In this instance the `root` (``/'') and `/metrics` -URLs have returned `HTTP 200` responses `20` and `3` times respectively. It also appears -that the `root` URL returned `HTTP 401` (unauthorized) `4` times. +information provided by `SystemPublicMetrics` along with some HTTP metrics. In this +instance the `root` (``/'') and `/metrics` URLs have returned `HTTP 200` responses `20` +and `3` times respectively. It also appears that the `root` URL returned `HTTP 401` +(unauthorized) `4` times. The `gauge` shows the last response time for a request. So the last request to `root` took `2ms` to respond and the last to `/metrics` took `3ms`. @@ -661,6 +668,13 @@ TIP: You can use any string as a metric name but you should follow guidelines of store/graphing technology. Some good guidelines for Graphite are available on http://matt.aimonetti.net/posts/2013/06/26/practical-guide-to-graphite-monitoring/[Matt Aimonetti's Blog]. +[[production-ready-public-metrics]] +=== Adding your own public metrics + +To add additional metrics that are computed every time the metrics endpoint is invoked, +simply register additional `PublicMetrics` implementation bean(s). By default, all such +beans are gathered by the endpoint. You can easily change that by defining your own +`MetricsEndpoint`. [[production-ready-metric-repositories]]