Repository: camel Updated Branches: refs/heads/master 99166b8e7 -> 52cede016
CAMEL-11484: Optimise - Simple Language / ExpressionBuilder can use cache of frequent used expressions when having nested functions Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/b8f7e427 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/b8f7e427 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/b8f7e427 Branch: refs/heads/master Commit: b8f7e4276754920bb60d118742fcfee7b12479b9 Parents: 99166b8 Author: Claus Ibsen <davscl...@apache.org> Authored: Fri Jun 30 08:59:01 2017 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Fri Jun 30 08:59:09 2017 +0200 ---------------------------------------------------------------------- .../main/java/org/apache/camel/Exchange.java | 1 + .../camel/language/simple/SimpleLanguage.java | 68 ++++++++--- .../apache/camel/util/CamelContextHelper.java | 31 +++++ .../SimpleLanguageTransformRandomTest.java | 44 ++++++++ .../itest/jmh/SimpleCachedExpressionTest.java | 112 ------------------- .../camel/itest/jmh/SimpleExpressionTest.java | 6 +- 6 files changed, 133 insertions(+), 129 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/b8f7e427/camel-core/src/main/java/org/apache/camel/Exchange.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/Exchange.java b/camel-core/src/main/java/org/apache/camel/Exchange.java index b4e1992..bfe9251 100644 --- a/camel-core/src/main/java/org/apache/camel/Exchange.java +++ b/camel-core/src/main/java/org/apache/camel/Exchange.java @@ -171,6 +171,7 @@ public interface Exchange { String MAXIMUM_CACHE_POOL_SIZE = "CamelMaximumCachePoolSize"; String MAXIMUM_ENDPOINT_CACHE_SIZE = "CamelMaximumEndpointCacheSize"; + String MAXIMUM_SIMPLE_CACHE_SIZE = "CamelMaximumSimpleCacheSize"; String MAXIMUM_TRANSFORMER_CACHE_SIZE = "CamelMaximumTransformerCacheSize"; String MAXIMUM_VALIDATOR_CACHE_SIZE = "CamelMaximumValidatorCacheSize"; String MESSAGE_HISTORY = "CamelMessageHistory"; http://git-wip-us.apache.org/repos/asf/camel/blob/b8f7e427/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java b/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java index 20878c9..ab0aa36 100644 --- a/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java +++ b/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java @@ -16,10 +16,14 @@ */ package org.apache.camel.language.simple; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; import org.apache.camel.Expression; import org.apache.camel.Predicate; import org.apache.camel.builder.ExpressionBuilder; import org.apache.camel.support.LanguageSupport; +import org.apache.camel.util.CamelContextHelper; +import org.apache.camel.util.LRUCache; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.PredicateToExpressionAdapter; @@ -93,6 +97,10 @@ public class SimpleLanguage extends LanguageSupport { // singleton for expressions without a result type private static final SimpleLanguage SIMPLE = new SimpleLanguage(); + // use caches to avoid re-parsing the same expressions over and over again + private LRUCache<String, Expression> cacheExpression; + private LRUCache<String, Predicate> cachePredicate; + protected boolean allowEscape = true; /** @@ -101,35 +109,63 @@ public class SimpleLanguage extends LanguageSupport { public SimpleLanguage() { } + @Override + public void setCamelContext(CamelContext camelContext) { + super.setCamelContext(camelContext); + + // setup cache which requires CamelContext to be set first + if (cacheExpression == null && cachePredicate == null && camelContext != null) { + int maxSize = CamelContextHelper.getMaximumSimpleCacheSize(camelContext); + cacheExpression = new LRUCache<>(16, maxSize, false); + cachePredicate = new LRUCache<>(16, maxSize, false); + } + } + + @SuppressWarnings("deprecation") public Predicate createPredicate(String expression) { ObjectHelper.notNull(expression, "expression"); - expression = loadResource(expression); - - // support old simple language syntax - @SuppressWarnings("deprecation") - Predicate answer = SimpleBackwardsCompatibleParser.parsePredicate(expression, allowEscape); + Predicate answer = cachePredicate != null ? cachePredicate.get(expression) : null; if (answer == null) { - // use the new parser - SimplePredicateParser parser = new SimplePredicateParser(expression, allowEscape); - answer = parser.parsePredicate(); + + expression = loadResource(expression); + + // support old simple language syntax + answer = SimpleBackwardsCompatibleParser.parsePredicate(expression, allowEscape); + if (answer == null) { + // use the new parser + SimplePredicateParser parser = new SimplePredicateParser(expression, allowEscape); + answer = parser.parsePredicate(); + } + if (cachePredicate != null) { + cachePredicate.put(expression, answer); + } } + return answer; } + @SuppressWarnings("deprecation") public Expression createExpression(String expression) { ObjectHelper.notNull(expression, "expression"); - expression = loadResource(expression); - - // support old simple language syntax - @SuppressWarnings("deprecation") - Expression answer = SimpleBackwardsCompatibleParser.parseExpression(expression, allowEscape); + Expression answer = cacheExpression != null ? cacheExpression.get(expression) : null; if (answer == null) { - // use the new parser - SimpleExpressionParser parser = new SimpleExpressionParser(expression, allowEscape); - answer = parser.parseExpression(); + + expression = loadResource(expression); + + // support old simple language syntax + answer = SimpleBackwardsCompatibleParser.parseExpression(expression, allowEscape); + if (answer == null) { + // use the new parser + SimpleExpressionParser parser = new SimpleExpressionParser(expression, allowEscape); + answer = parser.parseExpression(); + } + if (cacheExpression != null) { + cacheExpression.put(expression, answer); + } } + return answer; } http://git-wip-us.apache.org/repos/asf/camel/blob/b8f7e427/camel-core/src/main/java/org/apache/camel/util/CamelContextHelper.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/CamelContextHelper.java b/camel-core/src/main/java/org/apache/camel/util/CamelContextHelper.java index d126005..1ee219c 100644 --- a/camel-core/src/main/java/org/apache/camel/util/CamelContextHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/CamelContextHelper.java @@ -268,6 +268,37 @@ public final class CamelContextHelper { } /** + * Gets the maximum simple cache size. + * <p/> + * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_SIMPLE_CACHE_SIZE}. + * If no property has been set, then it will fallback to return a size of 1000. + * + * @param camelContext the camel context + * @return the maximum cache size + * @throws IllegalArgumentException is thrown if the property is illegal + */ + public static int getMaximumSimpleCacheSize(CamelContext camelContext) throws IllegalArgumentException { + if (camelContext != null) { + String s = camelContext.getGlobalOption(Exchange.MAXIMUM_SIMPLE_CACHE_SIZE); + if (s != null) { + // we cannot use Camel type converters as they may not be ready this early + try { + Integer size = Integer.valueOf(s); + if (size == null || size <= 0) { + throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_SIMPLE_CACHE_SIZE + " must be a positive number, was: " + s); + } + return size; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_SIMPLE_CACHE_SIZE + " must be a positive number, was: " + s, e); + } + } + } + + // 1000 is the default fallback + return 1000; + } + + /** * Gets the maximum transformer cache size. * <p/> * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_TRANSFORMER_CACHE_SIZE}. http://git-wip-us.apache.org/repos/asf/camel/blob/b8f7e427/camel-core/src/test/java/org/apache/camel/language/SimpleLanguageTransformRandomTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/language/SimpleLanguageTransformRandomTest.java b/camel-core/src/test/java/org/apache/camel/language/SimpleLanguageTransformRandomTest.java new file mode 100644 index 0000000..6280f4c --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/language/SimpleLanguageTransformRandomTest.java @@ -0,0 +1,44 @@ +/** + * 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.language; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; + +/** + * Unit test routing with simple language using random function which will reuse cached expressions. + */ +public class SimpleLanguageTransformRandomTest extends ContextTestSupport { + + public void testSimpleTransform() throws Exception { + int out = template.requestBodyAndHeader("direct:start", "Random number", "max", "5", int.class); + assertTrue(out <= 5); + + out = template.requestBodyAndHeader("direct:start", "Random number", "max", "20", int.class); + assertTrue(out <= 20); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + from("direct:start") + .transform().simple("${random(1,${header.max})}"); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b8f7e427/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleCachedExpressionTest.java ---------------------------------------------------------------------- diff --git a/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleCachedExpressionTest.java b/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleCachedExpressionTest.java deleted file mode 100644 index 0532dde..0000000 --- a/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleCachedExpressionTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * 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.itest.jmh; - -import java.util.concurrent.TimeUnit; - -import org.apache.camel.CamelContext; -import org.apache.camel.Exchange; -import org.apache.camel.Expression; -import org.apache.camel.builder.ExpressionBuilder; -import org.apache.camel.impl.DefaultCamelContext; -import org.apache.camel.impl.DefaultExchange; -import org.junit.Test; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Level; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; -import org.openjdk.jmh.runner.options.TimeValue; - -/** - * Tests a Simple expression - */ -public class SimpleCachedExpressionTest { - - @Test - public void launchBenchmark() throws Exception { - Options opt = new OptionsBuilder() - // Specify which benchmarks to run. - // You can be more specific if you'd like to run only one benchmark per test. - .include(this.getClass().getName() + ".*") - // Set the following options as needed - .mode(Mode.All) - .timeUnit(TimeUnit.MICROSECONDS) - .warmupTime(TimeValue.seconds(1)) - .warmupIterations(2) - .measurementTime(TimeValue.seconds(1)) - .measurementIterations(2) - .threads(2) - .forks(1) - .shouldFailOnError(true) - .shouldDoGC(true) - .build(); - - new Runner(opt).run(); - } - - // The JMH samples are the best documentation for how to use it - // http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/ - @State(Scope.Thread) - public static class BenchmarkState { - CamelContext camel; - String expression = "Hello ${body}"; - Exchange exchange; - Expression cached; - - @Setup(Level.Trial) - public void initialize() { - camel = new DefaultCamelContext(); - try { - camel.start(); - exchange = new DefaultExchange(camel); - exchange.getIn().setBody("World"); - cached = ExpressionBuilder.simpleExpression(expression); - } catch (Exception e) { - // ignore - } - } - - @TearDown(Level.Trial) - public void close() { - try { - camel.stop(); - } catch (Exception e) { - // ignore - } - } - - } - - @Benchmark - @Measurement(batchSize = 1000) - public void simpleCachedExpression(BenchmarkState state, Blackhole bh) { - String out = state.cached.evaluate(state.exchange, String.class); - if (!out.equals("Hello World")) { - throw new IllegalArgumentException("Evaluation failed"); - } - bh.consume(out); - } - -} http://git-wip-us.apache.org/repos/asf/camel/blob/b8f7e427/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleExpressionTest.java ---------------------------------------------------------------------- diff --git a/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleExpressionTest.java b/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleExpressionTest.java index 505504f..1c45768 100644 --- a/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleExpressionTest.java +++ b/tests/camel-jmh/src/test/java/org/apache/camel/itest/jmh/SimpleExpressionTest.java @@ -55,7 +55,7 @@ public class SimpleExpressionTest { .timeUnit(TimeUnit.MICROSECONDS) .warmupTime(TimeValue.seconds(1)) .warmupIterations(2) - .measurementTime(TimeValue.seconds(1)) + .measurementTime(TimeValue.seconds(10)) .measurementIterations(2) .threads(2) .forks(1) @@ -81,6 +81,10 @@ public class SimpleExpressionTest { camel.start(); exchange = new DefaultExchange(camel); exchange.getIn().setBody("World"); + + // warm up as we use cache on simple expression + ExpressionBuilder.simpleExpression(expression); + } catch (Exception e) { // ignore }