package org.erlide.runtime.rpc; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import org.erlide.util.ErlLogger; import org.erlide.util.erlang.OtpErlang; import com.ericsson.otp.erlang.OtpErlangAtom; import com.ericsson.otp.erlang.OtpErlangObject; import com.ericsson.otp.erlang.OtpErlangRef; import com.ericsson.otp.erlang.OtpNode; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class RpcMonitor { private static final boolean DISABLED = System .getProperty("erlide.rpcmonitor") == null; private static final int COUNT = Integer .parseInt(System.getProperty("erlide.rpcmonitor.count", "50")); private static final boolean FULL = System .getProperty("erlide.rpcmonitor.full") != null; private static class RpcData { public final long startTime; public final String node; public final String module; public final String fun; public final SoftReference<Collection<OtpErlangObject>> args; public final long size; public RpcData(final long startTime, final String node, final String module, final String fun, final OtpErlangObject[] args, final long size) { this.startTime = startTime; this.node = node; this.module = module; this.fun = fun; this.args = new SoftReference<>( Collections.unmodifiableCollection(Lists.newArrayList(args))); this.size = size; } } private static class RpcInfo { public final String node; public final String module; public final String fun; public final SoftReference<Collection<OtpErlangObject>> args; public final long callSize; public final SoftReference<OtpErlangObject> result; public final long answerSize; public final long callTime; public final long answerTime; private final int argsSize; public RpcInfo(final RpcData data, final OtpErlangObject result, final long answerTime) { node = data.node; module = data.module; fun = data.fun; args = data.args; final Collection<OtpErlangObject> collection = args.get(); argsSize = collection == null ? 0 : collection.size(); this.result = new SoftReference<>(result); callTime = data.startTime; this.answerTime = answerTime; callSize = data.size; answerSize = OtpErlang.sizeOf(result); } public void dump(final PrintStream out, final boolean full) { Collection<OtpErlangObject> myArgs = args.get(); myArgs = myArgs == null ? new ArrayList<OtpErlangObject>() : myArgs; final String argsString = full ? args.toString().replaceAll("\n|\r", " ") : "..."; OtpErlangObject val = result.get(); val = val == null ? new OtpErlangAtom("null") : val; String resultString = full ? val.toString().replaceAll("\n|\r", " ") : "..."; if (resultString.length() > 100) { resultString = new String(resultString.substring(0, 99) + "..."); } out.format( "%30s|%25s:%-20s/%d in=%9d, out=%9d, t=%6d, args=%s -> result=%s%n", node.substring(0, Math.min(29, node.length() - 1)), module, fun, argsSize, callSize, answerSize, answerTime - callTime, argsString, resultString); } } private static int callCount = 0; private static final Map<OtpErlangRef, RpcData> ongoing = Maps.newHashMap(); private static Comparator<RpcInfo> timeComparator = new Comparator<RpcInfo>() { @Override public int compare(final RpcInfo o1, final RpcInfo o2) { return (int) (o2.answerTime - o2.callTime - (o1.answerTime - o1.callTime)); } }; private static Comparator<RpcInfo> sizeComparator = new Comparator<RpcInfo>() { @Override public int compare(final RpcInfo o1, final RpcInfo o2) { return (int) (o2.callSize + o2.answerSize - (o1.callSize + o1.answerSize)); } }; private static final List<RpcInfo> slowest = Lists.newLinkedList(); private static final List<RpcInfo> largest = Lists.newLinkedList(); public static synchronized void recordResponse(final OtpErlangRef ref, final OtpErlangObject result) { if (DISABLED) { return; } final RpcData data = ongoing.remove(ref); final long now = System.currentTimeMillis(); final RpcInfo info = new RpcInfo(data, result, now); add(largest, sizeComparator, info); add(slowest, timeComparator, info); } private static void add(final List<RpcInfo> list, final Comparator<RpcInfo> comparator, final RpcInfo info) { final int index = Collections.binarySearch(list, info, comparator); if (index < 0) { // Add the non-existent item to the list list.add(-index - 1, info); } else { list.add(index, info); } if (list.size() > COUNT) { list.remove(list.size() - 1); } } public static OtpErlangRef recordRequest(final OtpNode node, final String peer, final String module, final String fun, final OtpErlangObject[] args, final long callSize) { callCount++; if (DISABLED) { return null; } final RpcData data = new RpcData(System.currentTimeMillis(), peer, module, fun, args, callSize); final OtpErlangRef ref = node.createRef(); ongoing.put(ref, data); return ref; } public static void dump() { dump(System.out, COUNT, FULL); } public static void dump(final String file) { dump(file, COUNT, FULL); } public static void dump(final String file, final int n) { dump(file, n, FULL); } public static void dump(final String file, final boolean full) { dump(file, COUNT, full); } public static void dump(final String fileName, final int n, final boolean full) { try (final PrintStream os = new PrintStream( new FileOutputStream(fileName, true))) { dump(os, n, full); } catch (final FileNotFoundException e) { ErlLogger.error(e); } } public static synchronized void dump(final PrintStream out, final int n, final boolean full) { final String delim = "--------------------------------------------------------------"; out.println("\n" + delim); out.format("*** RpcMonitor statistics%n - %d calls%n", callCount); if (DISABLED) { out.println( "\nRpcMonitor was not enabled.\n\nUse -Derlide.rpcmonitor to enable it."); return; } out.println(delim); out.println(); out.format("Slowest %d calls%n", slowest.size()); out.println(delim); for (final RpcInfo info : slowest) { info.dump(out, full); } out.println(delim); out.println(); out.format("Largest %d calls%n", largest.size()); out.println(delim); for (final RpcInfo info : largest) { if (info != null) { info.dump(out, full); } } out.println(delim); out.close(); } public static void cleanupOldLogs(final String dirName, final String prefix) { final File dir = new File(dirName); for (final File f : dir.listFiles()) { final long now = System.currentTimeMillis(); final int aWeek = 7 * 24 * 3600 * 1000; if (f.getName().startsWith(prefix) && now - f.lastModified() > aWeek) { f.delete(); } } } }