Browse Source

Expanded metrics support to include Servo, Spectator, and Atlas

* For Java 8 users, Spectator is the recommended metrics library as Servo is now deprecated in favor of Spectator
* Servo support now needs to be specifically enabled via @EnableServo, even if servo-core is included on the classpath
* Added starter modules for both Spectator and Atlas support
* ServoMetricReader defines a convention for mapping tag-based metrics to hiearchical names for rendering by the Actuator /metrics endpoint
pull/6/head
Jon Schneider 10 years ago committed by Spencer Gibb
parent
commit
c61a48735d
  1. 25
      pom.xml
  2. 15
      spring-cloud-netflix-core/pom.xml
  3. 85
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/DefaultMetricsTagProvider.java
  4. 82
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsClientHttpRequestInterceptor.java
  5. 95
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsHandlerInterceptor.java
  6. 80
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsInterceptorConfiguration.java
  7. 43
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsTagProvider.java
  8. 39
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsTagProviderAdapter.java
  9. 39
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/RestTemplateUrlTemplateCapturingAspect.java
  10. 39
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/RestTemplateUrlTemplateHolder.java
  11. 66
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasAutoConfiguration.java
  12. 37
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasExporter.java
  13. 236
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasMetricObserver.java
  14. 50
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasMetricObserverConfigBean.java
  15. 26
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasTagProvider.java
  16. 34
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/EnableAtlas.java
  17. 38
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/DimensionalServoMetricNaming.java
  18. 36
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/HierarchicalServoMetricNaming.java
  19. 14
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricNaming.java
  20. 87
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricReader.java
  21. 145
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricServices.java
  22. 85
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricsAutoConfiguration.java
  23. 30
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricsConfigBean.java
  24. 59
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMonitorCache.java
  25. 79
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricCollector.java
  26. 54
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricObserver.java
  27. 87
      spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java
  28. 4
      spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories
  29. 27
      spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/AbstractMetricsTests.java
  30. 89
      spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/MetricsClientHttpRequestInterceptorTests.java
  31. 160
      spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/MetricsHandlerInterceptorIntegrationTests.java
  32. 156
      spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/atlas/AtlasMetricObserverTests.java
  33. 78
      spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/servo/DimensionalServoMetricNamingTests.java
  34. 61
      spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/servo/HierarchicalServoMetricNamingTests.java
  35. 49
      spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricReaderTests.java
  36. 0
      spring-cloud-netflix-spectator/.jdk8
  37. 95
      spring-cloud-netflix-spectator/pom.xml
  38. 69
      spring-cloud-netflix-spectator/src/main/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricReader.java
  39. 134
      spring-cloud-netflix-spectator/src/main/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricServices.java
  40. 91
      spring-cloud-netflix-spectator/src/main/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricsAutoConfiguration.java
  41. 2
      spring-cloud-netflix-spectator/src/main/resources/META-INF/spring.factories
  42. 99
      spring-cloud-netflix-spectator/src/test/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricReaderTests.java
  43. 39
      spring-cloud-netflix-spectator/src/test/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricServicesTests.java
  44. 160
      spring-cloud-netflix-spectator/src/test/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricsHandlerInterceptorIntegrationTests.java
  45. 40
      spring-cloud-starter-atlas/pom.xml
  46. 1
      spring-cloud-starter-atlas/src/main/resources/META-INF/spring.provides
  47. 0
      spring-cloud-starter-spectator/.jdk8
  48. 32
      spring-cloud-starter-spectator/pom.xml
  49. 1
      spring-cloud-starter-spectator/src/main/resources/META-INF/spring.provides

25
pom.xml

@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
<feign.version>8.11.0</feign.version>
<hystrix.version>1.4.18</hystrix.version>
<ribbon.version>2.1.0</ribbon.version>
<servo.version>0.9.4</servo.version>
<servo.version>0.10.0</servo.version>
<zuul.version>1.1.0-rc.1</zuul.version>
<rxjava.version>1.0.11</rxjava.version>
<java.version>1.7</java.version>
@ -83,6 +83,11 @@ @@ -83,6 +83,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-atlas</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
@ -113,6 +118,11 @@ @@ -113,6 +118,11 @@
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-spectator</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
@ -153,6 +163,11 @@ @@ -153,6 +163,11 @@
<artifactId>spring-cloud-netflix-sidecar</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-spectator</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-turbine</artifactId>
@ -221,6 +236,11 @@ @@ -221,6 +236,11 @@
<artifactId>servo-core</artifactId>
<version>${servo.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-smile</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
@ -410,15 +430,18 @@ @@ -410,15 +430,18 @@
<module>spring-cloud-netflix-hystrix-amqp</module>
<module>spring-cloud-netflix-hystrix-stream</module>
<module>spring-cloud-netflix-eureka-server</module>
<module>spring-cloud-netflix-spectator</module>
<module>spring-cloud-netflix-turbine</module>
<module>spring-cloud-netflix-turbine-stream</module>
<module>spring-cloud-netflix-sidecar</module>
<module>spring-cloud-starter-atlas</module>
<module>spring-cloud-starter-eureka</module>
<module>spring-cloud-starter-eureka-server</module>
<module>spring-cloud-starter-feign</module>
<module>spring-cloud-starter-hystrix</module>
<module>spring-cloud-starter-hystrix-dashboard</module>
<module>spring-cloud-starter-ribbon</module>
<module>spring-cloud-starter-spectator</module>
<module>spring-cloud-starter-turbine</module>
<module>spring-cloud-starter-turbine-amqp</module>
<module>spring-cloud-starter-turbine-stream</module>

15
spring-cloud-netflix-core/pom.xml

@ -208,5 +208,20 @@ @@ -208,5 +208,20 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-smile</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

85
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/DefaultMetricsTagProvider.java

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
/*
* Copyright 2013-2015 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.metrics;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.servlet.HandlerMapping;
import com.google.common.collect.ImmutableMap;
/**
* @author Jon Schneider
*/
public class DefaultMetricsTagProvider implements MetricsTagProvider {
@Override
public Map<String, String> clientHttpRequestTags(HttpRequest request,
ClientHttpResponse response) {
String urlTemplate = RestTemplateUrlTemplateHolder.getRestTemplateUrlTemplate();
if (urlTemplate == null)
urlTemplate = "none";
String status;
try {
status = (response == null) ? "CLIENT_ERROR" : ((Integer) response
.getRawStatusCode()).toString();
}
catch (IOException e) {
status = "IO_ERROR";
}
String host = request.getURI().getHost();
return ImmutableMap.of("method", request.getMethod().name(), "uri",
sanitizeUrlTemplate(urlTemplate.replaceAll("^https?://[^/]+/", "")),
"status", status, "clientName", host != null ? host : "none");
}
@Override
public Map<String, String> httpRequestTags(HttpServletRequest request,
HttpServletResponse response, Object handler, String caller) {
Map<String, String> tags = new HashMap<>();
tags.put("method", request.getMethod());
tags.put("status", ((Integer) response.getStatus()).toString());
String uri = sanitizeUrlTemplate(request
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString()
.substring(1));
tags.put("uri", uri.isEmpty() ? "root" : uri);
Object exception = request.getAttribute("exception");
if (exception != null)
tags.put("exception", exception.getClass().getSimpleName());
if (caller != null)
tags.put("caller", caller);
return tags;
}
/**
* As is, the urlTemplate is not suitable for use with Atlas, as all interactions with
* Atlas take place via query parameters
*/
private String sanitizeUrlTemplate(String urlTemplate) {
return urlTemplate.replaceAll("/", "_").replaceAll("[{}]", "-");
}
}

82
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsClientHttpRequestInterceptor.java

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
/*
* Copyright 2013-2015 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.metrics;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.servo.tag.SmallTagMap;
import com.netflix.servo.tag.Tags;
/**
* Intercepts RestTemplate requests and records metrics about execution time and results.
*
* @author Jon Schneider
*/
public class MetricsClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
/**
* The interceptor writes to a Servo MonitorRegistry, which we get away with for now
* because our Spectator implementation is underpinned by a ServoRegistry. When Spring
* Boot (Actuator) provides a more general purpose abstraction for dimensional metrics
* systems, this can be moved there and rewritten against that abstraction.
*/
@Autowired
MonitorRegistry registry;
@Autowired
Collection<MetricsTagProvider> tagProviders;
@Value("${netflix.metrics.restClient.metricName:restclient}")
String metricName;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
long startTime = System.nanoTime();
ClientHttpResponse response = null;
try {
response = execution.execute(request, body);
return response;
}
finally {
SmallTagMap.Builder builder = SmallTagMap.builder();
for (MetricsTagProvider tagProvider : tagProviders) {
for (Map.Entry<String, String> tag : tagProvider.clientHttpRequestTags(
request, response).entrySet()) {
builder.add(Tags.newTag(tag.getKey(), tag.getValue()));
}
}
MonitorConfig.Builder monitorConfigBuilder = MonitorConfig
.builder(metricName);
monitorConfigBuilder.withTags(builder);
ServoMonitorCache.getTimer(monitorConfigBuilder.build()).record(
System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
}
}
}

