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