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