95
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsHandlerInterceptor.java

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
/*
* Copyright 2013-2015 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.metrics;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.servo.tag.SmallTagMap;
import com.netflix.servo.tag.Tags;
import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;
/**
* Intercepts incoming HTTP requests and records metrics about execution time and results.
*
* @author Jon Schneider
*/
public class MetricsHandlerInterceptor extends HandlerInterceptorAdapter {
@Value("${netflix.metrics.rest.metricName:rest}")
String metricName;
@Value("${netflix.metrics.rest.callerHeader:#{null}}")
String callerHeader;
@Autowired
MonitorRegistry registry;
@Autowired
Collection<MetricsTagProvider> tagProviders;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
RequestContextHolder.getRequestAttributes().setAttribute("requestStartTime",
System.nanoTime(), SCOPE_REQUEST);
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
RequestContextHolder.getRequestAttributes().setAttribute("exception", ex,
SCOPE_REQUEST);
Long startTime = (Long) RequestContextHolder.getRequestAttributes().getAttribute(
"requestStartTime", SCOPE_REQUEST);
if (startTime != null)
recordMetric(request, response, handler, startTime);
super.afterCompletion(request, response, handler, ex);
}
protected void recordMetric(HttpServletRequest request, HttpServletResponse response,
Object handler, Long startTime) {
String caller = null;
if (callerHeader != null) {
caller = request.getHeader(callerHeader);
}
SmallTagMap.Builder builder = SmallTagMap.builder();
for (MetricsTagProvider tagProvider : tagProviders) {
Map<String, String> tags = tagProvider.httpRequestTags(request, response,
handler, caller);
for (Map.Entry<String, String> tag : tags.entrySet()) {
builder.add(Tags.newTag(tag.getKey(), tag.getValue()));
}
}
MonitorConfig.Builder monitorConfigBuilder = MonitorConfig.builder(metricName);
monitorConfigBuilder.withTags(builder);
ServoMonitorCache.getTimer(monitorConfigBuilder.build()).record(
System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
}
}

80
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsInterceptorConfiguration.java

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
/*
* Copyright 2013-2015 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.metrics;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.netflix.servo.monitor.Monitors;
/**
* @author Jon Schneider
*/
@Configuration
@ConditionalOnClass({ Monitors.class, MetricReader.class })
public class MetricsInterceptorConfiguration {
@Configuration
@ConditionalOnWebApplication
static class MetricsWebResourceConfiguration extends WebMvcConfigurerAdapter {
@Bean
MetricsHandlerInterceptor spectatorMonitoringWebResourceInterceptor() {
return new MetricsHandlerInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(spectatorMonitoringWebResourceInterceptor());
}
}
@Configuration
@ConditionalOnBean({ RestTemplate.class })
static class MetricsRestTemplateConfiguration {
@Bean
RestTemplateUrlTemplateCapturingAspect restTemplateUrlTemplateCapturingAspect() {
return new RestTemplateUrlTemplateCapturingAspect();
}
@Bean
MetricsClientHttpRequestInterceptor spectatorLoggingClientHttpRequestInterceptor() {
return new MetricsClientHttpRequestInterceptor();
}
@Bean
BeanPostProcessor spectatorRestTemplateInterceptorPostProcessor(
final MetricsClientHttpRequestInterceptor interceptor) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof RestTemplate)
((RestTemplate) bean).getInterceptors().add(interceptor);
return bean;
}
};
}
}
}

43
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsTagProvider.java

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
/*
* Copyright 2013-2015 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.metrics;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;
/**
* @author Jon Schneider
*/
public interface MetricsTagProvider {
/**
* @param request RestTemplate client HTTP request
* @param response may be null in the event of a client error
* @return a map of tags added to every client HTTP request metric
*/
Map<String, String> clientHttpRequestTags(HttpRequest request,
ClientHttpResponse response);
/**
* @param request HTTP request
* @param response HTTP response
* @param handler the request method that is responsible for handling the request
* @return a map of tags added to every Spring MVC HTTP request metric
*/
Map<String, String> httpRequestTags(HttpServletRequest request,
HttpServletResponse response, Object handler, String caller);
}

39
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/MetricsTagProviderAdapter.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* Copyright 2013-2015 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.metrics;
import java.util.Collections;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;
/**
* @author Jon Schneider
*/
public class MetricsTagProviderAdapter implements MetricsTagProvider {
@Override
public Map<String, String> clientHttpRequestTags(HttpRequest request,
ClientHttpResponse response) {
return Collections.emptyMap();
}
@Override
public Map<String, String> httpRequestTags(HttpServletRequest request,
HttpServletResponse response, Object handler, String caller) {
return Collections.emptyMap();
}
}

39
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/RestTemplateUrlTemplateCapturingAspect.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* Copyright 2013-2015 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.metrics;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* Captures the still-templated URI because currently the ClientHttpRequestInterceptor
* currently only gives us the means to retrieve the substituted URI.
*
* @author Jon Schneider
*/
@Aspect
public class RestTemplateUrlTemplateCapturingAspect {
@Around("execution(* org.springframework.web.client.RestTemplate.*(String, ..))")
void captureUrlTemplate(ProceedingJoinPoint joinPoint) throws Throwable {
try {
String urlTemplate = (String) joinPoint.getArgs()[0];
RestTemplateUrlTemplateHolder.setRestTemplateUrlTemplate(urlTemplate);
joinPoint.proceed();
}
finally {
RestTemplateUrlTemplateHolder.clear();
}
}
}

39
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/RestTemplateUrlTemplateHolder.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* Copyright 2013-2015 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.metrics;
import org.springframework.core.NamedThreadLocal;
/**
* Holding area for the still-templated URI because currently the
* ClientHttpRequestInterceptor only gives us the means to retrieve the substituted URI.
*
* @author Jon Schneider
*/
public class RestTemplateUrlTemplateHolder {
private static final ThreadLocal<String> restTemplateUrlTemplateHolder = new NamedThreadLocal<String>(
"Rest Template URL Template");
public static String getRestTemplateUrlTemplate() {
return restTemplateUrlTemplateHolder.get();
}
public static void setRestTemplateUrlTemplate(String urlTemplate) {
restTemplateUrlTemplateHolder.set(urlTemplate);
}
public static void clear() {
restTemplateUrlTemplateHolder.remove();
}
}

66
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasAutoConfiguration.java

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
/*
* Copyright 2013-2015 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.metrics.atlas;
import java.util.Collection;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.export.Exporter;
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 org.springframework.web.client.RestTemplate;
import com.netflix.servo.publish.MetricPoller;
import com.netflix.servo.tag.BasicTagList;
/**
* Configures the Atlas metrics backend, also configuring Spectator to collect metrics if necessary.
*
* @author Jon Schneider
*/
@Configuration
@ConditionalOnClass(AtlasMetricObserver.class)
public class AtlasAutoConfiguration {
@Autowired(required = false)
private Collection<AtlasTagProvider> tagProviders;
@Bean
public AtlasMetricObserverConfigBean atlasObserverConfig() {
return new AtlasMetricObserverConfigBean();
}
@Bean
@ConditionalOnMissingBean
public AtlasMetricObserver atlasObserver(AtlasMetricObserverConfigBean atlasObserverConfig, RestTemplate restTemplate) {
BasicTagList tags = (BasicTagList) BasicTagList.EMPTY;
if (tagProviders != null) {
for (AtlasTagProvider tagProvider : tagProviders) {
for (Map.Entry<String, String> tag : tagProvider.defaultTags().entrySet()) {
if (tag.getValue() != null)
tags = tags.copy(tag.getKey(), tag.getValue());
}
}
}
return new AtlasMetricObserver(atlasObserverConfig, restTemplate, tags);
}
@Bean
@ConditionalOnMissingBean
public Exporter exporter(AtlasMetricObserver observer, MetricPoller poller) {
return new AtlasExporter(observer, poller);
}
}

37
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasExporter.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
/*
* Copyright 2013-2015 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.metrics.atlas;
import org.springframework.boot.actuate.metrics.export.Exporter;
import com.netflix.servo.publish.BasicMetricFilter;
import com.netflix.servo.publish.MetricPoller;
/**
* @author Jon Schneider
*/
public class AtlasExporter implements Exporter {
private AtlasMetricObserver observer;
private MetricPoller poller;
public AtlasExporter(AtlasMetricObserver observer, MetricPoller poller) {
this.observer = observer;
this.poller = poller;
}
@Override
public void export() {
observer.update(poller.poll(BasicMetricFilter.MATCH_ALL));
}
}

236
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasMetricObserver.java

