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 b9c19ecf28b2daf2dc1ff3bff72faaf5700e00ac
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jan 19 18:22:21 2021 +0100

    CAMEL-16056: Added StartupStep to diagnose startup exeuction times for 
various steps. To be integated with JFR later.
---
 .../org/apache/camel/ExtendedCamelContext.java     |  11 ++
 .../main/java/org/apache/camel/StartupStep.java    |  72 ++++++++
 .../org/apache/camel/spi/StartupStepRecorder.java  |  76 +++++++++
 .../camel/impl/engine/AbstractCamelContext.java    |  69 +++++++-
 .../impl/engine/InternalRouteStartupManager.java   |   9 +
 .../camel/impl/ExtendedCamelContextConfigurer.java |   6 +
 .../camel/impl/lw/LightweightCamelContext.java     |  11 ++
 .../impl/lw/LightweightRuntimeCamelContext.java    |  11 ++
 .../apache/camel/impl/StartupStepLoggingTest.java  |  46 +++++
 .../apache/camel/support/DefaultStartupStep.java   |  85 +++++++++
 .../camel/support/DefaultStartupStepRecorder.java  | 189 +++++++++++++++++++++
 11 files changed, 581 insertions(+), 4 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java 
b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
index bcbb4e4..6a392ad 100644
--- a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
@@ -59,6 +59,7 @@ import org.apache.camel.spi.RestBindingJaxbDataFormatFactory;
 import org.apache.camel.spi.RouteController;
 import org.apache.camel.spi.RouteFactory;
 import org.apache.camel.spi.RouteStartupOrder;
+import org.apache.camel.spi.StartupStepRecorder;
 import org.apache.camel.spi.UnitOfWorkFactory;
 import org.apache.camel.spi.UriFactoryResolver;
 import org.apache.camel.spi.XMLRoutesDefinitionLoader;
