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; 018import static org.opends.messages.PluginMessages.*; 019import static org.opends.server.util.StaticUtils.*; 020 021import java.io.File; 022import java.util.List; 023import java.util.Set; 024 025import org.forgerock.i18n.LocalizableMessage; 026import org.forgerock.i18n.slf4j.LocalizedLogger; 027import org.forgerock.opendj.config.server.ConfigException; 028import org.forgerock.opendj.config.server.ConfigurationChangeListener; 029import org.forgerock.opendj.server.config.meta.PluginCfgDefn; 030import org.forgerock.opendj.server.config.server.PluginCfg; 031import org.forgerock.opendj.server.config.server.ProfilerPluginCfg; 032import org.opends.server.api.plugin.DirectoryServerPlugin; 033import org.opends.server.api.plugin.PluginResult; 034import org.opends.server.api.plugin.PluginType; 035import org.forgerock.opendj.config.server.ConfigChangeResult; 036import org.forgerock.opendj.ldap.DN; 037import org.opends.server.types.DirectoryConfig; 038import org.opends.server.util.TimeThread; 039 040/** 041 * This class defines a Directory Server startup plugin that will register 042 * itself as a configurable component that can allow for a simple sample-based 043 * profiling mechanism within the Directory Server. When profiling is enabled, 044 * the server will periodically (e.g., every few milliseconds) retrieve all the 045 * stack traces for all threads in the server and aggregates them so that they 046 * can be analyzed to see where the server is spending all of its processing 047 * time. 048 */ 049public final class ProfilerPlugin 050 extends DirectoryServerPlugin<ProfilerPluginCfg> 051 implements ConfigurationChangeListener<ProfilerPluginCfg> 052{ 053 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 054 055 /** 056 * The value to use for the profiler action when no action is necessary. 057 */ 058 public static final String PROFILE_ACTION_NONE = "none"; 059 060 061 062 /** 063 * The value to use for the profiler action when it should start capturing 064 * information. 065 */ 066 public static final String PROFILE_ACTION_START = "start"; 067 068 069 070 /** 071 * The value to use for the profiler action when it should stop capturing 072 * data and write the information it has collected to disk. 073 */ 074 public static final String PROFILE_ACTION_STOP = "stop"; 075 076 077 078 /** 079 * The value to use for the profiler action when it should stop capturing 080 * data and discard any information that has been collected. 081 */ 082 public static final String PROFILE_ACTION_CANCEL = "cancel"; 083 084 085 086 /** The DN of the configuration entry for this plugin. */ 087 private DN configEntryDN; 088 089 /** The current configuration for this plugin. */ 090 private ProfilerPluginCfg currentConfig; 091 092 /** The thread that is actually capturing the profile information. */ 093 private ProfilerThread profilerThread; 094 095 096 097 /** 098 * Creates a new instance of this Directory Server plugin. Every plugin must 099 * implement a default constructor (it is the only one that will be used to 100 * create plugins defined in the configuration), and every plugin constructor 101 * must call <CODE>super()</CODE> as its first element. 102 */ 103 public ProfilerPlugin() 104 { 105 super(); 106 107 } 108 109 110 111 /** {@inheritDoc} */ 112 @Override 113 public final void initializePlugin(Set<PluginType> pluginTypes, 114 ProfilerPluginCfg configuration) 115 throws ConfigException 116 { 117 configuration.addProfilerChangeListener(this); 118 119 currentConfig = configuration; 120 configEntryDN = configuration.dn(); 121 122 123 // Make sure that this plugin is only registered as a startup plugin. 124 if (pluginTypes.isEmpty()) 125 { 126 throw new ConfigException(ERR_PLUGIN_PROFILER_NO_PLUGIN_TYPES.get(configEntryDN)); 127 } 128 else 129 { 130 for (PluginType t : pluginTypes) 131 { 132 if (t != PluginType.STARTUP) 133 { 134 throw new ConfigException(ERR_PLUGIN_PROFILER_INVALID_PLUGIN_TYPE.get(configEntryDN, t)); 135 } 136 } 137 } 138 139 140 // Make sure that the profile directory exists. 141 File profileDirectory = getFileForPath(configuration.getProfileDirectory()); 142 if (!profileDirectory.exists() || !profileDirectory.isDirectory()) 143 { 144 LocalizableMessage message = WARN_PLUGIN_PROFILER_INVALID_PROFILE_DIR.get( 145 profileDirectory.getAbsolutePath(), configEntryDN); 146 throw new ConfigException(message); 147 } 148 } 149 150 151 152 /** {@inheritDoc} */ 153 @Override 154 public final void finalizePlugin() 155 { 156 currentConfig.removeProfilerChangeListener(this); 157 158 // If the profiler thread is still active, then cause it to dump the 159 // information it has captured and exit. 160 synchronized (this) 161 { 162 if (profilerThread != null) 163 { 164 profilerThread.stopProfiling(); 165 166 String filename = currentConfig.getProfileDirectory() + File.separator + 167 "profile." + TimeThread.getGMTTime(); 168 try 169 { 170 profilerThread.writeCaptureData(filename); 171 } 172 catch (Exception e) 173 { 174 logger.traceException(e); 175 176 logger.error(ERR_PLUGIN_PROFILER_CANNOT_WRITE_PROFILE_DATA, configEntryDN, filename, 177 stackTraceToSingleLineString(e)); 178 } 179 } 180 } 181 } 182 183 184 185 /** {@inheritDoc} */ 186 @Override 187 public final PluginResult.Startup doStartup() 188 { 189 ProfilerPluginCfg config = currentConfig; 190 191 // If the profiler should be started automatically, then do so now. 192 if (config.isEnableProfilingOnStartup()) 193 { 194 profilerThread = new ProfilerThread(config.getProfileSampleInterval()); 195 profilerThread.start(); 196 } 197 198 return PluginResult.Startup.continueStartup(); 199 } 200 201 202 203 /** {@inheritDoc} */ 204 @Override 205 public boolean isConfigurationAcceptable(PluginCfg configuration, 206 List<LocalizableMessage> unacceptableReasons) 207 { 208 ProfilerPluginCfg config = (ProfilerPluginCfg) configuration; 209 return isConfigurationChangeAcceptable(config, unacceptableReasons); 210 } 211 212 213 214 /** {@inheritDoc} */ 215 @Override 216 public boolean isConfigurationChangeAcceptable( 217 ProfilerPluginCfg configuration, 218 List<LocalizableMessage> unacceptableReasons) 219 { 220 boolean configAcceptable = true; 221 DN cfgEntryDN = configuration.dn(); 222 223 // Make sure that the plugin is only registered as a startup plugin. 224 if (configuration.getPluginType().isEmpty()) 225 { 226 unacceptableReasons.add(ERR_PLUGIN_PROFILER_NO_PLUGIN_TYPES.get(cfgEntryDN)); 227 configAcceptable = false; 228 } 229 else 230 { 231 for (PluginCfgDefn.PluginType t : configuration.getPluginType()) 232 { 233 if (t != PluginCfgDefn.PluginType.STARTUP) 234 { 235 unacceptableReasons.add(ERR_PLUGIN_PROFILER_INVALID_PLUGIN_TYPE.get(cfgEntryDN, t)); 236 configAcceptable = false; 237 break; 238 } 239 } 240 } 241 242 243 // Make sure that the profile directory exists. 244 File profileDirectory = getFileForPath(configuration.getProfileDirectory()); 245 if (!profileDirectory.exists() || !profileDirectory.isDirectory()) 246 { 247 unacceptableReasons.add(WARN_PLUGIN_PROFILER_INVALID_PROFILE_DIR.get( 248 profileDirectory.getAbsolutePath(), cfgEntryDN)); 249 configAcceptable = false; 250 } 251 252 return configAcceptable; 253 } 254 255 256 257 /** 258 * Applies the configuration changes to this change listener. 259 * 260 * @param configuration 261 * The new configuration containing the changes. 262 * @return Returns information about the result of changing the 263 * configuration. 264 */ 265 @Override 266 public ConfigChangeResult applyConfigurationChange( 267 ProfilerPluginCfg configuration) 268 { 269 final ConfigChangeResult ccr = new ConfigChangeResult(); 270 271 currentConfig = configuration; 272 273 // See if we need to perform any action. 274 switch (configuration.getProfileAction()) 275 { 276 case START: 277 // See if the profiler thread is running. If so, then don't do 278 // anything. Otherwise, start it. 279 synchronized (this) 280 { 281 if (profilerThread == null) 282 { 283 profilerThread = 284 new ProfilerThread(configuration.getProfileSampleInterval()); 285 profilerThread.start(); 286 287 ccr.addMessage(INFO_PLUGIN_PROFILER_STARTED_PROFILING.get(configEntryDN)); 288 } 289 else 290 { 291 ccr.addMessage(INFO_PLUGIN_PROFILER_ALREADY_PROFILING.get(configEntryDN)); 292 } 293 } 294 break; 295 296 case STOP: 297 // See if the profiler thread is running. If so, then stop it and write 298 // the information captured to disk. Otherwise, don't do anything. 299 synchronized (this) 300 { 301 if (profilerThread == null) 302 { 303 ccr.addMessage(INFO_PLUGIN_PROFILER_NOT_RUNNING.get(configEntryDN)); 304 } 305 else 306 { 307 profilerThread.stopProfiling(); 308 309 ccr.addMessage(INFO_PLUGIN_PROFILER_STOPPED_PROFILING.get(configEntryDN)); 310 311 String filename = 312 getFileForPath( 313 configuration.getProfileDirectory()).getAbsolutePath() + 314 File.separator + "profile." + TimeThread.getGMTTime(); 315 316 try 317 { 318 profilerThread.writeCaptureData(filename); 319 320 ccr.addMessage(INFO_PLUGIN_PROFILER_WROTE_PROFILE_DATA.get(configEntryDN, filename)); 321 } 322 catch (Exception e) 323 { 324 logger.traceException(e); 325 326 ccr.addMessage(ERR_PLUGIN_PROFILER_CANNOT_WRITE_PROFILE_DATA.get( 327 configEntryDN, filename, stackTraceToSingleLineString(e))); 328 ccr.setResultCode(DirectoryConfig.getServerErrorResultCode()); 329 } 330 331 profilerThread = null; 332 } 333 } 334 break; 335 336 case CANCEL: 337 // See if the profiler thread is running. If so, then stop it but don't 338 // write anything to disk. Otherwise, don't do anything. 339 synchronized (this) 340 { 341 if (profilerThread == null) 342 { 343 ccr.addMessage(INFO_PLUGIN_PROFILER_NOT_RUNNING.get(configEntryDN)); 344 } 345 else 346 { 347 profilerThread.stopProfiling(); 348 349 ccr.addMessage(INFO_PLUGIN_PROFILER_STOPPED_PROFILING.get(configEntryDN)); 350 351 profilerThread = null; 352 } 353 } 354 break; 355 } 356 357 return ccr; 358 } 359}