001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.db.timing; 029 030import java.io.ByteArrayOutputStream; 031import java.io.IOException; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Comparator; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Map; 038 039import org.dom4j.Document; 040import org.dom4j.DocumentHelper; 041import org.dom4j.Element; 042import org.dom4j.io.OutputFormat; 043import org.dom4j.io.XMLWriter; 044 045import com.google.common.collect.Lists; 046 047/** 048 * Builds up a tree whose nodes correspond to stack trace lines of the threads calling this 049 * profiling handler. The resulting tree can be dumped as XML.<p> 050 */ 051public class CmsThreadStatsTreeProfilingHandler implements I_CmsProfilingHandler { 052 053 /** 054 * The tree node.<p> 055 */ 056 public static class Node { 057 058 /** The key identifying the node among its siblings. */ 059 private Object m_key; 060 061 /** The counted nanos for driver calls. */ 062 private long m_nanos; 063 064 /** The driver call count. */ 065 private int m_count; 066 067 /** The cumulative nanos for driver calls. */ 068 private long m_cumulativeNanos; 069 070 /** The cumulative driver call count. */ 071 private int m_cumulativeCount; 072 073 /** The children of this node, with their respective keys as map keys. */ 074 private Map<Object, Node> m_children = new HashMap<>(); 075 076 /** 077 * Creates a new node.<p> 078 * 079 * @param key the key for this node 080 */ 081 public Node(Object key) { 082 083 m_key = key; 084 } 085 086 /** 087 * Computes the cumulative stats for a tree and dumps it to an XML document.<p> 088 * 089 * @param node the root node 090 * @return the resulting XML document 091 */ 092 public static Document dumpTree(Node node) { 093 094 node.computeCumulativeData(); 095 Document doc = DocumentHelper.createDocument(); 096 Element root = doc.addElement("root"); 097 node.dump(root); 098 return doc; 099 } 100 101 /** 102 * Updates the count / nanos for this node.<p> 103 * 104 * @param nanos the nanoseconds to add 105 */ 106 public void addCall(long nanos) { 107 108 m_count += 1; 109 m_nanos += nanos; 110 } 111 112 /** 113 * Gets the child for a given key, or adds it if it doesn't exist yet.<p> 114 * 115 * @param key the key 116 * @return the child 117 */ 118 public Node addOrGetChild(Object key) { 119 120 Node child = m_children.get(key); 121 if (child == null) { 122 child = new Node(key); 123 m_children.put(key, child); 124 } 125 return child; 126 127 } 128 129 /** 130 * Gets the descendant for a given path, or adds it if it doesn't exist yet.<p> 131 * 132 * @param path the path 133 * @return the descendant 134 */ 135 public Node addOrGetDescendant(List<?> path) { 136 137 Node current = this; 138 for (Object key : path) { 139 current = current.addOrGetChild(key); 140 } 141 return current; 142 } 143 144 /** 145 * Computes the cumulative driver call count and nanos.<p> 146 */ 147 public void computeCumulativeData() { 148 149 m_cumulativeCount = m_count; 150 m_cumulativeNanos = m_nanos; 151 for (Node child : m_children.values()) { 152 child.computeCumulativeData(); 153 m_cumulativeNanos += child.getCumulativeNanos(); 154 m_cumulativeCount += child.getCumulativeCount(); 155 } 156 } 157 158 /** 159 * Computes XML structure for this node and its descendants and appends it to a given element.<p> 160 * 161 * @param parent the parent element 162 */ 163 public void dump(Element parent) { 164 165 Element elem = parent.addElement("location").addAttribute("key", "" + m_key).addAttribute( 166 "count", 167 "" + m_cumulativeCount).addAttribute("millis", "" + (m_cumulativeNanos / 1000000)); 168 169 // Sort children so the "most expensive" ones go first 170 List<Node> children = new ArrayList<>(m_children.values()); 171 children.sort(new Comparator<Node>() { 172 173 @SuppressWarnings("synthetic-access") 174 public int compare(Node o1, Node o2) { 175 176 return -Long.compare(o1.getCumulativeNanos(), o2.getCumulativeNanos()); 177 } 178 }); 179 for (Node child : children) { 180 child.dump(elem); 181 } 182 } 183 184 /** 185 * Gets the key for the node.<p> 186 * 187 * @return tbe key 188 */ 189 public Object getKey() { 190 191 return m_key; 192 } 193 194 /** 195 * Gets the cumulative driver call count (does not automatically update).<p> 196 * 197 * @return the cumulative driver call count 198 */ 199 private int getCumulativeCount() { 200 201 return m_cumulativeCount; 202 } 203 204 /** 205 * Gets the cumulative driver call nanos (does not automatically update).<p> 206 * 207 * @return the cumulative driver call nanos 208 */ 209 private long getCumulativeNanos() { 210 211 return m_cumulativeNanos; 212 } 213 214 } 215 216 /** The root node. */ 217 private Node m_root = new Node("ROOT"); 218 219 /** Flag which records whether we have measured data yet. */ 220 private boolean m_hasData; 221 222 /** 223 * Dumps the tree as XML.<p> 224 * 225 * @return the tree in XML format 226 */ 227 public String dump() { 228 229 try { 230 Document doc = Node.dumpTree(m_root); 231 OutputFormat outformat = OutputFormat.createPrettyPrint(); 232 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 233 outformat.setEncoding("UTF-8"); 234 XMLWriter writer = new XMLWriter(buffer, outformat); 235 writer.write(doc); 236 writer.flush(); 237 return new String(buffer.toByteArray(), "UTF-8"); 238 } catch (IOException e) { 239 // CAn't happen 240 return null; 241 } 242 } 243 244 /** 245 * Returns true if we received any data yet.<p> 246 * 247 * @return true if the handler has received data 248 */ 249 public boolean hasData() { 250 251 return m_hasData; 252 } 253 254 /** 255 * @see org.opencms.db.timing.I_CmsProfilingHandler#putTime(java.lang.String, long) 256 */ 257 public synchronized void putTime(String key, long nanos) { 258 259 m_hasData = true; 260 StackTraceElement[] traceElems = (new Throwable()).getStackTrace(); 261 List<StackTraceElement> trace = Arrays.asList(traceElems); 262 263 List<Object> keys = new ArrayList<>(); 264 // Since this handler is used during startup, which mostly happens in a single thread, 265 // we want to distinguish calls from different threads, e.g. scheduled jobs. 266 keys.add("THREAD " + Thread.currentThread().getId()); 267 keys.addAll(Lists.reverse(trace)); 268 // Cut off proxy-specific garbage from stack trace 269 keys = keys.subList(0, keys.size() - 3); 270 Node child = m_root.addOrGetDescendant(keys); 271 child.addCall(nanos); 272 } 273 274}