package ex11; import org.objectweb.asm.Opcodes; import java.util.HashMap; import java.util.Map; import java.util.Objects; public final class Profiler { private static final Map count = new HashMap<>(); private static boolean stop = false; static { Runtime.getRuntime().addShutdownHook(new Thread(() -> { stop = true; for (final var entry : count.entrySet()) { System.out.println(entry.getKey().toString() + "\n#invokes " + entry.getValue()[0] + "\n"); } })); } private Profiler() { } public static void registerCall(final CallInfo info) { if (stop) return; count.computeIfAbsent(info, ignored -> new long[1])[0]++; } public record CallInfo(int instruction, long index, String caller, String callee, String callTarget) { private static final String FORMAT = "CallerMethodFQIN %s\nCallerBCI %d\ninvoketype %s\nCalleeMethodFQIN %s\nCallTargetFQIN %s"; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CallInfo callInfo = (CallInfo) o; return index == callInfo.index && instruction == callInfo.instruction && caller.equals(callInfo.caller) && callee.equals(callInfo.callee) && callTarget.equals(callInfo.callTarget); } @Override public int hashCode() { return Objects.hash(instruction, index, caller, callee, callTarget); } public String invokeType() { return switch (instruction) { case Opcodes.INVOKEDYNAMIC -> "dynamic"; case Opcodes.INVOKESTATIC -> "static"; case Opcodes.INVOKEINTERFACE -> "interface"; case Opcodes.INVOKESPECIAL -> "special"; case Opcodes.INVOKEVIRTUAL -> "virtual"; default -> throw new IllegalStateException("Not an INVOKE* bytecode instruction: " + instruction); }; } @Override public String toString() { return String.format(FORMAT, caller, index, invokeType(), callee, callTarget); } } }