@@ -663,6 +664,16 @@ public interface ExtendedCamelContext extends CamelContext 
{
     EndpointUriFactory getEndpointUriFactory(String scheme);
 
     /**
+     * Gets the {@link StartupStepRecorder} to use.
+     */
+    StartupStepRecorder getStartupStepRecorder();
+
+    /**
+     * Sets the {@link StartupStepRecorder} to use.
+     */
+    void setStartupStepRecorder(StartupStepRecorder startupStepRecorder);
+
+    /**
      * Internal API for adding routes. Do not use this as end user.
      */
     void addRoute(Route route);
diff --git a/core/camel-api/src/main/java/org/apache/camel/StartupStep.java 
b/core/camel-api/src/main/java/org/apache/camel/StartupStep.java
new file mode 100644
index 0000000..18f23cd
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/StartupStep.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel;
+
+/**
+ * Recording state of steps during startup to capture execution time, and 
being able to emit events to diagnostic tools
+ * such as Java Flight Recorder.
+ */
+public interface StartupStep {
+
+    /**
+     * The source class type of the step
+     */
+    String getType();
+
+    /**
+     * Name of the step
+     */
+    String getName();
+
+    /**
+     * Description of the step
+     */
+    String getDescription();
+
+    /**
+     * The id of the step
+     */
+    int getId();
+
+    /**
+     * The id of the parent step
+     */
+    int getParentId();
+
+    /**
+     * The step level (sub step of previous steps)
+     */
+    int getLevel();
+
+    /**
+     * Ends the step.
+     */
+    void end();
+
+    /**
+     * Gets the begin time (optional).
+     */
+    long getBeginTime();
+
+    /**
+     * Add metadata.
+     *
+     * @param key   the key
+     * @param value the value
+     */
+    void addTag(String key, String value);
+}
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
new file mode 100644
index 0000000..60df638
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/StartupStepRecorder.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.spi;
+
+import org.apache.camel.StartupStep;
+import org.apache.camel.StaticService;
+
+/**
+ * To record {@link StartupStep} during startup to allow to capture diagnostic 
information to help troubleshoot Camel
+ * applications via various tooling such as Java Flight Recorder.
+ */
+public interface StartupStepRecorder extends StaticService {
+
+    /**
+     * Whether recording is enabled
+     */
+    boolean isEnabled();
+
+    /**
+     * Whether recording is enabled
+     */
+    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.
+     */
+    boolean isDisableAfterStarted();
+
+    /**
+     * 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.
+     */
+    void setDisableAfterStarted(boolean disableAfterStarted);
+
+    /**
+     * To filter our sub steps at a maximum depth
+     */
+    void setMaxDepth(int level);
+
+    /**
+     * To filter our sub steps at a maximum depth
+     */
+    int getMaxDepth();
+
+    /**
+     * Beings a new step.
+     *
+     * Important must call {@link #endStep(StartupStep)} to end the step.
+     *
+     * @param type        the source
+     * @param name        name of the step
+     * @param description description of the step
+     */
+    StartupStep beginStep(Class<?> type, String name, String description);
+
+    /**
+     * Ends the step
+     */
+    void endStep(StartupStep 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 ca96cce..b4ccabd 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
@@ -69,6 +69,7 @@ import org.apache.camel.ServiceStatus;
 import org.apache.camel.ShutdownRoute;
 import org.apache.camel.ShutdownRunningTask;
 import org.apache.camel.StartupListener;
+import org.apache.camel.StartupStep;
 import org.apache.camel.Suspendable;
 import org.apache.camel.SuspendableService;
 import org.apache.camel.TypeConverter;
@@ -140,6 +141,7 @@ import org.apache.camel.spi.RouteStartupOrder;
 import org.apache.camel.spi.RouteTemplateParameterSource;
 import org.apache.camel.spi.RuntimeEndpointRegistry;
 import org.apache.camel.spi.ShutdownStrategy;
+import org.apache.camel.spi.StartupStepRecorder;
 import org.apache.camel.spi.StreamCachingStrategy;
 import org.apache.camel.spi.Tracer;
 import org.apache.camel.spi.Transformer;
@@ -152,6 +154,7 @@ import org.apache.camel.spi.Validator;
 import org.apache.camel.spi.ValidatorRegistry;
 import org.apache.camel.spi.XMLRoutesDefinitionLoader;
 import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.DefaultStartupStepRecorder;
 import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.support.EventHelper;
 import org.apache.camel.support.LRUCacheFactory;
@@ -305,12 +308,14 @@ public abstract class AbstractCamelContext extends 
BaseService
     private volatile boolean eventNotificationApplicable;
     private volatile TransformerRegistry<TransformerKey> transformerRegistry;
     private volatile ValidatorRegistry<ValidatorKey> validatorRegistry;
+    private volatile StartupStepRecorder startupStepRecorder = new 
DefaultStartupStepRecorder();
     private EndpointRegistry<EndpointKey> endpoints;
     private RuntimeEndpointRegistry runtimeEndpointRegistry;
     private ShutdownRoute shutdownRoute = ShutdownRoute.Default;
     private ShutdownRunningTask shutdownRunningTask = 
ShutdownRunningTask.CompleteCurrentTaskOnly;
     private Debugger debugger;
     private long startDate;
+    private long bootDate;
 
     private SSLContextParameters sslContextParameters;
 
@@ -576,7 +581,9 @@ public abstract class AbstractCamelContext extends 
BaseService
             if (component != null && created.get() && autoStart && 
(isStarted() || isStarting())) {
                 // If the component is looked up after the context is started,
                 // lets start it up.
+                StartupStep step = 
startupStepRecorder.beginStep(Component.class, name, "Starting component");
                 startService(component);
+                startupStepRecorder.endStep(step);
             }
 
             return component;
@@ -594,6 +601,7 @@ public abstract class AbstractCamelContext extends 
BaseService
     private Component initComponent(String name, boolean autoCreateComponents) 
{
         Component component = null;
         if (autoCreateComponents) {
+            StartupStep step = startupStepRecorder.beginStep(Component.class, 
name, "Resolving component");
             try {
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("Using ComponentResolver: {} to resolve 
component with name: {}", getComponentResolver(), name);
@@ -647,6 +655,7 @@ public abstract class AbstractCamelContext extends 
BaseService
             } catch (Exception e) {
                 throw new RuntimeCamelException("Cannot auto create component: 
" + name, e);
             }
+            startupStepRecorder.endStep(step);
         }
         return component;
     }
@@ -794,7 +803,17 @@ public abstract class AbstractCamelContext extends 
BaseService
 
     @Override
     public Endpoint getEndpoint(String uri) {
-        return doGetEndpoint(uri, null, false, false);
+        StartupStep step = null;
+        // only record startup step during startup (not started)
+        if (!isStarted() && startupStepRecorder.isEnabled()) {
+            String u = URISupport.sanitizeUri(uri);
+            step = startupStepRecorder.beginStep(Endpoint.class, u, "Getting 
endpoint");
+        }
+        Endpoint answer = doGetEndpoint(uri, null, false, false);
+        if (step != null) {
+            startupStepRecorder.endStep(step);
+        }
+        return answer;
     }
 
     @Override
@@ -2507,25 +2526,35 @@ public abstract class AbstractCamelContext extends 
BaseService
 
     @Override
     public void doBuild() throws Exception {
+        bootDate = System.currentTimeMillis();
+        startupStepRecorder.start();
+        StartupStep step = startupStepRecorder.beginStep(CamelContext.class, 
null, "Building context");
+
         // Initialize LRUCacheFactory as eager as possible,
         // to let it warm up concurrently while Camel is startup up
         if (initialization != Initialization.Lazy) {
+            StartupStep step2 = 
startupStepRecorder.beginStep(CamelContext.class, null, "Setting up 
LRUCacheFactory");
             LRUCacheFactory.init();
+            startupStepRecorder.endStep(step2);
         }
 
         // Setup management first since end users may use it to add event
         // notifiers using the management strategy before the CamelContext has 
been started
+        StartupStep step3 = startupStepRecorder.beginStep(CamelContext.class, 
null, "Setting up Management");
         setupManagement(null);
+        startupStepRecorder.endStep(step3);
 
         // setup health-check registry as its needed this early phase for 3rd 
party to register custom repositories
         HealthCheckRegistry hcr = getExtension(HealthCheckRegistry.class);
         if (hcr == null) {
+            StartupStep step4 = 
startupStepRecorder.beginStep(CamelContext.class, null, "Setting up 
HealthCheckRegistry");
             hcr = createHealthCheckRegistry();
             if (hcr != null) {
                 // install health-check registry if it was discovered from 
classpath (camel-health)
                 hcr.setCamelContext(this);
                 setExtension(HealthCheckRegistry.class, hcr);
             }
+            startupStepRecorder.endStep(step4);
         }
 
         // Call all registered trackers with this context
@@ -2534,15 +2563,20 @@ public abstract class AbstractCamelContext extends 
BaseService
 
         // Setup type converter eager as its highly in use and should not be 
lazy initialized
         if (eagerCreateTypeConverter()) {
+            StartupStep step5 = 
startupStepRecorder.beginStep(CamelContext.class, null, "Setting up 
TypeConverter");
             getOrCreateTypeConverter();
+            startupStepRecorder.endStep(step5);
         }
+
+        startupStepRecorder.endStep(step);
     }
 
     @Override
     public void doInit() throws Exception {
-        // start the route controller
+        StartupStep step = startupStepRecorder.beginStep(CamelContext.class, 
null, "Initializing context");
+
+        // init the route controller
         this.routeController = getRouteController();
-        ServiceHelper.initService(this.routeController);
 
         // optimize - before starting routes lets check if event notifications 
is possible
         eventNotificationApplicable = EventHelper.eventsApplicable(this);
@@ -2655,7 +2689,9 @@ public abstract class AbstractCamelContext extends 
BaseService
         }
 
         // start the route definitions before the routes is started
+        StartupStep step2 = startupStepRecorder.beginStep(CamelContext.class, 
getName(), "Initializing routes");
         startRouteDefinitions();
+        startupStepRecorder.endStep(step2);
 
         for (LifecycleStrategy strategy : lifecycleStrategies) {
             try {
@@ -2673,10 +2709,14 @@ public abstract class AbstractCamelContext extends 
BaseService
         }
 
         EventHelper.notifyCamelContextInitialized(this);
+
+        startupStepRecorder.endStep(step);
     }
 
     @Override
     protected void doStart() throws Exception {
+        StartupStep step = startupStepRecorder.beginStep(CamelContext.class, 
getName(), "Starting context");
+
         try {
             doStartContext();
         } catch (Exception e) {
@@ -2685,6 +2725,12 @@ public abstract class AbstractCamelContext extends 
BaseService
             // rethrow cause
             throw e;
         }
+
+        startupStepRecorder.endStep(step);
+
+        if (startupStepRecorder.isDisableAfterStarted()) {
+            startupStepRecorder.stop();
+        }
     }
 
     protected void doStartContext() throws Exception {
@@ -2797,7 +2843,9 @@ public abstract class AbstractCamelContext extends 
BaseService
             }
         }
 
-        LOG.info("Apache Camel {} ({}) started in {}", getVersion(), 
getName(), TimeUtils.printDuration(stopWatch.taken()));
+        String start = TimeUtils.printDuration(stopWatch.taken());
+        String boot = TimeUtils.printDuration(new StopWatch(bootDate).taken());
+        LOG.info("Apache Camel {} ({}) started in {} (incl boot {})", 
getVersion(), getName(), start, boot);
     }
 
     protected void doStartCamel() throws Exception {
@@ -2935,9 +2983,11 @@ public abstract class AbstractCamelContext extends 
BaseService
         }
 
         // invoke this logic to warmup the routes and if possible also start 
the routes
+        StartupStep step2 = startupStepRecorder.beginStep(CamelContext.class, 
getName(), "Starting routes");
         EventHelper.notifyCamelContextRoutesStarting(this);
         internalRouteStartupManager.doStartOrResumeRoutes(routeServices, true, 
!doNotStartRoutesOnFirstStart, false, true);
         EventHelper.notifyCamelContextRoutesStarted(this);
+        startupStepRecorder.endStep(step2);
 
         long cacheCounter = beanIntrospection != null ? 
beanIntrospection.getCachedClassesCounter() : 0;
         if (cacheCounter > 0) {
@@ -3082,6 +3132,7 @@ public abstract class AbstractCamelContext extends 
BaseService
 
         // and clear start date
         startDate = 0;
+        bootDate = 0;
 
         // Call all registered trackers with this context
         // Note, this may use a partially constructed object
@@ -4499,6 +4550,16 @@ public abstract class AbstractCamelContext extends 
BaseService
         return getUriFactoryResolver().resolveFactory(scheme, this);
     }
 
+    @Override
+    public StartupStepRecorder getStartupStepRecorder() {
+        return startupStepRecorder;
+    }
+
+    @Override
+    public void setStartupStepRecorder(StartupStepRecorder 
startupStepRecorder) {
+        this.startupStepRecorder = startupStepRecorder;
+    }
+
     @Deprecated
     public enum Initialization {
         Eager,
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteStartupManager.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteStartupManager.java
index f8ee94d..4427fda 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteStartupManager.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/InternalRouteStartupManager.java
@@ -32,6 +32,7 @@ import org.apache.camel.MultipleConsumersSupport;
 import org.apache.camel.Route;
 import org.apache.camel.ServiceStatus;
 import org.apache.camel.StartupListener;
+import org.apache.camel.StartupStep;
 import org.apache.camel.StatefulService;
 import org.apache.camel.SuspendableService;
 import org.apache.camel.spi.CamelLogger;
@@ -264,12 +265,15 @@ class InternalRouteStartupManager {
             // will then be prepared in time before we start inputs which will
             // consume messages to be routed
             RouteService routeService = entry.getValue().getRouteService();
+            StartupStep step = 
abstractCamelContext.getStartupStepRecorder().beginStep(Route.class, 
routeService.getId(),
+                    "Warming up route");
             try {
                 LOG.debug("Warming up route id: {} having autoStartup={}", 
routeService.getId(), autoStartup);
                 setupRoute.set(routeService.getRoute());
                 routeService.warmUp();
             } finally {
                 setupRoute.remove();
+                abstractCamelContext.getStartupStepRecorder().endStep(step);
             }
         }
     }
@@ -306,6 +310,9 @@ class InternalRouteStartupManager {
                 continue;
             }
 
+            StartupStep step = 
abstractCamelContext.getStartupStepRecorder().beginStep(Route.class, 
route.getRouteId(),
+                    "Starting route");
+
             // start the service
             for (Consumer consumer : routeService.getInputs().values()) {
                 Endpoint endpoint = consumer.getEndpoint();
@@ -404,6 +411,8 @@ class InternalRouteStartupManager {
                     throw e;
                 }
             }
+
+            abstractCamelContext.getStartupStepRecorder().endStep(step);
         }
     }
 
diff --git 
a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java
 
b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java
index b45c5b5..43a721e 100644
--- 
a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java
+++ 
b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/ExtendedCamelContextConfigurer.java
@@ -147,6 +147,8 @@ public class ExtendedCamelContextConfigurer extends 
org.apache.camel.support.com
         case "ShutdownRunningTask": 
target.setShutdownRunningTask(property(camelContext, 
org.apache.camel.ShutdownRunningTask.class, value)); return true;
         case "shutdownstrategy":
         case "ShutdownStrategy": 
target.setShutdownStrategy(property(camelContext, 
org.apache.camel.spi.ShutdownStrategy.class, value)); return true;
+        case "startupsteprecorder":
+        case "StartupStepRecorder": 
target.setStartupStepRecorder(property(camelContext, 
org.apache.camel.spi.StartupStepRecorder.class, value)); return true;
         case "streamcaching":
         case "StreamCaching": target.setStreamCaching(property(camelContext, 
java.lang.Boolean.class, value)); return true;
         case "streamcachingstrategy":
@@ -308,6 +310,8 @@ public class ExtendedCamelContextConfigurer extends 
org.apache.camel.support.com
         case "ShutdownRunningTask": return 
org.apache.camel.ShutdownRunningTask.class;
         case "shutdownstrategy":
         case "ShutdownStrategy": return 
org.apache.camel.spi.ShutdownStrategy.class;
+        case "startupsteprecorder":
+        case "StartupStepRecorder": return 
org.apache.camel.spi.StartupStepRecorder.class;
         case "streamcaching":
         case "StreamCaching": return java.lang.Boolean.class;
         case "streamcachingstrategy":
@@ -470,6 +474,8 @@ public class ExtendedCamelContextConfigurer extends 
org.apache.camel.support.com
         case "ShutdownRunningTask": return target.getShutdownRunningTask();
         case "shutdownstrategy":
         case "ShutdownStrategy": return target.getShutdownStrategy();
+        case "startupsteprecorder":
+        case "StartupStepRecorder": return target.getStartupStepRecorder();
         case "streamcaching":
         case "StreamCaching": return target.isStreamCaching();
         case "streamcachingstrategy":
diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
index d44f0a9..59e8480 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
@@ -124,6 +124,7 @@ import org.apache.camel.spi.RoutePolicyFactory;
 import org.apache.camel.spi.RouteStartupOrder;
 import org.apache.camel.spi.RuntimeEndpointRegistry;
 import org.apache.camel.spi.ShutdownStrategy;
+import org.apache.camel.spi.StartupStepRecorder;
 import org.apache.camel.spi.StreamCachingStrategy;
 import org.apache.camel.spi.Tracer;
 import org.apache.camel.spi.Transformer;
@@ -1537,6 +1538,16 @@ public class LightweightCamelContext implements 
ExtendedCamelContext, CatalogCam
         return getExtendedCamelContext().isLightweight();
     }
 
+    @Override
+    public StartupStepRecorder getStartupStepRecorder() {
+        return getExtendedCamelContext().getStartupStepRecorder();
+    }
+
+    @Override
+    public void setStartupStepRecorder(StartupStepRecorder 
startupStepRecorder) {
+        getExtendedCamelContext().setStartupStepRecorder(startupStepRecorder);
+    }
+
     //
     // CatalogCamelContext
     //
diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
index 6abd435..c99f0c8 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
@@ -121,6 +121,7 @@ import org.apache.camel.spi.RoutePolicyFactory;
 import org.apache.camel.spi.RouteStartupOrder;
 import org.apache.camel.spi.RuntimeEndpointRegistry;
 import org.apache.camel.spi.ShutdownStrategy;
+import org.apache.camel.spi.StartupStepRecorder;
 import org.apache.camel.spi.StreamCachingStrategy;
 import org.apache.camel.spi.SupervisingRouteController;
 import org.apache.camel.spi.Tracer;
@@ -1963,6 +1964,16 @@ public class LightweightRuntimeCamelContext implements 
ExtendedCamelContext, Cat
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public StartupStepRecorder getStartupStepRecorder() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setStartupStepRecorder(StartupStepRecorder 
startupStepRecorder) {
+        throw new UnsupportedOperationException();
+    }
+
     private void startService(Service service) throws Exception {
         // and register startup aware so they can be notified when
         // camel context has been started
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/StartupStepLoggingTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/StartupStepLoggingTest.java
new file mode 100644
index 0000000..291421a
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/StartupStepLoggingTest.java
@@ -0,0 +1,46 @@
+package org.apache.camel.impl;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.ExtendedCamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class StartupStepLoggingTest extends ContextTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = new DefaultCamelContext(false);
+        
context.adapt(ExtendedCamelContext.class).getStartupStepRecorder().setEnabled(true);
+        // you can restrict the sub steps to a max depth level
+        // 
context.adapt(ExtendedCamelContext.class).getStartupStepRecorder().setMaxDepth(1);
+        context.setLoadTypeConverters(true);
+        return context;
+    }
+
+    @Test
+    public void testLog() throws Exception {
+        assertEquals(1, context.getRoutesSize());
+
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("Hello World");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                
from("direct:start").to("log:foo").to("log:bar").to("mock:result");
+            }
+        };
+    }
+
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultStartupStep.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultStartupStep.java
new file mode 100644
index 0000000..c8e9e46
--- /dev/null
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultStartupStep.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.support;
+
+import org.apache.camel.StartupStep;
+
+public class DefaultStartupStep implements StartupStep {
+
+    private final String type;
+    private final String name;
+    private final String description;
+    private final int id;
+    private final int parentId;
+    private final int level;
+    private final long time;
+
+    public DefaultStartupStep(String type, String name, String description, 
int id, int parentId, int level, long time) {
+        this.type = type;
+        this.name = name;
+        this.description = description;
+        this.id = id;
+        this.parentId = parentId;
+        this.level = level;
+        this.time = time;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public int getId() {
+        return id;
+    }
+
+    @Override
+    public int getParentId() {
+        return parentId;
+    }
+
+    @Override
+    public int getLevel() {
+        return level;
+    }
+
+    @Override
+    public long getBeginTime() {
+        return time;
+    }
+
+    @Override
+    public void end() {
+        // noop
+    }
+
+    @Override
+    public void addTag(String key, String value) {
+
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultStartupStepRecorder.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultStartupStepRecorder.java
new file mode 100644
index 0000000..e07e1ce
--- /dev/null
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultStartupStepRecorder.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.support;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.StartupStep;
+import org.apache.camel.spi.StartupStepRecorder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default {@link StartupStepRecorder} that outputs to log.
+ */
+public class DefaultStartupStepRecorder implements StartupStepRecorder {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(StartupStepRecorder.class);
+
+    // TODO: jfr implementation
+    // TODO: spring-boot implementation
+
+    private final AtomicInteger stepCounter = new AtomicInteger();
+    private final Deque<Integer> currentSteps = new ArrayDeque<>();
+
+    private static final StartupStep DISABLED_STEP = new StartupStep() {
+        @Override
+        public String getType() {
+            return null;
+        }
+
+        @Override
+        public String getName() {
+            return null;
+        }
+
+        @Override
+        public String getDescription() {
+            return null;
+        }
+
+        @Override
+        public int getId() {
+            return 0;
+        }
+
+        @Override
+        public int getParentId() {
+            return 0;
+        }
+
+        @Override
+        public int getLevel() {
+            return 0;
+        }
+
+        @Override
+        public void end() {
+            // noop
+        }
+
+        @Override
+        public long getBeginTime() {
+            return 0;
+        }
+
+        @Override
+        public void addTag(String key, String value) {
+            // noop
+        }
+    };
+
+    public DefaultStartupStepRecorder() {
+    }
+
+    private boolean enabled;
+    private boolean disableAfterStarted = true;
+    private int maxDepth = -1;
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    public boolean isDisableAfterStarted() {
+        return disableAfterStarted;
+    }
+
+    public void setDisableAfterStarted(boolean disableAfterStarted) {
+        this.disableAfterStarted = disableAfterStarted;
+    }
+
+    @Override
+    public int getMaxDepth() {
+        return maxDepth;
+    }
+
+    public void setMaxDepth(int maxDepth) {
+        this.maxDepth = maxDepth;
+    }
+
+    @Override
+    public void start() {
+        currentSteps.offerFirst(0);
+    }
+
+    @Override
+    public void stop() {
+        enabled = false;
+        currentSteps.clear();
+    }
+
+    public StartupStep beginStep(Class<?> type, String name, String 
description) {
+        if (enabled) {
+            int level = currentSteps.size() - 1;
+            if (maxDepth != -1 && level >= maxDepth) {
+                return DISABLED_STEP;
+            }
+            int id = stepCounter.incrementAndGet();
+            Integer parent = currentSteps.peekFirst();
+            int pid = parent != null ? parent : 0;
+            StartupStep step = createStartupStep(type.getSimpleName(), name, 
description, id, pid, level);
+            onBeginStep(step);
+            currentSteps.offerFirst(id);
+            return step;
+        } else {
+            return DISABLED_STEP;
+        }
+    }
+
+    public void endStep(StartupStep step) {
+        if (step != DISABLED_STEP) {
+            currentSteps.pollFirst();
+            step.end();
+            onEndStep(step);
+        }
+    }
+
+    public StartupStep createStartupStep(String type, String name, String 
description, int id, int parentId, int level) {
+        return new DefaultStartupStep(type, name, description, id, parentId, 
level, System.currentTimeMillis());
+    }
+
+    protected void onBeginStep(StartupStep step) {
+        // noop
+    }
+
+    protected void onEndStep(StartupStep step) {
+        if (LOG.isInfoEnabled()) {
+            long delta = System.currentTimeMillis() - step.getBeginTime();
+            String pad = padString(step.getLevel());
+            String out = String.format("%s", pad + step.getType());
+            String out2 = String.format("%6s ms", delta);
+            String out3 = String.format("%s(%s)", step.getDescription(), 
step.getName());
+            LOG.info("{} : {} - {}", out2, out, out3);
+        }
+    }
+
+    public static String padString(int level) {
+        if (level == 0) {
+            return "";
+        } else {
+            byte[] arr = new byte[level * 2];
+            byte space = ' ';
+            Arrays.fill(arr, space);
+            return new String(arr);
+        }
+    }
+
+}

Reply via email to