mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[jsscripting] Improve performance & reduce memory usage (#14113)
* [jsscripting] Share org.graalvm.polyglot.Engine across all OpenhabGraalJSScriptEngine instances See https://github.com/oracle/graaljs/issues/121#issuecomment-880056648, 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>
This commit is contained in:
parent
931f004ab8
commit
23cfec7684
@ -16,6 +16,8 @@ import static org.openhab.core.automation.module.script.ScriptEngineFactory.*;
|
||||
|
||||
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;
|
||||
@ -42,6 +44,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
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;
|
||||
@ -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
|
||||
@ -95,7 +111,7 @@ 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
|
||||
@ -103,14 +119,12 @@ public class OpenhabGraalJSScriptEngine
|
||||
*/
|
||||
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
|
||||
@ -230,7 +244,10 @@ public class OpenhabGraalJSScriptEngine
|
||||
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);
|
||||
}
|
||||
@ -273,4 +290,18 @@ public class OpenhabGraalJSScriptEngine
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user