@ -0,0 +1,236 @@ @@ -0,0 +1,236 @@
/*
* Copyright 2013-2015 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.metrics.atlas;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
import com.netflix.servo.Metric;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.publish.MetricObserver;
import com.netflix.servo.tag.BasicTag;
import com.netflix.servo.tag.Tag;
import com.netflix.servo.tag.TagList;
/**
* Observer that forwards metrics to atlas. In addition to being a MetricObserver, it also
* supports a push model that sends metrics as soon as possible (asynchronously).
*
* @author Jon Schneider
*/
public class AtlasMetricObserver implements MetricObserver {
private static final Log logger = LogFactory.getLog(AtlasMetricObserver.class);
private static final SmileFactory smileFactory = new SmileFactory();
private static final Tag atlasRateTag = new BasicTag("atlas.dstype", "rate");
private static final Tag atlasCounterTag = new BasicTag("atlas.dstype", "counter");
private static final Tag atlasGaugeTag = new BasicTag("atlas.dstype", "gauge");
private static final Pattern validAtlasTag = Pattern.compile("[\\.\\-\\w]+");
private AtlasMetricObserverConfigBean config;
private RestTemplate restTemplate;
private TagList commonTags;
private String uri;
public AtlasMetricObserver(AtlasMetricObserverConfigBean config,
RestTemplate restTemplate, TagList commonTags) {
this.config = config;
this.commonTags = commonTags;
this.restTemplate = restTemplate;
this.uri = normalizeAtlasUri(config.getUri());
if (!validTags(commonTags)) {
throw new IllegalArgumentException(
"One or more atlas tags contain invalid characters, must match [\\.\\-\\w]+");
}
}
@Override
public String getName() {
return "atlas";
}
protected static boolean validTags(TagList tags) {
for (Tag tag : tags) {
if (!validAtlasTag.matcher(tag.getKey()).matches()) {
logger.error("Invalid tag key " + tag.getKey());
return false;
}
if (!validAtlasTag.matcher(tag.getValue()).matches()) {
logger.error("Invalid tag value " + tag.getValue());
return false;
}
}
return true;
}
protected static String normalizeAtlasUri(String uri) {
Matcher matcher = Pattern.compile("(.+?)(/api/v1/publish)?/?").matcher(uri);
matcher.matches();
return matcher.group(1) + "/api/v1/publish";
}
@Override
public void update(List<Metric> rawMetrics) {
if (!config.isEnabled()) {
logger.debug("Atlas metric observer disabled. Not sending metrics.");
return;
}
if (rawMetrics.isEmpty()) {
logger.debug("Metrics list is empty, no data being sent to server.");
return;
}
List<Metric> metrics = addTypeTagsAsNecessary(rawMetrics);
for (int i = 0; i < metrics.size(); i += config.getBatchSize()) {
List<Metric> batch = metrics.subList(i,
Math.min(metrics.size(), config.getBatchSize() + i));
logger.debug("Sending a metrics batch of size " + batch.size());
sendMetricsBatch(batch);
}
}
private void sendMetricsBatch(List<Metric> metrics) {
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
JsonGenerator gen = smileFactory.createGenerator(output, JsonEncoding.UTF8);
gen.writeStartObject();
writeCommonTags(gen);
if (writeMetrics(gen, metrics) == 0)
return; // short circuit this batch if no valid/numeric metrics existed
gen.writeEndObject();
gen.flush();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/x-jackson-smile"));
HttpEntity<byte[]> entity = new HttpEntity<>(output.toByteArray(), headers);
try {
restTemplate.exchange(uri, HttpMethod.POST, entity, Map.class);
}
catch (HttpClientErrorException e) {
logger.error(
"Failed to write metrics to atlas: "
+ e.getResponseBodyAsString(), e);
}
catch (RestClientException e) {
logger.error("Failed to write metrics to atlas", e);
}
}
catch (IOException e) {
// an IOException stemming from the generator writing to a
// ByteArrayOutputStream is impossible
throw new RuntimeException(e);
}
}
private void writeCommonTags(JsonGenerator gen) throws IOException {
gen.writeObjectFieldStart("tags");
for (Tag tag : commonTags)
gen.writeStringField(tag.getKey(), tag.getValue());
gen.writeEndObject();
}
private int writeMetrics(JsonGenerator gen, List<Metric> metrics) throws IOException {
int totalMetricsInBatch = 0;
gen.writeArrayFieldStart("metrics");
for (Metric m : metrics) {
if (!validTags(m.getConfig().getTags()))
continue;
if (!Number.class.isAssignableFrom(m.getValue().getClass()))
continue;
gen.writeStartObject();
gen.writeObjectFieldStart("tags");
gen.writeStringField("name", m.getConfig().getName());
for (Tag tag : m.getConfig().getTags())
gen.writeStringField(tag.getKey(), tag.getValue());
gen.writeEndObject();
gen.writeNumberField("start", m.getTimestamp());
gen.writeNumberField("value", m.getNumberValue().doubleValue());
gen.writeEndObject();
totalMetricsInBatch++;
}
gen.writeEndArray();
return totalMetricsInBatch;
}
protected static List<Metric> addTypeTagsAsNecessary(List<Metric> metrics) {
List<Metric> typedMetrics = new ArrayList<>();
for (Metric m : metrics) {
String value = m.getConfig().getTags().getValue(DataSourceType.KEY);
Metric transformed;
// Atlas will not normalize metrics tagged with atlas.dstype=gauge. Since
// these metric types are
// pre-normalized, we do not want Atlas to touch the value
if (DataSourceType.GAUGE.name().equals(value)
|| DataSourceType.RATE.name().equals(value)
|| DataSourceType.NORMALIZED.name().equals(value)) {
transformed = new Metric(m.getConfig().withAdditionalTag(atlasGaugeTag),
m.getTimestamp(), m.getValue());
}
// atlas.dstype=counter means you're sending the absolute value of the counter
// (a monotonically
// increasing value), and Atlas will keep the previous value and convert it to
// a rate per second
// when the metric is received
else if (DataSourceType.COUNTER.name().equals(value)) {
transformed = new Metric(
m.getConfig().withAdditionalTag(atlasCounterTag),
m.getTimestamp(), m.getValue());
}
// Atlas will normalize the value to a minute boundary based on its timestamp
else {
transformed = new Metric(m.getConfig().withAdditionalTag(atlasRateTag),
m.getTimestamp(), m.getValue());
}
typedMetrics.add(transformed);
}
return typedMetrics;
}
}

50
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasMetricObserverConfigBean.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
/*
* Copyright 2013-2015 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.metrics.atlas;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Jon Schneider
*/
@ConfigurationProperties("netflix.atlas")
public class AtlasMetricObserverConfigBean {
private String uri;
private boolean enabled = true;
private Integer batchSize = 10000;
public boolean isEnabled() {
return enabled;
}
public int getBatchSize() {
return batchSize;
}
public String getUri() {
return uri;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setBatchSize(Integer batchSize) {
this.batchSize = batchSize;
}
public void setUri(String uri) {
this.uri = uri;
}
}

26
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/AtlasTagProvider.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*
* Copyright 2013-2015 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.metrics.atlas;
import java.util.Map;
/**
* Provide implementations of this interface in your application context to add a set of static tags to every metric
* sent to Atlas.
*
* @author Jon Schneider
*/
public interface AtlasTagProvider {
Map<String, String> defaultTags();
}

34
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/atlas/EnableAtlas.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2013-2015 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.metrics.atlas;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Annotation for clients to enable Atlas metrics publishing.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AtlasAutoConfiguration.class)
public @interface EnableAtlas {
}

38
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/DimensionalServoMetricNaming.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
/*
* Copyright 2013-2015 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.metrics.servo;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import com.netflix.servo.monitor.Monitor;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.servo.tag.Tag;
/**
* @author Jon Schneider
*/
public class DimensionalServoMetricNaming implements ServoMetricNaming {
@Override
public String asHierarchicalName(Monitor<?> monitor) {
MonitorConfig config = monitor.getConfig();
List<String> tags = new ArrayList<>(config.getTags().size());
for (Tag t : config.getTags()) {
tags.add(t.getKey() + "=" + t.getValue());
}
return config.getName() + "(" + StringUtils.join(tags, ",") + ")";
}
}

36
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/DefaultServoMetricNaming.java → spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/HierarchicalServoMetricNaming.java

