From 4390b7950155a5b9b6e9042ea4193a89d3336d17 Mon Sep 17 00:00:00 2001 From: Christian Dupuis Date: Thu, 30 Oct 2014 13:14:02 +0100 Subject: [PATCH] Retrieve servo metrics directly instead of grabbing them from JMX fixes #16 --- .../eureka/EurekaClientAutoConfiguration.java | 9 +- .../netflix/servo/ServoMetricReader.java | 107 ++++++++++++------ .../servo/ServoMetricsAutoConfiguration.java | 17 ++- .../netflix/servo/ServoPublicMetrics.java | 12 +- 4 files changed, 87 insertions(+), 58 deletions(-) diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java index 1fdb41b3..8bc06f97 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java @@ -16,10 +16,8 @@ package org.springframework.cloud.netflix.eureka; import javax.annotation.PostConstruct; -import javax.management.MBeanServer; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -70,11 +68,8 @@ public class EurekaClientAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnBean(MBeanServer.class) - @ConditionalOnExpression("${spring.jmx.enabled:true}") - public EurekaHealthIndicator eurekaHealthIndicator(MBeanServer server, - EurekaInstanceConfig config) { - return new EurekaHealthIndicator(discoveryClient, new ServoMetricReader(server), + public EurekaHealthIndicator eurekaHealthIndicator(EurekaInstanceConfig config) { + return new EurekaHealthIndicator(discoveryClient, new ServoMetricReader(), config); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricReader.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricReader.java index bcf81e8d..29cbcd1d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricReader.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricReader.java @@ -13,72 +13,107 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.netflix.servo; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; - -import javax.management.MBeanServer; -import javax.management.ObjectInstance; -import javax.management.ObjectName; +import java.util.concurrent.TimeUnit; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.reader.MetricReader; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.publish.BaseMetricObserver; +import com.netflix.servo.publish.BasicMetricFilter; +import com.netflix.servo.publish.MetricObserver; +import com.netflix.servo.publish.MonitorRegistryMetricPoller; +import com.netflix.servo.publish.PollRunnable; +import com.netflix.servo.publish.PollScheduler; + /** + * {@link MetricReader} implementation that registers a {@link MetricObserver} with the + * Netflix Servo library and exposes Servo metrics to the /metric endpoint. + * * @author Dave Syer - * + * @author Christian Dupuis */ public class ServoMetricReader implements MetricReader { - private final MBeanServer server; + private static final Object monitor = new Object(); - public ServoMetricReader(MBeanServer server) { - this.server = server; + private final Map> metrics = new HashMap>(); + + public ServoMetricReader() { + List observers = new ArrayList(); + observers.add(new ServoMetricObserver(this.metrics)); + PollRunnable task = new PollRunnable(new MonitorRegistryMetricPoller(), + BasicMetricFilter.MATCH_ALL, true, observers); + + if (!PollScheduler.getInstance().isStarted()) { + PollScheduler.getInstance().start(); + } + // TODO Make poll interval configurable + PollScheduler.getInstance().addPoller(task, 5, TimeUnit.SECONDS); } @Override public Metric findOne(String metricName) { - return getServoMetrics().get(metricName); + synchronized (monitor) { + return this.metrics.get(metricName); + } } @Override public Iterable> findAll() { - return getServoMetrics().values(); + synchronized (monitor) { + return Collections.unmodifiableCollection(this.metrics.values()); + } } @Override public long count() { - return getServoMetrics().size(); + synchronized (monitor) { + return this.metrics.size(); + } } - - private Map> getServoMetrics() { - Map> metrics = getServoMetrics("COUNTER"); - metrics.putAll(getServoMetrics("GAUGE")); - return metrics; - } - - private Map> getServoMetrics(String type) { - Map> metrics = new HashMap>(); - try { - ObjectName name = new ObjectName("com.netflix.servo:type="+type+",*"); - Set beans = server.queryMBeans(name, null); - for (ObjectInstance bean : beans) { - // example: com.netflix.servo:name=DiscoveryClient_Failed,class=DiscoveryClient,type=COUNTER - String key = type.toLowerCase() + ".servo." - + bean.getObjectName().getKeyProperty("name"); - Object attribute = server.getAttribute(bean.getObjectName(), "value"); - if (attribute instanceof Number) { - Number value = (Number) attribute; - metrics.put(key, new Metric(key, value)); + + /** + * {@link MetricObserver} to convert Servo metrics into Spring Boot {@link Metric} instances. + */ + private static final class ServoMetricObserver extends BaseMetricObserver { + + private final Map> metrics; + + public ServoMetricObserver(Map> metrics) { + super("spring-boot"); + this.metrics = metrics; + } + + @Override + public void updateImpl(List servoMetrics) { + Map> newMetrics = new HashMap>(); + for (com.netflix.servo.Metric servoMetric : servoMetrics) { + MonitorConfig config = servoMetric.getConfig(); + String type = config.getTags().getValue("type"); + String key = new StringBuilder(type).append(".servo.").append(config.getName()) + .toString().toLowerCase(); + + if (servoMetric.hasNumberValue()) { + newMetrics.put(key, new Metric(key, servoMetric.getNumberValue(), + new Date(servoMetric.getTimestamp()))); } } + + synchronized (monitor) { + this.metrics.clear(); + this.metrics.putAll(newMetrics); + } } - catch (Exception e) { - // Really? - } - return metrics; } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java index eae0df05..e58195a5 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cloud.netflix.servo; -import javax.management.MBeanServer; +package org.springframework.cloud.netflix.servo; import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration; @@ -24,30 +23,28 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.netflix.servo.monitor.Monitors; /** + * Auto configuration to configure Servo support. + * * @author Dave Syer - * + * @author Christian Dupuis */ @Configuration @ConditionalOnClass({ Monitors.class, MetricReader.class }) @ConditionalOnBean(MetricReader.class) @AutoConfigureBefore(EndpointAutoConfiguration.class) -@AutoConfigureAfter({MetricRepositoryAutoConfiguration.class, JmxAutoConfiguration.class}) -@ConditionalOnExpression("${spring.jmx.enabled:true}") +@AutoConfigureAfter({MetricRepositoryAutoConfiguration.class}) public class ServoMetricsAutoConfiguration { @Bean @ConditionalOnMissingBean - public ServoPublicMetrics servoPublicMetrics(MetricReader reader, MBeanServer server) { - return new ServoPublicMetrics(reader, server); + public ServoPublicMetrics servoPublicMetrics(MetricReader reader) { + return new ServoPublicMetrics(reader); } - } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoPublicMetrics.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoPublicMetrics.java index 75259297..245bb97d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoPublicMetrics.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoPublicMetrics.java @@ -13,27 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.cloud.netflix.servo; import java.util.Collection; -import javax.management.MBeanServer; - +import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.VanillaPublicMetrics; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.reader.MetricReader; /** + * {@link PublicMetrics} implementation for Servo metrics. + * * @author Dave Syer - * + * @author Christian Dupuis */ public class ServoPublicMetrics extends VanillaPublicMetrics { private final ServoMetricReader servo; - public ServoPublicMetrics(MetricReader reader, MBeanServer server) { + public ServoPublicMetrics(MetricReader reader) { super(reader); - this.servo = new ServoMetricReader(server); + this.servo = new ServoMetricReader(); } @Override