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