@ -1,23 +1,20 @@ @@ -1,23 +1,20 @@
/*
* Copyright 2013-2015 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.
*
* 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;
package org.springframework.cloud.netflix.metrics.servo;
import com.netflix.servo.Metric;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.monitor.Monitor;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.servo.tag.Tag;
import com.netflix.servo.tag.TagList;
@ -25,21 +22,20 @@ import com.netflix.servo.tag.TagList; @@ -25,21 +22,20 @@ import com.netflix.servo.tag.TagList;
/**
* @author Spencer Gibb
*/
public class DefaultServoMetricNaming implements ServoMetricNaming {
public class HierarchicalServoMetricNaming implements ServoMetricNaming {
private static final String JMX_DOMAIN_KEY = "JmxDomain";
public static final String SERVO = "servo.";
@Override
public String getName(Metric metric) {
MonitorConfig config = metric.getConfig();
public String asHierarchicalName(Monitor<?> monitor) {
MonitorConfig config = monitor.getConfig();
TagList tags = config.getTags();
Tag domainTag = tags.getTag(JMX_DOMAIN_KEY);
String name;
if (domainTag != null) { // jmx metric
name = handleJmxMetric(config, tags);
}
else {
} else {
name = handleMetric(config, tags);
}
return name.toLowerCase();
@ -111,4 +107,4 @@ public class DefaultServoMetricNaming implements ServoMetricNaming { @@ -111,4 +107,4 @@ public class DefaultServoMetricNaming implements ServoMetricNaming {
}
return s.replace(" ", "_");
}
}
}

14
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricNaming.java → spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricNaming.java

@ -14,13 +14,19 @@ @@ -14,13 +14,19 @@
* limitations under the License.
*/
package org.springframework.cloud.netflix.servo;
package org.springframework.cloud.netflix.metrics.servo;
import com.netflix.servo.Metric;
import com.netflix.servo.monitor.Monitor;
/**
* @author Spencer Gibb
*/
public interface ServoMetricNaming {
String getName(Metric metric);
}
/**
* @param monitor a monitor representing a single statistic (not a CompositeMonitor)
* @return a hierarchical name representing a single statistic for a servo monitor;
* note that this method will be called once for each statistic on a composite servo
* Monitor like a Timer.
*/
String asHierarchicalName(Monitor<?> monitor);
}

87
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricReader.java

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
/*
* Copyright 2013-2015 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.metrics.servo;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.CompositeMonitor;
import com.netflix.servo.monitor.Monitor;
/**
* @author Jon Schneider
*/
public class ServoMetricReader implements MetricReader {
MonitorRegistry monitorRegistry;
ServoMetricNaming metricNaming;
public ServoMetricReader(MonitorRegistry monitorRegistry,
ServoMetricNaming metricNaming) {
this.monitorRegistry = monitorRegistry;
this.metricNaming = metricNaming;
}
@Override
public Metric<?> findOne(String s) {
throw new UnsupportedOperationException(
"cannot construct a tag-based Servo id from a hierarchical name");
}
@Override
public Iterable<Metric<?>> findAll() {
Collection<Metric<?>> metrics = new ArrayList<>();
for (Monitor<?> monitor : monitorRegistry.getRegisteredMonitors()) {
addToMetrics(monitor, metrics);
}
return metrics;
}
private void addToMetrics(Monitor<?> monitor, Collection<Metric<?>> metrics) {
if (monitor instanceof CompositeMonitor) {
for (Monitor<?> nestedMonitor : ((CompositeMonitor<?>) monitor).getMonitors()) {
addToMetrics(nestedMonitor, metrics);
}
}
else if (monitor.getValue() instanceof Number) {
// Servo does support non-numeric values, but there is no such concept in
// Spring Boot
metrics.add(new Metric<>(metricNaming.asHierarchicalName(monitor),
(Number) monitor.getValue()));
}
}
@Override
public long count() {
long count = 0;
for (Monitor<?> monitor : monitorRegistry.getRegisteredMonitors()) {
count += countMetrics(monitor);
}
return count;
}
private static long countMetrics(Monitor<?> monitor) {
if (monitor instanceof CompositeMonitor) {
long count = 0;
for (Monitor<?> nestedMonitor : ((CompositeMonitor<?>) monitor).getMonitors()) {
count += countMetrics(nestedMonitor);
}
return count;
}
return 1;
}
}

145
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricServices.java

@ -0,0 +1,145 @@ @@ -0,0 +1,145 @@
/*
* Copyright 2013-2015 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.metrics.servo;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.BasicCounter;
import com.netflix.servo.monitor.BasicDistributionSummary;
import com.netflix.servo.monitor.BasicTimer;
import com.netflix.servo.monitor.DoubleGauge;
import com.netflix.servo.monitor.LongGauge;
import com.netflix.servo.monitor.MonitorConfig;
/**
* Provides a <code>CounterService</code> and <code>GaugeService</code> implementation
* backed by Servo.
*
* @author Jon Schneider
*/
public class ServoMetricServices implements CounterService, GaugeService {
private final MonitorRegistry registry;
private final ConcurrentMap<String, BasicCounter> counters = new ConcurrentHashMap<>();
private final ConcurrentMap<String, LongGauge> longGauges = new ConcurrentHashMap<>();
private final ConcurrentMap<String, DoubleGauge> doubleGauges = new ConcurrentHashMap<>();
private final ConcurrentMap<String, BasicDistributionSummary> distributionSummaries = new ConcurrentHashMap<>();
private final ConcurrentMap<String, BasicTimer> timers = new ConcurrentHashMap<>();
public ServoMetricServices(MonitorRegistry registry) {
this.registry = registry;
}
protected static String stripMetricName(String metricName) {
return metricName.replaceFirst("^(timer|histogram|meter)\\.", "");
}
@Override
public void increment(String name) {
incrementInternal(name, 1L);
}
@Override
public void decrement(String name) {
incrementInternal(name, -1L);
}
private void incrementInternal(String name, long value) {
String strippedName = stripMetricName(name);
if (name.startsWith("status.")) {
// drop this metric since we are capturing it already with
// ServoHandlerInterceptor,
// and we are able to glean more information like exceptionType from that
// mechanism than what
// boot provides us
}
else if (name.startsWith("meter.")) {
BasicCounter counter = counters.get(strippedName);
if (counter == null) {
counter = new BasicCounter(MonitorConfig.builder(strippedName).build());
counters.put(strippedName, counter);
registry.register(counter);
}
counter.increment(value);
}
else {
LongGauge gauge = longGauges.get(strippedName);
if (gauge == null) {
gauge = new LongGauge(MonitorConfig.builder(strippedName).build());
longGauges.put(strippedName, gauge);
registry.register(gauge);
}
gauge.set(value);
}
}
@Override
public void reset(String name) {
String strippedName = stripMetricName(name);
BasicCounter counter = counters.remove(strippedName);
if (counter != null)
registry.unregister(counter);
LongGauge gauge = longGauges.remove(strippedName);
if (gauge != null)
registry.unregister(gauge);
BasicDistributionSummary distributionSummary = distributionSummaries
.remove(strippedName);
if (distributionSummary != null)
registry.unregister(distributionSummary);
}
@Override
public void submit(String name, double dValue) {
long value = ((Double) dValue).longValue();
String strippedName = stripMetricName(name);
if (name.startsWith("histogram.")) {
BasicDistributionSummary distributionSummary = distributionSummaries
.get(strippedName);
if (distributionSummary == null) {
distributionSummary = new BasicDistributionSummary(MonitorConfig.builder(
strippedName).build());
distributionSummaries.put(strippedName, distributionSummary);
registry.register(distributionSummary);
}
distributionSummary.record(value);
}
else if (name.startsWith("timer.")) {
BasicTimer timer = timers.get(strippedName);
if (timer == null) {
timer = new BasicTimer(MonitorConfig.builder(strippedName).build());
timers.put(strippedName, timer);
registry.register(timer);
}
timer.record(value, TimeUnit.MILLISECONDS);
}
else {
DoubleGauge gauge = doubleGauges.get(strippedName);
if (gauge == null) {
gauge = new DoubleGauge(MonitorConfig.builder(strippedName).build());
doubleGauges.put(strippedName, gauge);
registry.register(gauge);
}
gauge.set(dValue);
}
}
}

85
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricsAutoConfiguration.java

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
/*
* 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.metrics.servo;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.cloud.netflix.metrics.DefaultMetricsTagProvider;
import org.springframework.cloud.netflix.metrics.MetricsInterceptorConfiguration;
import org.springframework.cloud.netflix.metrics.MetricsTagProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import com.netflix.servo.DefaultMonitorRegistry;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.Monitors;
/**
* Auto configuration to configure Servo support.
*
* @author Dave Syer
* @author Christian Dupuis
* @author Jon Schneider
*/
@Configuration
@ConditionalOnClass({ Monitors.class, MetricReader.class })
@ConditionalOnMissingClass("com.netflix.spectator.api.Registry")
@AutoConfigureBefore(EndpointAutoConfiguration.class)
@Import(MetricsInterceptorConfiguration.class)
public class ServoMetricsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ServoMetricsConfigBean servoMetricsConfig() {
return new ServoMetricsConfigBean();
}
@Bean
@ConditionalOnMissingBean
public ServoMetricNaming servoMetricNaming() {
return new HierarchicalServoMetricNaming();
}
@Bean
@ConditionalOnMissingBean
public MonitorRegistry monitorRegistry() {
System.setProperty(DefaultMonitorRegistry.class.getCanonicalName() + ".registryClass", servoMetricsConfig()
.getRegistryClass());
return DefaultMonitorRegistry.getInstance();
}
@Bean
public MetricReaderPublicMetrics servoPublicMetrics(MonitorRegistry monitorRegistry, ServoMetricNaming servoMetricNaming) {
ServoMetricReader reader = new ServoMetricReader(monitorRegistry, servoMetricNaming);
return new MetricReaderPublicMetrics(reader);
}
@Bean
@ConditionalOnMissingBean({ ServoMetricServices.class, CounterService.class, GaugeService.class })
public ServoMetricServices spectatorMetricServices(MonitorRegistry monitorRegistry) {
return new ServoMetricServices(monitorRegistry);
}
@Bean
public MetricsTagProvider defaultMetricsTagProvider() {
return new DefaultMetricsTagProvider();
}
}

30
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricsConfigBean.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
/*
* Copyright 2013-2015 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.metrics.servo;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties to configure Servo support.
*
* @author Jon Schneider
*/
@ConfigurationProperties("netflix.metrics.servo")
public class ServoMetricsConfigBean {
String registryClass = "com.netflix.servo.BasicMonitorRegistry";
public String getRegistryClass() {
return registryClass;
}
}

