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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.plugins.profiler; 028 import org.opends.messages.Message; 029 030 031 032 import java.io.File; 033 import java.util.ArrayList; 034 import java.util.List; 035 import java.util.Set; 036 037 import org.opends.server.admin.server.ConfigurationChangeListener; 038 import org.opends.server.admin.std.meta.PluginCfgDefn; 039 import org.opends.server.admin.std.server.PluginCfg; 040 import org.opends.server.admin.std.server.ProfilerPluginCfg; 041 import org.opends.server.api.plugin.DirectoryServerPlugin; 042 import org.opends.server.api.plugin.PluginType; 043 import org.opends.server.api.plugin.PluginResult; 044 import org.opends.server.config.ConfigException; 045 import org.opends.server.types.ConfigChangeResult; 046 import org.opends.server.types.DirectoryConfig; 047 import org.opends.server.types.DN; 048 import org.opends.server.types.ResultCode; 049 import org.opends.server.util.TimeThread; 050 051 import org.opends.server.types.DebugLogLevel; 052 import static org.opends.server.loggers.debug.DebugLogger.*; 053 import org.opends.server.loggers.debug.DebugTracer; 054 import org.opends.server.loggers.ErrorLogger; 055 import static org.opends.messages.PluginMessages.*; 056 057 import static org.opends.server.util.StaticUtils.*; 058 059 060 061 /** 062 * This class defines a Directory Server startup plugin that will register 063 * itself as a configurable component that can allow for a simple sample-based 064 * profiling mechanism within the Directory Server. When profiling is enabled, 065 * the server will periodically (e.g., every few milliseconds) retrieve all the 066 * stack traces for all threads in the server and aggregates them so that they 067 * can be analyzed to see where the server is spending all of its processing 068 * time. 069 */ 070 public final class ProfilerPlugin 071 extends DirectoryServerPlugin<ProfilerPluginCfg> 072 implements ConfigurationChangeListener<ProfilerPluginCfg> 073 { 074 /** 075 * The tracer object for the debug logger. 076 */ 077 private static final DebugTracer TRACER = getTracer(); 078 079 /** 080 * The value to use for the profiler action when no action is necessary. 081 */ 082 public static final String PROFILE_ACTION_NONE = "none"; 083 084 085 086 /** 087 * The value to use for the profiler action when it should start capturing 088 * information. 089 */ 090 public static final String PROFILE_ACTION_START = "start"; 091 092 093 094 /** 095 * The value to use for the profiler action when it should stop capturing 096 * data and write the information it has collected to disk. 097 */ 098 public static final String PROFILE_ACTION_STOP = "stop"; 099 100 101 102 /** 103 * The value to use for the profiler action when it should stop capturing 104 * data and discard any information that has been collected. 105 */ 106 public static final String PROFILE_ACTION_CANCEL = "cancel"; 107 108 109 110 // The DN of the configuration entry for this plugin. 111 private DN configEntryDN; 112 113 // The current configuration for this plugin. 114 private ProfilerPluginCfg currentConfig; 115 116 // The thread that is actually capturing the profile information. 117 private ProfilerThread profilerThread; 118 119 120 121 /** 122 * Creates a new instance of this Directory Server plugin. Every plugin must 123 * implement a default constructor (it is the only one that will be used to 124 * create plugins defined in the configuration), and every plugin constructor 125 * must call <CODE>super()</CODE> as its first element. 126 */ 127 public ProfilerPlugin() 128 { 129 super(); 130 131 } 132 133 134 135 /** 136 * {@inheritDoc} 137 */ 138 @Override() 139 public final void initializePlugin(Set<PluginType> pluginTypes, 140 ProfilerPluginCfg configuration) 141 throws ConfigException 142 { 143 configuration.addProfilerChangeListener(this); 144 145 currentConfig = configuration; 146 configEntryDN = configuration.dn(); 147 148 149 // Make sure that this plugin is only registered as a startup plugin. 150 if (pluginTypes.isEmpty()) 151 { 152 Message message = ERR_PLUGIN_PROFILER_NO_PLUGIN_TYPES.get( 153 String.valueOf(configEntryDN)); 154 throw new ConfigException(message); 155 } 156 else 157 { 158 for (PluginType t : pluginTypes) 159 { 160 if (t != PluginType.STARTUP) 161 { 162 Message message = ERR_PLUGIN_PROFILER_INVALID_PLUGIN_TYPE.get( 163 String.valueOf(configEntryDN), String.valueOf(t)); 164 throw new ConfigException(message); 165 } 166 } 167 } 168 169 170 // Make sure that the profile directory exists. 171 File profileDirectory = getFileForPath(configuration.getProfileDirectory()); 172 if (! (profileDirectory.exists() && profileDirectory.isDirectory())) 173 { 174 Message message = WARN_PLUGIN_PROFILER_INVALID_PROFILE_DIR.get( 175 profileDirectory.getAbsolutePath(), String.valueOf(configEntryDN)); 176 throw new ConfigException(message); 177 } 178 } 179 180 181 182 /** 183 * {@inheritDoc} 184 */ 185 @Override() 186 public final void finalizePlugin() 187 { 188 currentConfig.removeProfilerChangeListener(this); 189 190 // If the profiler thread is still active, then cause it to dump the 191 // information it has captured and exit. 192 synchronized (this) 193 { 194 if (profilerThread != null) 195 { 196 profilerThread.stopProfiling(); 197 198 String filename = currentConfig.getProfileDirectory() + File.separator + 199 "profile." + TimeThread.getGMTTime(); 200 try 201 { 202 profilerThread.writeCaptureData(filename); 203 } 204 catch (Exception e) 205 { 206 if (debugEnabled()) 207 { 208 TRACER.debugCaught(DebugLogLevel.ERROR, e); 209 } 210 211 Message message = ERR_PLUGIN_PROFILER_CANNOT_WRITE_PROFILE_DATA. 212 get(String.valueOf(configEntryDN), filename, 213 stackTraceToSingleLineString(e)); 214 ErrorLogger.logError(message); 215 } 216 } 217 } 218 } 219 220 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override() 226 public final PluginResult.Startup doStartup() 227 { 228 ProfilerPluginCfg config = currentConfig; 229 230 // If the profiler should be started automatically, then do so now. 231 if (config.isEnableProfilingOnStartup()) 232 { 233 profilerThread = new ProfilerThread(config.getProfileSampleInterval()); 234 profilerThread.start(); 235 } 236 237 return PluginResult.Startup.continueStartup(); 238 } 239 240 241 242 /** 243 * {@inheritDoc} 244 */ 245 @Override() 246 public boolean isConfigurationAcceptable(PluginCfg configuration, 247 List<Message> unacceptableReasons) 248 { 249 ProfilerPluginCfg config = (ProfilerPluginCfg) configuration; 250 return isConfigurationChangeAcceptable(config, unacceptableReasons); 251 } 252 253 254 255 /** 256 * {@inheritDoc} 257 */ 258 public boolean isConfigurationChangeAcceptable( 259 ProfilerPluginCfg configuration, 260 List<Message> unacceptableReasons) 261 { 262 boolean configAcceptable = true; 263 DN cfgEntryDN = configuration.dn(); 264 265 // Make sure that the plugin is only registered as a startup plugin. 266 if (configuration.getPluginType().isEmpty()) 267 { 268 Message message = ERR_PLUGIN_PROFILER_NO_PLUGIN_TYPES.get( 269 String.valueOf(cfgEntryDN)); 270 unacceptableReasons.add(message); 271 configAcceptable = false; 272 } 273 else 274 { 275 for (PluginCfgDefn.PluginType t : configuration.getPluginType()) 276 { 277 if (t != PluginCfgDefn.PluginType.STARTUP) 278 { 279 Message message = ERR_PLUGIN_PROFILER_INVALID_PLUGIN_TYPE.get( 280 String.valueOf(cfgEntryDN), 281 String.valueOf(t)); 282 unacceptableReasons.add(message); 283 configAcceptable = false; 284 break; 285 } 286 } 287 } 288 289 290 // Make sure that the profile directory exists. 291 File profileDirectory = getFileForPath(configuration.getProfileDirectory()); 292 if (! (profileDirectory.exists() && profileDirectory.isDirectory())) 293 { 294 unacceptableReasons.add(WARN_PLUGIN_PROFILER_INVALID_PROFILE_DIR.get( 295 profileDirectory.getAbsolutePath(), 296 String.valueOf(cfgEntryDN))); 297 configAcceptable = false; 298 } 299 300 return configAcceptable; 301 } 302 303 304 305 /** 306 * Applies the configuration changes to this change listener. 307 * 308 * @param configuration 309 * The new configuration containing the changes. 310 * @return Returns information about the result of changing the 311 * configuration. 312 */ 313 public ConfigChangeResult applyConfigurationChange( 314 ProfilerPluginCfg configuration) 315 { 316 ResultCode resultCode = ResultCode.SUCCESS; 317 boolean adminActionRequired = false; 318 ArrayList<Message> messages = new ArrayList<Message>(); 319 320 currentConfig = configuration; 321 322 // See if we need to perform any action. 323 switch (configuration.getProfileAction()) 324 { 325 case START: 326 // See if the profiler thread is running. If so, then don't do 327 // anything. Otherwise, start it. 328 synchronized (this) 329 { 330 if (profilerThread == null) 331 { 332 profilerThread = 333 new ProfilerThread(configuration.getProfileSampleInterval()); 334 profilerThread.start(); 335 336 messages.add(INFO_PLUGIN_PROFILER_STARTED_PROFILING.get( 337 String.valueOf(configEntryDN))); 338 } 339 else 340 { 341 messages.add(INFO_PLUGIN_PROFILER_ALREADY_PROFILING.get( 342 String.valueOf(configEntryDN))); 343 } 344 } 345 break; 346 347 case STOP: 348 // See if the profiler thread is running. If so, then stop it and write 349 // the information captured to disk. Otherwise, don't do anything. 350 synchronized (this) 351 { 352 if (profilerThread == null) 353 { 354 messages.add(INFO_PLUGIN_PROFILER_NOT_RUNNING.get( 355 String.valueOf(configEntryDN))); 356 } 357 else 358 { 359 profilerThread.stopProfiling(); 360 361 messages.add(INFO_PLUGIN_PROFILER_STOPPED_PROFILING.get( 362 String.valueOf(configEntryDN))); 363 364 String filename = 365 getFileForPath( 366 configuration.getProfileDirectory()).getAbsolutePath() + 367 File.separator + "profile." + TimeThread.getGMTTime(); 368 369 try 370 { 371 profilerThread.writeCaptureData(filename); 372 373 messages.add(INFO_PLUGIN_PROFILER_WROTE_PROFILE_DATA.get( 374 String.valueOf(configEntryDN), 375 filename)); 376 } 377 catch (Exception e) 378 { 379 if (debugEnabled()) 380 { 381 TRACER.debugCaught(DebugLogLevel.ERROR, e); 382 } 383 384 messages.add(ERR_PLUGIN_PROFILER_CANNOT_WRITE_PROFILE_DATA.get( 385 String.valueOf(configEntryDN), 386 filename, 387 stackTraceToSingleLineString(e))); 388 389 resultCode = DirectoryConfig.getServerErrorResultCode(); 390 } 391 392 profilerThread = null; 393 } 394 } 395 break; 396 397 case CANCEL: 398 // See if the profiler thread is running. If so, then stop it but don't 399 // write anything to disk. Otherwise, don't do anything. 400 synchronized (this) 401 { 402 if (profilerThread == null) 403 { 404 messages.add(INFO_PLUGIN_PROFILER_NOT_RUNNING.get( 405 String.valueOf(configEntryDN))); 406 } 407 else 408 { 409 profilerThread.stopProfiling(); 410 411 messages.add(INFO_PLUGIN_PROFILER_STOPPED_PROFILING.get( 412 String.valueOf(configEntryDN))); 413 414 profilerThread = null; 415 } 416 } 417 break; 418 } 419 420 return new ConfigChangeResult(resultCode, adminActionRequired, messages); 421 } 422 } 423