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.main; 029 030import java.io.ByteArrayOutputStream; 031import java.io.FileOutputStream; 032import java.lang.management.ManagementFactory; 033import java.lang.management.MonitorInfo; 034import java.lang.management.ThreadInfo; 035import java.lang.management.ThreadMXBean; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.LinkedHashMap; 039import java.util.List; 040import java.util.Map; 041import java.util.zip.ZipEntry; 042import java.util.zip.ZipOutputStream; 043 044import org.dom4j.Document; 045import org.dom4j.DocumentHelper; 046import org.dom4j.Element; 047import org.dom4j.io.OutputFormat; 048import org.dom4j.io.XMLWriter; 049 050import com.google.common.collect.Lists; 051 052/** 053 * Profiling thread used for the startup process.<p> 054 * 055 * Periodically creates thread dumps for a single thread, saves them zo a ZIP file, and also 056 * generates a summary XML files presenting the stack frames with with their sample counts as a tree 057 * structure.<p> 058 */ 059public class CmsSingleThreadDumperThread extends Thread { 060 061 /** 062 * Node for the summary tree generated from the thread dumps.<p> 063 * 064 * The tree keeps track of sample counts for stack trace frames. 065 */ 066 public static class SampleNode { 067 068 /** The map of children, with their keys used as map keys. */ 069 private Map<Object, SampleNode> m_children = new LinkedHashMap<>(); 070 071 /** The key for the node. */ 072 private Object m_key; 073 074 /** The sample count. */ 075 private long m_samples; 076 077 /** 078 * Creates a new node.<p> 079 * 080 * @param key the key of the node, identifying it among its siblings 081 */ 082 public SampleNode(Object key) { 083 084 m_key = key; 085 } 086 087 /** 088 * Compare nodes by descending sample count.<p> 089 * 090 * @param a first node 091 * @param b second node 092 * @return the comparison result 093 */ 094 public static int compareBySamplesDescending(SampleNode a, SampleNode b) { 095 096 return -Long.compare(a.getSamples(), b.getSamples()); 097 } 098 099 /** 100 * Increment sample count for all nodes along the given path.<p> 101 * 102 * @param root the root node 103 * @param path a sequence of keys constituting a path in the tree 104 */ 105 public static void incrementPath(SampleNode root, List<?> path) { 106 107 List<SampleNode> nodes = nodesForPath(root, path); 108 for (SampleNode node : nodes) { 109 node.increment(); 110 } 111 112 } 113 114 /** 115 * Given a path consisting of a list of node keys, this method collects all nodes along that path, 116 * including the given root node, and creates nodes if they don't exist in the tree yet.<p> 117 * 118 * @param root the root of the tree 119 * @param path the path 120 * 121 * @return the sequence of nodes along the path 122 */ 123 public static List<SampleNode> nodesForPath(SampleNode root, List<?> path) { 124 125 List<SampleNode> result = new ArrayList<>(); 126 result.add(root); 127 SampleNode current = root; 128 for (Object key : path) { 129 current = current.getOrAddChild(key); 130 result.add(current); 131 } 132 return result; 133 } 134 135 /** 136 * Dumps the tree node to XML.<p> 137 * 138 * @param parent the parent XML to append the XML to 139 */ 140 public void appendToXml(Element parent) { 141 142 Element element = parent.addElement("location").addAttribute("key", "" + m_key).addAttribute( 143 "samples", 144 "" + m_samples); 145 for (SampleNode child : m_children.values()) { 146 child.appendToXml(element); 147 } 148 } 149 150 /** 151 * Gets the key of the node.<p> 152 * 153 * @return the key of the node 154 */ 155 public Object getKey() { 156 157 return m_key; 158 } 159 160 /** 161 * Gets the child node for the given key, creating it it it doesn't exist yet.<p> 162 * 163 * @param key the key 164 * @return the child for the key 165 */ 166 public SampleNode getOrAddChild(Object key) { 167 168 SampleNode child = m_children.get(key); 169 if (child == null) { 170 child = new SampleNode(key); 171 m_children.put(key, child); 172 } 173 return child; 174 } 175 176 /** 177 * Returns the sample count.<p> 178 * 179 * @return the sample count 180 */ 181 public long getSamples() { 182 183 return m_samples; 184 } 185 186 /** 187 * Check if this node has children.<p> 188 * 189 * @return true if this node has children 190 */ 191 public boolean hasChildren() { 192 193 return m_children.size() > 0; 194 } 195 196 /** 197 * Increments the sample count for this node.<p> 198 */ 199 public void increment() { 200 201 m_samples += 1; 202 } 203 204 /** 205 * Sorts the children of this node by descending sample count.<p> 206 */ 207 public void sortChildren() { 208 209 ArrayList<SampleNode> children = new ArrayList<>(m_children.values()); 210 children.sort(SampleNode::compareBySamplesDescending); 211 m_children.clear(); 212 for (SampleNode node : children) { 213 m_children.put(node.getKey(), node); 214 } 215 } 216 217 /** 218 * Recursively sorts this node's and all its descendants' children.<p> 219 */ 220 public void sortTree() { 221 222 sortChildren(); 223 for (SampleNode child : m_children.values()) { 224 child.sortTree(); 225 } 226 } 227 228 } 229 230 /** The file name to save the zip file to. */ 231 private String m_filename; 232 233 /** The file name for the summary file. */ 234 private String m_summaryFilename; 235 236 /** The id of the thread to monitor. */ 237 private long m_threadId; 238 239 /** The thread management bean. */ 240 private ThreadMXBean m_threadMx; 241 242 /** 243 * Creates a new instance.<p> 244 * 245 * @param filename the name of the zip file to generate 246 * @param summaryFilename the name of the summary file to generate 247 * @param id the id of the thread to monitor 248 */ 249 public CmsSingleThreadDumperThread(String filename, String summaryFilename, long id) { 250 251 super("" + CmsSingleThreadDumperThread.class.getName()); 252 m_threadMx = ManagementFactory.getThreadMXBean(); 253 m_threadId = id; 254 m_filename = filename; 255 m_summaryFilename = summaryFilename; 256 // If startup process fails, we don't want this thread to hang around 257 setDaemon(true); 258 } 259 260 /** 261 * @see java.lang.Thread#run() 262 */ 263 @Override 264 public void run() { 265 266 int count = 1; 267 SampleNode root = new SampleNode("ROOT"); 268 269 try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(m_filename))) { 270 while (!isInterrupted()) { 271 ThreadInfo info = m_threadMx.getThreadInfo(m_threadId, Integer.MAX_VALUE); 272 List<StackTraceElement> path = Lists.reverse(Arrays.asList(info.getStackTrace())); 273 SampleNode.incrementPath(root, path); 274 StringBuffer buffer = new StringBuffer(); 275 buffer.append(formatThreadInfo(info)); 276 byte[] dumpData = buffer.toString().getBytes("UTF-8"); 277 ZipEntry entry = new ZipEntry("dump_" + count + ".txt"); 278 zip.putNextEntry(entry); 279 zip.write(dumpData); 280 count += 1; 281 Thread.sleep(10); 282 } 283 284 } catch (InterruptedException e) { 285 return; 286 } catch (Exception e) { 287 e.printStackTrace(); 288 } finally { 289 saveSummaryXml(root); 290 } 291 } 292 293 /** 294 * Formats the thread information.<p> 295 * 296 * @param info thread information bean 297 * @return the formatted thread info 298 */ 299 String formatThreadInfo(ThreadInfo info) { 300 301 StringBuilder sb = new StringBuilder( 302 "\"" + info.getThreadName() + "\"" + " Id=" + info.getThreadId() + " " + info.getThreadState()); 303 if (info.getLockName() != null) { 304 sb.append(" on " + info.getLockName()); 305 } 306 if (info.getLockOwnerName() != null) { 307 sb.append(" owned by \"" + info.getLockOwnerName() + "\" Id=" + info.getLockOwnerId()); 308 } 309 if (info.isSuspended()) { 310 sb.append(" (suspended)"); 311 } 312 if (info.isInNative()) { 313 sb.append(" (in native)"); 314 } 315 sb.append('\n'); 316 int i = 0; 317 for (; (i < info.getStackTrace().length); i++) { 318 StackTraceElement ste = info.getStackTrace()[i]; 319 sb.append("\tat " + ste.toString()); 320 sb.append('\n'); 321 if ((i == 0) && (info.getLockInfo() != null)) { 322 Thread.State ts = info.getThreadState(); 323 switch (ts) { 324 case BLOCKED: 325 sb.append("\t- blocked on " + info.getLockInfo()); 326 sb.append('\n'); 327 break; 328 case WAITING: 329 sb.append("\t- waiting on " + info.getLockInfo()); 330 sb.append('\n'); 331 break; 332 case TIMED_WAITING: 333 sb.append("\t- waiting on " + info.getLockInfo()); 334 sb.append('\n'); 335 break; 336 default: 337 } 338 } 339 340 for (MonitorInfo mi : info.getLockedMonitors()) { 341 if (mi.getLockedStackDepth() == i) { 342 sb.append("\t- locked " + mi); 343 sb.append('\n'); 344 } 345 } 346 } 347 return sb.toString(); 348 349 } 350 351 /** 352 * Saves the stack trace summary tree starting from the given root node to an XML file.<p> 353 * 354 * @param root the root node 355 */ 356 private void saveSummaryXml(SampleNode root) { 357 358 root.sortTree(); 359 Document doc = DocumentHelper.createDocument(); 360 Element rootElem = doc.addElement("root"); 361 root.appendToXml(rootElem); 362 OutputFormat outformat = OutputFormat.createPrettyPrint(); 363 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 364 outformat.setEncoding("UTF-8"); 365 try { 366 XMLWriter writer = new XMLWriter(buffer, outformat); 367 writer.write(doc); 368 writer.flush(); 369 try (FileOutputStream fos = new FileOutputStream(m_summaryFilename)) { 370 fos.write(buffer.toByteArray()); 371 } 372 } catch (Exception e) { 373 e.printStackTrace(); 374 } 375 } 376 377}