59
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/metrics/servo/ServoMonitorCache.java

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
/*
* Copyright 2013-2015 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.metrics.servo;
import java.util.HashMap;
import java.util.Map;
import com.netflix.servo.DefaultMonitorRegistry;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.BasicTimer;
import com.netflix.servo.monitor.Monitor;
import com.netflix.servo.monitor.MonitorConfig;
/**
* Servo does not provide a mechanism to retrieve an existing monitor by name + tags.
*
* @author Jon Schneider
*/
public class ServoMonitorCache {
private static final Map<MonitorConfig, BasicTimer> timerCache = new HashMap<>();
/**
* @param config contains the name and tags that uniquely identify a timer
* @return an already registered timer if it exists, otherwise create/register one and
* return it.
*/
public synchronized static BasicTimer getTimer(MonitorConfig config) {
BasicTimer t = timerCache.get(config);
if (t != null)
return t;
t = new BasicTimer(config);
timerCache.put(config, t);
DefaultMonitorRegistry.getInstance().register(t);
return t;
}
/**
* Useful for tests to clear the monitor registry between runs
*/
public static void unregisterAll() {
MonitorRegistry registry = DefaultMonitorRegistry.getInstance();
for (Monitor<?> monitor : registry.getRegisteredMonitors()) {
registry.unregister(monitor);
}
timerCache.clear();
}
}

79
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricCollector.java

@ -1,79 +0,0 @@ @@ -1,79 +0,0 @@
/*
* 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.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
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 <code>/metric</code> endpoint.
*
* @author Dave Syer
* @author Christian Dupuis
*/
@CommonsLog
public class ServoMetricCollector implements DisposableBean {
public ServoMetricCollector(MetricWriter metrics, ServoMetricNaming naming) {
List<MetricObserver> observers = new ArrayList<>();
observers.add(new ServoMetricObserver(metrics, naming));
PollRunnable task = new PollRunnable(new MonitorRegistryMetricPoller(),
BasicMetricFilter.MATCH_ALL, true, observers);
if (!PollScheduler.getInstance().isStarted()) {
try {
PollScheduler.getInstance().start();
}
catch (Exception e) {
// Can fail due to race condition with evil singletons (if more than one
// app in same JVM)
log.error("Could not start servo metrics poller", e);
return;
}
}
// TODO Make poll interval configurable
PollScheduler.getInstance().addPoller(task, 5, TimeUnit.SECONDS);
}
@Override
public void destroy() throws Exception {
log.info("Stopping Servo PollScheduler");
if (PollScheduler.getInstance().isStarted()) {
try {
PollScheduler.getInstance().stop();
}
catch (Exception e) {
log.error("Could not stop servo metrics poller", e);
}
}
}
}

54
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricObserver.java

@ -1,54 +0,0 @@ @@ -1,54 +0,0 @@
/*
* Copyright 2013-2015 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 com.netflix.servo.Metric;
import com.netflix.servo.publish.BaseMetricObserver;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import java.util.Date;
import java.util.List;
/**
* {@link com.netflix.servo.publish.MetricObserver} to convert Servo metrics into Spring Boot {@link org.springframework.boot.actuate.metrics.Metric}
* instances.
*/
final class ServoMetricObserver extends BaseMetricObserver {
private final MetricWriter metrics;
private final ServoMetricNaming naming;
public ServoMetricObserver(MetricWriter metrics, ServoMetricNaming naming) {
super("spring-boot");
this.metrics = metrics;
this.naming = naming;
}
@Override
public void updateImpl(List<Metric> servoMetrics) {
for (Metric servoMetric : servoMetrics) {
String key = naming.getName(servoMetric);
if (servoMetric.hasNumberValue()) {
this.metrics.set(new org.springframework.boot.actuate.metrics.Metric<>(key,
servoMetric.getNumberValue(), new Date(servoMetric
.getTimestamp())));
}
}
}
}

87
spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/servo/ServoMetricsAutoConfiguration.java

@ -1,87 +0,0 @@ @@ -1,87 +0,0 @@
/*
* 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 org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
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;
/**
* Auto configuration to configure Servo support.
*
* @author Dave Syer
* @author Christian Dupuis
*/
@Configuration
@ConditionalOnClass({ Monitors.class, MetricReader.class })
@ConditionalOnBean(GaugeService.class)
@AutoConfigureBefore(EndpointAutoConfiguration.class)
@AutoConfigureAfter({ MetricRepositoryAutoConfiguration.class })
public class ServoMetricsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ServoMetricNaming servoMetricNaming() {
return new DefaultServoMetricNaming();
}
@Bean
@ConditionalOnMissingBean
public ServoMetricCollector servoMetricCollector(GaugeService gauges,
ServoMetricNaming naming) {
return new ServoMetricCollector(new ActuatorMetricWriter(gauges), naming);
}
// TODO: refactor this when Spring Boot 1.3 is mandatory
private static class ActuatorMetricWriter implements MetricWriter {
private GaugeService gauges;
public ActuatorMetricWriter(GaugeService gauges) {
this.gauges = gauges;
}
@Override
public void increment(Delta<?> delta) {
}
@Override
public void set(Metric<?> value) {
gauges.submit(value.getName(), value.getValue().doubleValue());
}
@Override
public void reset(String metricName) {
gauges.submit(metricName, 0.);
}
}
}

4
spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories

@ -11,7 +11,7 @@ org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\ @@ -11,7 +11,7 @@ org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.rx.RxJavaAutoConfiguration,\
org.springframework.cloud.netflix.servo.ServoMetricsAutoConfiguration
org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.config.DiscoveryClientConfigServiceBootstrapConfiguration
@ -20,4 +20,4 @@ org.springframework.cloud.client.discovery.EnableDiscoveryClient=\ @@ -20,4 +20,4 @@ org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\
org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration
org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration

27
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/AbstractMetricsTests.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright 2013-2015 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.metrics;
import org.junit.Before;
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
/**
* @author Jon Schneider
*/
public class AbstractMetricsTests {
@Before
public void setup() {
ServoMonitorCache.unregisterAll();
}
}

