Obtain observed timers in the Integration Graph (#3967)

* Obtain observed timers in the Integration Graph

The `Observation` populates slightly different Micrometer timer names and their tags

* Fix `MicrometerNodeEnhancer` to search timers in the registry according to the `Observation`
convention if the provided component is observed
* For that purpose expose an `IntegrationManagement.isObserved()` option to check
of component is instrumented with an `Observation`
* Improve the logic in the `IntegrationGraphServer` to delegate into a `Function`
to call `micrometerEnhancer.enhance()` instead of `static` property which might not
be OK in the environment where several integration applications are ran in the same JVM

* * Collect times into graph for inbound gateways & producers

* Fix language in javadoc of new `IntegrationManagement.isObserved()`

* Add `@Nullable` to `MicrometerNodeEnhancer.buildTimerStats()` param to satisfy this method contract

Co-authored-by: Gary Russell <grussell@vmware.com>

Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
Artem Bilan
2022-12-20 11:46:48 -05:00
committed by GitHub
parent d38e0d8720
commit 49a9b7e314
11 changed files with 231 additions and 103 deletions

View File

@@ -26,8 +26,11 @@ import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.ObservationRegistry;
import net.minidev.json.JSONArray;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -49,6 +52,7 @@ import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.EnableIntegrationManagement;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.context.IntegrationFlowContext;
import org.springframework.integration.dsl.context.IntegrationFlowContext.IntegrationFlowRegistration;
@@ -117,7 +121,7 @@ public class IntegrationGraphServerTests {
assertThat(map.size()).isEqualTo(3);
List<Map<?, ?>> nodes = (List<Map<?, ?>>) map.get("nodes");
assertThat(nodes).isNotNull();
assertThat(nodes.size()).isEqualTo(34);
assertThat(nodes.size()).isEqualTo(35);
JSONArray jsonArray =
JsonPathUtils.evaluate(baos.toByteArray(), "$..nodes[?(@.componentType == 'gateway')]");
@@ -166,7 +170,7 @@ public class IntegrationGraphServerTests {
assertThat(map.size()).isEqualTo(3);
nodes = (List<Map<?, ?>>) map.get("nodes");
assertThat(nodes).isNotNull();
assertThat(nodes.size()).isEqualTo(34);
assertThat(nodes.size()).isEqualTo(35);
links = (List<Map<?, ?>>) map.get("links");
assertThat(links).isNotNull();
assertThat(links.size()).isEqualTo(35);
@@ -239,20 +243,51 @@ public class IntegrationGraphServerTests {
@Test
void testIncludesDynamic() {
Graph graph = this.server.getGraph();
assertThat(graph.getNodes().size()).isEqualTo(34);
assertThat(graph.getNodes().size()).isEqualTo(35);
IntegrationFlow flow = f -> f.handle(m -> {
});
IntegrationFlowRegistration reg = this.flowContext.registration(flow).register();
graph = this.server.rebuild();
assertThat(graph.getNodes().size()).isEqualTo(36);
assertThat(graph.getNodes().size()).isEqualTo(37);
this.flowContext.remove(reg.getId());
graph = this.server.rebuild();
assertThat(graph.getNodes().size()).isEqualTo(34);
assertThat(graph.getNodes().size()).isEqualTo(35);
}
@Autowired
MessageChannel filterInputChannel;
@Test
void timersViaObservationArePopulated() {
MessagingTemplate messagingTemplate = new MessagingTemplate();
assertThat(messagingTemplate.convertSendAndReceive(this.filterInputChannel, "test", String.class))
.isEqualTo("test");
Graph graph = this.server.getGraph();
IntegrationNode myFilter =
graph.getNodes()
.stream()
.filter(node -> node.getName().equals("myFilter"))
.findFirst()
.get();
assertThat(myFilter)
.asInstanceOf(InstanceOfAssertFactories.type(MessageHandlerNode.class))
.extracting(MessageHandlerNode::getSendTimers)
.isNotNull()
.satisfies(sendTimers -> {
assertThat(sendTimers.getFailures().getCount()).isEqualTo(0);
assertThat(sendTimers.getFailures().getMax()).isEqualTo(0);
assertThat(sendTimers.getFailures().getMean()).isEqualTo(0);
assertThat(sendTimers.getSuccesses().getCount()).isEqualTo(1);
assertThat(sendTimers.getSuccesses().getMax()).isGreaterThan(0);
assertThat(sendTimers.getSuccesses().getMean()).isGreaterThan(0);
});
}
@Configuration
@EnableIntegration
@EnableIntegrationManagement
@EnableIntegrationManagement(observationPatterns = "myFilter")
@IntegrationComponentScan
@ImportResource("org/springframework/integration/graph/integration-graph-context.xml")
public static class Config {
@@ -262,14 +297,20 @@ public class IntegrationGraphServerTests {
return new SimpleMeterRegistry();
}
@Bean
public ObservationRegistry observationRegistry(MeterRegistry meterRegistry) {
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(new DefaultMeterObservationHandler(meterRegistry));
return registry;
}
@Bean
public IntegrationGraphServer server() {
IntegrationGraphServer server = new IntegrationGraphServer();
server.setApplicationName("myAppName:1.0");
server.setAdditionalPropertiesCallback(namedComponent -> {
Map<String, Object> properties = null;
if (namedComponent instanceof SmartLifecycle) {
SmartLifecycle smartLifecycle = (SmartLifecycle) namedComponent;
if (namedComponent instanceof SmartLifecycle smartLifecycle) {
properties = new HashMap<>();
properties.put("auto-startup", smartLifecycle.isAutoStartup());
properties.put("running", smartLifecycle.isRunning());

View File

@@ -15,7 +15,7 @@
<int:service-activator expression="payload" />
</int:chain>
<int:filter id="myFilter" input-channel="filterChannel" discard-channel="three" expression="true" />
<int:filter id="myFilter" input-channel="filterInputChannel" discard-channel="three" expression="true" />
<int:aggregator id="myAggregator" input-channel="polledChannel" discard-channel="three" />