Commit 4390b795 by Christian Dupuis

Retrieve servo metrics directly instead of grabbing them from JMX

fixes #16
parent c4a53a52
...@@ -16,10 +16,8 @@ ...@@ -16,10 +16,8 @@
package org.springframework.cloud.netflix.eureka; package org.springframework.cloud.netflix.eureka;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.management.MBeanServer;
import org.springframework.beans.factory.annotation.Autowired; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
...@@ -70,11 +68,8 @@ public class EurekaClientAutoConfiguration { ...@@ -70,11 +68,8 @@ public class EurekaClientAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnBean(MBeanServer.class) public EurekaHealthIndicator eurekaHealthIndicator(EurekaInstanceConfig config) {
@ConditionalOnExpression("${spring.jmx.enabled:true}") return new EurekaHealthIndicator(discoveryClient, new ServoMetricReader(),
public EurekaHealthIndicator eurekaHealthIndicator(MBeanServer server,
EurekaInstanceConfig config) {
return new EurekaHealthIndicator(discoveryClient, new ServoMetricReader(server),
config); config);
} }
......
...@@ -13,72 +13,107 @@ ...@@ -13,72 +13,107 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.cloud.netflix.servo; 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.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.concurrent.TimeUnit;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader; 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;
/** /**
* @author Dave Syer * {@link MetricReader} implementation that registers a {@link MetricObserver} with the
* Netflix Servo library and exposes Servo metrics to the <code>/metric</code> endpoint.
* *
* @author Dave Syer
* @author Christian Dupuis
*/ */
public class ServoMetricReader implements MetricReader { public class ServoMetricReader implements MetricReader {
private final MBeanServer server; private static final Object monitor = new Object();
public ServoMetricReader(MBeanServer server) { private final Map<String, Metric<?>> metrics = new HashMap<String, Metric<?>>();
this.server = server;
public ServoMetricReader() {
List<MetricObserver> observers = new ArrayList<MetricObserver>();
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 @Override
public Metric<?> findOne(String metricName) { public Metric<?> findOne(String metricName) {
return getServoMetrics().get(metricName); synchronized (monitor) {
return this.metrics.get(metricName);
}
} }
@Override @Override
public Iterable<Metric<?>> findAll() { public Iterable<Metric<?>> findAll() {
return getServoMetrics().values(); synchronized (monitor) {
return Collections.unmodifiableCollection(this.metrics.values());
}
} }
@Override @Override
public long count() { public long count() {
return getServoMetrics().size(); synchronized (monitor) {
return this.metrics.size();
}
} }
private Map<String,Metric<?>> getServoMetrics() { /**
Map<String, Metric<?>> metrics = getServoMetrics("COUNTER"); * {@link MetricObserver} to convert Servo metrics into Spring Boot {@link Metric} instances.
metrics.putAll(getServoMetrics("GAUGE")); */
return metrics; private static final class ServoMetricObserver extends BaseMetricObserver {
private final Map<String, Metric<?>> metrics;
public ServoMetricObserver(Map<String, Metric<?>> metrics) {
super("spring-boot");
this.metrics = metrics;
} }
private Map<String,Metric<?>> getServoMetrics(String type) { @Override
Map<String,Metric<?>> metrics = new HashMap<String, Metric<?>>(); public void updateImpl(List<com.netflix.servo.Metric> servoMetrics) {
try { Map<String, Metric<?>> newMetrics = new HashMap<String, Metric<?>>();
ObjectName name = new ObjectName("com.netflix.servo:type="+type+",*"); for (com.netflix.servo.Metric servoMetric : servoMetrics) {
Set<ObjectInstance> beans = server.queryMBeans(name, null); MonitorConfig config = servoMetric.getConfig();
for (ObjectInstance bean : beans) { String type = config.getTags().getValue("type");
// example: com.netflix.servo:name=DiscoveryClient_Failed,class=DiscoveryClient,type=COUNTER String key = new StringBuilder(type).append(".servo.").append(config.getName())
String key = type.toLowerCase() + ".servo." .toString().toLowerCase();
+ bean.getObjectName().getKeyProperty("name");
Object attribute = server.getAttribute(bean.getObjectName(), "value"); if (servoMetric.hasNumberValue()) {
if (attribute instanceof Number) { newMetrics.put(key, new Metric<Number>(key, servoMetric.getNumberValue(),
Number value = (Number) attribute; new Date(servoMetric.getTimestamp())));
metrics.put(key, new Metric<Number>(key, value));
} }
} }
synchronized (monitor) {
this.metrics.clear();
this.metrics.putAll(newMetrics);
} }
catch (Exception e) {
// Really?
} }
return metrics;
} }
} }
...@@ -13,9 +13,8 @@ ...@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration;
...@@ -24,30 +23,28 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; ...@@ -24,30 +23,28 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.netflix.servo.monitor.Monitors; import com.netflix.servo.monitor.Monitors;
/** /**
* @author Dave Syer * Auto configuration to configure Servo support.
* *
* @author Dave Syer
* @author Christian Dupuis
*/ */
@Configuration @Configuration
@ConditionalOnClass({ Monitors.class, MetricReader.class }) @ConditionalOnClass({ Monitors.class, MetricReader.class })
@ConditionalOnBean(MetricReader.class) @ConditionalOnBean(MetricReader.class)
@AutoConfigureBefore(EndpointAutoConfiguration.class) @AutoConfigureBefore(EndpointAutoConfiguration.class)
@AutoConfigureAfter({MetricRepositoryAutoConfiguration.class, JmxAutoConfiguration.class}) @AutoConfigureAfter({MetricRepositoryAutoConfiguration.class})
@ConditionalOnExpression("${spring.jmx.enabled:true}")
public class ServoMetricsAutoConfiguration { public class ServoMetricsAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ServoPublicMetrics servoPublicMetrics(MetricReader reader, MBeanServer server) { public ServoPublicMetrics servoPublicMetrics(MetricReader reader) {
return new ServoPublicMetrics(reader, server); return new ServoPublicMetrics(reader);
} }
} }
...@@ -13,27 +13,29 @@ ...@@ -13,27 +13,29 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.cloud.netflix.servo; package org.springframework.cloud.netflix.servo;
import java.util.Collection; 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.endpoint.VanillaPublicMetrics;
import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.reader.MetricReader;
/** /**
* @author Dave Syer * {@link PublicMetrics} implementation for Servo metrics.
* *
* @author Dave Syer
* @author Christian Dupuis
*/ */
public class ServoPublicMetrics extends VanillaPublicMetrics { public class ServoPublicMetrics extends VanillaPublicMetrics {
private final ServoMetricReader servo; private final ServoMetricReader servo;
public ServoPublicMetrics(MetricReader reader, MBeanServer server) { public ServoPublicMetrics(MetricReader reader) {
super(reader); super(reader);
this.servo = new ServoMetricReader(server); this.servo = new ServoMetricReader();
} }
@Override @Override
......
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