89
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/MetricsClientHttpRequestInterceptorTests.java

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
/*
* Copyright 2013-2015 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.metrics;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
import org.springframework.boot.autoconfigure.test.ImportAutoConfiguration;
import org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration;
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
import org.springframework.test.web.client.response.MockRestResponseCreators;
import org.springframework.web.client.RestTemplate;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.BasicTimer;
import com.netflix.servo.monitor.MonitorConfig;
/**
* @author Jon Schneider
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MetricsRestTemplateRestTemplateConfig.class,
MetricsRestTemplateTestConfig.class })
@TestPropertySource(properties = { "netflix.metrics.restClient.metricName=metricName",
"spring.aop.proxy-target-class=true" })
public class MetricsClientHttpRequestInterceptorTests extends AbstractMetricsTests {
@Autowired
MonitorRegistry registry;
@Autowired
RestTemplate restTemplate;
@Test
public void metricsGatheredWhenSuccessful() {
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(MockRestRequestMatchers.requestTo("/test/123"))
.andExpect(MockRestRequestMatchers.method(HttpMethod.GET))
.andRespond(MockRestResponseCreators.withSuccess("{\"status\" : \"OK\"}", MediaType.APPLICATION_JSON));
restTemplate.getForObject("/test/{id}", String.class, 123);
MonitorConfig.Builder builder = new MonitorConfig.Builder("metricName")
.withTag("method", "GET")
.withTag("uri", "_test_-id-")
.withTag("status", "200")
.withTag("clientName", "none");
BasicTimer timer = ServoMonitorCache.getTimer(builder.build());
Assert.assertEquals(1L, (long) timer.getCount());
mockServer.verify();
}
}
@Configuration
@ImportAutoConfiguration({ ServoMetricsAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, AopAutoConfiguration.class })
class MetricsRestTemplateTestConfig {
}
@Configuration
class MetricsRestTemplateRestTemplateConfig {
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}

160
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/MetricsHandlerInterceptorIntegrationTests.java

@ -0,0 +1,160 @@ @@ -0,0 +1,160 @@
/*
* Copyright 2013-2015 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.metrics;
import javax.servlet.http.HttpServletRequest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.test.ImportAutoConfiguration;
import org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration;
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.BasicTimer;
import com.netflix.servo.monitor.MonitorConfig;
import static org.junit.Assert.assertFalse;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Jon Schneider
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MetricsTestConfig.class)
@WebAppConfiguration
@TestPropertySource(properties = "netflix.metrics.rest.metricName=metricName")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class MetricsHandlerInterceptorIntegrationTests extends AbstractMetricsTests {
@Autowired
WebApplicationContext webAppContext;
@Autowired
MonitorRegistry registry;
MockMvc mvc;
@Test
public void autoConfigurationWiresTheMetricsInterceptor() {
assertFalse(webAppContext.getBeansOfType(MetricsHandlerInterceptor.class)
.isEmpty());
}
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();
}
@Test
public void metricsGatheredWhenSuccess() throws Exception {
mvc.perform(get("/test/some/request/10")).andExpect(status().isOk());
assertTimer("test_some_request_-id-", null, 200);
}
@Test
public void metricsGatheredWhenClientRequestBad() throws Exception {
mvc.perform(get("/test/some/request/oops"))
.andExpect(status().is4xxClientError());
assertTimer("test_some_request_-id-", null, 400);
}
@Test
public void metricsGatheredWhenUnhandledError() throws Exception {
try {
mvc.perform(get("/test/some/unhandledError/10")).andExpect(status().isOk());
}
catch (Exception e) {
}
assertTimer("test_some_unhandledError_-id-", "RuntimeException", 200);
}
@Test
public void metricsGatheredWhenHandledError() throws Exception {
mvc.perform(get("/test/some/error/10")).andExpect(status().is4xxClientError());
assertTimer("test_some_error_-id-", null, 422);
}
protected void assertTimer(String uriTag, String exceptionType, Integer status) {
MonitorConfig.Builder builder = new MonitorConfig.Builder("metricName")
.withTag("method", "GET").withTag("uri", uriTag)
.withTag("status", status.toString());
if (exceptionType != null)
builder = builder.withTag("exception", exceptionType);
BasicTimer timer = ServoMonitorCache.getTimer(builder.build());
Assert.assertEquals(1L, (long) timer.getCount());
}
}
@Configuration
@EnableWebMvc
@ImportAutoConfiguration({ ServoMetricsAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
class MetricsTestConfig {
@Bean
MetricsTestController testController() {
return new MetricsTestController();
}
}
@RestController
@RequestMapping("/test/some")
@ControllerAdvice
class MetricsTestController {
@RequestMapping("/request/{id}")
public String testSomeRequest(@PathVariable Long id) {
return id.toString();
}
@RequestMapping("/error/{id}")
public String testSomeHandledError(@PathVariable Long id) {
throw new IllegalStateException("Boom on $id!");
}
@RequestMapping("/unhandledError/{id}")
public String testSomeUnhandledError(@PathVariable Long id) {
throw new RuntimeException("Boom on $id!");
}
@ExceptionHandler(value = IllegalStateException.class)
@ResponseStatus(code = HttpStatus.UNPROCESSABLE_ENTITY)
ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) {
return new ModelAndView("error");
}
}

156
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/atlas/AtlasMetricObserverTests.java

@ -0,0 +1,156 @@ @@ -0,0 +1,156 @@
/*
* Copyright 2013-2015 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.metrics.atlas;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
import org.springframework.test.web.client.response.MockRestResponseCreators;
import org.springframework.web.client.RestTemplate;
import com.netflix.servo.Metric;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.servo.tag.BasicTagList;
import static com.netflix.servo.annotations.DataSourceType.COUNTER;
import static com.netflix.servo.annotations.DataSourceType.GAUGE;
import static com.netflix.servo.annotations.DataSourceType.INFORMATIONAL;
import static com.netflix.servo.annotations.DataSourceType.KEY;
import static com.netflix.servo.annotations.DataSourceType.NORMALIZED;
import static com.netflix.servo.annotations.DataSourceType.RATE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Jon Schneider
*/
public class AtlasMetricObserverTests {
@Test
public void normalizeAtlasUri() {
String normalized = "http://localhost:7001/api/v1/publish";
assertEquals(normalized, AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001"));
assertEquals(normalized, AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001/"));
assertEquals(normalized, AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001/api/v1/publish"));
assertEquals(normalized, AtlasMetricObserver.normalizeAtlasUri("http://localhost:7001/api/v1/publish/"));
}
@Test
public void checkValidityOfTags() {
assertTrue(AtlasMetricObserver.validTags(BasicTagList.of("foo", "bar")));
assertFalse(AtlasMetricObserver.validTags(BasicTagList.of("{foo}", "bar")));
assertFalse(AtlasMetricObserver.validTags(BasicTagList.of("foo", "{bar}")));
}
@Test
public void assignTypesToMetrics() {
assertHasAtlasType("counter", metricWithType("foo", COUNTER));
assertHasAtlasType("gauge", metricWithType("foo", GAUGE));
assertHasAtlasType("gauge", metricWithType("foo", NORMALIZED));
assertHasAtlasType("gauge", metricWithType("foo", RATE));
assertHasAtlasType("rate", metricWithType("foo", INFORMATIONAL));
assertHasAtlasType("rate", new Metric(new MonitorConfig.Builder("foo").build(),
0, "bar"));
// already has type
Metric m = new Metric(new MonitorConfig.Builder("foo")
.withTag(KEY, COUNTER.name()).withTag("atlas.dstype", "counter").build(),
0, "bar");
assertHasAtlasType("counter", m);
assertEquals(2, m.getConfig().getTags().size());
}
private void assertHasAtlasType(String atlasType, Metric m) {
assertEquals(atlasType,
AtlasMetricObserver.addTypeTagsAsNecessary(Collections.singletonList(m))
.get(0).getConfig().getTags().getValue("atlas.dstype"));
}
private Metric metricWithType(String key, DataSourceType type) {
return new Metric(new MonitorConfig.Builder(key).withTag(KEY, type.name())
.build(), 0, 1);
}
@Test
public void metricsSentInBatches() {
RestTemplate restTemplate = new RestTemplate();
AtlasMetricObserverConfigBean config = new AtlasMetricObserverConfigBean();
config.setBatchSize(2);
config.setUri("atlas");
AtlasMetricObserver obs = new AtlasMetricObserver(config, restTemplate,
BasicTagList.EMPTY);
// batch size is divisible by metric size
MockRestServiceServer mockServer = MockRestServiceServer
.createServer(restTemplate);
expectTotalBatches(mockServer, 2);
obs.update(generateMetrics(4));
mockServer.verify();
// batch size is not divisible by metric size
mockServer = MockRestServiceServer.createServer(restTemplate);
expectTotalBatches(mockServer, 3);
obs.update(generateMetrics(5));
mockServer.verify();
// metric size is less than batch size
mockServer = MockRestServiceServer.createServer(restTemplate);
expectTotalBatches(mockServer, 1);
obs.update(generateMetrics(1));
mockServer.verify();
// no metrics to send
mockServer = MockRestServiceServer.createServer(restTemplate);
expectTotalBatches(mockServer, 0);
obs.update(Collections.<Metric> emptyList());
mockServer.verify();
// a single non-numeric metric does not result in a post
mockServer = MockRestServiceServer.createServer(restTemplate);
expectTotalBatches(mockServer, 0);
obs.update(Collections.singletonList(new Metric(new MonitorConfig.Builder("foo")
.build(), 0, "nonumber")));
mockServer.verify();
}
private List<Metric> generateMetrics(int numberOfMetrics) {
List<Metric> metrics = new ArrayList<>();
for (int i = 0; i < numberOfMetrics; i++)
metrics.add(metricWithType("foo" + i, DataSourceType.GAUGE));
return metrics;
}
private void expectTotalBatches(MockRestServiceServer mockServer,
int totalBatchesExpected) {
for (int i = 0; i < totalBatchesExpected; i++) {
mockServer
.expect(MockRestRequestMatchers.requestTo("atlas/api/v1/publish"))
.andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
.andRespond(
MockRestResponseCreators.withSuccess("{\"status\" : \"OK\"}",
MediaType.APPLICATION_JSON));
}
}
}

78
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/servo/DimensionalServoMetricNamingTests.java

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
package org.springframework.cloud.netflix.metrics.servo;
import org.junit.Test;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.monitor.AbstractMonitor;
import com.netflix.servo.monitor.MonitorConfig;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class DimensionalServoMetricNamingTests {
private DimensionalServoMetricNaming naming = new DimensionalServoMetricNaming();
@Test
public void vanillaServoMetricWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("testMetric()")));
}
@Test
public void nameWithPeriodWorks() {
MonitorConfig config = MonitorConfig.builder("test.Metric").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("test.Metric()")));
}
@Test
public void typeTagWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag(DataSourceType.KEY, DataSourceType.COUNTER.getValue()).build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("testMetric(type=COUNTER)")));
}
@Test
public void instanceTagWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag("instance", "instance0").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("testMetric(instance=instance0)")));
}
@Test
public void statisticTagWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag("statistic", "min").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("testMetric(statistic=min)")));
}
@Test
public void allTagsWork() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag(DataSourceType.KEY, DataSourceType.COUNTER.getValue())
.withTag("instance", "instance0").withTag("statistic", "min").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name,
is(equalTo("testMetric(instance=instance0,statistic=min,type=COUNTER)")));
}
private class FixedValueMonitor<T> extends AbstractMonitor<T> {
T value;
protected FixedValueMonitor(MonitorConfig config, T value) {
super(config);
this.value = value;
}
@Override
public T getValue(int pollerIndex) {
return value;
}
}
}

61
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/servo/DefaultServerMetricNamingTests.java → spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/servo/HierarchicalServoMetricNamingTests.java

@ -14,62 +14,61 @@ @@ -14,62 +14,61 @@
* limitations under the License.
*/
package org.springframework.cloud.netflix.servo;
package org.springframework.cloud.netflix.metrics.servo;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import org.junit.Test;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.monitor.AbstractMonitor;
import com.netflix.servo.monitor.MonitorConfig;
import org.junit.Test;
import com.netflix.servo.Metric;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* @author Spencer Gibb
*/
public class DefaultServerMetricNamingTests {
public class HierarchicalServoMetricNamingTests {
private DefaultServoMetricNaming naming = new DefaultServoMetricNaming();
private HierarchicalServoMetricNaming naming = new HierarchicalServoMetricNaming();
@Test
public void vanillaServoMetricWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric") .build();
String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0));
MonitorConfig config = MonitorConfig.builder("testMetric").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("servo.testmetric")));
}
@Test
public void nameWithPeriodWorks() {
MonitorConfig config = MonitorConfig.builder("test.Metric") .build();
String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0));
MonitorConfig config = MonitorConfig.builder("test.Metric").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("servo.test.metric")));
}
@Test
public void typeTagWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag(DataSourceType.KEY, DataSourceType.COUNTER.getValue())
.build();
String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0));
.withTag(DataSourceType.KEY, DataSourceType.COUNTER.getValue()).build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("counter.servo.testmetric")));
}
@Test
public void instanceTagWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag("instance", "instance0")
.build();
String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0));
.withTag("instance", "instance0").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("servo.instance0.testmetric")));
}
@Test
public void statisticTagWorks() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag("statistic", "min")
.build();
String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0));
.withTag("statistic", "min").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("servo.testmetric.min")));
}
@ -77,10 +76,22 @@ public class DefaultServerMetricNamingTests { @@ -77,10 +76,22 @@ public class DefaultServerMetricNamingTests {
public void allTagsWork() {
MonitorConfig config = MonitorConfig.builder("testMetric")
.withTag(DataSourceType.KEY, DataSourceType.COUNTER.getValue())
.withTag("instance", "instance0")
.withTag("statistic", "min")
.build();
String name = naming.getName(new Metric(config, System.currentTimeMillis(), 0));
.withTag("instance", "instance0").withTag("statistic", "min").build();
String name = naming.asHierarchicalName(new FixedValueMonitor<>(config, 0));
assertThat(name, is(equalTo("counter.servo.instance0.testmetric.min")));
}
}
private class FixedValueMonitor<T> extends AbstractMonitor<T> {
T value;
protected FixedValueMonitor(MonitorConfig config, T value) {
super(config);
this.value = value;
}
@Override
public T getValue(int pollerIndex) {
return value;
}
}
}

