001package org.javasimon.console.plugin; 002 003import java.io.IOException; 004 005import org.javasimon.Simon; 006import org.javasimon.Stopwatch; 007import org.javasimon.callback.calltree.CallTree; 008import org.javasimon.callback.calltree.CallTreeCallback; 009import org.javasimon.callback.calltree.CallTreeNode; 010import org.javasimon.console.ActionContext; 011import org.javasimon.console.SimonCallbacks; 012import org.javasimon.console.action.DetailHtmlBuilder; 013import org.javasimon.console.action.DetailPlugin; 014import org.javasimon.console.html.HtmlResourceType; 015import org.javasimon.console.json.ArrayJS; 016import org.javasimon.console.json.ObjectJS; 017import org.javasimon.console.text.StringifierFactory; 018 019/** 020 * Detail plugin to display call tree. 021 * @author gquintana 022 */ 023public class CallTreeDetailPlugin extends DetailPlugin { 024 025 /** 026 * Message: Callback not registered 027 */ 028 public static final String NO_CALLBACK_MESSAGE = "CallTree callback not registered"; 029 /** 030 * Message: Data not found in Simon 031 */ 032 private static final String NO_DATA_MESSAGE = "No call tree available yet"; 033 034 public CallTreeDetailPlugin() { 035 super("callTree", "Call Tree"); 036 addResource("js/javasimon-callTreePlugin.js", HtmlResourceType.JS); 037 addResource("js/javasimon-dataTreeTable.js", HtmlResourceType.JS); 038 } 039 040 /** 041 * Indicate that this plugin only applies on Stopwatches. 042 */ 043 @Override 044 public boolean supports(Simon simon) { 045 return simon instanceof Stopwatch; 046 } 047 048 /** 049 * Indicate whether {@link CallTreeCallback} was registered in manager 050 */ 051 private boolean isCallTreeCallbackRegistered(ActionContext context) { 052 return SimonCallbacks.getCallbackByType(context.getManager(), CallTreeCallback.class) != null; 053 } 054 055 /** 056 * Get CallTree data from Simon 057 */ 058 private CallTree getData(Simon simon) { 059 return CallTreeCallback.getLastCallTree((Stopwatch) simon); 060 } 061 062 /** 063 * Generate an HTML message row 064 */ 065 private void htmlMessage(DetailHtmlBuilder htmlBuilder, String message) throws IOException { 066 htmlBuilder.beginRow() 067 .labelCell("Message").valueCell(" colspan=\"3\"", message) 068 .endRow(); 069 } 070 /** 071 * Generate a HTML call tree node list 072 */ 073 private DetailHtmlBuilder htmlTreeNode(CallTreeNode node, DetailHtmlBuilder htmlBuilder, StringifierFactory htmlStringifierFactory) throws IOException { 074 htmlBuilder.begin("li") 075 .text(node.getName()).text(": "); 076 if (node.getParent()!=null) { 077 htmlBuilder 078 .text(htmlStringifierFactory.toString(node.getPercent())).text("%") 079 .text(", "); 080 } 081 htmlBuilder 082 .text("total ").text(htmlStringifierFactory.toString(node.getTotal(), "Time")) 083 .text(", ") 084 .text("count ").text(htmlStringifierFactory.toString(node.getSplitCount())); 085 if (!node.getChildren().isEmpty()) { 086 htmlBuilder.begin("ul"); 087 for(CallTreeNode childNode:node.getChildren()) { 088 htmlTreeNode(childNode, htmlBuilder, htmlStringifierFactory); 089 } 090 htmlBuilder.end("ul"); 091 } 092 return htmlBuilder.end("li"); 093 } 094 @Override 095 public DetailHtmlBuilder executeHtml(ActionContext context, DetailHtmlBuilder htmlBuilder, StringifierFactory htmlStringifierFactory, Simon simon) throws IOException { 096 if (isCallTreeCallbackRegistered(context)) { 097 CallTree callTree = getData(simon); 098 if (callTree == null) { 099 htmlMessage(htmlBuilder, NO_DATA_MESSAGE); 100 } else { 101 htmlBuilder 102 .beginRow() 103 .labelCell("Threshold") 104 .valueCell(htmlStringifierFactory.toString(callTree.getLogThreshold(), "Time")) 105 .endRow() 106 .beginRow() 107 .labelCell("Tree") 108 .beginValueCell().begin("ul"); 109 htmlTreeNode(callTree.getRootNode(), htmlBuilder, htmlStringifierFactory) 110 .end("ul").endValueCell() 111 .endRow(); 112 } 113 } else { 114 htmlMessage(htmlBuilder, NO_CALLBACK_MESSAGE); 115 } 116 return htmlBuilder; 117 } 118 119 /** 120 * Generate a JSON message object 121 */ 122 private ObjectJS jsonMessage(String message, StringifierFactory jsonStringifierFactory) { 123 ObjectJS callTreeJS = new ObjectJS(); 124 callTreeJS.setSimpleAttribute("message", message, jsonStringifierFactory.getStringifier(String.class)); 125 return callTreeJS; 126 } 127 /** 128 * Generate a JSON call tree node object 129 */ 130 private ObjectJS jsonTreeNode(CallTreeNode node, StringifierFactory jsonStringifierFactory) { 131 final ObjectJS nodeJS = ObjectJS.create(node, jsonStringifierFactory); 132 if (!node.getChildren().isEmpty()) { 133 final ArrayJS childNodesJS=new ArrayJS(node.getChildren().size()); 134 for(CallTreeNode childNode:node.getChildren()) { 135 childNodesJS.addElement(jsonTreeNode(childNode, jsonStringifierFactory)); 136 } 137 nodeJS.setAttribute("children", childNodesJS); 138 } 139 return nodeJS; 140 } 141 142 /** 143 * Generate a JSON call tree object or an error string if no call tree 144 */ 145 @Override 146 public ObjectJS executeJson(ActionContext context, StringifierFactory jsonStringifierFactory, Simon simon) { 147 ObjectJS callTreeJS; 148 if (isCallTreeCallbackRegistered(context)) { 149 CallTree callTree = getData(simon); 150 if (callTree == null) { 151 callTreeJS = jsonMessage(NO_DATA_MESSAGE, jsonStringifierFactory); 152 } else { 153 callTreeJS = ObjectJS.create(callTree, jsonStringifierFactory); 154 callTreeJS.setAttribute("rootNode", jsonTreeNode(callTree.getRootNode(), jsonStringifierFactory)); 155 } 156 } else { 157 callTreeJS = jsonMessage(NO_CALLBACK_MESSAGE, jsonStringifierFactory); 158 } 159 return callTreeJS; 160 } 161}