diff --git a/spring-cloud-task-core/pom.xml b/spring-cloud-task-core/pom.xml index 1d177bc4..140e4916 100755 --- a/spring-cloud-task-core/pom.xml +++ b/spring-cloud-task-core/pom.xml @@ -106,6 +106,15 @@ spring-boot-autoconfigure-processor true + + io.micrometer + micrometer-core + + + io.pivotal.cfenv + java-cfenv-test-support + test + diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/CloudFoundryMicrometerTagsConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/CloudFoundryMicrometerTagsConfiguration.java new file mode 100644 index 00000000..0c3d3fc3 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/CloudFoundryMicrometerTagsConfiguration.java @@ -0,0 +1,79 @@ +/* + * Copyright 2019-2019 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 + * + * https://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.task.configuration; + +import io.micrometer.core.instrument.MeterRegistry; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.task.metrics.fork.MeterRegistryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +/** + * Micrometer common tags for Cloud Foundry deployment properties. Based on the CF + * application environment variables: + * https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html + * + * Tags are set only if the "cloud" Spring profile is set. The "cloud" profile is + * activated automatically when an application is deployed in CF: + * https://docs.cloudfoundry.org/buildpacks/java/configuring-service-connections/spring-service-bindings.html#cloud-profiles + * + * Use the spring.cloud.task.metrics.cf.tags.enabled=false property to disable inserting + * those tags. + * + * @author Christian Tzolov + */ +@Configuration +@Profile("cloud") +@ConditionalOnProperty(name = "spring.cloud.task.metrics.cf.tags.enabled", + havingValue = "true", matchIfMissing = true) +public class CloudFoundryMicrometerTagsConfiguration { + + @Value("${vcap.application.org_name:default}") + private String organizationName; + + @Value("${vcap.application.space_id:unknown}") + private String spaceId; + + @Value("${vcap.application.space_name:unknown}") + private String spaceName; + + @Value("${vcap.application.application_name:unknown}") + private String applicationName; + + @Value("${vcap.application.application_id:unknown}") + private String applicationId; + + @Value("${vcap.application.application_version:unknown}") + private String applicationVersion; + + @Value("${vcap.application.instance_index:0}") + private String instanceIndex; + + @Bean + public MeterRegistryCustomizer cloudFoundryMetricsCommonTags() { + return registry -> registry.config().commonTags("cf.org.name", organizationName) + .commonTags("cf.space.id", spaceId).commonTags("cf.space.name", spaceName) + .commonTags("cf.app.id", applicationId) + .commonTags("cf.app.name", applicationName) + .commonTags("cf.app.version", applicationVersion) + .commonTags("cf.instance.index", instanceIndex); + } + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SpringCloudTaskMicrometerCommonTagsConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SpringCloudTaskMicrometerCommonTagsConfiguration.java new file mode 100644 index 00000000..b7bfc2ab --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SpringCloudTaskMicrometerCommonTagsConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019-2019 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 + * + * https://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.task.configuration; + +import io.micrometer.core.instrument.MeterRegistry; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.task.metrics.fork.MeterRegistryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration extends the micrometer metrics with additional tags such as: task + * name, application name, instance index and guids. Later are necessary to allow + * discrimination and aggregation of app metrics by external metrics collection and + * visualizaiton tools. + * + * Use the spring.cloud.task.metrics.common.tags.enabled=false property to disable + * inserting those tags. + * + * @author Christian Tzolov + */ +@Configuration +@ConditionalOnProperty(name = "spring.cloud.task.metrics.common.tags.enabled", + havingValue = "true", matchIfMissing = true) +public class SpringCloudTaskMicrometerCommonTagsConfiguration { + + @Value("${spring.cloud.task.name:unknown}") + private String taskName; + + @Value("${spring.cloud.task.executionid:unknown}") + private String taskExecutionId; + + @Value("${spring.cloud.task.external-execution-id:unknown}") + private String taskExternalExecutionId; + + @Value("${spring.cloud.task.parent-execution-id:unknown}") + private String taskParentExecutionId; + + @Bean + public MeterRegistryCustomizer metricsCommonTags() { + return registry -> registry.config().commonTags("task.name", taskName) + .commonTags("task.execution.id", taskExecutionId) + .commonTags("task.external.execution.id", taskExternalExecutionId) + .commonTags("task.parent.execution.id", taskParentExecutionId); + } + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java index cc151362..b2580996 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java @@ -88,6 +88,8 @@ public class TaskLifecycleListener implements ApplicationListener startupListenerList = new ArrayList<>( this.taskExecutionListeners); @@ -334,6 +338,7 @@ public class TaskLifecycleListener implements ApplicationListener + * Customizers are guaranteed to be applied before any {@link Meter} is registered with + * the registry. + * + * @author Michael Minella + */ +@FunctionalInterface +public interface MeterRegistryCustomizer { + + /** + * Customize the given {@code registry}. + * @param registry the registry to customize + */ + void customize(T registry); + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/metrics/fork/MetricsAutoConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/metrics/fork/MetricsAutoConfiguration.java new file mode 100644 index 00000000..d2b2ccc1 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/metrics/fork/MetricsAutoConfiguration.java @@ -0,0 +1,139 @@ +/* + * Copyright 2019-2019 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 + * + * https://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.task.metrics.fork; + +import java.util.List; +import java.util.stream.Collectors; + +import io.micrometer.core.annotation.Timed; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.util.LambdaSafe; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Forked and stripped down version of the + * org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration form + * spring-boot-actuator + * + * {@link EnableAutoConfiguration Auto-configuration} for Micrometer-based metrics. + * + * @author Michael Minella + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(Timed.class) +@AutoConfigureBefore(name = { + "org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration" }) +public class MetricsAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public Clock micrometerClockFork() { + return Clock.SYSTEM; + } + + @Bean + public static MeterRegistryPostProcessor meterRegistryPostProcessorFork( + ObjectProvider> meterRegistryCustomizers) { + return new MeterRegistryPostProcessor(meterRegistryCustomizers); + } + + /** + * Forked and stripped down version of the + * org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryPostProcessor + * form spring-boot-actuator + * + * {@link BeanPostProcessor} that delegates to a lazily created + * {@link MeterRegistryConfigurer} to post-process {@link MeterRegistry} beans. + * + */ + static class MeterRegistryPostProcessor implements BeanPostProcessor { + + private final ObjectProvider> meterRegistryCustomizers; + + private volatile MeterRegistryConfigurer configurer; + + MeterRegistryPostProcessor( + ObjectProvider> meterRegistryCustomizers) { + this.meterRegistryCustomizers = meterRegistryCustomizers; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof MeterRegistry) { + getConfigurer().configure((MeterRegistry) bean); + } + return bean; + } + + private MeterRegistryConfigurer getConfigurer() { + if (this.configurer == null) { + this.configurer = new MeterRegistryConfigurer( + this.meterRegistryCustomizers); + } + return this.configurer; + } + + } + + /** + * Forked and stripped down version of the + * org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryConfigurer form + * spring-boot-actuator + * + * Configurer to apply {@link MeterRegistryCustomizer customizers} to + * {@link MeterRegistry meter registries}. + * + */ + static class MeterRegistryConfigurer { + + private final ObjectProvider> customizers; + + MeterRegistryConfigurer(ObjectProvider> customizers) { + this.customizers = customizers; + } + + void configure(MeterRegistry registry) { + customize(registry); + } + + @SuppressWarnings("unchecked") + private void customize(MeterRegistry registry) { + LambdaSafe + .callbacks(MeterRegistryCustomizer.class, + asOrderedList(this.customizers), registry) + .withLogger(MeterRegistryConfigurer.class) + .invoke((customizer) -> customizer.customize(registry)); + } + + private List asOrderedList(ObjectProvider provider) { + return provider.orderedStream().collect(Collectors.toList()); + } + + } + +} diff --git a/spring-cloud-task-core/src/main/resources/META-INF/spring.factories b/spring-cloud-task-core/src/main/resources/META-INF/spring.factories index 243ca91b..aeba503d 100644 --- a/spring-cloud-task-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-task-core/src/main/resources/META-INF/spring.factories @@ -1,3 +1,7 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.task.configuration.SingleTaskConfiguration,\ -org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration\ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.task.configuration.SingleTaskConfiguration,\ +org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration,\ +org.springframework.cloud.task.configuration.CloudFoundryMicrometerTagsConfiguration,\ +org.springframework.cloud.task.configuration.SpringCloudTaskMicrometerCommonTagsConfiguration,\ +org.springframework.cloud.task.metrics.fork.MetricsAutoConfiguration\ diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/AbstractMicrometerTest.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/AbstractMicrometerTest.java new file mode 100644 index 00000000..97bc286d --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/AbstractMicrometerTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2019-2019 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 + * + * https://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.task.micrometer; + +import java.io.IOException; +import java.nio.charset.Charset; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.simple.SimpleConfig; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.pivotal.cfenv.test.CfEnvTestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.StreamUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Tzolov + * @author Soby Chacko + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = { AbstractMicrometerTest.AutoConfigurationApplication.class }) +@DirtiesContext +public class AbstractMicrometerTest { + + @Autowired + protected SimpleMeterRegistry simpleMeterRegistry; + + @Autowired + protected ConfigurableApplicationContext context; + + protected Meter meter; + + @Before + public void before() { + Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove); + assertThat(simpleMeterRegistry).isNotNull(); + meter = simpleMeterRegistry.find("spring.integration.handlers").meter(); + assertThat(meter).isNotNull().withFailMessage( + "The spring.integration.handlers meter must be present in SpringBoot apps!"); + } + + @After + public void after() { + Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove); + } + + @BeforeClass + public static void setup() throws IOException { + String serviceJson = StreamUtils.copyToString(new DefaultResourceLoader() + .getResource("classpath:/micrometer/pcf-scs-info.json").getInputStream(), + Charset.forName("UTF-8")); + CfEnvTestUtils.mockVcapServicesFromString(serviceJson); + } + + @SpringBootApplication + public static class AutoConfigurationApplication { + + public static void main(String[] args) { + SpringApplication.run(AutoConfigurationApplication.class, args); + } + + @Bean + @ConditionalOnMissingBean + public SimpleMeterRegistry simpleMeterRegistry(SimpleConfig config, Clock clock) { + return new SimpleMeterRegistry(config, clock); + } + + @Bean + @ConditionalOnMissingBean + public SimpleConfig simpleConfig() { + return key -> null; + } + + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/CloudFoundryMicrometerTagsConfigurationTest.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/CloudFoundryMicrometerTagsConfigurationTest.java new file mode 100644 index 00000000..17385606 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/CloudFoundryMicrometerTagsConfigurationTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2019-2019 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 + * + * https://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.task.micrometer; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Tzolov + */ +@RunWith(Enclosed.class) +public class CloudFoundryMicrometerTagsConfigurationTest { + + @ActiveProfiles("cloud") + public static class ActiveCloudProfileDefaultValues extends AbstractMicrometerTest { + + @Test + public void testDefaultTagValues() { + assertThat(meter.getId().getTag("cf.org.name")).isEqualTo("default"); + assertThat(meter.getId().getTag("cf.space.id")).isEqualTo("unknown"); + assertThat(meter.getId().getTag("cf.space.name")).isEqualTo("unknown"); + assertThat(meter.getId().getTag("cf.app.name")).isEqualTo("unknown"); + assertThat(meter.getId().getTag("cf.app.id")).isEqualTo("unknown"); + assertThat(meter.getId().getTag("cf.app.version")).isEqualTo("unknown"); + assertThat(meter.getId().getTag("cf.instance.index")).isEqualTo("0"); + } + + } + + @TestPropertySource(properties = { "vcap.application.org_name=PivotalOrg", + "vcap.application.space_id=SpringSpaceId", + "vcap.application.space_name=SpringSpace", + "vcap.application.application_name=App123", + "vcap.application.application_id=123guid", + "vcap.application.application_version=2.0", + "vcap.application.instance_index=123" }) + @ActiveProfiles("cloud") + public static class ActiveCloudProfile extends AbstractMicrometerTest { + + @Test + public void testPresetTagValues() { + assertThat(meter.getId().getTag("cf.org.name")).isEqualTo("PivotalOrg"); + assertThat(meter.getId().getTag("cf.space.id")).isEqualTo("SpringSpaceId"); + assertThat(meter.getId().getTag("cf.space.name")).isEqualTo("SpringSpace"); + assertThat(meter.getId().getTag("cf.app.name")).isEqualTo("App123"); + assertThat(meter.getId().getTag("cf.app.id")).isEqualTo("123guid"); + assertThat(meter.getId().getTag("cf.app.version")).isEqualTo("2.0"); + assertThat(meter.getId().getTag("cf.instance.index")).isEqualTo("123"); + } + + } + + @TestPropertySource(properties = { "vcap.application.org_name=PivotalOrg", + "vcap.application.space_id=SpringSpaceId", + "vcap.application.space_name=SpringSpace", + "vcap.application.application_name=App123", + "vcap.application.application_id=123guid", + "vcap.application.application_version=2.0", + "vcap.application.instance_index=123" }) + public static class InactiveCloudProfile extends AbstractMicrometerTest { + + @Test + public void testDisabledTagValues() { + assertThat(meter.getId().getTag("cf.org.name")).isNull(); + assertThat(meter.getId().getTag("cf.space.id")).isNull(); + assertThat(meter.getId().getTag("cf.space.name")).isNull(); + assertThat(meter.getId().getTag("cf.app.name")).isNull(); + assertThat(meter.getId().getTag("cf.app.id")).isNull(); + assertThat(meter.getId().getTag("cf.app.version")).isNull(); + assertThat(meter.getId().getTag("cf.instance.index")).isNull(); + } + + } + + @TestPropertySource( + properties = { "spring.cloud.task.metrics.cf.tags.enabled=false" }) + @ActiveProfiles("cloud") + public static class ActiveCloudProfileDisabledProperty extends InactiveCloudProfile { + + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/SpringCloudTaskMicrometerCommonTagsConfigurationTest.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/SpringCloudTaskMicrometerCommonTagsConfigurationTest.java new file mode 100644 index 00000000..d00c013e --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/SpringCloudTaskMicrometerCommonTagsConfigurationTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2019-2019 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 + * + * https://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.task.micrometer; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Tzolov + */ +@RunWith(Enclosed.class) +public class SpringCloudTaskMicrometerCommonTagsConfigurationTest { + + public static class TestDefaultTagValues extends AbstractMicrometerTest { + + @Test + public void testDefaultTagValues() { + assertThat(meter.getId().getTag("task.name")).isEqualTo("unknown"); + assertThat(meter.getId().getTag("task.execution.id")).isEqualTo("unknown"); + assertThat(meter.getId().getTag("task.parent.execution.id")) + .isEqualTo("unknown"); + assertThat(meter.getId().getTag("task.external.execution.id")) + .isEqualTo("unknown"); + } + + } + + @TestPropertySource(properties = { "spring.cloud.task.name=myTask", + "spring.cloud.task.executionid=123", + "spring.cloud.task.parent-execution-id=999", + "spring.cloud.task.external-execution-id=696" }) + public static class TestPresetTagValues extends AbstractMicrometerTest { + + @Test + public void testPresetTagValues() { + assertThat(meter.getId().getTag("task.name")).isEqualTo("myTask"); + assertThat(meter.getId().getTag("task.execution.id")).isEqualTo("123"); + assertThat(meter.getId().getTag("task.parent.execution.id")).isEqualTo("999"); + assertThat(meter.getId().getTag("task.external.execution.id")) + .isEqualTo("696"); + } + + } + + @TestPropertySource( + properties = { "spring.cloud.task.metrics.common.tags.enabled=false" }) + public static class TestDisabledTagValues extends AbstractMicrometerTest { + + @Test + public void testDefaultTagValues() { + assertThat(meter.getId().getTag("task.name")).isNull(); + assertThat(meter.getId().getTag("task.execution.id")).isNull(); + assertThat(meter.getId().getTag("task.parent.execution.id")).isNull(); + assertThat(meter.getId().getTag("task.external.execution.id")).isNull(); + } + + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskMetricsTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskMetricsTests.java new file mode 100644 index 00000000..942197f7 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskMetricsTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2019-2019 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 + * + * https://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.task.micrometer; + +import java.util.ArrayList; +import java.util.Date; + +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.cloud.task.listener.TaskMetrics; +import org.springframework.cloud.task.repository.TaskExecution; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Tzolov + */ +public class TaskMetricsTests { + + private TaskMetrics taskMetrics; + + private SimpleMeterRegistry simpleMeterRegistry; + + @Before + public void before() { + Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove); + simpleMeterRegistry = new SimpleMeterRegistry(); + Metrics.addRegistry(simpleMeterRegistry); + taskMetrics = new TaskMetrics(); + } + + @After + public void after() { + Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove); + } + + @Test + public void successfulTask() { + + TaskExecution taskExecution = new TaskExecution(123L, 0, "myTask72", new Date(), + new Date(), null, new ArrayList<>(), null, null, -1L); + + // Start Task + taskMetrics.onTaskStartup(taskExecution); + + //// Test Long Task Timer while the task is running. + LongTaskTimer longTaskTimer = simpleMeterRegistry + .find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer(); + assertThat(longTaskTimer) + .withFailMessage("LongTask timer should be created on Task start") + .isNotNull(); + // assertThat(longTaskTimer.activeTasks()).isEqualTo(1); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) + .isEqualTo("myTask72"); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) + .isEqualTo("123"); + + // Finish Task + taskMetrics.onTaskEnd(taskExecution); + + // Test Timer + Timer taskTimer = simpleMeterRegistry.find(TaskMetrics.SPRING_CLOUD_TASK_METER) + .timer(); + assertThat(taskTimer).isNotNull(); + // assertThat(taskTimer.count()).isEqualTo(1L); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) + .isEqualTo("myTask72"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) + .isEqualTo("123"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXTERNAL_EXECUTION_ID_TAG)) + .isEqualTo("unknown"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_PARENT_EXECUTION_ID_TAG)) + .isEqualTo("-1"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXIT_CODE_TAG)) + .isEqualTo("0"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXCEPTION_TAG)) + .isEqualTo("none"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_STATUS_TAG)) + .isEqualTo(TaskMetrics.STATUS_SUCCESS); + + // Test Long Task Timer after the task has completed. + // LongTaskTimer longTaskTimer = simpleMeterRegistry + // .find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer(); + assertThat(longTaskTimer.activeTasks()).isEqualTo(0); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) + .isEqualTo("myTask72"); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) + .isEqualTo("123"); + } + + @Test + public void failingTask() { + + TaskExecution taskExecution = new TaskExecution(123L, 0, "myTask", new Date(), + new Date(), null, new ArrayList<>(), null, null, -1L); + + // Start Task + taskMetrics.onTaskStartup(taskExecution); + + // Test Long Task Timer while the task is running. + LongTaskTimer longTaskTimer = simpleMeterRegistry + .find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer(); + assertThat(longTaskTimer) + .withFailMessage("LongTask timer should be created on Task start") + .isNotNull(); + assertThat(longTaskTimer.activeTasks()).isEqualTo(1); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) + .isEqualTo("myTask"); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) + .isEqualTo("123"); + + taskMetrics.onTaskFailed(taskExecution, new RuntimeException("Test")); + + // Finish Task. TaskLifecycleListen calls onTaskEnd after the onTaskFailed. Make + // sure that the counter status + // is not affected by this. + taskMetrics.onTaskEnd(taskExecution); + + Timer taskTimer = simpleMeterRegistry.find(TaskMetrics.SPRING_CLOUD_TASK_METER) + .timer(); + assertThat(taskTimer).isNotNull(); + + assertThat(taskTimer).isNotNull(); + // assertThat(taskTimer.count()).isEqualTo(1L); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) + .isEqualTo("myTask"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) + .isEqualTo("123"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXTERNAL_EXECUTION_ID_TAG)) + .isEqualTo("unknown"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_PARENT_EXECUTION_ID_TAG)) + .isEqualTo("-1"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXIT_CODE_TAG)) + .isEqualTo("0"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXCEPTION_TAG)) + .isEqualTo("RuntimeException"); + assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_STATUS_TAG)) + .isEqualTo(TaskMetrics.STATUS_FAILURE); + + // Test Long Task Timer after the task has completed. + assertThat(longTaskTimer.activeTasks()).isEqualTo(0); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) + .isEqualTo("myTask"); + assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) + .isEqualTo("123"); + } + +} diff --git a/spring-cloud-task-core/src/test/resources/micrometer/pcf-scs-info.json b/spring-cloud-task-core/src/test/resources/micrometer/pcf-scs-info.json new file mode 100644 index 00000000..adede92b --- /dev/null +++ b/spring-cloud-task-core/src/test/resources/micrometer/pcf-scs-info.json @@ -0,0 +1,13 @@ +{ + "sso":[{ + "name": "sso", + "label": "sso", + "plan": "notfree", + "tags": ["configuration"], + "credentials":{ + "uri": "https://pivotal.io", + "client_id": "fakeClientId", + "client_secret": "fakeSecret", + "access_token_uri": "token" + } + }]} diff --git a/spring-cloud-task-dependencies/pom.xml b/spring-cloud-task-dependencies/pom.xml index 9f34f679..e06c3d7c 100644 --- a/spring-cloud-task-dependencies/pom.xml +++ b/spring-cloud-task-dependencies/pom.xml @@ -14,6 +14,10 @@ + + 1.0.1.RELEASE + + @@ -36,6 +40,11 @@ spring-cloud-task-stream 2.2.0.BUILD-SNAPSHOT + + io.pivotal.cfenv + java-cfenv-test-support + ${java-cfenv-boot.version} +