package er.profiling; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.webobjects.appserver.WOApplication; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOElement; import com.webobjects.appserver.WORequest; import com.webobjects.appserver.WOResponse; import er.profiling.delegates.PFHeatMap; import er.profiling.delegates.PFMarkup; import er.profiling.delegates.PFSummary; public class PFProfiler { public static interface Delegate { public void requestStarted(WORequest request); public void requestEnded(WORequest request); public void responseEnded(WOResponse response, WOContext context); public void willAppendToResponse(WOElement element, WOResponse response, WOContext context); public void didAppendToResponse(WOElement element, WOResponse response, WOContext context); } private static ThreadLocal<PFStatsNode> _currentStats; private static Map<String, PFStatsNode> _stats; private static List<PFProfiler.Delegate> _delegates; private static long _statsID; static { _currentStats = new ThreadLocal<>(); // ideally this should match your backtrack cache size, but i didn't // want to touch WOApplication too early _stats = new LRUMap<>(30); _delegates = new LinkedList<>(); _statsID = 0; _delegates.add(new PFSummary()); _delegates.add(new PFMarkup()); _delegates.add(new PFHeatMap()); } public static synchronized long nextStatsID() { return _statsID++; } public static void setCurrentStats(PFStatsNode stats) { _currentStats.set(stats); } public static PFStatsNode currentStats() { return _currentStats.get(); } public static void startRequest(WORequest request) { PFProfiler._currentStats.set(new PFStatsNode("request", null, request, null)); PFProfiler._currentStats.get().start(); for (PFProfiler.Delegate delegate : _delegates) { delegate.requestStarted(request); } } public static void endRequest(WORequest request) { for (PFProfiler.Delegate delegate : _delegates) { delegate.requestEnded(request); } } public static void pushStats(String name, String type, Object target, Object context) { PFStatsNode currentStats = PFProfiler._currentStats.get(); if (currentStats != null) { currentStats.push(name, type, target, context); } } public static PFStatsNode popStats() { PFStatsNode currentStats = PFProfiler._currentStats.get(); if (currentStats != null) { currentStats.pop(); } return currentStats; } public static void incrementCounter(String counterName) { PFStatsNode currentStats = PFProfiler._currentStats.get(); if (currentStats != null) { currentStats.incrementCounter(counterName); } } public static void willAppendToResponse(WOElement element, WOResponse response, WOContext context) { for (PFProfiler.Delegate delegate : _delegates) { delegate.willAppendToResponse(element, response, context); } } public static void didAppendToResponse(WOElement element, WOResponse response, WOContext context) { PFStatsNode stats = PFProfiler._currentStats.get(); if (stats != null) { stats.end(); } for (PFProfiler.Delegate delegate : _delegates) { delegate.didAppendToResponse(element, response, context); } if (stats != null && stats.parentStats().isRoot()) { for (PFProfiler.Delegate delegate : _delegates) { delegate.responseEnded(response, context); } } } public static void setStatsWithID(PFStatsNode stats, String id) { synchronized (_stats) { _stats.put(id, stats); } } public static PFStatsNode statsWithID(String id) { synchronized (_stats) { return _stats.get(id); } } public static void registerRequestHandler() { WOApplication.application().registerRequestHandler(new PFProfilerRequestHandler(), "profiler"); } protected static class LRUMap<U, V> extends LinkedHashMap<U, V> { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; private int _maxSize; public LRUMap(int maxSize) { super(16, 0.75f, true); _maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry<U, V> eldest) { return size() > _maxSize; } } }