Add Micrometer support for Task applications
- New dependencies - micrometer-core - java-cfenv - required by the CF tags configuration. - Add TaskMetrics configuration that installs two Meters (Timer and LongTaskTimer) that produce the following time-series: - spring.cloud.task - shows task duration (updated only after the task has completed). - spring.cloud.task.active - provides run time information about the non-completed tasks. Both meters are started at TaskExecutionListener#onTaskStartup() and ended at onTaskEnd() or onTaskFailure(). - Add common Task tags assigned to all time-series produced by the Task (not only the s.c.task and s.c.task.active). This allows to analyze CPU, Memory and other measurements grouped by task/execution ids. - Add Cloud Found tags - Activated only for CF target platform. Resolves #608
This commit is contained in:
committed by
Michael Minella
parent
aa29153656
commit
d4b51a074b
@@ -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<MeterRegistry> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<MeterRegistry> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,6 +88,8 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
|
||||
|
||||
private final TaskListenerExecutorObjectFactory taskListenerExecutorObjectFactory;
|
||||
|
||||
private final TaskMetrics taskMetrics;
|
||||
|
||||
@Autowired
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@@ -144,6 +146,7 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
|
||||
this.taskExplorer = taskExplorer;
|
||||
this.taskProperties = taskProperties;
|
||||
this.taskListenerExecutorObjectFactory = taskListenerExecutorObjectFactory;
|
||||
this.taskMetrics = new TaskMetrics();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,6 +315,7 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
|
||||
}
|
||||
|
||||
private TaskExecution invokeOnTaskStartup(TaskExecution taskExecution) {
|
||||
this.taskMetrics.onTaskStartup(taskExecution);
|
||||
TaskExecution listenerTaskExecution = getTaskExecutionCopy(taskExecution);
|
||||
List<TaskExecutionListener> startupListenerList = new ArrayList<>(
|
||||
this.taskExecutionListeners);
|
||||
@@ -334,6 +338,7 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
|
||||
}
|
||||
|
||||
private TaskExecution invokeOnTaskEnd(TaskExecution taskExecution) {
|
||||
this.taskMetrics.onTaskEnd(taskExecution);
|
||||
TaskExecution listenerTaskExecution = getTaskExecutionCopy(taskExecution);
|
||||
if (this.taskExecutionListeners != null) {
|
||||
try {
|
||||
@@ -357,6 +362,7 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
|
||||
|
||||
private TaskExecution invokeOnTaskError(TaskExecution taskExecution,
|
||||
Throwable throwable) {
|
||||
this.taskMetrics.onTaskFailed(taskExecution, throwable);
|
||||
TaskExecution listenerTaskExecution = getTaskExecutionCopy(taskExecution);
|
||||
if (this.taskExecutionListeners != null) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.listener;
|
||||
|
||||
import io.micrometer.core.instrument.LongTaskTimer;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class TaskMetrics {
|
||||
|
||||
/**
|
||||
* Task timer measurements. Records information about task duration and status.
|
||||
*/
|
||||
public static final String SPRING_CLOUD_TASK_METER = "spring.cloud.task";
|
||||
|
||||
/**
|
||||
* LongTask timer measurement. Records the run-time status of long-time lasting tasks.
|
||||
*/
|
||||
public static final String SPRING_CLOUD_TASK_ACTIVE_METER = "spring.cloud.task.active";
|
||||
|
||||
/**
|
||||
* Successful task execution status indicator.
|
||||
*/
|
||||
public static final String STATUS_SUCCESS = "success";
|
||||
|
||||
/**
|
||||
* Failing task execution status indicator.
|
||||
*/
|
||||
public static final String STATUS_FAILURE = "failure";
|
||||
|
||||
/**
|
||||
* task name measurement tag.
|
||||
*/
|
||||
public static final String TASK_NAME_TAG = "task.name";
|
||||
|
||||
/**
|
||||
* task execution id tag.
|
||||
*/
|
||||
public static final String TASK_EXECUTION_ID_TAG = "task.execution.id";
|
||||
|
||||
/**
|
||||
* task parent execution id tag.
|
||||
*/
|
||||
public static final String TASK_PARENT_EXECUTION_ID_TAG = "task.parent.execution.id";
|
||||
|
||||
/**
|
||||
* task external execution id tag.
|
||||
*/
|
||||
public static final String TASK_EXTERNAL_EXECUTION_ID_TAG = "task.external.execution.id";
|
||||
|
||||
/**
|
||||
* task exit code tag.
|
||||
*/
|
||||
public static final String TASK_EXIT_CODE_TAG = "task.exit.code";
|
||||
|
||||
/**
|
||||
* task status tag. Can be either STATUS_SUCCESS or STATUS_FAILURE
|
||||
*/
|
||||
public static final String TASK_STATUS_TAG = "task.status";
|
||||
|
||||
/**
|
||||
* task exception tag. Contains the name of the exception class in case of error or
|
||||
* none otherwise.
|
||||
*/
|
||||
public static final String TASK_EXCEPTION_TAG = "task.exception";
|
||||
|
||||
private Timer.Sample taskSample;
|
||||
|
||||
private LongTaskTimer.Sample longTaskSample;
|
||||
|
||||
private Throwable exception;
|
||||
|
||||
public void onTaskStartup(TaskExecution taskExecution) {
|
||||
LongTaskTimer longTaskTimer = LongTaskTimer
|
||||
.builder(SPRING_CLOUD_TASK_ACTIVE_METER).description("Long task duration")
|
||||
.tags(commonTags(taskExecution)).register(Metrics.globalRegistry);
|
||||
|
||||
this.longTaskSample = longTaskTimer.start();
|
||||
this.taskSample = Timer.start(Metrics.globalRegistry);
|
||||
}
|
||||
|
||||
public void onTaskFailed(TaskExecution taskExecution, Throwable throwable) {
|
||||
this.exception = throwable;
|
||||
}
|
||||
|
||||
public void onTaskEnd(TaskExecution taskExecution) {
|
||||
if (this.taskSample != null) {
|
||||
this.taskSample.stop(Timer.builder(SPRING_CLOUD_TASK_METER)
|
||||
.description("Task duration").tags(commonTags(taskExecution))
|
||||
.tag(TASK_EXIT_CODE_TAG, "" + taskExecution.getExitCode())
|
||||
.tag(TASK_EXCEPTION_TAG,
|
||||
(this.exception == null) ? "none"
|
||||
: this.exception.getClass().getSimpleName())
|
||||
.tag(TASK_STATUS_TAG,
|
||||
(this.exception == null) ? STATUS_SUCCESS : STATUS_FAILURE)
|
||||
.register(Metrics.globalRegistry));
|
||||
this.taskSample = null;
|
||||
}
|
||||
|
||||
if (this.longTaskSample != null) {
|
||||
this.longTaskSample.stop();
|
||||
this.longTaskSample = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Tags commonTags(TaskExecution taskExecution) {
|
||||
return Tags.of(TASK_NAME_TAG, taskExecution.getTaskName())
|
||||
.and(TASK_EXECUTION_ID_TAG, "" + taskExecution.getExecutionId())
|
||||
.and(TASK_PARENT_EXECUTION_ID_TAG,
|
||||
"" + taskExecution.getParentExecutionId())
|
||||
.and(TASK_EXTERNAL_EXECUTION_ID_TAG,
|
||||
(taskExecution.getExternalExecutionId() == null) ? "unknown"
|
||||
: "" + taskExecution.getExternalExecutionId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
/**
|
||||
* Forked from the
|
||||
* org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer form
|
||||
* spring-boot-actuator
|
||||
*
|
||||
* Callback interface that can be used to customize auto-configured {@link MeterRegistry
|
||||
* MeterRegistries}.
|
||||
* <p>
|
||||
* Customizers are guaranteed to be applied before any {@link Meter} is registered with
|
||||
* the registry.
|
||||
*
|
||||
* @author Michael Minella
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MeterRegistryCustomizer<T extends MeterRegistry> {
|
||||
|
||||
/**
|
||||
* Customize the given {@code registry}.
|
||||
* @param registry the registry to customize
|
||||
*/
|
||||
void customize(T registry);
|
||||
|
||||
}
|
||||
@@ -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<MeterRegistryCustomizer<?>> 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<MeterRegistryCustomizer<?>> meterRegistryCustomizers;
|
||||
|
||||
private volatile MeterRegistryConfigurer configurer;
|
||||
|
||||
MeterRegistryPostProcessor(
|
||||
ObjectProvider<MeterRegistryCustomizer<?>> 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<MeterRegistryCustomizer<?>> customizers;
|
||||
|
||||
MeterRegistryConfigurer(ObjectProvider<MeterRegistryCustomizer<?>> 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 <T> List<T> asOrderedList(ObjectProvider<T> provider) {
|
||||
return provider.orderedStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}]}
|
||||
Reference in New Issue
Block a user