This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
commit 3b112be98d34a06bd37112992625a878c9d198d7 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Jan 20 11:29:15 2021 +0100 CAMEL-16056: Added camel-jfr for java flight recorder integration --- .../org/apache/camel/catalog/docs/jfr.adoc | 14 +-- components/camel-jfr/src/main/docs/jfr.adoc | 14 +-- .../jfr/FlightRecorderStartupStepRecorder.java | 88 ++++++++++++++++++ .../org/apache/camel/spi/StartupStepRecorder.java | 42 +++++++-- .../camel/impl/engine/AbstractCamelContext.java | 9 +- .../MainConfigurationPropertiesConfigurer.java | 24 +++++ .../camel-main-configuration-metadata.json | 4 + core/camel-main/src/main/docs/main.adoc | 4 + .../org/apache/camel/main/BaseMainSupport.java | 73 ++++++++++++++- .../camel/main/DefaultConfigurationConfigurer.java | 8 +- .../camel/main/DefaultConfigurationProperties.java | 103 +++++++++++++++++++++ .../src/main/java/org/apache/camel/main/Main.java | 11 +-- .../startup/DefaultStartupStepRecorder.java | 55 ++++++++--- docs/components/modules/others/pages/jfr.adoc | 14 +-- 14 files changed, 393 insertions(+), 70 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jfr.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jfr.adoc index 3a8fa2e..0b66da7 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jfr.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jfr.adoc @@ -17,20 +17,8 @@ The camel-jfr component emits lifecycle events for startup to JFR. This can for example be used to pin-point which Camel routes may be slow to startup. [[jfr-Options]] -== Options - -[width="100%",cols="10%,10%,80%",options="header",] -|=== -|Option -|Default -|Description - -|enabled -|true -|Whether Camel flight recorder is enabled globally - -|=== +See the __startupRecorder__ options from xref:components:others:main.adoc[Camel Main] [[jfr-Example]] == Example diff --git a/components/camel-jfr/src/main/docs/jfr.adoc b/components/camel-jfr/src/main/docs/jfr.adoc index 3a8fa2e..0b66da7 100644 --- a/components/camel-jfr/src/main/docs/jfr.adoc +++ b/components/camel-jfr/src/main/docs/jfr.adoc @@ -17,20 +17,8 @@ The camel-jfr component emits lifecycle events for startup to JFR. This can for example be used to pin-point which Camel routes may be slow to startup. [[jfr-Options]] -== Options - -[width="100%",cols="10%,10%,80%",options="header",] -|=== -|Option -|Default -|Description - -|enabled -|true -|Whether Camel flight recorder is enabled globally - -|=== +See the __startupRecorder__ options from xref:components:others:main.adoc[Camel Main] [[jfr-Example]] == Example diff --git a/components/camel-jfr/src/main/java/org/apache/camel/startup/jfr/FlightRecorderStartupStepRecorder.java b/components/camel-jfr/src/main/java/org/apache/camel/startup/jfr/FlightRecorderStartupStepRecorder.java index 6c706a5..e9e4ac0 100644 --- a/components/camel-jfr/src/main/java/org/apache/camel/startup/jfr/FlightRecorderStartupStepRecorder.java +++ b/components/camel-jfr/src/main/java/org/apache/camel/startup/jfr/FlightRecorderStartupStepRecorder.java @@ -16,10 +16,22 @@ */ package org.apache.camel.startup.jfr; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; + +import jdk.jfr.Configuration; +import jdk.jfr.FlightRecorder; +import jdk.jfr.FlightRecorderListener; +import jdk.jfr.Recording; +import jdk.jfr.RecordingState; import org.apache.camel.StartupStep; import org.apache.camel.spi.StartupStepRecorder; import org.apache.camel.spi.annotations.JdkService; import org.apache.camel.support.startup.DefaultStartupStepRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * To capture startup steps to be emitted to Java Flight Recorder. @@ -27,8 +39,84 @@ import org.apache.camel.support.startup.DefaultStartupStepRecorder; @JdkService(StartupStepRecorder.FACTORY) public class FlightRecorderStartupStepRecorder extends DefaultStartupStepRecorder { + private static final Logger LOG = LoggerFactory.getLogger(FlightRecorderStartupStepRecorder.class); + + private Recording rec; + private FlightRecorderListener frl; + + @Override + public void doStart() throws Exception { + super.doStart(); + + if (isRecording()) { + FlightRecorder.register(FlightRecorderStartupStep.class); + Configuration config = Configuration.getConfiguration(getRecordingProfile()); + rec = new Recording(config); + rec.setName("Camel Recording"); + if (getStartupRecorderDuration() > 0) { + rec.setDuration(Duration.ofSeconds(getStartupRecorderDuration())); + LOG.info("Starting Java flight recorder with profile: {} and duration: {} seconds", getRecordingProfile(), + getStartupRecorderDuration()); + + // add listener to trigger auto-save when duration is hit + frl = new FlightRecorderListener() { + @Override + public void recordingStateChanged(Recording recording) { + if (recording == rec && recording.getState().equals(RecordingState.STOPPED)) { + LOG.info("Stopping Java flight recorder after {} seconds elapsed", getStartupRecorderDuration()); + dumpRecording(); + } + } + }; + FlightRecorder.addListener(frl); + } else { + LOG.info("Starting Java flight recorder with profile: {}", getRecordingProfile()); + } + rec.start(); + } + } + + @Override + public void doStop() throws Exception { + super.doStop(); + + if (rec != null) { + dumpRecording(); + } + } + + protected void dumpRecording() { + if (!"false".equals(getRecordingDir())) { + try { + Path dir = getRecordingDir() != null ? Paths.get(getRecordingDir()) : Paths.get(System.getenv().get("HOME")); + Path file = Files.createTempFile(dir, "camel-recording", ".jfr"); + if (rec.getState().equals(RecordingState.RUNNING)) { + // need to do GC to capture details to the recording (specially when its short running) + LOG.info("Stopping Java flight recorder"); + System.gc(); + rec.stop(); + } + if (rec.getState().equals(RecordingState.STOPPED)) { + rec.dump(file); + LOG.info("Java flight recorder saved to file: {}", file); + } + } catch (Exception e) { + LOG.warn("Error saving Java flight recorder recording to file", e); + } + } + FlightRecorder.unregister(FlightRecorderStartupStep.class); + if (frl != null) { + FlightRecorder.removeListener(frl); + } + + rec = null; + frl = null; + } + public FlightRecorderStartupStepRecorder() { setEnabled(true); + // pre-empty enable recording so we have as early as possible recording started + setRecording(true); } @Override diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/StartupStepRecorder.java b/core/camel-api/src/main/java/org/apache/camel/spi/StartupStepRecorder.java index f0e3918..be3f68a 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/StartupStepRecorder.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/StartupStepRecorder.java @@ -41,16 +41,36 @@ public interface StartupStepRecorder extends StaticService { void setEnabled(boolean enabled); /** - * Whether to automatic disable this recorder after Camel has been started. This is done by default to remove any - * overhead after the startup process is done. + * How long time to run the startup recorder. + * + * Use 0 (default) to stop the recorder after Camel has been started. Use -1 to keep the recorder running until + * Camel is being stopped. A positive value is to run the recorder for N seconds. */ - boolean isDisableAfterStarted(); + long getStartupRecorderDuration(); /** - * Whether to automatic disable this recorder after Camel has been started. This is done by default to remove any - * overhead after the startup process is done. + * How long time to run the startup recorder. + * + * Use 0 (default) to stop the recorder after Camel has been started. Use -1 to keep the recorder running until + * Camel is being stopped. A positive value is to run the recorder for N seconds. */ - void setDisableAfterStarted(boolean disableAfterStarted); + void setStartupRecorderDuration(long startupRecorderDuration); + + String getRecordingDir(); + + /** + * Directory to store the recording. By default the user home directory will be used. + */ + void setRecordingDir(String recordingDir); + + String getRecordingProfile(); + + /** + * To use a specific Java Flight Recorder profile configuration, such as default or profile. + * + * The default is default. + */ + void setRecordingProfile(String profile); /** * To filter our sub steps at a maximum depth @@ -63,6 +83,16 @@ public interface StartupStepRecorder extends StaticService { int getMaxDepth(); /** + * Whether to start flight recorder recording. This is only in use if camel-jfr is being used. + */ + void setRecording(boolean recording); + + /** + * Whether to start flight recorder recording. This is only in use if camel-jfr is being used. + */ + boolean isRecording(); + + /** * Beings a new step. * <p> * Important must call {@link #endStep(StartupStep)} to end the step. diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java index 646bca5..91a1b36 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java @@ -2528,8 +2528,8 @@ public abstract class AbstractCamelContext extends BaseService public void doBuild() throws Exception { bootDate = System.currentTimeMillis(); - // auto-detect step recorder from classpath if not explicit configured - if (startupStepRecorder instanceof DefaultStartupStepRecorder) { + // auto-detect step recorder from classpath if none has been explicit configured + if (startupStepRecorder.getClass().getSimpleName().equals("DefaultStartupStepRecorder")) { StartupStepRecorder fr = getBootstrapFactoryFinder() .newInstance(StartupStepRecorder.FACTORY, StartupStepRecorder.class).orElse(null); if (fr != null) { @@ -2742,7 +2742,7 @@ public abstract class AbstractCamelContext extends BaseService startupStepRecorder.endStep(step); - if (startupStepRecorder.isDisableAfterStarted()) { + if (startupStepRecorder.getStartupRecorderDuration() == 0) { startupStepRecorder.stop(); } } @@ -3144,6 +3144,9 @@ public abstract class AbstractCamelContext extends BaseService TimeUtils.printDuration(stopWatch.taken())); } + // ensure any recorder is stopped in case it was kept running + startupStepRecorder.stop(); + // and clear start date startDate = 0; bootDate = 0; diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java index 0ec603e..63940e0 100644 --- a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java +++ b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java @@ -145,8 +145,16 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "ShutdownTimeout": target.setShutdownTimeout(property(camelContext, int.class, value)); return true; case "startuprecorder": case "StartupRecorder": target.setStartupRecorder(property(camelContext, java.lang.String.class, value)); return true; + case "startuprecorderdir": + case "StartupRecorderDir": target.setStartupRecorderDir(property(camelContext, java.lang.String.class, value)); return true; + case "startuprecorderduration": + case "StartupRecorderDuration": target.setStartupRecorderDuration(property(camelContext, long.class, value)); return true; case "startuprecordermaxdepth": case "StartupRecorderMaxDepth": target.setStartupRecorderMaxDepth(property(camelContext, int.class, value)); return true; + case "startuprecorderprofile": + case "StartupRecorderProfile": target.setStartupRecorderProfile(property(camelContext, java.lang.String.class, value)); return true; + case "startuprecorderrecording": + case "StartupRecorderRecording": target.setStartupRecorderRecording(property(camelContext, boolean.class, value)); return true; case "streamcachinganyspoolrules": case "StreamCachingAnySpoolRules": target.setStreamCachingAnySpoolRules(property(camelContext, boolean.class, value)); return true; case "streamcachingbuffersize": @@ -316,8 +324,16 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "ShutdownTimeout": return int.class; case "startuprecorder": case "StartupRecorder": return java.lang.String.class; + case "startuprecorderdir": + case "StartupRecorderDir": return java.lang.String.class; + case "startuprecorderduration": + case "StartupRecorderDuration": return long.class; case "startuprecordermaxdepth": case "StartupRecorderMaxDepth": return int.class; + case "startuprecorderprofile": + case "StartupRecorderProfile": return java.lang.String.class; + case "startuprecorderrecording": + case "StartupRecorderRecording": return boolean.class; case "streamcachinganyspoolrules": case "StreamCachingAnySpoolRules": return boolean.class; case "streamcachingbuffersize": @@ -488,8 +504,16 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "ShutdownTimeout": return target.getShutdownTimeout(); case "startuprecorder": case "StartupRecorder": return target.getStartupRecorder(); + case "startuprecorderdir": + case "StartupRecorderDir": return target.getStartupRecorderDir(); + case "startuprecorderduration": + case "StartupRecorderDuration": return target.getStartupRecorderDuration(); case "startuprecordermaxdepth": case "StartupRecorderMaxDepth": return target.getStartupRecorderMaxDepth(); + case "startuprecorderprofile": + case "StartupRecorderProfile": return target.getStartupRecorderProfile(); + case "startuprecorderrecording": + case "StartupRecorderRecording": return target.isStartupRecorderRecording(); case "streamcachinganyspoolrules": case "StreamCachingAnySpoolRules": return target.isStreamCachingAnySpoolRules(); case "streamcachingbuffersize": diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index 9429f05..640fe32 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -72,7 +72,11 @@ { "name": "camel.main.shutdownSuppressLoggingOnTimeout", "description": "Whether Camel should try to suppress logging during shutdown and timeout was triggered, meaning forced shutdown is happening. And during forced shutdown we want to avoid logging errors\/warnings et all in the logs as a side-effect of the forced timeout. Notice the suppress is a best effort as there may still be some logs coming from 3rd party libraries and whatnot, which Camel cannot control. This option is defa [...] { "name": "camel.main.shutdownTimeout", "description": "Timeout in seconds to graceful shutdown Camel.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 45 }, { "name": "camel.main.startupRecorder", "description": "To use startup recorder for capturing execution time during starting Camel. The recorder can be one of: false, logging, java-flight-recorder The default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, + { "name": "camel.main.startupRecorderDir", "description": "Directory to store the recording. By default the user home directory will be used. Use false to turn off saving recording to disk.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String" }, + { "name": "camel.main.startupRecorderDuration", "description": "How long time to run the startup recorder. Use 0 (default) to stop the recorder after Camel has been started. Use -1 to keep the recorder running until Camel is being stopped. A positive value is to run the recorder for N seconds. When the recorder is stopped then the recording is auto saved to disk", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "long" }, { "name": "camel.main.startupRecorderMaxDepth", "description": "To filter our sub steps at a maximum depth. Use -1 for no maximum. Use 0 for no sub steps. Use 1 for max 1 sub step, and so forth. The default is -1.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": -1 }, + { "name": "camel.main.startupRecorderProfile", "description": "To use a specific Java Flight Recorder profile configuration, such as default or profile. The default is default.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "default" }, + { "name": "camel.main.startupRecorderRecording", "description": "To enable Java Flight Recorder to start a recording and automatic dump the recording to disk after startup is complete. This requires that camel-jfr is on the classpath. The default is true.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true }, { "name": "camel.main.streamCachingAnySpoolRules", "description": "Sets whether if just any of the org.apache.camel.spi.StreamCachingStrategy.SpoolRule rules returns true then shouldSpoolCache(long) returns true, to allow spooling to disk. If this option is false, then all the org.apache.camel.spi.StreamCachingStrategy.SpoolRule must return true. The default value is false which means that all the rules must return true.", "sourceType": "org.apache.camel.main.DefaultConfigurationProp [...] { "name": "camel.main.streamCachingBufferSize", "description": "Sets the stream caching buffer size to use when allocating in-memory buffers used for in-memory stream caches. The default size is 4096.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int" }, { "name": "camel.main.streamCachingEnabled", "description": "Sets whether stream caching is enabled or not. Default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean" }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 7c60056..f00aef5 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -84,7 +84,11 @@ The following table lists all the options: | *camel.main.shutdownSuppress{zwsp}LoggingOnTimeout* | Whether Camel should try to suppress logging during shutdown and timeout was triggered, meaning forced shutdown is happening. And during forced shutdown we want to avoid logging errors/warnings et all in the logs as a side-effect of the forced timeout. Notice the suppress is a best effort as there may still be some logs coming from 3rd party libraries and whatnot, which Camel cannot control. This option is default false. | | boolean | *camel.main.shutdownTimeout* | Timeout in seconds to graceful shutdown Camel. | 45 | int | *camel.main.startupRecorder* | To use startup recorder for capturing execution time during starting Camel. The recorder can be one of: false, logging, java-flight-recorder The default is false. | | String +| *camel.main.startupRecorderDir* | Directory to store the recording. By default the user home directory will be used. Use false to turn off saving recording to disk. | | String +| *camel.main.startupRecorder{zwsp}Duration* | How long time to run the startup recorder. Use 0 (default) to stop the recorder after Camel has been started. Use -1 to keep the recorder running until Camel is being stopped. A positive value is to run the recorder for N seconds. When the recorder is stopped then the recording is auto saved to disk | | long | *camel.main.startupRecorderMax{zwsp}Depth* | To filter our sub steps at a maximum depth. Use -1 for no maximum. Use 0 for no sub steps. Use 1 for max 1 sub step, and so forth. The default is -1. | -1 | int +| *camel.main.startupRecorder{zwsp}Profile* | To use a specific Java Flight Recorder profile configuration, such as default or profile. The default is default. | default | String +| *camel.main.startupRecorder{zwsp}Recording* | To enable Java Flight Recorder to start a recording and automatic dump the recording to disk after startup is complete. This requires that camel-jfr is on the classpath. The default is true. | true | boolean | *camel.main.streamCachingAny{zwsp}SpoolRules* | Sets whether if just any of the org.apache.camel.spi.StreamCachingStrategy.SpoolRule rules returns true then shouldSpoolCache(long) returns true, to allow spooling to disk. If this option is false, then all the org.apache.camel.spi.StreamCachingStrategy.SpoolRule must return true. The default value is false which means that all the rules must return true. | | boolean | *camel.main.streamCachingBuffer{zwsp}Size* | Sets the stream caching buffer size to use when allocating in-memory buffers used for in-memory stream caches. The default size is 4096. | | int | *camel.main.streamCaching{zwsp}Enabled* | Sets whether stream caching is enabled or not. Default is false. | | boolean diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 1eb9f1d..c3ea133 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -51,11 +51,13 @@ import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.Language; import org.apache.camel.spi.PropertiesComponent; import org.apache.camel.spi.RouteTemplateParameterSource; +import org.apache.camel.spi.StartupStepRecorder; import org.apache.camel.support.CamelContextHelper; import org.apache.camel.support.LifecycleStrategySupport; import org.apache.camel.support.PropertyBindingSupport; import org.apache.camel.support.ResourceHelper; import org.apache.camel.support.service.BaseService; +import org.apache.camel.support.startup.LoggingStartupStepRecorder; import org.apache.camel.util.FileUtil; import org.apache.camel.util.IOHelper; import org.apache.camel.util.ObjectHelper; @@ -440,6 +442,70 @@ public abstract class BaseMainSupport extends BaseService { } } + protected void configureStartupRecorder(CamelContext camelContext) { + // we need to load these configurations early as they control the startup recorder when using camel-jfr + // and we want to start jfr recording as early as possible to also capture details during bootstrapping Camel + + // load properties + Properties prop = camelContext.getPropertiesComponent().loadProperties(name -> name.startsWith("camel.")); + + Object value = prop.remove("camel.main.startupRecorder"); + if (value == null) { + value = prop.remove("camel.main.startup-recorder"); + if (value != null) { + mainConfigurationProperties.setStartupRecorder(value.toString()); + } + } + value = prop.remove("camel.main.startupRecorderRecording"); + if (value == null) { + value = prop.remove("camel.main.startup-recorder-recording"); + if (value != null) { + mainConfigurationProperties.setStartupRecorderRecording("true".equalsIgnoreCase(value.toString())); + } + } + value = prop.remove("camel.main.startupRecorderProfile"); + if (value == null) { + value = prop.remove("camel.main.startup-recorder-profile"); + if (value != null) { + mainConfigurationProperties.setStartupRecorderProfile( + CamelContextHelper.parseText(camelContext, value.toString())); + } + } + value = prop.remove("camel.main.startupRecorderDuration"); + if (value == null) { + value = prop.remove("camel.main.startup-recorder-duration"); + if (value != null) { + mainConfigurationProperties.setStartupRecorderDuration(Long.parseLong(value.toString())); + } + } + value = prop.remove("camel.main.startupRecorderMaxDepth"); + if (value == null) { + value = prop.remove("camel.main.startup-recorder-max-depth"); + if (value != null) { + mainConfigurationProperties.setStartupRecorderMaxDepth(Integer.parseInt(value.toString())); + } + } + + if ("false".equals(mainConfigurationProperties.getStartupRecorder())) { + camelContext.adapt(ExtendedCamelContext.class).getStartupStepRecorder().setEnabled(false); + } else if ("logging".equals(mainConfigurationProperties.getStartupRecorder())) { + camelContext.adapt(ExtendedCamelContext.class).setStartupStepRecorder(new LoggingStartupStepRecorder()); + } else if ("java-flight-recorder".equals(mainConfigurationProperties.getStartupRecorder()) + || mainConfigurationProperties.getStartupRecorder() == null) { + // try to auto discover camel-jfr to use + StartupStepRecorder fr = camelContext.adapt(ExtendedCamelContext.class).getBootstrapFactoryFinder() + .newInstance(StartupStepRecorder.FACTORY, StartupStepRecorder.class).orElse(null); + if (fr != null) { + LOG.debug("Discovered startup recorder: {} from classpath", fr); + fr.setRecording(mainConfigurationProperties.isStartupRecorderRecording()); + fr.setStartupRecorderDuration(mainConfigurationProperties.getStartupRecorderDuration()); + fr.setRecordingProfile(mainConfigurationProperties.getStartupRecorderProfile()); + fr.setMaxDepth(mainConfigurationProperties.getStartupRecorderMaxDepth()); + camelContext.adapt(ExtendedCamelContext.class).setStartupStepRecorder(fr); + } + } + } + protected void configureRoutes(CamelContext camelContext) throws Exception { // try to load the route builders loadRouteBuilders(camelContext); @@ -450,6 +516,11 @@ public abstract class BaseMainSupport extends BaseService { } protected void postProcessCamelContext(CamelContext camelContext) throws Exception { + // setup properties + configurePropertiesService(camelContext); + // setup startup recorder before building context + configureStartupRecorder(camelContext); + // ensure camel is initialized camelContext.build(); @@ -457,8 +528,6 @@ public abstract class BaseMainSupport extends BaseService { listener.beforeInitialize(this); } - configurePropertiesService(camelContext); - // allow to do configuration before its started for (MainListener listener : listeners) { listener.beforeConfigure(this); diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java index 13ae341..d2481db 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java @@ -96,7 +96,9 @@ public final class DefaultConfigurationConfigurer { if ("false".equals(config.getStartupRecorder())) { ecc.getStartupStepRecorder().setEnabled(false); } else if ("logging".equals(config.getStartupRecorder())) { - ecc.setStartupStepRecorder(new LoggingStartupStepRecorder()); + if (!(ecc.getStartupStepRecorder() instanceof LoggingStartupStepRecorder)) { + ecc.setStartupStepRecorder(new LoggingStartupStepRecorder()); + } } else if ("java-flight-recorder".equals(config.getStartupRecorder())) { if (!ecc.getStartupStepRecorder().getClass().getName().startsWith("org.apache.camel.startup.jfr")) throw new IllegalArgumentException( @@ -104,6 +106,10 @@ public final class DefaultConfigurationConfigurer { } } ecc.getStartupStepRecorder().setMaxDepth(config.getStartupRecorderMaxDepth()); + ecc.getStartupStepRecorder().setRecording(config.isStartupRecorderRecording()); + ecc.getStartupStepRecorder().setStartupRecorderDuration(config.getStartupRecorderDuration()); + ecc.getStartupStepRecorder().setRecordingDir(config.getStartupRecorderDir()); + ecc.getStartupStepRecorder().setRecordingProfile(config.getStartupRecorderProfile()); ecc.setLightweight(config.isLightweight()); ecc.getBeanPostProcessor().setEnabled(config.isBeanPostProcessorEnabled()); diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java index 8b71105..070b9f9 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java @@ -102,6 +102,10 @@ public abstract class DefaultConfigurationProperties<T> { private boolean routeControllerUnhealthyOnExhausted; private String startupRecorder; private int startupRecorderMaxDepth = -1; + private boolean startupRecorderRecording = true; + private String startupRecorderProfile = "default"; + private long startupRecorderDuration; + private String startupRecorderDir; // getter and setters // -------------------------------------------------------------- @@ -1127,6 +1131,63 @@ public abstract class DefaultConfigurationProperties<T> { this.startupRecorderMaxDepth = startupRecorderMaxDepth; } + public boolean isStartupRecorderRecording() { + return startupRecorderRecording; + } + + /** + * To enable Java Flight Recorder to start a recording and automatic dump the recording to disk after startup is + * complete. + * + * This requires that camel-jfr is on the classpath. + * + * The default is true. + */ + public void setStartupRecorderRecording(boolean startupRecorderRecording) { + this.startupRecorderRecording = startupRecorderRecording; + } + + public String getStartupRecorderProfile() { + return startupRecorderProfile; + } + + /** + * To use a specific Java Flight Recorder profile configuration, such as default or profile. + * + * The default is default. + */ + public void setStartupRecorderProfile(String startupRecorderProfile) { + this.startupRecorderProfile = startupRecorderProfile; + } + + public long getStartupRecorderDuration() { + return startupRecorderDuration; + } + + /** + * How long time to run the startup recorder. + * + * Use 0 (default) to stop the recorder after Camel has been started. Use -1 to keep the recorder running until + * Camel is being stopped. A positive value is to run the recorder for N seconds. + * + * When the recorder is stopped then the recording is auto saved to disk + */ + public void setStartupRecorderDuration(long startupRecorderDuration) { + this.startupRecorderDuration = startupRecorderDuration; + } + + public String getStartupRecorderDir() { + return startupRecorderDir; + } + + /** + * Directory to store the recording. By default the user home directory will be used. Use false to turn off saving + * recording to disk. + */ + public void setStartupRecorderDir(String startupRecorderDir) { + this.startupRecorderDir = startupRecorderDir; + } + // fluent builders // -------------------------------------------------------------- @@ -1901,4 +1962,46 @@ public abstract class DefaultConfigurationProperties<T> { return (T) this; } + /** + * To enable Java Flight Recorder to start a recording and automatic dump the recording to disk after startup is + * complete. + * + * This requires that camel-jfr is in use. + * + * The default is false. + */ + public T withStartupRecorderRecording(boolean startupRecorderRecording) { + this.startupRecorderRecording = startupRecorderRecording; + return (T) this; + } + + /** + * To use a specific Java Flight Recorder profile configuration, such as default or profile. + * + * The default is default. + */ + public T withStartupRecorderProfile(String startupRecorderProfile) { + this.startupRecorderProfile = startupRecorderProfile; + return (T) this; + } + + /** + * How long time to run the startup recorder. + * + * Use 0 (default) to stop the recorder after Camel has been started. Use -1 to keep the recorder running until + * Camel is being stopped. A positive value is to run the recorder for N seconds. + */ + public T withStartupRecorderDuration(long startupRecorderDuration) { + this.startupRecorderDuration = startupRecorderDuration; + return (T) this; + } + + /** + * Directory to store the recording. By default the user home directory will be used. + */ + public T withStartupRecorderDir(String startupRecorderDir) { + this.startupRecorderDir = startupRecorderDir; + return (T) this; + } + } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/Main.java b/core/camel-main/src/main/java/org/apache/camel/main/Main.java index af29695..fcc4633 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/Main.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/Main.java @@ -139,13 +139,10 @@ public class Main extends MainCommandLineSupport { @Override protected CamelContext createCamelContext() { - return new DefaultCamelContext(registry); - // TODO: LightweightCamelContext is not ready yet - //if (mainConfigurationProperties.isLightweight()) { - // return new LightweightCamelContext(registry); - //} else { - // return new DefaultCamelContext(registry); - //} + // do not build/init camel context yet + DefaultCamelContext answer = new DefaultCamelContext(false); + answer.setRegistry(registry); + return answer; } } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/startup/DefaultStartupStepRecorder.java b/core/camel-support/src/main/java/org/apache/camel/support/startup/DefaultStartupStepRecorder.java index 67a8bff..d2e3b9b 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/startup/DefaultStartupStepRecorder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/startup/DefaultStartupStepRecorder.java @@ -22,11 +22,12 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.camel.StartupStep; import org.apache.camel.spi.StartupStepRecorder; +import org.apache.camel.support.service.ServiceSupport; /** * Default {@link StartupStepRecorder} that is always disabled. */ -public class DefaultStartupStepRecorder implements StartupStepRecorder { +public class DefaultStartupStepRecorder extends ServiceSupport implements StartupStepRecorder { private final AtomicInteger stepCounter = new AtomicInteger(); private final Deque<Integer> currentSteps = new ArrayDeque<>(); @@ -74,13 +75,12 @@ public class DefaultStartupStepRecorder implements StartupStepRecorder { }; - public DefaultStartupStepRecorder() { - currentSteps.offerFirst(0); - } - private boolean enabled; - private boolean disableAfterStarted = true; private int maxDepth = -1; + private long startupRecorderDuration; + private boolean recording; + private String recordingDir; + private String recordingProfile = "default"; public boolean isEnabled() { return enabled; @@ -91,12 +91,13 @@ public class DefaultStartupStepRecorder implements StartupStepRecorder { } @Override - public boolean isDisableAfterStarted() { - return disableAfterStarted; + public long getStartupRecorderDuration() { + return startupRecorderDuration; } - public void setDisableAfterStarted(boolean disableAfterStarted) { - this.disableAfterStarted = disableAfterStarted; + @Override + public void setStartupRecorderDuration(long startupRecorderDuration) { + this.startupRecorderDuration = startupRecorderDuration; } @Override @@ -109,12 +110,42 @@ public class DefaultStartupStepRecorder implements StartupStepRecorder { } @Override - public void start() { + public boolean isRecording() { + return recording; + } + + @Override + public void setRecording(boolean recording) { + this.recording = recording; + } + + @Override + public String getRecordingDir() { + return recordingDir; + } + + @Override + public void setRecordingDir(String recordingDir) { + this.recordingDir = recordingDir; + } + + @Override + public String getRecordingProfile() { + return recordingProfile; + } + + @Override + public void setRecordingProfile(String recordingProfile) { + this.recordingProfile = recordingProfile; + } + + @Override + protected void doStart() throws Exception { currentSteps.offerFirst(0); } @Override - public void stop() { + public void doStop() throws Exception { enabled = false; currentSteps.clear(); } diff --git a/docs/components/modules/others/pages/jfr.adoc b/docs/components/modules/others/pages/jfr.adoc index f9f2af7..45f5abb 100644 --- a/docs/components/modules/others/pages/jfr.adoc +++ b/docs/components/modules/others/pages/jfr.adoc @@ -19,20 +19,8 @@ The camel-jfr component emits lifecycle events for startup to JFR. This can for example be used to pin-point which Camel routes may be slow to startup. [[jfr-Options]] -== Options - -[width="100%",cols="10%,10%,80%",options="header",] -|=== -|Option -|Default -|Description - -|enabled -|true -|Whether Camel flight recorder is enabled globally - -|=== +See the __startupRecorder__ options from xref:components:others:main.adoc[Camel Main] [[jfr-Example]] == Example
