/** * erlyberly, erlang trace debugger * Copyright (C) 2016 Andy Till * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package erlyberly; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import com.ericsson.otp.erlang.OtpErlangException; import com.ericsson.otp.erlang.OtpErlangList; import com.ericsson.otp.erlang.OtpErlangTuple; import erlyberly.node.NodeAPI; import erlyberly.node.NodeAPI.RpcCallback; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.Initializable; public class DbgController implements Initializable { /** * The maximum trace logs that erlyberly can show in the table before it must * load shed them e.g. start throwing them away so that the UI does not attempt * to take an unlimited amount of memory, and fail! * * This is only checked at start up, so if it is changed it requires a restart. */ private static final int MAX_TRACE_LOGS; static { Number maxTraceLogs = (Number) PrefBind.getOrDefault("maxTraceLogs", 1000); MAX_TRACE_LOGS = maxTraceLogs.intValue(); } private final ObservableList<TraceLog> traceLogs = FXCollections.observableArrayList(); private final ObservableList<ModFunc> traces = FXCollections.observableArrayList(); private final ObservableList<SeqTraceLog> seqTraces = FXCollections.observableArrayList(); private volatile boolean collectingSeqTraces; private TraceLog loadSheddingLog; @Override public void initialize(URL url, ResourceBundle r) { ErlyBerly.nodeAPI().setTraceLogCallback((traceLog) -> { if(traceLogs.size() > MAX_TRACE_LOGS) { if(loadSheddingLog == null) { loadSheddingLog = TraceLog.newLoadShedding(); traceLogs.add(loadSheddingLog); } return; } if(loadSheddingLog != null) { traceLogs.remove(loadSheddingLog); loadSheddingLog = null; } traceLogs.add(traceLog); }); ErlyBerly.nodeAPI().suspendedProperty().addListener((o, oldv, suspended) -> { // an un-suspended is similar to a node coming back online, reapply our known traces if(!suspended && ErlyBerly.nodeAPI().isConnected()) { reapplyTraces(); } }); ErlyBerly.nodeAPI().connectedProperty().addListener( (o, oldV, newV) -> { if(oldV && !newV) { traceLogs.add(TraceLog.newNodeDown()); } }); new SeqTraceCollectorThread((seqs) -> { seqTraces.addAll(seqs); }).start(); } public ObservableList<TraceLog> getTraceLogs() { return traceLogs; } public ObservableList<SeqTraceLog> getSeqTraceLogs() { return seqTraces; } public boolean isTraced(ModFunc function) { return traces.contains(function); } public void toggleTraceModFunc(ModFunc function) { if(traces.contains(function)) onRemoveTracer(null, function); else traceModFunc(function); } private void traceModFunc(ModFunc function) { try { ErlyBerly.nodeAPI().startTrace(function, getMaxTraceQueueLengthConfig()); traces.add(function); } catch (Exception ex) { ex.printStackTrace(); } } private int getMaxTraceQueueLengthConfig() { Number maxTraceQueueLength = (Number) PrefBind.getOrDefault("maxTraceQueueLength", 1000); return maxTraceQueueLength.intValue(); } private void onRemoveTracer(ActionEvent e, ModFunc function) { try { ErlyBerly.nodeAPI().stopTrace(function); traces.remove(function); } catch (Exception ex) { ex.printStackTrace(); } } public void reapplyTraces() { ArrayList<ModFunc> tracesCopy = new ArrayList<ModFunc>(traces); for (ModFunc function : tracesCopy) { try { ErlyBerly.nodeAPI().startTrace(function, getMaxTraceQueueLengthConfig()); } catch (Exception e) { e.printStackTrace(); traces.remove(function); } } } public void addTraceListener(InvalidationListener listener) { traces.addListener(listener); } public void requestModFuncs(NodeAPI.RpcCallback<OtpErlangList> rpcCallback) { new GetModulesThread(rpcCallback).start(); } public void seqTrace(ModFunc value) { try { ErlyBerly.nodeAPI().seqTrace(value); collectingSeqTraces = true; } catch (OtpErlangException | IOException e) { e.printStackTrace(); } } class SeqTraceCollectorThread extends Thread { private final RpcCallback<SeqTraceLog> callback; public SeqTraceCollectorThread(RpcCallback<SeqTraceLog> aCallback) { callback = aCallback; setDaemon(true); setName("Erlyberly Collect Seq Traces"); } @Override public void run() { while (true) { if(collectingSeqTraces && ErlyBerly.nodeAPI().isConnected()) { try { final List<SeqTraceLog> seqTraceLogs = ErlyBerly.nodeAPI().collectSeqTraceLogs(); for (SeqTraceLog seqTraceLog : seqTraceLogs) { Platform.runLater(() -> { callback.callback(seqTraceLog); }); } } catch (Exception e) { e.printStackTrace(); } } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class GetModulesThread extends Thread { private final RpcCallback<OtpErlangList> rpcCallback; public GetModulesThread(RpcCallback<OtpErlangList> anRpcCallback) { rpcCallback = anRpcCallback; setDaemon(true); setName("Erlyberly Get Modules"); } @Override public void run() { try { OtpErlangList functions = ErlyBerly.nodeAPI().requestFunctions(); Platform.runLater(() -> { rpcCallback.callback(functions); }); } catch (Exception e) { e.printStackTrace(); } } } public String moduleFunctionSourceCode(String module, String function, Integer arity) throws IOException, OtpErlangException { String moduleCode = ErlyBerly.nodeAPI().moduleFunctionSourceCode(module, function, arity); return moduleCode; } public String moduleFunctionSourceCode(String module) throws IOException, OtpErlangException { String moduleCode = ErlyBerly.nodeAPI().moduleFunctionSourceCode(module); return moduleCode; } public String moduleFunctionAbstCode(String module) throws IOException, OtpErlangException { String moduleCode = ErlyBerly.nodeAPI().moduleFunctionAbstCode(module); return moduleCode; } public String moduleFunctionAbstCode(String module, String function, Integer arity) throws IOException, OtpErlangException { String moduleCode = ErlyBerly.nodeAPI().moduleFunctionAbstCode(module, function, arity); return moduleCode; } public void setModuleLoadedCallback(RpcCallback<OtpErlangTuple> moduleLoadedCallback) { ErlyBerly.nodeAPI().setModuleLoadedCallback(moduleLoadedCallback); } }