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