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); + } + } + +}
