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}