Skip to content

Commit b135b51

Browse files
committed
Introduce strategy for running HealthIndicators
1 parent 58740d7 commit b135b51

File tree

6 files changed

+241
-8
lines changed

6 files changed

+241
-8
lines changed

Diff for: spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CompositeHealthIndicatorConfiguration.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2016 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
2323
import org.springframework.boot.actuate.health.HealthAggregator;
2424
import org.springframework.boot.actuate.health.HealthIndicator;
25+
import org.springframework.boot.actuate.health.HealthIndicatorRunner;
2526
import org.springframework.core.ResolvableType;
2627

2728
/**
@@ -31,19 +32,26 @@
3132
* @param <H> the health indicator type
3233
* @param <S> the bean source type
3334
* @author Stephane Nicoll
35+
* @author Vedran Pavic
3436
* @since 1.4.0
3537
*/
3638
public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndicator, S> {
3739

3840
@Autowired
3941
private HealthAggregator healthAggregator;
4042

43+
@Autowired(required = false)
44+
private HealthIndicatorRunner healthIndicatorRunner;
45+
4146
protected HealthIndicator createHealthIndicator(Map<String, S> beans) {
4247
if (beans.size() == 1) {
4348
return createHealthIndicator(beans.values().iterator().next());
4449
}
4550
CompositeHealthIndicator composite = new CompositeHealthIndicator(
4651
this.healthAggregator);
52+
if (this.healthIndicatorRunner != null) {
53+
composite.setHealthIndicatorRunner(this.healthIndicatorRunner);
54+
}
4755
for (Map.Entry<String, S> entry : beans.entrySet()) {
4856
composite.addHealthIndicator(entry.getKey(),
4957
createHealthIndicator(entry.getValue()));

Diff for: spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/HealthEndpoint.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2016 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,10 +18,12 @@
1818

1919
import java.util.Map;
2020

21+
import org.springframework.beans.factory.annotation.Autowired;
2122
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
2223
import org.springframework.boot.actuate.health.Health;
2324
import org.springframework.boot.actuate.health.HealthAggregator;
2425
import org.springframework.boot.actuate.health.HealthIndicator;
26+
import org.springframework.boot.actuate.health.HealthIndicatorRunner;
2527
import org.springframework.boot.context.properties.ConfigurationProperties;
2628
import org.springframework.util.Assert;
2729

@@ -31,11 +33,12 @@
3133
* @author Dave Syer
3234
* @author Christian Dupuis
3335
* @author Andy Wilkinson
36+
* @author Vedran Pavic
3437
*/
3538
@ConfigurationProperties(prefix = "endpoints.health")
3639
public class HealthEndpoint extends AbstractEndpoint<Health> {
3740

38-
private final HealthIndicator healthIndicator;
41+
private final CompositeHealthIndicator healthIndicator;
3942

4043
/**
4144
* Time to live for cached result, in milliseconds.
@@ -60,6 +63,12 @@ public HealthEndpoint(HealthAggregator healthAggregator,
6063
this.healthIndicator = healthIndicator;
6164
}
6265

66+
@Autowired(required = false)
67+
public void setHealthIndicatorRunner(HealthIndicatorRunner healthIndicatorRunner) {
68+
Assert.notNull("HealthIndicatorRunner must not be null");
69+
this.healthIndicator.setHealthIndicatorRunner(healthIndicatorRunner);
70+
}
71+
6372
/**
6473
* Time to live for cached result. This is particularly useful to cache the result of
6574
* this endpoint to prevent a DOS attack if it is accessed anonymously.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.concurrent.Future;
22+
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
25+
26+
import org.springframework.core.task.AsyncTaskExecutor;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* A {@link HealthIndicatorRunner} implementation that uses {@link AsyncTaskExecutor} to
31+
* invoke {@link HealthIndicator} instances.
32+
*
33+
* @author Vedran Pavic
34+
* @since 2.0.0
35+
*/
36+
public class AsynchronousHealthIndicatorRunner implements HealthIndicatorRunner {
37+
38+
private static final Log logger = LogFactory.getLog(
39+
AsynchronousHealthIndicatorRunner.class);
40+
41+
private final AsyncTaskExecutor taskExecutor;
42+
43+
/**
44+
* Create an {@link AsynchronousHealthIndicatorRunner} instance.
45+
* @param taskExecutor task executor used to run {@link HealthIndicator}s
46+
*/
47+
public AsynchronousHealthIndicatorRunner(AsyncTaskExecutor taskExecutor) {
48+
Assert.notNull(taskExecutor, "TaskExecutor must not be null");
49+
this.taskExecutor = taskExecutor;
50+
}
51+
52+
@Override
53+
public Map<String, Health> run(Map<String, HealthIndicator> healthIndicators) {
54+
Map<String, Health> healths = new HashMap<>(healthIndicators.size());
55+
Map<String, Future<Health>> futures = new HashMap<>();
56+
for (final Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
57+
Future<Health> future = this.taskExecutor.submit(() ->
58+
entry.getValue().health());
59+
futures.put(entry.getKey(), future);
60+
}
61+
for (Map.Entry<String, Future<Health>> entry : futures.entrySet()) {
62+
try {
63+
healths.put(entry.getKey(), entry.getValue().get());
64+
}
65+
catch (Exception e) {
66+
logger.warn("Error invoking health indicator '" + entry.getKey() + "'", e);
67+
healths.put(entry.getKey(), Health.down(e).build());
68+
}
69+
}
70+
return healths;
71+
}
72+
73+
}

Diff for: spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java

+32-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
* @author Tyler J. Frederick
2828
* @author Phillip Webb
2929
* @author Christian Dupuis
30+
* @author Vedran Pavic
3031
* @since 1.1.0
3132
*/
3233
public class CompositeHealthIndicator implements HealthIndicator {
@@ -35,6 +36,9 @@ public class CompositeHealthIndicator implements HealthIndicator {
3536

3637
private final HealthAggregator healthAggregator;
3738

39+
private HealthIndicatorRunner healthIndicatorRunner =
40+
new SynchronousHealthIndicatorRunner();
41+
3842
/**
3943
* Create a new {@link CompositeHealthIndicator}.
4044
* @param healthAggregator the health aggregator
@@ -61,13 +65,36 @@ public void addHealthIndicator(String name, HealthIndicator indicator) {
6165
this.indicators.put(name, indicator);
6266
}
6367

68+
/**
69+
* Set the health indicator runner to invoke the health indicators.
70+
* @param healthIndicatorRunner the health indicator runner
71+
*/
72+
public void setHealthIndicatorRunner(HealthIndicatorRunner healthIndicatorRunner) {
73+
Assert.notNull("HealthIndicatorRunner must not be null");
74+
this.healthIndicatorRunner = healthIndicatorRunner;
75+
}
76+
6477
@Override
6578
public Health health() {
66-
Map<String, Health> healths = new LinkedHashMap<String, Health>();
67-
for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) {
68-
healths.put(entry.getKey(), entry.getValue().health());
69-
}
79+
Map<String, Health> healths = this.healthIndicatorRunner.run(this.indicators);
7080
return this.healthAggregator.aggregate(healths);
7181
}
7282

83+
/**
84+
* {@link HealthIndicatorRunner} for sequential execution of {@link HealthIndicator}s.
85+
*/
86+
private static class SynchronousHealthIndicatorRunner
87+
implements HealthIndicatorRunner {
88+
89+
@Override
90+
public Map<String, Health> run(Map<String, HealthIndicator> healthIndicators) {
91+
Map<String, Health> healths = new LinkedHashMap<String, Health>();
92+
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
93+
healths.put(entry.getKey(), entry.getValue().health());
94+
}
95+
return healths;
96+
}
97+
98+
}
99+
73100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.Map;
20+
21+
/**
22+
* Strategy interface used by {@link CompositeHealthIndicator} to invoke
23+
* {@link HealthIndicator} instances.
24+
* <p>
25+
* This is useful for customization of invocation in scenarios with many
26+
* {@link HealthIndicator} instances in the system and/or resource demanding ones.
27+
*
28+
* @author Vedran Pavic
29+
* @since 2.0.0
30+
*/
31+
public interface HealthIndicatorRunner {
32+
33+
Map<String, Health> run(Map<String, HealthIndicator> healthIndicators);
34+
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
import org.junit.Before;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.junit.rules.ExpectedException;
26+
import org.junit.runner.RunWith;
27+
import org.mockito.Mock;
28+
import org.mockito.junit.MockitoJUnitRunner;
29+
30+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.mockito.BDDMockito.given;
34+
35+
/**
36+
* Tests for {@link AsynchronousHealthIndicatorRunner}.
37+
*
38+
* @author Vedran Pavic
39+
*/
40+
@RunWith(MockitoJUnitRunner.class)
41+
public class AsyncHealthIndicatorRunnerTests {
42+
43+
@Rule
44+
public ExpectedException thrown = ExpectedException.none();
45+
46+
@Mock
47+
private HealthIndicator one;
48+
49+
@Mock
50+
private HealthIndicator two;
51+
52+
@Before
53+
public void setUp() {
54+
given(this.one.health()).willReturn(new Health.Builder().up().build());
55+
given(this.two.health()).willReturn(new Health.Builder().unknown().build());
56+
}
57+
58+
@Test
59+
public void createAndRun() {
60+
Map<String, HealthIndicator> indicators = new HashMap<>();
61+
indicators.put("one", this.one);
62+
indicators.put("two", this.two);
63+
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
64+
taskExecutor.setMaxPoolSize(2);
65+
taskExecutor.afterPropertiesSet();
66+
HealthIndicatorRunner healthIndicatorRunner = new AsynchronousHealthIndicatorRunner(
67+
taskExecutor);
68+
Map<String, Health> healths = healthIndicatorRunner.run(indicators);
69+
assertThat(healths.size()).isEqualTo(2);
70+
assertThat(healths.get("one").getStatus()).isEqualTo(Status.UP);
71+
assertThat(healths.get("two").getStatus()).isEqualTo(Status.UNKNOWN);
72+
}
73+
74+
@Test
75+
public void createWithNullTaskExecutor() {
76+
this.thrown.expect(IllegalArgumentException.class);
77+
this.thrown.expectMessage("TaskExecutor must not be null");
78+
new AsynchronousHealthIndicatorRunner(null);
79+
}
80+
81+
}

0 commit comments

Comments
 (0)