001package org.javasimon.callback.calltree; 002 003import java.io.PrintWriter; 004import java.io.StringWriter; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011 012import org.javasimon.Split; 013import org.javasimon.utils.SimonUtils; 014 015/** 016 * Call tree node is one Simon one for one call level, all splits for this 017 * Simon+Level(+Thread) tuple are kept for later analysis. 018 * Simon name is unique within parent tree node. Said differently a tree node can 019 * not have two children with same name. 020 * 021 * @author gquintana 022 * @since 3.2 023 */ 024public class CallTreeNode { 025 /** 026 * Name, used as a key. 027 */ 028 private final String name; 029 030 /** 031 * Splits. 032 * Size defaults to 1, because most of the time there are no loops. 033 */ 034 private final List<Split> splits = new ArrayList<>(1); 035 036 /** 037 * Child tree nodes. 038 */ 039 private Map<String, CallTreeNode> children; 040 041 /** 042 * Parent tree node. {@code null} for root tree node. 043 */ 044 private CallTreeNode parent; 045 046 /** 047 * Main constructor. 048 * 049 * @param name Simon name 050 */ 051 public CallTreeNode(String name) { 052 this.name = name; 053 } 054 055 /** 056 * Returns Simon name. 057 * 058 * @return Name 059 */ 060 public String getName() { 061 return name; 062 } 063 064 /** 065 * Adds a split to the current tree node. 066 * In case of loops, child nodes can have many splits. 067 * 068 * @param split Split 069 */ 070 public void addSplit(Split split) { 071 splits.add(split); 072 } 073 074 /** 075 * Returns the number of splits in this node. 076 * 077 * @return Split count 078 */ 079 public int getSplitCount() { 080 return splits.size(); 081 } 082 083 /** 084 * Returns the total time of splits using {@link org.javasimon.Split#runningFor()}. 085 * 086 * @return total time of splits 087 */ 088 public long getTotal() { 089 long total = 0; 090 for (Split split : splits) { 091 total += split.runningFor(); 092 } 093 return total; 094 } 095 096 /** 097 * Returns the part of time spent in this node compared to parent. 098 * 099 * @return Percent time 100 */ 101 public Integer getPercent() { 102 Integer percent; 103 if (parent == null) { 104 percent = null; 105 } else { 106 percent = (int) (getTotal() * 100L / getParent().getTotal()); 107 } 108 return percent; 109 } 110 111 /** 112 * Adds a child to this tree node. 113 * 114 * @param name Child Simon name 115 * @return Created child node 116 */ 117 public CallTreeNode addChild(String name) { 118 if (children == null) { 119 children = new HashMap<>(); 120 } 121 CallTreeNode child = new CallTreeNode(name); 122 children.put(name, child); 123 child.parent = this; 124 return child; 125 } 126 127 /** 128 * Returns the child node by Simon name. 129 * 130 * @param name Simon name 131 * @return Child corresponding to given name, or null if any 132 */ 133 public CallTreeNode getChild(String name) { 134 return (children == null) ? null : children.get(name); 135 } 136 137 /** 138 * Returns all child nodes. 139 * 140 * @return children 141 */ 142 public Collection<CallTreeNode> getChildren() { 143 return children == null ? Collections.<CallTreeNode>emptyList() : children.values(); 144 } 145 146 /** 147 * Returns a child node with given name or creates it if it does not exists. 148 * 149 * @param name Simon name 150 * @return Child node 151 */ 152 public CallTreeNode getOrAddChild(String name) { 153 CallTreeNode child = getChild(name); 154 if (child == null) { 155 child = addChild(name); 156 } 157 return child; 158 } 159 160 /** 161 * Returns parent tree node. 162 * 163 * @return Parent tree node 164 */ 165 public CallTreeNode getParent() { 166 return parent; 167 } 168 169 /** 170 * Recursively prints this tree node to given print writer. 171 * 172 * @param printWriter Output print writer 173 * @param prefix Line prefix (used internally for indentation) 174 * @param parentTotal Duration of parent node (used to compute duration ratio for child nodes), null for root nodes 175 */ 176 private void print(PrintWriter printWriter, String prefix, Long parentTotal) { 177 long total = getTotal(); 178 printWriter.print(prefix); 179 printWriter.print(name); 180 printWriter.print(' '); 181 if (parentTotal != null && parentTotal != 0L) { 182 printWriter.print(total * 100 / parentTotal); 183 printWriter.print("%, "); 184 } 185 printWriter.print(SimonUtils.presentNanoTime(total)); 186 long counter = getSplitCount(); 187 if (counter > 1) { 188 printWriter.print(", "); 189 printWriter.print(counter); 190 } 191 printWriter.println(); 192 for (CallTreeNode child : getChildren()) { 193 child.print(printWriter, prefix + "\t", total); 194 } 195 } 196 197 /** 198 * Recursively prints this tree node to given print writer. 199 * 200 * @param printWriter Output print writer 201 */ 202 public void print(PrintWriter printWriter) { 203 print(printWriter, "", null); 204 } 205 206 /** 207 * Returns a string representing the tree from this tree node, visiting recursively this tree branch. 208 * 209 * @return String 210 */ 211 @Override 212 public String toString() { 213 StringWriter stringWriter = new StringWriter(); 214 PrintWriter printWriter = new PrintWriter(stringWriter); 215 print(printWriter); 216 return stringWriter.toString(); 217 } 218}