/**
* 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.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangLong;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import erlyberly.node.OtpUtil;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;
public class TraceLog implements Comparable<TraceLog> {
private static final OtpErlangAtom FN_ATOM = new OtpErlangAtom("fn");
public static final OtpErlangAtom EXCEPTION_FROM_ATOM = new OtpErlangAtom("exception_from");
public static final OtpErlangAtom RESULT_ATOM = new OtpErlangAtom("result");
public static final OtpErlangAtom ATOM_PID = new OtpErlangAtom("pid");
public static final OtpErlangAtom ATOM_REG_NAME = new OtpErlangAtom("reg_name");
public static final OtpErlangAtom ATOM_UNDEFINED = new OtpErlangAtom("undefined");
private static final Object TIMESTAMP_CALL_ATOM = new OtpErlangAtom("timetamp_call_us");
private static final Object TIMESTAMP_RETURN_ATOM = new OtpErlangAtom("timetamp_return_us");
private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
private final Map<Object, Object> map;
private final long instanceNum;
private final SimpleStringProperty summary = new SimpleStringProperty("");
private final SimpleLongProperty duration = new SimpleLongProperty(0L);
private final SimpleStringProperty result = new SimpleStringProperty("");
private final SimpleBooleanProperty complete = new SimpleBooleanProperty(false);
private String tracePropsToString;
private final String pid, registeredName, function, argsString;
private final String cssClass;
public TraceLog(Map<Object, Object> map) {
this.map = map;
instanceNum = INSTANCE_COUNTER.incrementAndGet();
pid = getPidString();
registeredName = regNameString().intern();
function = appendModFuncArity(new StringBuilder()).toString().intern();
argsString = appendArgsToString(new StringBuilder(), getArgsList().elements()).toString();
cssClass = null;
}
public TraceLog(String aCssClass, String text) {
function = text;
instanceNum = INSTANCE_COUNTER.incrementAndGet();
map = new HashMap<>(0);
cssClass = aCssClass;
pid = "";
registeredName = "";
argsString = "";
}
public long getInstanceNum() {
return instanceNum;
}
private String regNameString() {
Object object = map.get(ATOM_REG_NAME);
if(ATOM_UNDEFINED.equals(object))
return "";
return object.toString();
}
public SimpleStringProperty summaryProperty() {
if(summary.get().isEmpty()) {
summary.set(toString());
}
return summary;
}
@Override
public String toString() {
if(tracePropsToString == null)
tracePropsToString = tracePropsToString();
return tracePropsToString;
}
private String tracePropsToString() {
StringBuilder sb = new StringBuilder(1024);
boolean appendArity = false;
toCallString(sb, appendArity);
sb.append(" => ");
if(map.containsKey(RESULT_ATOM)) {
ErlyBerly.getTermFormatter().appendToString((OtpErlangObject) map.get(RESULT_ATOM), sb);
}
else if(isExceptionThrower()) {
OtpErlangTuple exception = (OtpErlangTuple) map.get(EXCEPTION_FROM_ATOM);
ErlyBerly.getTermFormatter().exceptionToString(
(OtpErlangAtom) exception.elementAt(0), exception.elementAt(1)
);
}
return sb.toString();
}
private StringBuilder toCallString(StringBuilder sb, boolean appendArity) {
OtpErlangAtom regName = (OtpErlangAtom) map.get(ATOM_REG_NAME);
if(regName != null && !ATOM_UNDEFINED.equals(regName)) {
sb.append(regName.atomValue());
}
else {
OtpErlangString pidString = (OtpErlangString) map.get(ATOM_PID);
if(pidString == null)
sb.append("<NULL PID>");
else
sb.append(pidString.stringValue());
}
sb.append(" ");
sb.append("+").append(durationFromMap()).append("us");
appendModFuncArity(sb);
return sb;
}
private long durationFromMap() {
Object tsCall = map.get(TIMESTAMP_CALL_ATOM);
Object tsReturn = map.get(TIMESTAMP_RETURN_ATOM);
if(tsCall == null|| tsReturn == null)
return 0;
return ((OtpErlangLong)tsReturn).longValue() - ((OtpErlangLong)tsCall).longValue();
}
/**
* Returns an MFA.
*/
private OtpErlangTuple getFunctionFromMap() {
return (OtpErlangTuple)map.get(FN_ATOM);
}
public boolean isExceptionThrower() {
return map.containsKey(EXCEPTION_FROM_ATOM);
}
public StringBuilder appendFunctionToString(StringBuilder sb) {
OtpErlangTuple tuple = getFunctionFromMap();
return sb.append(ErlyBerly.getTermFormatter().modFuncArgsToString(tuple));
}
public StringBuilder appendModFuncArity(StringBuilder sb) {
OtpErlangTuple fn = getFunctionFromMap();
// there might be no function if this TraceLog is acting as a NODE DOWN
if(fn == null) {
return sb;
}
return sb.append(ErlyBerly.getTermFormatter().modFuncArityToString(fn));
}
private StringBuilder appendArgsToString(StringBuilder sb, OtpErlangObject[] elements) {
if(elements.length > 0)
ErlyBerly.getTermFormatter().appendToString(elements[0], sb);
for(int i=1; i<elements.length; i++) {
sb.append(", ");
ErlyBerly.getTermFormatter().appendToString(elements[i], sb);
}
return sb;
}
public OtpErlangList getArgsList() {
OtpErlangTuple tuple = getFunctionFromMap();
OtpErlangList args = OtpUtil.toOtpList(tuple.elementAt(2));
return args;
}
public String getPidString() {
OtpErlangString s = (OtpErlangString)map.get(ATOM_PID);
return s.stringValue();
}
public OtpErlangObject getResultFromMap() {
Object object = map.get(RESULT_ATOM);
if(object == null) {
OtpErlangTuple exception = (OtpErlangTuple) map.get(EXCEPTION_FROM_ATOM);
if(exception != null) {
return exception.elementAt(1);
}
}
return (OtpErlangObject) object;
}
@Override
public int compareTo(TraceLog o) {
return Long.compare(instanceNum, o.instanceNum);
}
public void complete(Map<Object, Object> resultMap) {
tracePropsToString = null;
Object e = resultMap.get(EXCEPTION_FROM_ATOM);
Object ts = resultMap.get(TIMESTAMP_RETURN_ATOM);
Object r = resultMap.get(RESULT_ATOM);
if(e != null) {
map.put(EXCEPTION_FROM_ATOM, e);
if(r == null) {
r = e;
}
}
if(r != null) {
map.put(RESULT_ATOM, r);
String resultString = ErlyBerly.getTermFormatter().toString((OtpErlangObject) r);
result.set(resultString);
}
if(ts != null)
map.put(TIMESTAMP_RETURN_ATOM, ts);
duration.set(durationFromMap());
Platform.runLater(() -> { summary.set(toString()); complete.set(true); });
}
public ReadOnlyBooleanProperty isCompleteProperty() {
return complete;
}
public boolean isComplete() {
return complete.get();
}
/**
* A call string is the pid and function with arity.
*/
public String toCallString() {
StringBuilder sb = new StringBuilder(255);
boolean appendArity = true;
return toCallString(sb, appendArity).toString();
}
public String getPid() {
return pid;
}
public String getRegName() {
return registeredName;
}
public long getDuration() {
return duration.get();
}
public SimpleLongProperty durationProperty() {
return duration;
}
public String getFunction() {
return function;
}
public String getArgs() {
return argsString;
}
public String getResult() {
return result.get();
}
public SimpleStringProperty resultProperty() {
return result;
}
public String getCssClass() {
return cssClass;
}
public static TraceLog newBreakLog() {
return new TraceLog("breaker-row", "BREAK");
}
public static TraceLog newNodeDown() {
return new TraceLog("breaker-row", "NODE DOWN");
}
public static TraceLog newLoadShedding() {
return new TraceLog("breaker-row", "LOAD SHEDDING");
}
public OtpErlangList getStackTrace() {
OtpErlangList stacktrace = (OtpErlangList) map.get(OtpUtil.atom("stack_trace"));
if(stacktrace == null)
stacktrace = new OtpErlangList();
return stacktrace;
}
public ModFunc getModFunc() {
OtpErlangTuple mfa = getFunctionFromMap();
OtpErlangAtom module = (OtpErlangAtom) mfa.elementAt(0);
OtpErlangAtom function = (OtpErlangAtom) mfa.elementAt(1);
int arity = ((OtpErlangList) mfa.elementAt(2)).arity();
return new ModFunc(module.atomValue(), function.atomValue(), arity, false, false);
}
}