Polish metrics export feature added in 6f4d3f4

Closes gh-96
This commit is contained in:
Stephane Nicoll
2015-06-04 14:19:17 +02:00
parent 6f4d3f4e03
commit f1f8786631
11 changed files with 101 additions and 62 deletions

View File

@@ -7,6 +7,7 @@ order.
=== Release 1.0.0 (In progress) === Release 1.0.0 (In progress)
* https://github.com/spring-io/initializr/issues/96[#96]: export service metrics to redis
* https://github.com/spring-io/initializr/issues/115[#115]: rename /metadata/service to /metadata/config * https://github.com/spring-io/initializr/issues/115[#115]: rename /metadata/service to /metadata/config
* https://github.com/spring-io/initializr/issues/89[#89]: better describe service capability * https://github.com/spring-io/initializr/issues/89[#89]: better describe service capability
* https://github.com/spring-io/initializr/issues/105[#105]: support for dependencies group defaults * https://github.com/spring-io/initializr/issues/105[#105]: support for dependencies group defaults

View File

@@ -0,0 +1,3 @@
spring:
metrics:
export: enabled

View File

@@ -6,3 +6,5 @@ applications:
host: start-development host: start-development
domain: cfapps.io domain: cfapps.io
path: . path: .
services:
start-redis

View File

@@ -25,12 +25,12 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId> <artifactId>spring-boot-starter-groovy-templates</artifactId>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-groovy-templates</artifactId> <artifactId>spring-boot-starter-redis</artifactId>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>

View File

