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