001/*
002 * Copyright 2008-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.unboundidds.monitors;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031
032import com.unboundid.ldap.sdk.Attribute;
033import com.unboundid.ldap.sdk.Entry;
034import com.unboundid.util.Debug;
035import com.unboundid.util.NotMutable;
036import com.unboundid.util.StaticUtils;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039
040import static com.unboundid.ldap.sdk.unboundidds.monitors.MonitorMessages.*;
041
042
043
044/**
045 * This class defines a monitor entry that provides access to the Directory
046 * Server stack trace information.  The information that is available through
047 * this monitor is roughly the equivalent of what can be accessed using the
048 * {@link Thread#getAllStackTraces} method.  See the {@link ThreadStackTrace}
049 * class for more information about what is available in each stack trace.
050 * <BR>
051 * <BLOCKQUOTE>
052 *   <B>NOTE:</B>  This class, and other classes within the
053 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
054 *   supported for use against Ping Identity, UnboundID, and
055 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
056 *   for proprietary functionality or for external specifications that are not
057 *   considered stable or mature enough to be guaranteed to work in an
058 *   interoperable way with other types of LDAP servers.
059 * </BLOCKQUOTE>
060 * <BR>
061 * The server should present at most one stack trace monitor entry.  It can be
062 * retrieved using the {@link MonitorManager#getStackTraceMonitorEntry} method.
063 * The {@link StackTraceMonitorEntry#getStackTraces} method can be used to
064 * retrieve the stack traces for each thread.  Alternately, this information may
065 * be accessed using the generic API (although in this case, only the string
066 * representations of each stack trace frame are available).  See the
067 * {@link MonitorManager} class documentation for an example that demonstrates
068 * the use of the generic API for accessing monitor data.
069 */
070@NotMutable()
071@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
072public final class StackTraceMonitorEntry
073       extends MonitorEntry
074{
075  /**
076   * The structural object class used in stack trace monitor entries.
077   */
078  static final String STACK_TRACE_MONITOR_OC =
079       "ds-stack-trace-monitor-entry";
080
081
082
083  /**
084   * The name of the attribute that contains the JVM stack trace for each
085   * thread.
086   */
087  private static final String ATTR_JVM_STACK_TRACE = "jvmThread";
088
089
090
091  /**
092   * The serial version UID for this serializable class.
093   */
094  private static final long serialVersionUID = -9008690818438183908L;
095
096
097
098  // The list of thread stack traces.
099  private final List<ThreadStackTrace> stackTraces;
100
101
102
103  /**
104   * Creates a new stack trace monitor entry from the provided entry.
105   *
106   * @param  entry  The entry to be parsed as a stack trace monitor entry.
107   *                It must not be {@code null}.
108   */
109  public StackTraceMonitorEntry(final Entry entry)
110  {
111    super(entry);
112
113    final List<String> traceLines = getStrings(ATTR_JVM_STACK_TRACE);
114    if (traceLines.isEmpty())
115    {
116      stackTraces = Collections.emptyList();
117    }
118    else
119    {
120      final ArrayList<ThreadStackTrace> traces = new ArrayList<>(100);
121
122      try
123      {
124        int currentThreadID = -1;
125        String currentName  = null;
126        ArrayList<StackTraceElement> currentElements = new ArrayList<>(20);
127        for (final String line : traceLines)
128        {
129          final int equalPos = line.indexOf('=');
130          final int spacePos = line.indexOf(' ', equalPos);
131          final int id = Integer.parseInt(line.substring(equalPos+1, spacePos));
132          if (id != currentThreadID)
133          {
134            if (currentThreadID >= 0)
135            {
136              traces.add(new ThreadStackTrace(currentThreadID, currentName,
137                                              currentElements));
138            }
139
140            currentThreadID = id;
141            currentElements = new ArrayList<>(20);
142
143            final int dashesPos1 = line.indexOf("---------- ", spacePos);
144            final int dashesPos2 = line.indexOf(" ----------", dashesPos1);
145            currentName = line.substring((dashesPos1 + 11), dashesPos2);
146          }
147          else
148          {
149            final int bePos = line.indexOf("]=");
150            final String traceLine = line.substring(bePos+2);
151
152            final String fileName;
153            int lineNumber          = -1;
154            final int closeParenPos = traceLine.lastIndexOf(')');
155            final int openParenPos  = traceLine.lastIndexOf('(', closeParenPos);
156            final int colonPos      = traceLine.lastIndexOf(':', closeParenPos);
157            if (colonPos < 0)
158            {
159              fileName = traceLine.substring(openParenPos+1, closeParenPos);
160            }
161            else
162            {
163              fileName = traceLine.substring(openParenPos+1, colonPos);
164
165              final String lineNumberStr =
166                   traceLine.substring(colonPos+1, closeParenPos);
167              if (lineNumberStr.equalsIgnoreCase("native"))
168              {
169                lineNumber = -2;
170              }
171              else
172              {
173                try
174                {
175                  lineNumber = Integer.parseInt(lineNumberStr);
176                } catch (final Exception e) {}
177              }
178            }
179
180            final int periodPos     = traceLine.lastIndexOf('.', openParenPos);
181            final String className  = traceLine.substring(0, periodPos);
182            final String methodName =
183                 traceLine.substring(periodPos+1, openParenPos);
184
185            currentElements.add(new StackTraceElement(className, methodName,
186                                                      fileName, lineNumber));
187          }
188        }
189
190        if (currentThreadID >= 0)
191        {
192          traces.add(new ThreadStackTrace(currentThreadID, currentName,
193                                          currentElements));
194        }
195      }
196      catch (final Exception e)
197      {
198        Debug.debugException(e);
199      }
200
201      stackTraces = Collections.unmodifiableList(traces);
202    }
203  }
204
205
206
207  /**
208   * Retrieves the list of thread stack traces.
209   *
210   * @return  The list of thread stack traces, or an empty list if it was not
211   *          included in the monitor entry or a problem occurs while decoding
212   *          the stack traces.
213   */
214  public List<ThreadStackTrace> getStackTraces()
215  {
216    return stackTraces;
217  }
218
219
220
221  /**
222   * {@inheritDoc}
223   */
224  @Override()
225  public String getMonitorDisplayName()
226  {
227    return INFO_STACK_TRACE_MONITOR_DISPNAME.get();
228  }
229
230
231
232  /**
233   * {@inheritDoc}
234   */
235  @Override()
236  public String getMonitorDescription()
237  {
238    return INFO_STACK_TRACE_MONITOR_DESC.get();
239  }
240
241
242
243  /**
244   * {@inheritDoc}
245   */
246  @Override()
247  public Map<String,MonitorAttribute> getMonitorAttributes()
248  {
249    final LinkedHashMap<String,MonitorAttribute> attrs =
250         new LinkedHashMap<>(StaticUtils.computeMapCapacity(1));
251
252    final Attribute traceAttr = getEntry().getAttribute(ATTR_JVM_STACK_TRACE);
253    if (traceAttr != null)
254    {
255      addMonitorAttribute(attrs,
256           ATTR_JVM_STACK_TRACE,
257           INFO_STACK_TRACE_DISPNAME_TRACE.get(),
258           INFO_STACK_TRACE_DESC_TRACE.get(),
259           Collections.unmodifiableList(Arrays.asList(traceAttr.getValues())));
260    }
261
262    return Collections.unmodifiableMap(attrs);
263  }
264}