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