49
spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/metrics/servo/ServoMetricReaderTests.java

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
package org.springframework.cloud.netflix.metrics.servo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.cloud.netflix.metrics.AbstractMetricsTests;
import com.google.common.collect.Lists;
import com.netflix.servo.DefaultMonitorRegistry;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.BasicTimer;
import com.netflix.servo.monitor.MonitorConfig;
import static junit.framework.Assert.assertEquals;
public class ServoMetricReaderTests extends AbstractMetricsTests {
@Test
public void singleCompositeMonitorYieldsMultipleActuatorMetrics() {
MonitorRegistry registry = DefaultMonitorRegistry.getInstance();
ServoMetricReader reader = new ServoMetricReader(registry,
new DimensionalServoMetricNaming());
MonitorConfig.Builder builder = new MonitorConfig.Builder("metricName");
BasicTimer timer = ServoMonitorCache.getTimer(builder.build());
List<Metric<?>> metrics = Lists.newArrayList(reader.findAll());
List<String> metricNames = new ArrayList<>();
for (Metric<?> metric : metrics) {
metricNames.add(metric.getName());
}
Collections.sort(metricNames);
assertEquals(4, metrics.size());
assertEquals("metricName(statistic=count,type=NORMALIZED,unit=MILLISECONDS)",
metricNames.get(0));
assertEquals("metricName(statistic=max,type=GAUGE,unit=MILLISECONDS)",
metricNames.get(1));
assertEquals("metricName(statistic=min,type=GAUGE,unit=MILLISECONDS)",
metricNames.get(2));
assertEquals("metricName(statistic=totalTime,type=NORMALIZED,unit=MILLISECONDS)",
metricNames.get(3));
}
}

0
spring-cloud-netflix-spectator/.jdk8

