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:26 UTC
[camel] 01/06: CAMEL-16056: Added StartupStep to diagnose startup
exeuction times for various steps. To be integated with JFR later.
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 <cl...@gmail.com>
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);
+ }
+ }
+
+}