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 c1e9112f74c..4f73a2521f8 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 @@ -21,11 +21,13 @@ import java.util.concurrent.Executor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RichGaugeReaderPublicMetrics; import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.boot.actuate.metrics.export.Exporter; +import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader; import org.springframework.boot.actuate.metrics.repository.InMemoryMetricRepository; import org.springframework.boot.actuate.metrics.repository.MetricRepository; import org.springframework.boot.actuate.metrics.rich.RichGaugeReader; @@ -184,6 +186,12 @@ public class MetricRepositoryAutoConfiguration { public MetricWriter primaryMetricWriter(List writers) { return new CompositeMetricWriter(writers); } + + @Bean + public PublicMetrics codahalePublicMetrics(MetricRegistry metricRegistry) { + MetricRegistryMetricReader reader = new MetricRegistryMetricReader(metricRegistry); + return new MetricReaderPublicMetrics(reader); + } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/reader/MetricRegistryMetricReader.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/reader/MetricRegistryMetricReader.java new file mode 100644 index 00000000000..23bc1692815 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/reader/MetricRegistryMetricReader.java @@ -0,0 +1,235 @@ +/* + * Copyright 2013-2104 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.reader; + +import java.beans.PropertyDescriptor; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.util.ClassUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.MetricRegistryListener; +import com.codahale.metrics.Sampling; +import com.codahale.metrics.Timer; + +/** + * A Spring Boot {@link MetricReader} that reads metrics from a Codahale + * {@link MetricRegistry}. Gauges and Counters are reflected as a single value. Timers, + * Meters and Histograms are expanded into sets of metrics containing all the properties + * of type Number. + * + * @author Dave Syer + * + */ +public class MetricRegistryMetricReader implements MetricReader, MetricRegistryListener { + + private static Map, Set> numberKeys = new ConcurrentHashMap, Set>(); + + private MetricRegistry registry; + + private Map names = new HashMap(); + + private MultiValueMap reverse = new LinkedMultiValueMap(); + + public MetricRegistryMetricReader(MetricRegistry registry) { + this.registry = registry; + registry.addListener(this); + } + + @Override + public Metric findOne(String metricName) { + if (!names.containsKey(metricName)) { + return null; + } + com.codahale.metrics.Metric metric = registry.getMetrics().get( + names.get(metricName)); + if (metric instanceof Counter) { + Counter counter = (Counter) metric; + return new Metric(metricName, counter.getCount()); + } + if (metric instanceof Gauge) { + @SuppressWarnings("unchecked") + Gauge value = (Gauge) metric; + return new Metric(metricName, value.getValue()); + } + if (metric instanceof Sampling) { + if (metricName.contains(".snapshot.")) { + Number value = getMetric(((Sampling) metric).getSnapshot(), metricName); + if (metric instanceof Timer) { + // convert back to MILLISEC + value = TimeUnit.MILLISECONDS.convert(value.longValue(), + TimeUnit.NANOSECONDS); + } + return new Metric(metricName, value); + } + } + return new Metric(metricName, getMetric(metric, metricName)); + } + + @Override + public Iterable> findAll() { + return new Iterable>() { + @Override + public Iterator> iterator() { + return new MetricRegistryIterator(); + } + }; + } + + @Override + public long count() { + return names.size(); + } + + @Override + public void onGaugeAdded(String name, Gauge gauge) { + names.put(name, name); + reverse.add(name, name); + } + + @Override + public void onGaugeRemoved(String name) { + remove(name); + } + + public void onCounterAdded(String name, Counter counter) { + names.put(name, name); + reverse.add(name, name); + } + + @Override + public void onCounterRemoved(String name) { + remove(name); + } + + @Override + public void onHistogramAdded(String name, Histogram histogram) { + for (String key : getNumberKeys(histogram)) { + String metricName = name + "." + key; + names.put(metricName, name); + reverse.add(name, metricName); + } + for (String key : getNumberKeys(histogram.getSnapshot())) { + String metricName = name + ".snapshot." + key; + names.put(metricName, name); + reverse.add(name, metricName); + } + } + + @Override + public void onHistogramRemoved(String name) { + remove(name); + } + + @Override + public void onMeterAdded(String name, Meter meter) { + for (String key : getNumberKeys(meter)) { + String metricName = name + "." + key; + names.put(metricName, name); + reverse.add(name, metricName); + } + } + + @Override + public void onMeterRemoved(String name) { + remove(name); + } + + @Override + public void onTimerAdded(String name, Timer timer) { + for (String key : getNumberKeys(timer)) { + String metricName = name + "." + key; + names.put(metricName, name); + reverse.add(name, metricName); + } + for (String key : getNumberKeys(timer.getSnapshot())) { + String metricName = name + ".snapshot." + key; + names.put(metricName, name); + reverse.add(name, metricName); + } + } + + @Override + public void onTimerRemoved(String name) { + remove(name); + } + + private void remove(String name) { + for (String key : reverse.get(name)) { + names.remove(name + "." + key); + } + reverse.remove(name); + } + + private class MetricRegistryIterator implements Iterator> { + + private Iterator iterator; + + public MetricRegistryIterator() { + this.iterator = new HashSet( + MetricRegistryMetricReader.this.names.keySet()).iterator(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Metric next() { + String name = iterator.next(); + return MetricRegistryMetricReader.this.findOne(name); + } + + } + + private static Set getNumberKeys(Object metric) { + Set result = numberKeys.containsKey(metric.getClass()) ? numberKeys + .get(metric.getClass()) : new HashSet(); + if (result.isEmpty()) { + for (PropertyDescriptor descriptor : BeanUtils.getPropertyDescriptors(metric + .getClass())) { + if (ClassUtils.isAssignable(Number.class, descriptor.getPropertyType())) { + result.add(descriptor.getName()); + } + } + numberKeys.put(metric.getClass(), result); + } + return result; + } + + private static Number getMetric(Object metric, String metricName) { + String key = StringUtils.getFilenameExtension(metricName); + return (Number) new BeanWrapperImpl(metric).getPropertyValue(key); + } + +}