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