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-2015 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 public void run() 096 { 097 captureThread = currentThread(); 098 captureStartTime = System.currentTimeMillis(); 099 100 while (! stopProfiling) 101 { 102 // Get the current time so we can sleep more accurately. 103 long startTime = System.currentTimeMillis(); 104 105 106 // Get a stack trace of all threads that are currently active. 107 Map<Thread,StackTraceElement[]> stacks = getAllStackTraces(); 108 numIntervals++; 109 110 111 // Iterate through the threads and process their associated stack traces. 112 for (Thread t : stacks.keySet()) 113 { 114 // We don't want to capture information about the profiler thread. 115 if (t == currentThread()) 116 { 117 continue; 118 } 119 120 121 // We'll skip over any stack that doesn't have any information. 122 StackTraceElement[] threadStack = stacks.get(t); 123 if (threadStack == null || threadStack.length == 0) 124 { 125 continue; 126 } 127 128 129 // Create a profile stack for this thread stack trace and get its 130 // current count. Then put the incremented count. 131 ProfileStack profileStack = new ProfileStack(threadStack); 132 Long currentCount = stackTraces.get(profileStack); 133 if (currentCount == null) 134 { 135 // This is a new trace that we haven't seen, so its count will be 1. 136 stackTraces.put(profileStack, 1L); 137 } 138 else 139 { 140 // This is a repeated stack, so increment its count. 141 stackTraces.put(profileStack, 1L+currentCount.intValue()); 142 } 143 } 144 145 146 // Determine how long we should sleep and do so. 147 if (! stopProfiling) 148 { 149 long sleepTime = 150 sampleInterval - (System.currentTimeMillis() - startTime); 151 if (sleepTime > 0) 152 { 153 try 154 { 155 Thread.sleep(sleepTime); 156 } 157 catch (Exception e) 158 { 159 logger.traceException(e); 160 } 161 } 162 } 163 } 164 165 captureStopTime = System.currentTimeMillis(); 166 captureThread = null; 167 } 168 169 170 171 /** 172 * Causes the profiler thread to stop capturing stack traces. This method 173 * will not return until the thread has stopped. 174 */ 175 public void stopProfiling() 176 { 177 stopProfiling = true; 178 179 try 180 { 181 if (captureThread != null) 182 { 183 captureThread.join(); 184 } 185 } 186 catch (Exception e) 187 { 188 logger.traceException(e); 189 } 190 } 191 192 193 194 /** 195 * Writes the information captured by this profiler thread to the specified 196 * file. This should only be called after 197 * 198 * @param filename The path and name of the file to write. 199 * 200 * @throws IOException If a problem occurs while trying to write the 201 * capture data. 202 */ 203 public void writeCaptureData(String filename) 204 throws IOException 205 { 206 // Open the capture file for writing. We'll use an ASN.1 writer to write 207 // the data. 208 FileOutputStream fos = new FileOutputStream(filename); 209 ASN1Writer writer = ASN1.getWriter(fos); 210 211 212 try 213 { 214 if (captureStartTime < 0) 215 { 216 captureStartTime = System.currentTimeMillis(); 217 captureStopTime = captureStartTime; 218 } 219 else if (captureStopTime < 0) 220 { 221 captureStopTime = System.currentTimeMillis(); 222 } 223 224 225 // Write a header to the file containing the number of samples and the 226 // start and stop times. 227 writer.writeStartSequence(); 228 writer.writeInteger(numIntervals); 229 writer.writeInteger(captureStartTime); 230 writer.writeInteger(captureStopTime); 231 writer.writeEndSequence(); 232 233 234 // For each unique stack captured, write it to the file followed by the 235 // number of occurrences. 236 for (ProfileStack s : stackTraces.keySet()) 237 { 238 s.write(writer); 239 writer.writeInteger(stackTraces.get(s)); 240 } 241 } 242 finally 243 { 244 // Make sure to close the file when we're done. 245 close(writer, fos); 246 } 247 } 248} 249