/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2008 Sun Microsystems, Inc.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.opends.server.plugins.profiler;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.opends.server.api.DirectoryThread;
import org.forgerock.opendj.io.*;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import static org.opends.server.util.StaticUtils.*;
/**
* This class defines a thread that may be used to actually perform
* profiling in the Directory Server. When activated, it will repeatedly
* retrieve thread stack traces and store them so that they can be written out
* and analyzed with a separate utility.
*/
public class ProfilerThread
extends DirectoryThread
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** Indicates whether a request has been received to stop profiling. */
private boolean stopProfiling;
/** The time at which the capture started. */
private long captureStartTime;
/** The time at which the capture stopped. */
private long captureStopTime;
/** The number of intervals for which we have captured data. */
private long numIntervals;
/** The sampling interval that will be used by this thread. */
private long sampleInterval;
/** The set of thread stack traces captured by this profiler thread. */
private HashMap<ProfileStack,Long> stackTraces;
/** The thread that is actually performing the capture. */
private Thread captureThread;
/**
* Creates a new profiler thread that will obtain stack traces at the
* specified interval.
*
* @param sampleInterval The length of time in milliseconds between polls
* for stack trace information.
*/
public ProfilerThread(long sampleInterval)
{
super("Directory Server Profiler Thread");
this.sampleInterval = sampleInterval;
stackTraces = new HashMap<>();
numIntervals = 0;
stopProfiling = false;
captureStartTime = -1;
captureStopTime = -1;
captureThread = null;
}
/**
* Runs in a loop, periodically capturing a list of the stack traces for all
* active threads.
*/
public void run()
{
captureThread = currentThread();
captureStartTime = System.currentTimeMillis();
while (! stopProfiling)
{
// Get the current time so we can sleep more accurately.
long startTime = System.currentTimeMillis();
// Get a stack trace of all threads that are currently active.
Map<Thread,StackTraceElement[]> stacks = getAllStackTraces();
numIntervals++;
// Iterate through the threads and process their associated stack traces.
for (Thread t : stacks.keySet())
{
// We don't want to capture information about the profiler thread.
if (t == currentThread())
{
continue;
}
// We'll skip over any stack that doesn't have any information.
StackTraceElement[] threadStack = stacks.get(t);
if (threadStack == null || threadStack.length == 0)
{
continue;
}
// Create a profile stack for this thread stack trace and get its
// current count. Then put the incremented count.
ProfileStack profileStack = new ProfileStack(threadStack);
Long currentCount = stackTraces.get(profileStack);
if (currentCount == null)
{
// This is a new trace that we haven't seen, so its count will be 1.
stackTraces.put(profileStack, 1L);
}
else
{
// This is a repeated stack, so increment its count.
stackTraces.put(profileStack, 1L+currentCount.intValue());
}
}
// Determine how long we should sleep and do so.
if (! stopProfiling)
{
long sleepTime =
sampleInterval - (System.currentTimeMillis() - startTime);
if (sleepTime > 0)
{
try
{
Thread.sleep(sleepTime);
}
catch (Exception e)
{
logger.traceException(e);
}
}
}
}
captureStopTime = System.currentTimeMillis();
captureThread = null;
}
/**
* Causes the profiler thread to stop capturing stack traces. This method
* will not return until the thread has stopped.
*/
public void stopProfiling()
{
stopProfiling = true;
try
{
if (captureThread != null)
{
captureThread.join();
}
}
catch (Exception e)
{
logger.traceException(e);
}
}
/**
* Writes the information captured by this profiler thread to the specified
* file. This should only be called after
*
* @param filename The path and name of the file to write.
*
* @throws IOException If a problem occurs while trying to write the
* capture data.
*/
public void writeCaptureData(String filename)
throws IOException
{
// Open the capture file for writing. We'll use an ASN.1 writer to write
// the data.
FileOutputStream fos = new FileOutputStream(filename);
ASN1Writer writer = ASN1.getWriter(fos);
try
{
if (captureStartTime < 0)
{
captureStartTime = System.currentTimeMillis();
captureStopTime = captureStartTime;
}
else if (captureStopTime < 0)
{
captureStopTime = System.currentTimeMillis();
}
// Write a header to the file containing the number of samples and the
// start and stop times.
writer.writeStartSequence();
writer.writeInteger(numIntervals);
writer.writeInteger(captureStartTime);
writer.writeInteger(captureStopTime);
writer.writeEndSequence();
// For each unique stack captured, write it to the file followed by the
// number of occurrences.
for (ProfileStack s : stackTraces.keySet())
{
s.write(writer);
writer.writeInteger(stackTraces.get(s));
}
}
finally
{
// Make sure to close the file when we're done.
close(writer, fos);
}
}
}