parent
fbbfe6ac86
commit
f8555b9071
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 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.boot.actuate.autoconfigure.metrics.task;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link EnableAutoConfiguration Auto-configuration} for metrics on all available
|
||||||
|
* {@link ThreadPoolTaskExecutor task executors} and {@link ThreadPoolTaskScheduler task
|
||||||
|
* schedulers}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 2.6.0
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@AutoConfigureAfter({ MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class,
|
||||||
|
TaskExecutionAutoConfiguration.class, TaskSchedulingAutoConfiguration.class })
|
||||||
|
@ConditionalOnClass(ExecutorServiceMetrics.class)
|
||||||
|
@ConditionalOnBean({ Executor.class, MeterRegistry.class })
|
||||||
|
public class TaskExecutorMetricsAutoConfiguration {
|
||||||
|
|
||||||
|
private static final String TASK_EXECUTOR_SUFFIX = "taskExecutor";
|
||||||
|
|
||||||
|
private static final String TASK_SCHEDULER_SUFFIX = "taskScheduler";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void bindTaskExecutorsToRegistry(Map<String, Executor> executors, MeterRegistry registry) {
|
||||||
|
executors.forEach((beanName, executor) -> {
|
||||||
|
if (executor instanceof ThreadPoolTaskExecutor) {
|
||||||
|
monitor(registry, safeGetThreadPoolExecutor((ThreadPoolTaskExecutor) executor),
|
||||||
|
() -> getTaskExecutorName(beanName));
|
||||||
|
}
|
||||||
|
else if (executor instanceof ThreadPoolTaskScheduler) {
|
||||||
|
monitor(registry, safeGetThreadPoolExecutor((ThreadPoolTaskScheduler) executor),
|
||||||
|
() -> getTaskSchedulerName(beanName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void monitor(MeterRegistry registry, ThreadPoolExecutor threadPoolExecutor, Supplier<String> beanName) {
|
||||||
|
if (threadPoolExecutor != null) {
|
||||||
|
ExecutorServiceMetrics.monitor(registry, threadPoolExecutor, beanName.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadPoolExecutor safeGetThreadPoolExecutor(ThreadPoolTaskExecutor taskExecutor) {
|
||||||
|
try {
|
||||||
|
return taskExecutor.getThreadPoolExecutor();
|
||||||
|
}
|
||||||
|
catch (IllegalStateException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadPoolExecutor safeGetThreadPoolExecutor(ThreadPoolTaskScheduler taskScheduler) {
|
||||||
|
try {
|
||||||
|
return taskScheduler.getScheduledThreadPoolExecutor();
|
||||||
|
}
|
||||||
|
catch (IllegalStateException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of a {@link ThreadPoolTaskExecutor} based on its {@code beanName}.
|
||||||
|
* @param beanName the name of the {@link ThreadPoolTaskExecutor} bean
|
||||||
|
* @return a name for the given task executor
|
||||||
|
*/
|
||||||
|
private String getTaskExecutorName(String beanName) {
|
||||||
|
if (beanName.length() > TASK_EXECUTOR_SUFFIX.length()
|
||||||
|
&& StringUtils.endsWithIgnoreCase(beanName, TASK_EXECUTOR_SUFFIX)) {
|
||||||
|
return beanName.substring(0, beanName.length() - TASK_EXECUTOR_SUFFIX.length());
|
||||||
|
}
|
||||||
|
return beanName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of a {@link ThreadPoolTaskScheduler} based on its {@code beanName}.
|
||||||
|
* @param beanName the name of the {@link ThreadPoolTaskScheduler} bean
|
||||||
|
* @return a name for the given task scheduler
|
||||||
|
*/
|
||||||
|
private String getTaskSchedulerName(String beanName) {
|
||||||
|
if (beanName.equals(TASK_SCHEDULER_SUFFIX)) {
|
||||||
|
return "application";
|
||||||
|
}
|
||||||
|
if (beanName.length() > TASK_SCHEDULER_SUFFIX.length()
|
||||||
|
&& StringUtils.endsWithIgnoreCase(beanName, TASK_SCHEDULER_SUFFIX)) {
|
||||||
|
return beanName.substring(0, beanName.length() - TASK_SCHEDULER_SUFFIX.length());
|
||||||
|
}
|
||||||
|
return beanName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configuration for task execution and scheduling metrics.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.actuate.autoconfigure.metrics.task;
|
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 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.boot.actuate.autoconfigure.metrics.task;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.FunctionCounter;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link TaskExecutorMetricsAutoConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class TaskExecutorMetricsAutoConfigurationTests {
|
||||||
|
|
||||||
|
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
|
||||||
|
.withConfiguration(AutoConfigurations.of(TaskExecutorMetricsAutoConfiguration.class));
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskExecutorUsingAutoConfigurationIsInstrumented() {
|
||||||
|
this.contextRunner.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class))
|
||||||
|
.run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
|
||||||
|
assertThat(meters).singleElement()
|
||||||
|
.satisfies((meter) -> assertThat(meter.getId().getTag("name")).isEqualTo("application"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskExecutorsWithCustomNamesAreInstrumented() {
|
||||||
|
this.contextRunner.withBean("firstTaskExecutor", Executor.class, ThreadPoolTaskExecutor::new)
|
||||||
|
.withBean("customName", ThreadPoolTaskExecutor.class, ThreadPoolTaskExecutor::new).run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
|
||||||
|
assertThat(meters).map((meter) -> meter.getId().getTag("name")).containsExactlyInAnyOrder("first",
|
||||||
|
"customName");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void threadPoolTaskExecutorWithNoTaskExecutorIsIgnored() {
|
||||||
|
ThreadPoolTaskExecutor unavailableTaskExecutor = mock(ThreadPoolTaskExecutor.class);
|
||||||
|
given(unavailableTaskExecutor.getThreadPoolExecutor()).willThrow(new IllegalStateException("Test"));
|
||||||
|
this.contextRunner.withBean("firstTaskExecutor", ThreadPoolTaskExecutor.class, ThreadPoolTaskExecutor::new)
|
||||||
|
.withBean("customName", ThreadPoolTaskExecutor.class, () -> unavailableTaskExecutor).run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
|
||||||
|
assertThat(meters).singleElement()
|
||||||
|
.satisfies((meter) -> assertThat(meter.getId().getTag("name")).isEqualTo("first"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskExecutorInstrumentationCanBeDisabled() {
|
||||||
|
this.contextRunner.withPropertyValues("management.metrics.enable.executor=false")
|
||||||
|
.withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)).run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
assertThat(registry.find("executor.completed").tags("name", "application").functionCounter())
|
||||||
|
.isNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskSchedulerUsingAutoConfigurationIsInstrumented() {
|
||||||
|
this.contextRunner.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
|
||||||
|
.withUserConfiguration(SchedulingTestConfiguration.class).run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
|
||||||
|
assertThat(meters).singleElement()
|
||||||
|
.satisfies((meter) -> assertThat(meter.getId().getTag("name")).isEqualTo("application"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskSchedulersWithCustomNamesAreInstrumented() {
|
||||||
|
this.contextRunner.withBean("firstTaskScheduler", Executor.class, ThreadPoolTaskScheduler::new)
|
||||||
|
.withBean("customName", ThreadPoolTaskScheduler.class, ThreadPoolTaskScheduler::new).run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
|
||||||
|
assertThat(meters).map((meter) -> meter.getId().getTag("name")).containsExactlyInAnyOrder("first",
|
||||||
|
"customName");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void threadPoolTaskSchedulerWithNoTaskExecutorIsIgnored() {
|
||||||
|
ThreadPoolTaskScheduler unavailableTaskExecutor = mock(ThreadPoolTaskScheduler.class);
|
||||||
|
given(unavailableTaskExecutor.getScheduledThreadPoolExecutor()).willThrow(new IllegalStateException("Test"));
|
||||||
|
this.contextRunner.withBean("firstTaskScheduler", ThreadPoolTaskScheduler.class, ThreadPoolTaskScheduler::new)
|
||||||
|
.withBean("customName", ThreadPoolTaskScheduler.class, () -> unavailableTaskExecutor).run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
|
||||||
|
assertThat(meters).singleElement()
|
||||||
|
.satisfies((meter) -> assertThat(meter.getId().getTag("name")).isEqualTo("first"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void taskSchedulerInstrumentationCanBeDisabled() {
|
||||||
|
this.contextRunner.withPropertyValues("management.metrics.enable.executor=false")
|
||||||
|
.withConfiguration(AutoConfigurations.of(TaskSchedulingAutoConfiguration.class))
|
||||||
|
.withUserConfiguration(SchedulingTestConfiguration.class).run((context) -> {
|
||||||
|
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||||
|
assertThat(registry.find("executor.completed").tags("name", "application").functionCounter())
|
||||||
|
.isNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableScheduling
|
||||||
|
static class SchedulingTestConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue