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