JS Stats for Nashorn JS Executor
This commit is contained in:
parent
f23cfc9880
commit
cfe7e42602
@ -15,21 +15,33 @@
|
||||
*/
|
||||
package org.thingsboard.server.service.script;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import delight.nashornsandbox.NashornSandbox;
|
||||
import delight.nashornsandbox.NashornSandboxes;
|
||||
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService {
|
||||
@ -37,9 +49,46 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
|
||||
private NashornSandbox sandbox;
|
||||
private ScriptEngine engine;
|
||||
private ExecutorService monitorExecutorService;
|
||||
private ScheduledExecutorService timeoutExecutorService;
|
||||
|
||||
private final AtomicInteger jsPushedMsgs = new AtomicInteger(0);
|
||||
private final AtomicInteger jsInvokeMsgs = new AtomicInteger(0);
|
||||
private final AtomicInteger jsEvalMsgs = new AtomicInteger(0);
|
||||
private final AtomicInteger jsFailedMsgs = new AtomicInteger(0);
|
||||
private final AtomicInteger jsTimeoutMsgs = new AtomicInteger(0);
|
||||
private final FutureCallback<UUID> evalCallback = new JsStatCallback<UUID>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs);
|
||||
private final FutureCallback<Object> invokeCallback = new JsStatCallback<Object>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs);
|
||||
|
||||
@Autowired
|
||||
@Getter
|
||||
private JsExecutorService jsExecutor;
|
||||
|
||||
@Value("${js.local.max_requests_timeout:0}")
|
||||
private long maxRequestsTimeout;
|
||||
|
||||
@Value("${js.local.stats.enabled:false}")
|
||||
private boolean statsEnabled;
|
||||
|
||||
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms:10000}")
|
||||
public void printStats() {
|
||||
if (statsEnabled) {
|
||||
int pushedMsgs = jsPushedMsgs.getAndSet(0);
|
||||
int invokeMsgs = jsInvokeMsgs.getAndSet(0);
|
||||
int evalMsgs = jsEvalMsgs.getAndSet(0);
|
||||
int failed = jsFailedMsgs.getAndSet(0);
|
||||
int timedOut = jsTimeoutMsgs.getAndSet(0);
|
||||
if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) {
|
||||
log.info("Nashorn JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
|
||||
pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (maxRequestsTimeout > 0) {
|
||||
timeoutExecutorService = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
if (useJsSandbox()) {
|
||||
sandbox = NashornSandboxes.create();
|
||||
monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize());
|
||||
@ -59,6 +108,9 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
|
||||
if (monitorExecutorService != null) {
|
||||
monitorExecutorService.shutdownNow();
|
||||
}
|
||||
if (timeoutExecutorService != null) {
|
||||
timeoutExecutorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean useJsSandbox();
|
||||
@ -69,34 +121,49 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) {
|
||||
try {
|
||||
if (useJsSandbox()) {
|
||||
sandbox.eval(jsScript);
|
||||
} else {
|
||||
engine.eval(jsScript);
|
||||
jsPushedMsgs.incrementAndGet();
|
||||
ListenableFuture<UUID> result = jsExecutor.executeAsync(() -> {
|
||||
try {
|
||||
if (useJsSandbox()) {
|
||||
sandbox.eval(jsScript);
|
||||
} else {
|
||||
engine.eval(jsScript);
|
||||
}
|
||||
scriptIdToNameMap.put(scriptId, functionName);
|
||||
return scriptId;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
|
||||
throw new ExecutionException(e);
|
||||
}
|
||||
scriptIdToNameMap.put(scriptId, functionName);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
|
||||
return Futures.immediateFailedFuture(e);
|
||||
});
|
||||
if (maxRequestsTimeout > 0) {
|
||||
result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
|
||||
}
|
||||
return Futures.immediateFuture(scriptId);
|
||||
Futures.addCallback(result, evalCallback);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
|
||||
try {
|
||||
Object result;
|
||||
if (useJsSandbox()) {
|
||||
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
|
||||
} else {
|
||||
result = ((Invocable) engine).invokeFunction(functionName, args);
|
||||
jsPushedMsgs.incrementAndGet();
|
||||
ListenableFuture<Object> result = jsExecutor.executeAsync(() -> {
|
||||
try {
|
||||
if (useJsSandbox()) {
|
||||
return sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
|
||||
} else {
|
||||
return ((Invocable) engine).invokeFunction(functionName, args);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
onScriptExecutionError(scriptId);
|
||||
throw new ExecutionException(e);
|
||||
}
|
||||
return Futures.immediateFuture(result);
|
||||
} catch (Exception e) {
|
||||
onScriptExecutionError(scriptId);
|
||||
return Futures.immediateFailedFuture(e);
|
||||
});
|
||||
|
||||
if (maxRequestsTimeout > 0) {
|
||||
result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
|
||||
}
|
||||
Futures.addCallback(result, invokeCallback);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void doRelease(UUID scriptId, String functionName) throws ScriptException {
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
package org.thingsboard.server.service.script;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class JsStatCallback<T> implements FutureCallback<T> {
|
||||
|
||||
private final AtomicInteger jsSuccessMsgs;
|
||||
private final AtomicInteger jsTimeoutMsgs;
|
||||
private final AtomicInteger jsFailedMsgs;
|
||||
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable T result) {
|
||||
jsSuccessMsgs.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
|
||||
jsTimeoutMsgs.incrementAndGet();
|
||||
} else {
|
||||
jsFailedMsgs.incrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -464,6 +464,11 @@ js:
|
||||
max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:3000}"
|
||||
# Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
|
||||
max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}"
|
||||
# JS Eval max request timeout. 0 - no timeout
|
||||
max_requests_timeout: "${LOCAL_JS_MAX_REQUEST_TIMEOUT:0}"
|
||||
stats:
|
||||
enabled: "${TB_JS_LOCAL_STATS_ENABLED:false}"
|
||||
print_interval_ms: "${TB_JS_LOCAL_STATS_PRINT_INTERVAL_MS:10000}"
|
||||
# Remote JavaScript environment properties
|
||||
remote:
|
||||
# JS Eval request topic
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user