Skip to content

Commit

Permalink
[jsscripting] Improve performance & reduce memory usage (openhab#14113)
Browse files Browse the repository at this point in the history
* [jsscripting] Share org.graalvm.polyglot.Engine across all OpenhabGraalJSScriptEngine instances

See oracle/graaljs#121 (comment), it is not required to have one engine per GraalJSScriptEngine.

This might improve performance a bit on less powerful systems (Raspberry Pi) and decreases heap usage:
With 5 GraalJS UI scripts, heap usage is now below 100 MB. Before this change, it was over 100 MB.

* [jsscripting] Extend debug logging
* [jsscripting] Cache `@jsscripting-globals.js` across all engines

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
  • Loading branch information
florian-h05 authored and borazslo committed Jan 8, 2023
1 parent e1d846d commit 872c726
Showing 1 changed file with 38 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.FileSystems;
Expand All @@ -42,6 +44,7 @@
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.openhab.automation.jsscripting.internal.fs.DelegatingFileSystem;
import org.openhab.automation.jsscripting.internal.fs.PrefixedSeekableByteChannel;
Expand All @@ -67,10 +70,23 @@ public class OpenhabGraalJSScriptEngine
extends InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable<GraalJSScriptEngine> {

private static final Logger LOGGER = LoggerFactory.getLogger(OpenhabGraalJSScriptEngine.class);
private static final String GLOBAL_REQUIRE = "require(\"@jsscripting-globals\");";
private static Source GLOBAL_SOURCE;

static {
try {
GLOBAL_SOURCE = Source.newBuilder("js", getFileAsReader("node_modules/@jsscripting-globals.js"),
"@jsscripting-globals.js").cached(true).build();
} catch (IOException e) {
LOGGER.error("Failed to load global script", e);
}
}

private static final String REQUIRE_WRAPPER_NAME = "__wraprequire__";
/** Final CommonJS search path for our library */
private static final Path NODE_DIR = Paths.get("node_modules");
/** Shared Polyglot {@link Engine} across all instances of {@link OpenhabGraalJSScriptEngine} */
private static final Engine ENGINE = Engine.newBuilder().allowExperimentalOptions(true)
.option("engine.WarnInterpreterOnly", "false").build();
/** Provides unlimited host access as well as custom translations from JS to Java Objects */
private static final HostAccess HOST_ACCESS = HostAccess.newBuilder(HostAccess.ALL)
// Translate JS-Joda ZonedDateTime to java.time.ZonedDateTime
Expand All @@ -95,22 +111,20 @@ public class OpenhabGraalJSScriptEngine
private @Nullable Consumer<String> scriptDependencyListener;

private boolean initialized = false;
private final String globalScript;
private final String injectionCode;

/**
* Creates an implementation of ScriptEngine (& Invocable), wrapping the contained engine, that tracks the script
* lifecycle and provides hooks for scripts to do so too.
*/
public OpenhabGraalJSScriptEngine(@Nullable String injectionCode, JSScriptServiceUtil jsScriptServiceUtil) {
super(null); // delegate depends on fields not yet initialised, so we cannot set it immediately
this.globalScript = GLOBAL_REQUIRE + (injectionCode != null ? injectionCode : "");
this.injectionCode = (injectionCode != null ? injectionCode : "");
this.jsRuntimeFeatures = jsScriptServiceUtil.getJSRuntimeFeatures(lock);

LOGGER.debug("Initializing GraalJS script engine...");

delegate = GraalJSScriptEngine.create(
Engine.newBuilder().allowExperimentalOptions(true).option("engine.WarnInterpreterOnly", "false")
.build(),
delegate = GraalJSScriptEngine.create(ENGINE,
Context.newBuilder("js").allowExperimentalOptions(true).allowAllAccess(true)
.allowHostAccess(HOST_ACCESS).option("js.commonjs-require-cwd", JSDependencyTracker.LIB_PATH)
.option("js.nashorn-compat", "true") // Enable Nashorn compat mode as openhab-js relies on
Expand Down Expand Up @@ -230,7 +244,10 @@ protected void beforeInvocation() {
initialized = true;

try {
eval(globalScript);
LOGGER.debug("Evaluating global script...");
delegate.getPolyglotContext().eval(GLOBAL_SOURCE);
eval(injectionCode);
LOGGER.debug("Successfully initialized GraalJS script engine.");
} catch (ScriptException e) {
LOGGER.error("Could not inject global script", e);
}
Expand Down Expand Up @@ -273,4 +290,18 @@ private boolean isRootNodePath(Path path) {
private String nodeFileToResource(Path path) {
return "/" + path.subpath(0, path.getNameCount()).toString().replace('\\', '/');
}

/**
* @param fileName filename relative to the resources folder
* @return file as {@link InputStreamReader}
*/
private static Reader getFileAsReader(String fileName) {
InputStream ioStream = OpenhabGraalJSScriptEngine.class.getClassLoader().getResourceAsStream(fileName);

if (ioStream == null) {
throw new IllegalArgumentException(fileName + " not found");
}

return new InputStreamReader(ioStream);
}
}

0 comments on commit 872c726

Please sign in to comment.