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