95
spring-cloud-netflix-spectator/pom.xml

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-spectator</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Netflix Spectator</name>
<description>Spring Cloud Netflix Spectator</description>
<properties>
<main.basedir>${basedir}/..</main.basedir>
<spectator.version>0.30.0</spectator.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.netflix.spectator</groupId>
<artifactId>spectator-api</artifactId>
<version>${spectator.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.spectator</groupId>
<artifactId>spectator-reg-servo</artifactId>
<version>${spectator.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.servo</groupId>
<artifactId>servo-core</artifactId>
<version>${servo.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.netflix.servo</groupId>
<artifactId>servo-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.spectator</groupId>
<artifactId>spectator-api</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.spectator</groupId>
<artifactId>spectator-reg-servo</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

69
spring-cloud-netflix-spectator/src/main/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricReader.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* Copyright 2013-2015 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.metrics.spectator;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Registry;
import static java.util.stream.StreamSupport.stream;
/**
* Reads metrics from Spectator, placing Spectator tags in the form
* <code>name(k1=v1,k2=v2)</code> where <code>name</code> is the metric name and the
* contents of the parentheses are tag key-value pairs.
*
* @author Jon Schneider
*/
public class SpectatorMetricReader implements MetricReader {
private Registry registry;
public SpectatorMetricReader(Registry registry) {
this.registry = registry;
}
protected static String asHierarchicalName(Id id) {
List<String> tags = stream(id.tags().spliterator(), false).map(
t -> t.key() + "=" + t.value()).collect(Collectors.toList());
return id.name() + "(" + String.join(",", tags) + ")";
}
@Override
public Metric<?> findOne(String name) {
throw new UnsupportedOperationException(
"cannot construct a tag-based Spectator id from a hierarchical name");
}
@Override
public Iterable<Metric<?>> findAll() {
return stream(registry.spliterator(), false)
.flatMap(
metric -> stream(metric.measure().spliterator(), false).map(
measure -> new Metric<>(asHierarchicalName(measure.id()),
measure.value())))
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
.collect(Collectors.toList());
}
@Override
public long count() {
return stream(registry.spliterator(), false).flatMap(
m -> stream(m.measure().spliterator(), false)).count();
}
}

134
spring-cloud-netflix-spectator/src/main/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricServices.java

@ -0,0 +1,134 @@ @@ -0,0 +1,134 @@
/*
* Copyright 2013-2015 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.metrics.spectator;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.boot.actuate.metrics.GaugeService;
import com.netflix.spectator.api.AbstractMeter;
import com.netflix.spectator.api.Gauge;
import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Measurement;
import com.netflix.spectator.api.Registry;
import com.netflix.spectator.impl.AtomicDouble;
/**
* Provides a <code>CounterService</code> and <code>GaugeService</code> implementation
* backed by Spectator.
*
* @author Jon Schneider
*/
public class SpectatorMetricServices implements CounterService, GaugeService {
private final Registry registry;
private final ConcurrentMap<Id, AtomicLong> counters = new ConcurrentHashMap<>();
private final ConcurrentMap<Id, AtomicDouble> gauges = new ConcurrentHashMap<>();
public SpectatorMetricServices(Registry registry) {
this.registry = registry;
}
protected static String stripMetricName(String metricName) {
return metricName.replaceFirst("^(timer|histogram|meter)\\.", "");
}
@Override
public void increment(String name) {
incrementInternal(name, 1L);
}
@Override
public void decrement(String name) {
incrementInternal(name, -1L);
}
private void incrementInternal(String name, long value) {
if (name.startsWith("status.")) {
// drop this metric since we are capturing it already with
// SpectatorHandlerInterceptor,
// and we are able to glean more information like exceptionType from that
// mechanism than what
// boot provides us
}
else if (name.startsWith("meter.")) {
registry.counter(stripMetricName(name)).increment(value);
}
else {
final Id id = registry.createId(name);
final AtomicLong gauge = getCounterStorage(id);
gauge.addAndGet(value);
registry.register(new NumericGauge(id, gauge));
}
}
@Override
public void reset(String name) {
final Id id = registry.createId(stripMetricName(name));
counters.remove(id);
gauges.remove(id);
}
@Override
public void submit(String name, double dValue) {
long value = ((Double) dValue).longValue();
if (name.startsWith("histogram.")) {
registry.distributionSummary(stripMetricName(name)).record(value);
}
else if (name.startsWith("timer.")) {
registry.timer(stripMetricName(name)).record(value, TimeUnit.MILLISECONDS);
}
else {
final Id id = registry.createId(name);
final AtomicDouble gauge = getGaugeStorage(id);
gauge.set(dValue);
registry.register(new NumericGauge(id, gauge));
}
}
private AtomicDouble getGaugeStorage(Id id) {
final AtomicDouble newGauge = new AtomicDouble(0);
final AtomicDouble existingGauge = gauges.putIfAbsent(id, newGauge);
return existingGauge == null ? newGauge : existingGauge;
}
private AtomicLong getCounterStorage(Id id) {
final AtomicLong newCounter = new AtomicLong(0);
final AtomicLong existingCounter = counters.putIfAbsent(id, newCounter);
return existingCounter == null ? newCounter : existingCounter;
}
private class NumericGauge extends AbstractMeter<Number> implements Gauge {
NumericGauge(Id id, Number val) {
super(registry.clock(), id, val);
}
@Override
public Iterable<Measurement> measure() {
return Collections.singleton(new Measurement(this.id, this.clock.wallTime(),
this.value()));
}
@SuppressWarnings("ConstantConditions")
@Override
public double value() {
return this.ref.get().doubleValue();
}
}
}

91
spring-cloud-netflix-spectator/src/main/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricsAutoConfiguration.java

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
/*
* Copyright 2013-2015 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.metrics.spectator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
import org.springframework.boot.actuate.metrics.reader.MetricReader;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.netflix.metrics.DefaultMetricsTagProvider;
import org.springframework.cloud.netflix.metrics.MetricsInterceptorConfiguration;
import org.springframework.cloud.netflix.metrics.MetricsTagProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import com.netflix.servo.DefaultMonitorRegistry;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.publish.MetricPoller;
import com.netflix.servo.publish.MonitorRegistryMetricPoller;
import com.netflix.spectator.api.Registry;
import com.netflix.spectator.servo.ServoRegistry;
/**
* Configures a basic Spectator registry that bridges to the legacy Servo API. We use this
* bridge because servo contains an Atlas plugin that allows us to easily send all
* Spectator metrics to Atlas. Servo contains a similar plugin for Graphite.
*
* Conditionally configures both an MVC interceptor and a RestTemplate interceptor that
* records metrics for request handling timings.
*
* @author Jon Schneider
*/
@Configuration
@AutoConfigureBefore(EndpointAutoConfiguration.class)
@ConditionalOnClass({ Registry.class, MetricReader.class })
@Import(MetricsInterceptorConfiguration.class)
public class SpectatorMetricsAutoConfiguration {
@Value("${netflix.metrics.servo.registryClass:com.netflix.servo.BasicMonitorRegistry}")
String servoRegistryClass;
@Bean
@ConditionalOnMissingBean
public MonitorRegistry monitorRegistry() {
System.setProperty(DefaultMonitorRegistry.class.getCanonicalName()
+ ".registryClass", servoRegistryClass);
return DefaultMonitorRegistry.getInstance();
}
@Bean
@ConditionalOnMissingBean(Registry.class)
Registry registry(MonitorRegistry monitorRegistry) {
return new ServoRegistry();
}
@Bean
@ConditionalOnMissingBean(MetricPoller.class)
MetricPoller metricPoller() {
return new MonitorRegistryMetricPoller();
}
@Bean
@ConditionalOnMissingBean
public SpectatorMetricServices spectatorMetricServices(Registry metricRegistry) {
return new SpectatorMetricServices(metricRegistry);
}
@Bean
public MetricReaderPublicMetrics spectatorPublicMetrics(Registry metricRegistry) {
SpectatorMetricReader reader = new SpectatorMetricReader(metricRegistry);
return new MetricReaderPublicMetrics(reader);
}
@Bean
public MetricsTagProvider defaultMetricsTagProvider() {
return new DefaultMetricsTagProvider();
}
}

2
spring-cloud-netflix-spectator/src/main/resources/META-INF/spring.factories

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.metrics.spectator.SpectatorMetricsAutoConfiguration

99
spring-cloud-netflix-spectator/src/test/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricReaderTests.java

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
/*
* Copyright 2013-2015 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.metrics.spectator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.junit.Test;
import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Tag;
import static org.junit.Assert.assertEquals;
import static org.springframework.cloud.netflix.metrics.spectator.SpectatorMetricReader.asHierarchicalName;
/**
* @author Jon Schneider
*/
public class SpectatorMetricReaderTests {
@Test
public void convertSpectatorMetricWithTagsToHierarchicalName() {
Id mWithTags = new SimpleId("m", "t1", "val1", "t2", "val2");
assertEquals("m(t1=val1,t2=val2)", asHierarchicalName(mWithTags));
}
private class SimpleTag implements Tag {
String key;
String value;
public SimpleTag(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public String key() {
return key;
}
@Override
public String value() {
return value;
}
}
private class SimpleId implements Id {
String name;
Collection<Tag> tags;
public SimpleId(String name, String... tagPairs) {
this.name = name;
tags = new ArrayList<>();
for (int i = 0; i < tagPairs.length; i += 2)
tags.add(new SimpleTag(tagPairs[i], tagPairs[i + 1]));
}
@Override
public String name() {
return name;
}
@Override
public Iterable<Tag> tags() {
return tags;
}
@Override
public Id withTag(String s, String s1) {
return null;
}
@Override
public Id withTag(Tag tag) {
return null;
}
@Override
public Id withTags(Iterable<Tag> iterable) {
return null;
}
@Override
public Id withTags(Map<String, String> map) {
return null;
}
}
}

39
spring-cloud-netflix-spectator/src/test/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricServicesTests.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* Copyright 2013-2015 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.metrics.spectator;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.springframework.cloud.netflix.metrics.spectator.SpectatorMetricServices.stripMetricName;
public class SpectatorMetricServicesTests {
@Test
public void metricNameWithNoTypeIsLeftUnchanged() {
assertEquals("foo", stripMetricName("foo"));
assertEquals("foo.bar", stripMetricName("foo.bar"));
}
@Test
public void metricTypeIsStrippedFromMetricName() {
assertEquals("foo", stripMetricName("timer.foo"));
assertEquals("foo", stripMetricName("histogram.foo"));
assertEquals("foo", stripMetricName("meter.foo"));
}
@Test
public void metricTypeNameEmbeddedInMiddleOfMetricNameIsNotRemoved() {
assertEquals("bar.timer.foo", stripMetricName("bar.timer.foo"));
}
}

160
spring-cloud-netflix-spectator/src/test/java/org/springframework/cloud/netflix/metrics/spectator/SpectatorMetricsHandlerInterceptorIntegrationTests.java

@ -0,0 +1,160 @@ @@ -0,0 +1,160 @@
/*
* Copyright 2013-2015 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.metrics.spectator;
import javax.servlet.http.HttpServletRequest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.test.ImportAutoConfiguration;
import org.springframework.cloud.netflix.metrics.MetricsHandlerInterceptor;
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.netflix.servo.MonitorRegistry;
import com.netflix.servo.monitor.BasicTimer;
import com.netflix.servo.monitor.MonitorConfig;
import static org.junit.Assert.assertFalse;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Jon Schneider
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpectatorMetricsTestConfig.class)
@WebAppConfiguration
@TestPropertySource(properties = "netflix.metrics.rest.metricName=metricName")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class SpectatorMetricsHandlerInterceptorIntegrationTests {
@Autowired
WebApplicationContext webAppContext;
@Autowired
MonitorRegistry registry;
MockMvc mvc;
@Test
public void autoConfigurationWiresTheMetricsInterceptor() {
assertFalse(webAppContext.getBeansOfType(MetricsHandlerInterceptor.class)
.isEmpty());
}
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();
}
@Test
public void metricsGatheredWhenSuccess() throws Exception {
mvc.perform(get("/test/some/request/10")).andExpect(status().isOk());
assertTimer("test_some_request_-id-", null, 200);
}
@Test
public void metricsGatheredWhenClientRequestBad() throws Exception {
mvc.perform(get("/test/some/request/oops"))
.andExpect(status().is4xxClientError());
assertTimer("test_some_request_-id-", null, 400);
}
@Test
public void metricsGatheredWhenUnhandledError() throws Exception {
try {
mvc.perform(get("/test/some/unhandledError/10")).andExpect(status().isOk());
}
catch (Exception e) {
}
assertTimer("test_some_unhandledError_-id-", "RuntimeException", 200);
}
@Test
public void metricsGatheredWhenHandledError() throws Exception {
mvc.perform(get("/test/some/error/10")).andExpect(status().is4xxClientError());
assertTimer("test_some_error_-id-", null, 422);
}
protected void assertTimer(String uriTag, String exceptionType, Integer status) {
MonitorConfig.Builder builder = new MonitorConfig.Builder("metricName")
.withTag("method", "GET").withTag("uri", uriTag)
.withTag("status", status.toString());
if (exceptionType != null)
builder = builder.withTag("exception", exceptionType);
BasicTimer timer = ServoMonitorCache.getTimer(builder.build());
Assert.assertEquals(1L, (long) timer.getCount());
}
}
@Configuration
@EnableWebMvc
@ImportAutoConfiguration({ SpectatorMetricsAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
class SpectatorMetricsTestConfig {
@Bean
SpectatorMetricsTestController testController() {
return new SpectatorMetricsTestController();
}
}
@RestController
@RequestMapping("/test/some")
@ControllerAdvice
class SpectatorMetricsTestController {
@RequestMapping("/request/{id}")
public String testSomeRequest(@PathVariable Long id) {
return id.toString();
}
@RequestMapping("/error/{id}")
public String testSomeHandledError(@PathVariable Long id) {
throw new IllegalStateException("Boom on $id!");
}
@RequestMapping("/unhandledError/{id}")
public String testSomeUnhandledError(@PathVariable Long id) {
throw new RuntimeException("Boom on $id!");
}
@ExceptionHandler(value = IllegalStateException.class)
@ResponseStatus(code = HttpStatus.UNPROCESSABLE_ENTITY)
ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) {
return new ModelAndView("error");
}
}

40
spring-cloud-starter-atlas/pom.xml

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-atlas</artifactId>
<name>spring-cloud-starter-atlas</name>
<description>Spring Cloud Starter Atlas</description>
<url>http://projects.spring.io/spring-cloud</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.servo</groupId>
<artifactId>servo-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-smile</artifactId>
</dependency>
</dependencies>
</project>

1
spring-cloud-starter-atlas/src/main/resources/META-INF/spring.provides

@ -0,0 +1 @@ @@ -0,0 +1 @@
provides: spring-cloud-starter, spring-cloud-netflix-core, servo-core, jackson-dataformat-smile

0
spring-cloud-starter-spectator/.jdk8

32
spring-cloud-starter-spectator/pom.xml

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-spectator</artifactId>
<name>spring-cloud-starter-spectator</name>
<description>Spring Cloud Starter Spectator</description>
<url>http://projects.spring.io/spring-cloud</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-spectator</artifactId>
</dependency>
</dependencies>
</project>

1
spring-cloud-starter-spectator/src/main/resources/META-INF/spring.provides

@ -0,0 +1 @@ @@ -0,0 +1 @@
provides: spring-cloud-starter, spring-cloud-netflix-spectator
Loading…
Cancel
Save