From 28e2c816e8b3f10b3cb5f3ece4f5b586604f21ea Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Mon, 24 Sep 2018 09:53:10 +0200 Subject: [PATCH 1/2] Watcher: Reduce script cache churn by checking for mustache tags Watcher is using a lot of so called TextTemplate fields in a watch definition, which can use mustache to insert the watch id for example. For the user it is non-obvious which field is just a string field or which field is a text template. This also means, that for every such field, we currently do a script compilation, even if the field does not contain any mustache syntax. This will lead to an increased script cache churn, because those compiled scripts (that only contain a string), will evict other scripts. On top of that, this also means that an unneeded compilation has happened, instead of returning that string immediately. The usages of mustache templating are in all of the actions (most of the time far more than one compilation) as well as most of the inputs. Especially when running a lot of watches in parallel, this will reduce execution times and help reuse of real scripts. --- .../watcher/common/text/TextTemplateEngine.java | 6 ++++++ .../watcher/common/text/TextTemplateTests.java | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java index 7b87a9e87a542..117b08c9fd614 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java @@ -35,6 +35,12 @@ public String render(TextTemplate textTemplate, Map model) { String mediaType = compileParams(detectContentType(template)); template = trimContentType(textTemplate); + int indexStartMustacheExpression = template.indexOf("{{"); + int indexEndMustacheExpression = template.indexOf("}}"); + if (indexStartMustacheExpression == -1 || indexStartMustacheExpression > indexEndMustacheExpression) { + return template; + } + Map mergedModel = new HashMap<>(); if (textTemplate.getParams() != null) { mergedModel.putAll(textTemplate.getParams()); diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java index 0e084af23e1fb..d2abfedeee03e 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java @@ -32,6 +32,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class TextTemplateTests extends ESTestCase { @@ -47,7 +48,7 @@ public void init() throws Exception { } public void testRender() throws Exception { - String templateText = "_template"; + String templateText = "{{_template}}"; Map params = singletonMap("param_key", "param_val"); Map model = singletonMap("model_key", "model_val"); Map merged = new HashMap<>(params); @@ -72,7 +73,7 @@ public String execute() { } public void testRenderOverridingModel() throws Exception { - String templateText = "_template"; + String templateText = "{{_template}}"; Map params = singletonMap("key", "param_val"); Map model = singletonMap("key", "model_val"); ScriptType type = randomFrom(ScriptType.values()); @@ -94,7 +95,7 @@ public String execute() { } public void testRenderDefaults() throws Exception { - String templateText = "_template"; + String templateText = "{{_template}}"; Map model = singletonMap("key", "model_val"); TemplateScript.Factory compiledTemplate = templateParams -> @@ -113,6 +114,13 @@ public String execute() { assertThat(engine.render(template, model), is("rendered_text")); } + public void testDontInvokeScriptServiceOnNonMustacheText() { + String input = rarely() ? "}}{{" : "this is my text"; + String output = engine.render(new TextTemplate(input), Collections.emptyMap()); + assertThat(input, is(output)); + verifyZeroInteractions(service); + } + public void testParser() throws Exception { ScriptType type = randomScriptType(); TextTemplate template = From 76424f4609be773c8fc60469009baa33b31fddfc Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Wed, 26 Sep 2018 14:22:52 +0200 Subject: [PATCH 2/2] review comment: remove check for closed brackets, could be wrong --- .../common/text/TextTemplateEngine.java | 3 +- .../common/text/TextTemplateTests.java | 31 ++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java index 117b08c9fd614..d2159fd572f5d 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateEngine.java @@ -36,8 +36,7 @@ public String render(TextTemplate textTemplate, Map model) { template = trimContentType(textTemplate); int indexStartMustacheExpression = template.indexOf("{{"); - int indexEndMustacheExpression = template.indexOf("}}"); - if (indexStartMustacheExpression == -1 || indexStartMustacheExpression > indexEndMustacheExpression) { + if (indexStartMustacheExpression == -1) { return template; } diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java index d2abfedeee03e..002d833c20913 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/common/text/TextTemplateTests.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import static java.util.Collections.singletonMap; @@ -31,7 +32,9 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -115,12 +118,38 @@ public String execute() { } public void testDontInvokeScriptServiceOnNonMustacheText() { - String input = rarely() ? "}}{{" : "this is my text"; + assertNoCompilation("this is my text"); + assertScriptServiceInvoked("}}{{"); + assertScriptServiceInvoked("}}{{ctx.payload}}"); + } + + private void assertNoCompilation(String input) { String output = engine.render(new TextTemplate(input), Collections.emptyMap()); assertThat(input, is(output)); verifyZeroInteractions(service); } + private void assertScriptServiceInvoked(final String input) { + ScriptService scriptService = mock(ScriptService.class); + TextTemplateEngine e = new TextTemplateEngine(Settings.EMPTY, scriptService); + + TemplateScript.Factory compiledTemplate = templateParams -> + new TemplateScript(templateParams) { + @Override + public String execute() { + return input.toUpperCase(Locale.ROOT); + } + }; + + when(scriptService.compile(new Script(ScriptType.INLINE, lang, input, + Collections.singletonMap("content_type", "text/plain"), Collections.emptyMap()), Watcher.SCRIPT_TEMPLATE_CONTEXT)) + .thenReturn(compiledTemplate); + + String output = e.render(new TextTemplate(input), Collections.emptyMap()); + verify(scriptService).compile(any(), any()); + assertThat(output, is(input.toUpperCase(Locale.ROOT))); + } + public void testParser() throws Exception { ScriptType type = randomScriptType(); TextTemplate template =