diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java index d6e413ac..7b808c7d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfiguration.java @@ -16,13 +16,16 @@ package org.springframework.cloud.netflix.eureka; import javax.annotation.PreDestroy; +import javax.management.MBeanServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; 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.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.netflix.servo.ServoMetricReader; import org.springframework.context.ApplicationListener; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Bean; @@ -34,6 +37,7 @@ import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.Ordered; import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.appinfo.InstanceInfo.InstanceStatus; import com.netflix.discovery.DiscoveryClient; import com.netflix.discovery.DiscoveryManager; @@ -118,4 +122,10 @@ public class EurekaClientConfiguration implements return DiscoveryManager.getInstance().getDiscoveryClient(); } + @Bean + @ConditionalOnMissingBean + public EurekaHealthIndicator eurekaHealthIndicator(MBeanServer server, EurekaInstanceConfig config) { + return new EurekaHealthIndicator(discoveryClient(), new ServoMetricReader(server), config); + } + } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthIndicator.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthIndicator.java new file mode 100644 index 00000000..dba9d1c6 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthIndicator.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-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.cloud.netflix.eureka; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.reader.MetricReader; + +import com.netflix.appinfo.EurekaInstanceConfig; +import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.shared.Application; +import com.netflix.discovery.shared.Applications; + +/** + * @author Dave Syer + * + */ +public class EurekaHealthIndicator implements HealthIndicator { + + private EurekaInstanceConfig instanceConfig; + + private MetricReader metrics; + + private int failCount = 0; + + private DiscoveryClient discovery; + + public EurekaHealthIndicator(DiscoveryClient discovery, MetricReader metrics, + EurekaInstanceConfig instanceConfig) { + super(); + this.discovery = discovery; + this.metrics = metrics; + this.instanceConfig = instanceConfig; + } + + @Override + public Health health() { + Builder builder = Health.unknown(); + Status status = getStatus(builder); + return builder.status(status).withDetail("applications", getApplications()) + .build(); + } + + private Status getStatus(Builder builder) { + Status status = new Status(discovery.getInstanceRemoteStatus().toString(), + "Remote status from Eureka server"); + @SuppressWarnings("unchecked") + Metric value = (Metric) metrics + .findOne("counter.servo.DiscoveryClient_Failed"); + if (value != null) { + int renewalPeriod = instanceConfig.getLeaseRenewalIntervalInSeconds(); + int latest = value.getValue().intValue(); + builder.withDetail("failCount", latest); + builder.withDetail("renewalPeriod", renewalPeriod); + if (failCount < latest) { + status = new Status("UP", "Eureka discovery client is reporting failures"); + failCount = latest; + } else { + status = new Status("UP", "No new failures in Eureka discovery client"); + } + } + return status; + } + + private Map getApplications() { + Applications applications = discovery.getApplications(); + if (applications == null) { + return Collections.emptyMap(); + } + Map result = new HashMap(); + for (Application application : applications.getRegisteredApplications()) { + result.put(application.getName(), application.getInstances().size()); + } + return result; + } + +} 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 new file mode 100644 index 00000000..bcf81e8d --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricReader.java @@ -0,0 +1,84 @@ +/* + * Copyright 2013-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.cloud.netflix.servo; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +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.reader.MetricReader; + +/** + * @author Dave Syer + * + */ +public class ServoMetricReader implements MetricReader { + + private final MBeanServer server; + + public ServoMetricReader(MBeanServer server) { + this.server = server; + } + + @Override + public Metric findOne(String metricName) { + return getServoMetrics().get(metricName); + } + + @Override + public Iterable> findAll() { + return getServoMetrics().values(); + } + + @Override + public long count() { + return getServoMetrics().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)); + } + } + } + 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 new file mode 100644 index 00000000..bd0d0ccc --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-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.cloud.netflix.servo; + +import javax.management.MBeanServer; + +import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration; +import org.springframework.boot.actuate.metrics.reader.MetricReader; +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.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.netflix.servo.monitor.Monitors; + +/** + * @author Dave Syer + * + */ +@Configuration +@ConditionalOnClass({Monitors.class,MetricReader.class}) +@ConditionalOnBean({MBeanServer.class,MetricReader.class}) +@AutoConfigureBefore(EndpointAutoConfiguration.class) +@AutoConfigureAfter(MetricRepositoryAutoConfiguration.class) +public class ServoMetricsAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public ServoPublicMetrics servoPublicMetrics(MetricReader reader, MBeanServer server) { + return new ServoPublicMetrics(reader, server); + } + +} 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 new file mode 100644 index 00000000..75259297 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoPublicMetrics.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-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.cloud.netflix.servo; + +import java.util.Collection; + +import javax.management.MBeanServer; + +import org.springframework.boot.actuate.endpoint.VanillaPublicMetrics; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.reader.MetricReader; + +/** + * @author Dave Syer + * + */ +public class ServoPublicMetrics extends VanillaPublicMetrics { + + private final ServoMetricReader servo; + + public ServoPublicMetrics(MetricReader reader, MBeanServer server) { + super(reader); + this.servo = new ServoMetricReader(server); + } + + @Override + public Collection> metrics() { + Collection> metrics = super.metrics(); + if (servo != null) { + for (Metric metric : servo.findAll()) { + metrics.add(metric); + } + } + return metrics; + } + +} diff --git a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories index 95054048..7806da7b 100644 --- a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories @@ -1,3 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\ -org.springframework.cloud.netflix.feign.FeignAutoConfiguration \ No newline at end of file +org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\ +org.springframework.cloud.netflix.servo.ServoMetricsAutoConfiguration \ No newline at end of file