@@ -39,15 +39,19 @@ import org.springframework.scheduling.annotation.Scheduled
import org.springframework.util.ObjectUtils import org.springframework.util.ObjectUtils
/** /**
* @author Dave Syer * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} to export the metrics of an initializr instnace.
* *
* @author Dave Syer
* @since 1.0
*/ */
@Configuration @Configuration
@ConditionalOnBean(RedisConnectionFactory) @ConditionalOnBean(RedisConnectionFactory)
@ConditionalOnProperty(value='spring.metrics.export.enabled', matchIfMissing=true) @ConditionalOnProperty(value = 'spring.metrics.export.enabled')
@EnableScheduling @EnableScheduling
@EnableConfigurationProperties(MetricsProperties) @EnableConfigurationProperties(MetricsProperties)
@AutoConfigureAfter(value=RedisAutoConfiguration, name="org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration") @AutoConfigureAfter(value = RedisAutoConfiguration,
name = "org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration")
class InitializrMetricsExporterAutoConfiguration { class InitializrMetricsExporterAutoConfiguration {
@Autowired @Autowired
@@ -58,19 +62,19 @@ class InitializrMetricsExporterAutoConfiguration {
@Autowired @Autowired
ApplicationContext context ApplicationContext context
@Bean @Bean
// @ExportMetricWriter // Add this when upgrading to Boot 1.3 // @ExportMetricWriter // Add this when upgrading to Boot 1.3
MetricWriter writer() { MetricWriter writer() {
new RedisMetricRepository(connectionFactory, new RedisMetricRepository(connectionFactory,
metrics.prefix + metrics.getId(context.getId()) + '.' metrics.prefix + metrics.getId(context.getId()) + '.'
+ ObjectUtils.getIdentityHexString(context) + '.', + ObjectUtils.getIdentityHexString(context) + '.',
metrics.key) metrics.key)
} }
// Remove this when upgrading to Boot 1.3 // Remove this when upgrading to Boot 1.3
@Bean @Bean
@ConditionalOnMissingClass(name='org.springframework.boot.actuate.autoconfigure.ActuatorMetricWriter') @ConditionalOnMissingClass(name = 'org.springframework.boot.actuate.autoconfigure.ActuatorMetricWriter')
@Primary @Primary
MetricRepository reader() { MetricRepository reader() {
new InMemoryMetricRepository() new InMemoryMetricRepository()
@@ -78,15 +82,15 @@ class InitializrMetricsExporterAutoConfiguration {
// Remove this when upgrading to Boot 1.3 // Remove this when upgrading to Boot 1.3
@Bean @Bean
@ConditionalOnMissingClass(name='org.springframework.boot.actuate.autoconfigure.ActuatorMetricWriter') @ConditionalOnMissingClass(name = 'org.springframework.boot.actuate.autoconfigure.ActuatorMetricWriter')
Exporter exporter(InMemoryMetricRepository reader) { Exporter exporter(InMemoryMetricRepository reader) {
new MetricCopyExporter(reader, writer()) { new MetricCopyExporter(reader, writer()) {
@Override @Override
@Scheduled(fixedRateString = '${spring.metrics.export.default.delayMillis:5000}') @Scheduled(fixedRateString = '${spring.metrics.export.default.delayMillis:5000}')
void export() { void export() {
super.export() super.export()
} }
} }
} }
} }

View File

@@ -16,12 +16,14 @@
package io.spring.initializr.config package io.spring.initializr.config
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties
/** /**
* @author Dave Syer * Metrics-related configuration.
* *
* @author Dave Syer
* @since 1.0
*/ */
@ConfigurationProperties('initializr.metrics') @ConfigurationProperties('initializr.metrics')
class MetricsProperties { class MetricsProperties {
@@ -51,10 +53,6 @@ class MetricsProperties {
@Value('${spring.metrics.export.default.delayMillis:5000}') @Value('${spring.metrics.export.default.delayMillis:5000}')
long rateMillis = 5000L long rateMillis = 5000L
boolean isEnabled() {
rateMillis > 0
}
String getPrefix() { String getPrefix() {
if (prefix.endsWith('.')) { if (prefix.endsWith('.')) {
return prefix return prefix

View File

@@ -20,15 +20,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
/** /**
* Configuration of the initializr service. * Configuration of the initializr service.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 1.0 * @since 1.0
*/ */
@ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false) @ConfigurationProperties(prefix = 'initializr')
class InitializrProperties extends InitializrConfiguration { class InitializrProperties extends InitializrConfiguration {
@JsonIgnore @JsonIgnore

View File

@@ -14,21 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package io.spring.initializr.metrics; package io.spring.initializr.metrics
import static org.junit.Assert.*
import io.spring.initializr.generator.ProjectGenerationMetricsListener import io.spring.initializr.generator.ProjectGenerationMetricsListener
import io.spring.initializr.generator.ProjectRequest import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.DefaultMetadataElement
import io.spring.initializr.metadata.InitializrMetadata import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.InitializrMetadataProvider import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.support.DefaultInitializrMetadataProvider import io.spring.initializr.test.OfflineInitializrMetadataProvider
import io.spring.initializr.test.RedisRunning import io.spring.initializr.test.RedisRunning
import org.junit.Before
import org.junit.Before;
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository
@@ -39,41 +37,44 @@ import org.springframework.boot.test.SpringApplicationConfiguration
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import static org.junit.Assert.assertTrue
/** /**
* @author Dave Syer * @author Dave Syer
*
*/ */
@RunWith(SpringJUnit4ClassRunner) @RunWith(SpringJUnit4ClassRunner)
@SpringApplicationConfiguration(classes = Config) @SpringApplicationConfiguration(classes = Config)
@IntegrationTest(['spring.metrics.export.default.delayMillis:500','initializr.metrics.prefix:test.prefix','initializr.metrics.key:key.test']) @IntegrationTest(['spring.metrics.export.default.delayMillis:500',
'spring.metrics.export.enabled:true',
'initializr.metrics.prefix:test.prefix', 'initializr.metrics.key:key.test'])
public class MetricsExportTests { public class MetricsExportTests {
@Rule @Rule
public RedisRunning running = new RedisRunning() public RedisRunning running = new RedisRunning()
@Autowired @Autowired
ProjectGenerationMetricsListener listener ProjectGenerationMetricsListener listener
@Autowired @Autowired
@Qualifier("writer") @Qualifier("writer")
MetricWriter writer MetricWriter writer
RedisMetricRepository repository RedisMetricRepository repository
@Before @Before
void init() { void init() {
repository = (RedisMetricRepository) writer repository = (RedisMetricRepository) writer
repository.findAll().each { repository.findAll().each {
repository.reset(it.name) repository.reset(it.name)
} }
assertTrue("Metrics not empty", repository.findAll().size()==0) assertTrue("Metrics not empty", repository.findAll().size() == 0)
} }
@Test @Test
void exportAndCheckMetricsExist() { void exportAndCheckMetricsExist() {
listener.onGeneratedProject(new ProjectRequest()) listener.onGeneratedProject(new ProjectRequest())
Thread.sleep(1000L) Thread.sleep(1000L)
assertTrue("No metrics exported", repository.findAll().size()>0) assertTrue("No metrics exported", repository.findAll().size() > 0)
} }
@EnableAutoConfiguration @EnableAutoConfiguration
@@ -81,12 +82,7 @@ public class MetricsExportTests {
@Bean @Bean
InitializrMetadataProvider initializrMetadataProvider(InitializrMetadata metadata) { InitializrMetadataProvider initializrMetadataProvider(InitializrMetadata metadata) {
new DefaultInitializrMetadataProvider(metadata) { new OfflineInitializrMetadataProvider(metadata)
@Override
protected List<DefaultMetadataElement> fetchBootVersions() {
null // Disable metadata fetching from spring.io
}
}
} }
} }
} }

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-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 io.spring.initializr.test
import io.spring.initializr.metadata.DefaultMetadataElement
import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.support.DefaultInitializrMetadataProvider
/**
* A {@link DefaultInitializrMetadataProvider} that does not attempt to
* use the network to refresh its configuration.
*
* @author Stephane Nicoll
* @since 1.0
*/
class OfflineInitializrMetadataProvider extends DefaultInitializrMetadataProvider {
OfflineInitializrMetadataProvider(InitializrMetadata metadata) {
super(metadata)
}
@Override
protected List<DefaultMetadataElement> fetchBootVersions() {
null // Disable metadata fetching from spring.io
}
}

View File

@@ -20,19 +20,22 @@ import org.junit.Assume
import org.junit.rules.TestWatcher import org.junit.rules.TestWatcher
import org.junit.runner.Description import org.junit.runner.Description
import org.junit.runners.model.Statement import org.junit.runners.model.Statement
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory import org.springframework.data.redis.connection.jedis.JedisConnectionFactory
/** /**
* @author Dave Syer * A {@link org.junit.rules.TestRule} that validates Redis is available.
* *
* @author Dave Syer
* @since 1.0
*/ */
class RedisRunning extends TestWatcher { class RedisRunning extends TestWatcher {
JedisConnectionFactory connectionFactory; JedisConnectionFactory connectionFactory;
@Override @Override
Statement apply(Statement base, Description description) { Statement apply(Statement base, Description description) {
if (connectionFactory==null) { if (connectionFactory == null) {
connectionFactory = new JedisConnectionFactory() connectionFactory = new JedisConnectionFactory()
connectionFactory.afterPropertiesSet() connectionFactory.afterPropertiesSet()
} }

View File

@@ -19,10 +19,9 @@ package io.spring.initializr.web
import java.nio.charset.Charset import java.nio.charset.Charset
import io.spring.initializr.mapper.InitializrMetadataVersion import io.spring.initializr.mapper.InitializrMetadataVersion
import io.spring.initializr.metadata.DefaultMetadataElement
import io.spring.initializr.metadata.InitializrMetadata import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.support.DefaultInitializrMetadataProvider
import io.spring.initializr.metadata.InitializrMetadataProvider import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.test.OfflineInitializrMetadataProvider
import io.spring.initializr.test.ProjectAssert import io.spring.initializr.test.ProjectAssert
import org.json.JSONObject import org.json.JSONObject
import org.junit.Rule import org.junit.Rule
@@ -85,7 +84,7 @@ abstract class AbstractInitializrControllerIntegrationTests {
*/ */
protected void validateContentType(ResponseEntity<String> response, MediaType expected) { protected void validateContentType(ResponseEntity<String> response, MediaType expected) {
def actual = response.headers.getContentType() def actual = response.headers.getContentType()
assertTrue "Non compatible media-type, expected $expected, got $actual" , assertTrue "Non compatible media-type, expected $expected, got $actual",
actual.isCompatibleWith(expected) actual.isCompatibleWith(expected)
assertEquals 'All text content should be UTF-8 encoded', assertEquals 'All text content should be UTF-8 encoded',
'UTF-8', actual.getParameter('charset') 'UTF-8', actual.getParameter('charset')
@@ -93,7 +92,7 @@ abstract class AbstractInitializrControllerIntegrationTests {
protected void validateMetadata(ResponseEntity<String> response, MediaType mediaType, protected void validateMetadata(ResponseEntity<String> response, MediaType mediaType,
String version, JSONCompareMode compareMode) { String version, JSONCompareMode compareMode) {
validateContentType(response, mediaType) validateContentType(response, mediaType)
def json = new JSONObject(response.body) def json = new JSONObject(response.body)
def expected = readMetadataJson(version) def expected = readMetadataJson(version)
@@ -147,7 +146,7 @@ abstract class AbstractInitializrControllerIntegrationTests {
} }
protected <T> ResponseEntity<T> execute(String contextPath, Class<T> responseType, protected <T> ResponseEntity<T> execute(String contextPath, Class<T> responseType,
String userAgentHeader, String... acceptHeaders) { String userAgentHeader, String... acceptHeaders) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
if (userAgentHeader) { if (userAgentHeader) {
headers.set("User-Agent", userAgentHeader); headers.set("User-Agent", userAgentHeader);
@@ -217,12 +216,7 @@ abstract class AbstractInitializrControllerIntegrationTests {
@Bean @Bean
InitializrMetadataProvider initializrMetadataProvider(InitializrMetadata metadata) { InitializrMetadataProvider initializrMetadataProvider(InitializrMetadata metadata) {
new DefaultInitializrMetadataProvider(metadata) { new OfflineInitializrMetadataProvider(metadata)
@Override
protected List<DefaultMetadataElement> fetchBootVersions() {
null // Disable metadata fetching from spring.io
}
}
} }
} }