From 328a2e9161ddbcc7d4088f6fb5df6c81e9cceb8c Mon Sep 17 00:00:00 2001 From: jvalkeal Date: Sun, 11 Feb 2018 09:24:13 +0200 Subject: [PATCH] Implement custom trace repository - Upgrade to boot 2.0.0.RC1 - Copy original boot's tracing features to get used as a base impl for statemachine. - Fix samples, tests. - Fixes #491 --- build.gradle | 3 +- docs/src/reference/asciidoc/sm-examples.adoc | 137 +++++++++++------- docs/src/reference/asciidoc/sm.adoc | 12 +- docs/src/reference/asciidoc/whatsnew.adoc | 8 + .../InMemoryStateMachineTraceRepository.java | 79 ++++++++++ .../boot/actuate/StateMachineTrace.java | 66 +++++++++ .../actuate/StateMachineTraceEndpoint.java | 49 +++++++ .../actuate/StateMachineTraceRepository.java | 42 ++++++ .../StateMachineAutoConfiguration.java | 39 ++++- .../boot/support/BootStateMachineMonitor.java | 13 +- .../src/main/resources/application.yml | 4 +- .../java/demo/monitoring/MonitoringTests.java | 6 +- 12 files changed, 378 insertions(+), 80 deletions(-) create mode 100644 spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/InMemoryStateMachineTraceRepository.java create mode 100644 spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTrace.java create mode 100644 spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceEndpoint.java create mode 100644 spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceRepository.java diff --git a/build.gradle b/build.gradle index fab1fc81..f0bc0250 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { log4jVersion = '1.2.17' - springBootVersion = '2.0.0.M7' + springBootVersion = '2.0.0.RC1' eclipsePersistenceVersion = '2.1.1' kryoVersion = '3.0.3' springCloudClusterVersion = '1.0.2.RELEASE' @@ -195,6 +195,7 @@ project('spring-statemachine-boot') { dependencies { compile project(":spring-statemachine-core") compile "org.springframework.boot:spring-boot-autoconfigure" + compile "org.springframework.boot:spring-boot-actuator-autoconfigure" compile "org.springframework.boot:spring-boot-actuator" optional project(":spring-statemachine-data-common:spring-statemachine-data-jpa") optional project(":spring-statemachine-data-common:spring-statemachine-data-redis") diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index 40d8c38c..f6b5db7c 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -1605,21 +1605,57 @@ Metrics can be viewed from Boot. [source,json] ---- -# curl http://localhost:8080/application/metrics +# curl http://localhost:8080/actuator/metrics/ssm.transition.duration { -"gauge.ssm.transition.INITIAL_S1.duration":0.0, -"gauge.ssm.transition.EXTERNAL_S2_S3.duration":0.0, -"gauge.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$8/12546741@a522d0.duration":0.0, -"gauge.ssm.transition.EXTERNAL_S1_S2.duration":1.0, -"gauge.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$7/25284245@1c21333.duration":0.0, -"gauge.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$9/28306193@10069f9.duration":0.0, -"counter.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$8/12546741@a522d0.execute":1, -"counter.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$9/28306193@10069f9.execute":1, -"counter.ssm.transition.EXTERNAL_S2_S3.transit":1, -"counter.ssm.action.demo.monitoring.StateMachineConfig$Config$$Lambda$7/25284245@1c21333.execute":1, -"counter.ssm.transition.EXTERNAL_S1_S2.transit":1, -"counter.ssm.transition.INITIAL_S1.transit":2, + "name":"ssm.transition.duration", + "measurements":[ + { + "statistic":"COUNT", + "value":3.0 + }, + { + "statistic":"TOTAL_TIME", + "value":0.007 + }, + { + "statistic":"MAX", + "value":0.004 + } + ], + "availableTags":[ + { + "tag":"transitionName", + "values":[ + "INITIAL_S1", + "EXTERNAL_S1_S2" + ] + } + ] +} +---- + +[source,json] +---- +# curl http://localhost:8080/actuator/metrics/ssm.transition.transit + +{ + "name":"ssm.transition.transit", + "measurements":[ + { + "statistic":"COUNT", + "value":3.0 + } + ], + "availableTags":[ + { + "tag":"transitionName", + "values":[ + "EXTERNAL_S1_S2", + "INITIAL_S1" + ] + } + ] } ---- @@ -1627,49 +1663,40 @@ Tracing can be viewed from Boot. [source,json] ---- -# curl http://localhost:8080/application/trace +# curl http://localhost:8080/actuator/statemachinetrace -[{ - "timestamp":1478419121956, - "info":{ - "duration":0, - "machine":null, - "transition": - "EXTERNAL_S2_S3" +[ + { + "timestamp":"2018-02-11T06:44:12.723+0000", + "info":{ + "duration":2, + "machine":null, + "transition":"EXTERNAL_S1_S2" + } + }, + { + "timestamp":"2018-02-11T06:44:12.720+0000", + "info":{ + "duration":0, + "machine":null, + "action":"demo.monitoring.StateMachineConfig$Config$$Lambda$576/1499688007@22b47b2f" + } + }, + { + "timestamp":"2018-02-11T06:44:12.714+0000", + "info":{ + "duration":1, + "machine":null, + "transition":"INITIAL_S1" + } + }, + { + "timestamp":"2018-02-11T06:44:09.689+0000", + "info":{ + "duration":4, + "machine":null, + "transition":"INITIAL_S1" + } } - }, - { - "timestamp":1478419121956, - "info":{ - "duration":0, - "machine":null, - "action":"demo.monitoring.StateMachineConfig$Config$$Lambda$9/28306193@10069f9" - } - }, - { - "timestamp":1478419121956, - "info":{ - "duration":0, - "machine":null, - "action":"demo.monitoring.StateMachineConfig$Config$$Lambda$8/12546741@a522d0" - } - }, - { - "timestamp":1478419121956, - "info":{ - "duration":1, - "machine":null, - "transition":"EXTERNAL_S1_S2" - } - }, - { - "timestamp":1478419121955, - "info":{ - "duration":0, - "machine":null, - "action":"demo.monitoring.StateMachineConfig$Config$$Lambda$7/25284245@1c21333" - } - } ] - ---- diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 7b2a8b22..99ee33a7 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -1926,13 +1926,15 @@ integration logic with _Spring Boot_ providing functionality i.e. for auto-config and actuators. All what is needed is to have _State Machine_ as part of a boot application together with this library. +[[sm-boot-monitoring]] === Monitoring and Tracing `BootStateMachineMonitor` is created automatically and associated with -a state machine. This `BootStateMachineMonitor` will hook into Boot's -`CounterService`, `GaugeService` and `TraceRepository` if available. -Optionally this auto-configuration can be disabled by setting key -`spring.statemachine.monitor.enabled` to `false`. Use of this -auto-config is shown in sample <>. +a state machine. `BootStateMachineMonitor` is a custom `StateMachineMonitor` +implementation which integrates with boot's `MeterRegistry` and endpoints +via a custom `StateMachineTraceRepository`. Optionally this auto-configuration +can be disabled by setting key `spring.statemachine.monitor.enabled` to +`false`. Use of this auto-config is shown in sample +<>. === Repository Config Spring Data Repositories and Entity class scanning is auto-configured diff --git a/docs/src/reference/asciidoc/whatsnew.adoc b/docs/src/reference/asciidoc/whatsnew.adoc index 133f366e..3ff40558 100644 --- a/docs/src/reference/asciidoc/whatsnew.adoc +++ b/docs/src/reference/asciidoc/whatsnew.adoc @@ -47,3 +47,11 @@ _Spring Statemachine 1.3_. * Transition conflict policy mentioned in <> +== In 2.0 +_Spring Statemachine 2.0_ is focusing on _Spring Boot 2.x_ support. + + +=== In 2.0.0 + +* Format of monitoring and trancing has beenn changed <> + diff --git a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/InMemoryStateMachineTraceRepository.java b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/InMemoryStateMachineTraceRepository.java new file mode 100644 index 00000000..388038a9 --- /dev/null +++ b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/InMemoryStateMachineTraceRepository.java @@ -0,0 +1,79 @@ +/* + * Copyright 2018 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 + * + * http://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.statemachine.boot.actuate; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * In-memory implementation of {@link StateMachineTraceRepository}. + * + * @author Janne Valkealahti + * + */ +public class InMemoryStateMachineTraceRepository implements StateMachineTraceRepository { + + private int capacity = 100; + private boolean reverse = true; + private final List traces = new LinkedList(); + + /** + * Flag to say that the repository lists traces in reverse order. + * @param reverse flag value (default true) + */ + public void setReverse(boolean reverse) { + synchronized (this.traces) { + this.reverse = reverse; + } + } + + /** + * Set the capacity of the in-memory repository. + * @param capacity the capacity + */ + public void setCapacity(int capacity) { + synchronized (this.traces) { + this.capacity = capacity; + } + } + + @Override + public List findAll() { + synchronized (this.traces) { + return Collections.unmodifiableList(new ArrayList(this.traces)); + } + } + + @Override + public void add(Map map) { + StateMachineTrace trace = new StateMachineTrace(new Date(), map); + synchronized (this.traces) { + while (this.traces.size() >= this.capacity) { + this.traces.remove(this.reverse ? this.capacity - 1 : 0); + } + if (this.reverse) { + this.traces.add(0, trace); + } + else { + this.traces.add(trace); + } + } + } +} diff --git a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTrace.java b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTrace.java new file mode 100644 index 00000000..665f1c92 --- /dev/null +++ b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTrace.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 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 + * + * http://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.statemachine.boot.actuate; + +import java.util.Date; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * A value object representing a statemachine trace event: at a particular time + * with a simple (map) information. + * + * @author Janne Valkealahti + * + */ +public final class StateMachineTrace { + + private final Date timestamp; + private final Map info; + + /** + * Instantiate a new {@code StateMachineTrace}. + * + * @param timestamp the timestamp + * @param info the trace info + */ + public StateMachineTrace(Date timestamp, Map info) { + super(); + Assert.notNull(timestamp, "Timestamp must not be null"); + Assert.notNull(info, "Info must not be null"); + this.timestamp = timestamp; + this.info = info; + } + + /** + * Gets a timestamp + * + * @return a trace timestamp + */ + public Date getTimestamp() { + return this.timestamp; + } + + /** + * Gets a trace info. + * + * @return a trace info + */ + public Map getInfo() { + return this.info; + } +} diff --git a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceEndpoint.java b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceEndpoint.java new file mode 100644 index 00000000..78ce5221 --- /dev/null +++ b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceEndpoint.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 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 + * + * http://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.statemachine.boot.actuate; + +import java.util.List; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.util.Assert; + +/** + * {@code Endpoint} to expose {@link StateMachineTrace} information. + * + * @author Janne Valkealahti + * + */ +@Endpoint(id = "statemachinetrace") +public class StateMachineTraceEndpoint { + + private final StateMachineTraceRepository repository; + + /** + * Create a new {@link StateMachineTraceEndpoint} instance. + * + * @param repository the trace repository + */ + public StateMachineTraceEndpoint(StateMachineTraceRepository repository) { + Assert.notNull(repository, "Repository must not be null"); + this.repository = repository; + } + + @ReadOperation + public List invoke() { + return this.repository.findAll(); + } +} diff --git a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceRepository.java b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceRepository.java new file mode 100644 index 00000000..2cc1b196 --- /dev/null +++ b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/actuate/StateMachineTraceRepository.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 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 + * + * http://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.statemachine.boot.actuate; + +import java.util.List; +import java.util.Map; + +/** + * A repository for {@link StateMachineTrace}s. + * + * @author Janne Valkealahti + * + */ +public interface StateMachineTraceRepository { + + /** + * Find all {@link StateMachineTrace} objects contained in the repository. + * + * @return the results + */ + List findAll(); + + /** + * Add a new {@link StateMachineTrace} object at the current time. + * + * @param traceInfo trace information + */ + void add(Map traceInfo); +} diff --git a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/autoconfigure/StateMachineAutoConfiguration.java b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/autoconfigure/StateMachineAutoConfiguration.java index 03b563a6..cbb2274c 100644 --- a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/autoconfigure/StateMachineAutoConfiguration.java +++ b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/autoconfigure/StateMachineAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 the original author or authors. + * Copyright 2016-2018 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. @@ -16,14 +16,18 @@ package org.springframework.statemachine.boot.autoconfigure; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.trace.TraceRepository; +import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.boot.StateMachineProperties; +import org.springframework.statemachine.boot.actuate.InMemoryStateMachineTraceRepository; +import org.springframework.statemachine.boot.actuate.StateMachineTraceEndpoint; +import org.springframework.statemachine.boot.actuate.StateMachineTraceRepository; import org.springframework.statemachine.boot.support.BootStateMachineMonitor; import io.micrometer.core.instrument.MeterRegistry; @@ -36,25 +40,44 @@ import io.micrometer.core.instrument.MeterRegistry; */ @Configuration @EnableConfigurationProperties({ StateMachineProperties.class }) +@ConditionalOnClass(MeterRegistry.class) +@ConditionalOnProperty(prefix = "spring.statemachine.monitor", name = "enabled", havingValue = "true", matchIfMissing = true) public class StateMachineAutoConfiguration { + @ManagementContextConfiguration + public static class StateMachineTraceEndpointConfiguration { + + @Bean + public StateMachineTraceEndpoint stateMachieTraceEndpoint(StateMachineTraceRepository stateMachineTraceRepository) { + return new StateMachineTraceEndpoint(stateMachineTraceRepository); + } + } + + @Configuration + public static class StateMachineTraceRepositoryConfiguration { + + @ConditionalOnMissingBean(StateMachineTraceRepository.class) + @Bean + public InMemoryStateMachineTraceRepository stateMachineTraceRepository() { + return new InMemoryStateMachineTraceRepository(); + } + } + @Configuration - @ConditionalOnClass(MeterRegistry.class) - @ConditionalOnProperty(prefix = "spring.statemachine.monitor", name = "enabled", havingValue = "true", matchIfMissing = true) public static class StateMachineMonitoringConfiguration { private final MeterRegistry meterRegistry; - private final TraceRepository traceRepository; + private final StateMachineTraceRepository stateMachineTraceRepository; public StateMachineMonitoringConfiguration(ObjectProvider meterRegistryProvider, - ObjectProvider traceRepositoryProvider) { + ObjectProvider traceRepositoryProvider) { this.meterRegistry = meterRegistryProvider.getIfAvailable(); - this.traceRepository = traceRepositoryProvider.getIfAvailable(); + this.stateMachineTraceRepository = traceRepositoryProvider.getIfAvailable(); } @Bean public BootStateMachineMonitor bootStateMachineMonitor() { - return new BootStateMachineMonitor<>(meterRegistry, traceRepository); + return new BootStateMachineMonitor<>(meterRegistry, stateMachineTraceRepository); } } } diff --git a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/support/BootStateMachineMonitor.java b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/support/BootStateMachineMonitor.java index 6d61dd75..fefa0e65 100644 --- a/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/support/BootStateMachineMonitor.java +++ b/spring-statemachine-boot/src/main/java/org/springframework/statemachine/boot/support/BootStateMachineMonitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 the original author or authors. + * Copyright 2016-2018 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. @@ -19,9 +19,9 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.boot.actuate.StateMachineTraceRepository; import org.springframework.statemachine.monitor.AbstractStateMachineMonitor; import org.springframework.statemachine.monitor.StateMachineMonitor; import org.springframework.statemachine.state.State; @@ -44,18 +44,19 @@ import io.micrometer.core.instrument.Timer; */ public class BootStateMachineMonitor extends AbstractStateMachineMonitor { - private final TraceRepository traceRepository; + private final StateMachineTraceRepository traceRepository; private final MeterRegistry meterRegistry; /** * Instantiates a new boot state machine monitor. * * @param meterRegistry the meter registry - * @param traceRepository the trace repository + * @param stateMachineTraceRepository the statemachine trace repository */ - public BootStateMachineMonitor(MeterRegistry meterRegistry, TraceRepository traceRepository) { + public BootStateMachineMonitor(MeterRegistry meterRegistry, + StateMachineTraceRepository stateMachineTraceRepository) { this.meterRegistry = meterRegistry; - this.traceRepository = traceRepository; + this.traceRepository = stateMachineTraceRepository; } @Override diff --git a/spring-statemachine-samples/monitoring/src/main/resources/application.yml b/spring-statemachine-samples/monitoring/src/main/resources/application.yml index a90dc295..72d30ed2 100644 --- a/spring-statemachine-samples/monitoring/src/main/resources/application.yml +++ b/spring-statemachine-samples/monitoring/src/main/resources/application.yml @@ -4,7 +4,7 @@ logging: spring: autoconfigure: exclude: - - org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration endpoints: default: web: @@ -12,4 +12,4 @@ endpoints: management: endpoints: web: - expose: metrics,trace + expose: "*" diff --git a/spring-statemachine-samples/monitoring/src/test/java/demo/monitoring/MonitoringTests.java b/spring-statemachine-samples/monitoring/src/test/java/demo/monitoring/MonitoringTests.java index dd722603..f7d4654b 100644 --- a/spring-statemachine-samples/monitoring/src/test/java/demo/monitoring/MonitoringTests.java +++ b/spring-statemachine-samples/monitoring/src/test/java/demo/monitoring/MonitoringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 the original author or authors. + * Copyright 2016-2018 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. @@ -99,8 +99,8 @@ public class MonitoringTests { perform(get("/state")). andExpect(status().isOk()); mvc. - perform(get("/actuator/trace")). - andExpect(jsonPath("$.traces.*.info.transition", containsInAnyOrder("INITIAL_S1"))); + perform(get("/actuator/statemachinetrace")). + andExpect(jsonPath("$.*.info.transition", containsInAnyOrder("INITIAL_S1"))); } @Before