/* * Copyright (C) 2015 SoftIndex LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.datakernel.async; import io.datakernel.jmx.ConcurrentJmxMBean; import io.datakernel.jmx.JmxAttribute; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import static io.datakernel.jmx.MBeanFormat.formatDuration; public final class CallbackRegistry { private static final ConcurrentHashMap<Object, CallbackMetaData> REGISTRY = new ConcurrentHashMap<>(); private static volatile boolean storeStackTrace = false; // region public api public static void register(Object callback) { assert registerIfAssertionsEnabled(callback) : "Callback " + callback + " has already been registered"; } public static void complete(Object callback) { assert REGISTRY.remove(callback) != null : "Callback " + callback + " has already been completed"; } public static boolean getStoreStackTrace() { return storeStackTrace; } public static void setStoreStackTrace(boolean store) { storeStackTrace = store; } // endregion // region implementation private static boolean registerIfAssertionsEnabled(Object callback) { long timestamp = System.currentTimeMillis(); StackTraceElement[] stackTrace = null; if (storeStackTrace) { // TODO(vmykhalko): maybe use new Exception().getStackTrace instead ? according to performance issues StackTraceElement[] fullStackTrace = Thread.currentThread().getStackTrace(); // remove stack trace lines that stand for registration method calls stackTrace = Arrays.copyOfRange(fullStackTrace, 3, fullStackTrace.length); } return REGISTRY.put(callback, new CallbackMetaData(timestamp, stackTrace)) == null; } private static final class CallbackMetaData { private final long timestamp; private final StackTraceElement[] stackTrace; public CallbackMetaData(long timestamp, StackTraceElement[] stackTrace) { this.timestamp = timestamp; this.stackTrace = stackTrace; } public long getTimestamp() { return timestamp; } public StackTraceElement[] getStackTrace() { return stackTrace; } } // endregion // region jmx public static final class CallbackRegistryStats implements ConcurrentJmxMBean { private volatile int maxCallbacksToShow = 100; @JmxAttribute public List<String> getCurrentCallbacksSummary() { List<CallbackDetails> callbacksDetails = getCurrentCallbacksDetails(); List<String> lines = new ArrayList<>(); lines.add("Duration Callback"); for (CallbackDetails details : callbacksDetails) { lines.add(String.format("%s %s: %s", details.getDuration(), details.getClassName(), details.getStringRepresentation())); } return lines; } @JmxAttribute public List<CallbackDetails> getCurrentCallbacksDetails() { List<Map.Entry<Object, CallbackMetaData>> sortedEntries = sortByTimestamp(REGISTRY); List<CallbackDetails> detailsList = new ArrayList<>(); long currentTime = System.currentTimeMillis(); int maxCallbacksToShowCached = maxCallbacksToShow; for (Map.Entry<Object, CallbackMetaData> entry : sortedEntries) { if (detailsList.size() == maxCallbacksToShowCached) { break; } Object callback = entry.getKey(); CallbackMetaData metaData = entry.getValue(); String formattedDuration = formatDuration(currentTime - metaData.getTimestamp()); String className = callback.getClass().getName(); String callbackString = callback.toString(); List<String> stackTraceLines = new ArrayList<>(); StackTraceElement[] stackTrace = metaData.getStackTrace(); if (stackTrace != null) { for (StackTraceElement stackTraceElement : stackTrace) { stackTraceLines.add(stackTraceElement.toString()); } } detailsList.add(new CallbackDetails(className, callbackString, formattedDuration, stackTraceLines)); } return detailsList; } @JmxAttribute public boolean getStoreStackTrace() { return CallbackRegistry.getStoreStackTrace(); } @JmxAttribute public void setStoreStackTrace(boolean store) { CallbackRegistry.setStoreStackTrace(store); } @JmxAttribute public int getMaxCallbacksToShow() { return maxCallbacksToShow; } @JmxAttribute public void setMaxCallbacksToShow(int maxCallbacksToShow) { if (maxCallbacksToShow < 0) { throw new IllegalArgumentException("argument must be non-negative"); } this.maxCallbacksToShow = maxCallbacksToShow; } @JmxAttribute public int getTotalActiveCallbacks() { return CallbackRegistry.REGISTRY.size(); } private static List<Map.Entry<Object, CallbackMetaData>> sortByTimestamp(Map<Object, CallbackMetaData> map) { List<Map.Entry<Object, CallbackMetaData>> sortedEntries = new ArrayList<>(map.entrySet()); Collections.sort(sortedEntries, new Comparator<Map.Entry<Object, CallbackMetaData>>() { @Override public int compare(Map.Entry<Object, CallbackMetaData> o1, Map.Entry<Object, CallbackMetaData> o2) { long ts1 = o1.getValue().getTimestamp(); long ts2 = o2.getValue().getTimestamp(); return Long.compare(ts1, ts2); } }); return sortedEntries; } public static final class CallbackDetails { private final String className; private final String stringRepresentation; private final String duration; private final List<String> stackTrace; public CallbackDetails(String className, String stringRepresentation, String duration, List<String> stackTrace) { this.className = className; this.stringRepresentation = stringRepresentation; this.duration = duration; this.stackTrace = stackTrace; } @JmxAttribute public String getClassName() { return className; } @JmxAttribute public String getStringRepresentation() { return stringRepresentation; } @JmxAttribute public String getDuration() { return duration; } @JmxAttribute public List<String> getStackTrace() { return stackTrace; } } } // endregion }