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