001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.plugins.profiler; 028 029 030 031 import java.io.FileOutputStream; 032 import java.io.IOException; 033 import java.util.ArrayList; 034 import java.util.HashMap; 035 import java.util.Map; 036 037 import org.opends.server.api.DirectoryThread; 038 import org.opends.server.protocols.asn1.ASN1Element; 039 import org.opends.server.protocols.asn1.ASN1Long; 040 import org.opends.server.protocols.asn1.ASN1Sequence; 041 import org.opends.server.protocols.asn1.ASN1Writer; 042 043 import static org.opends.server.loggers.debug.DebugLogger.*; 044 import org.opends.server.loggers.debug.DebugTracer; 045 import org.opends.server.types.DebugLogLevel; 046 047 048 049 /** 050 * This class defines a thread that may be used to actually perform 051 * profiling in the Directory Server. When activated, it will repeatedly 052 * retrieve thread stack traces and store them so that they can be written out 053 * and analyzed with a separate utility. 054 */ 055 public class ProfilerThread 056 extends DirectoryThread 057 { 058 /** 059 * The tracer object for the debug logger. 060 */ 061 private static final DebugTracer TRACER = getTracer(); 062 063 064 065 066 // Indicates whether a request has been received to stop profiling. 067 private boolean stopProfiling; 068 069 // The time at which the capture started. 070 private long captureStartTime; 071 072 // The time at which the capture stopped. 073 private long captureStopTime; 074 075 // The number of intervals for which we have captured data. 076 private long numIntervals; 077 078 // The sampling interval that will be used by this thread. 079 private long sampleInterval; 080 081 // The set of thread stack traces captured by this profiler thread. 082 private HashMap<ProfileStack,Long> stackTraces; 083 084 // The thread that is actually performing the capture. 085 private Thread captureThread; 086 087 088 089 /** 090 * Creates a new profiler thread that will obtain stack traces at the 091 * specified interval. 092 * 093 * @param sampleInterval The length of time in milliseconds between polls 094 * for stack trace information. 095 */ 096 public ProfilerThread(long sampleInterval) 097 { 098 super("Directory Server Profiler Thread"); 099 100 101 this.sampleInterval = sampleInterval; 102 103 stackTraces = new HashMap<ProfileStack,Long>(); 104 numIntervals = 0; 105 stopProfiling = false; 106 captureStartTime = -1; 107 captureStopTime = -1; 108 captureThread = null; 109 } 110 111 112 113 /** 114 * Runs in a loop, periodically capturing a list of the stack traces for all 115 * active threads. 116 */ 117 public void run() 118 { 119 captureThread = currentThread(); 120 captureStartTime = System.currentTimeMillis(); 121 122 while (! stopProfiling) 123 { 124 // Get the current time so we can sleep more accurately. 125 long startTime = System.currentTimeMillis(); 126 127 128 // Get a stack trace of all threads that are currently active. 129 Map<Thread,StackTraceElement[]> stacks = getAllStackTraces(); 130 numIntervals++; 131 132 133 // Iterate through the threads and process their associated stack traces. 134 for (Thread t : stacks.keySet()) 135 { 136 // We don't want to capture information about the profiler thread. 137 if (t == currentThread()) 138 { 139 continue; 140 } 141 142 143 // We'll skip over any stack that doesn't have any information. 144 StackTraceElement[] threadStack = stacks.get(t); 145 if ((threadStack == null) || (threadStack.length == 0)) 146 { 147 continue; 148 } 149 150 151 // Create a profile stack for this thread stack trace and get its 152 // current count. Then put the incremented count. 153 ProfileStack profileStack = new ProfileStack(threadStack); 154 Long currentCount = stackTraces.get(profileStack); 155 if (currentCount == null) 156 { 157 // This is a new trace that we haven't seen, so its count will be 1. 158 stackTraces.put(profileStack, 1L); 159 } 160 else 161 { 162 // This is a repeated stack, so increment its count. 163 stackTraces.put(profileStack, 1L+currentCount.intValue()); 164 } 165 } 166 167 168 // Determine how long we should sleep and do so. 169 if (! stopProfiling) 170 { 171 long sleepTime = 172 sampleInterval - (System.currentTimeMillis() - startTime); 173 if (sleepTime > 0) 174 { 175 try 176 { 177 Thread.sleep(sleepTime); 178 } 179 catch (Exception e) 180 { 181 if (debugEnabled()) 182 { 183 TRACER.debugCaught(DebugLogLevel.ERROR, e); 184 } 185 } 186 } 187 } 188 } 189 190 captureStopTime = System.currentTimeMillis(); 191 captureThread = null; 192 } 193 194 195 196 /** 197 * Causes the profiler thread to stop capturing stack traces. This method 198 * will not return until the thread has stopped. 199 */ 200 public void stopProfiling() 201 { 202 stopProfiling = true; 203 204 try 205 { 206 if (captureThread != null) 207 { 208 captureThread.join(); 209 } 210 } 211 catch (Exception e) 212 { 213 if (debugEnabled()) 214 { 215 TRACER.debugCaught(DebugLogLevel.ERROR, e); 216 } 217 } 218 } 219 220 221 222 /** 223 * Writes the information captured by this profiler thread to the specified 224 * file. This should only be called after 225 * 226 * @param filename The path and name of the file to write. 227 * 228 * @throws IOException If a problem occurs while trying to write the 229 * capture data. 230 */ 231 public void writeCaptureData(String filename) 232 throws IOException 233 { 234 // Open the capture file for writing. We'll use an ASN.1 writer to write 235 // the data. 236 ASN1Writer writer = new ASN1Writer(new FileOutputStream(filename)); 237 238 239 try 240 { 241 if (captureStartTime < 0) 242 { 243 captureStartTime = System.currentTimeMillis(); 244 captureStopTime = captureStartTime; 245 } 246 else if (captureStopTime < 0) 247 { 248 captureStopTime = System.currentTimeMillis(); 249 } 250 251 252 // Write a header to the file containing the number of samples and the 253 // start and stop times. 254 ArrayList<ASN1Element> headerElements = new ArrayList<ASN1Element>(3); 255 headerElements.add(new ASN1Long(numIntervals)); 256 headerElements.add(new ASN1Long(captureStartTime)); 257 headerElements.add(new ASN1Long(captureStopTime)); 258 writer.writeElement(new ASN1Sequence(headerElements)); 259 260 261 // For each unique stack captured, write it to the file followed by the 262 // number of occurrences. 263 for (ProfileStack s : stackTraces.keySet()) 264 { 265 writer.writeElement(s.encode()); 266 writer.writeElement(new ASN1Long(stackTraces.get(s))); 267 } 268 } 269 finally 270 { 271 // Make sure to close the file when we're done. 272 writer.close(); 273 } 274 } 275 } 276