You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/01/20 11:20:30 UTC

[camel] 05/06: CAMEL-16056: Added camel-jfr for java flight recorder integration

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 <cl...@gmail.com>
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