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 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.monitors;
028
029
030
031import java.lang.management.GarbageCollectorMXBean;
032import java.lang.management.ManagementFactory;
033import java.lang.management.MemoryPoolMXBean;
034import java.lang.management.MemoryUsage;
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.List;
038import java.util.concurrent.TimeUnit;
039
040import org.forgerock.opendj.config.server.ConfigException;
041import org.opends.server.admin.std.server.MemoryUsageMonitorProviderCfg;
042import org.opends.server.api.MonitorProvider;
043import org.opends.server.types.Attribute;
044import org.opends.server.types.Attributes;
045import org.opends.server.types.InitializationException;
046
047/**
048 * This class defines a monitor provider that reports information about
049 * Directory Server memory usage.
050 */
051public class MemoryUsageMonitorProvider
052       extends MonitorProvider<MemoryUsageMonitorProviderCfg>
053       implements Runnable
054{
055  /** A map of the last GC counts seen by this monitor for calculating recent stats. */
056  private HashMap<String,Long> lastGCCounts = new HashMap<>();
057  /** A map of the last GC times seen by this monitor for calculating recent stats. */
058  private HashMap<String,Long> lastGCTimes = new HashMap<>();
059  /** A map of the most recent GC durations seen by this monitor. */
060  private HashMap<String,Long> recentGCDurations = new HashMap<>();
061  /** A map of the memory manager names to names that are safe for use in attribute names. */
062  private HashMap<String,String> gcSafeNames = new HashMap<>();
063
064
065  /** {@inheritDoc} */
066  public void initializeMonitorProvider(
067                   MemoryUsageMonitorProviderCfg configuration)
068         throws ConfigException, InitializationException
069  {
070    scheduleUpdate(this, 0, 1, TimeUnit.SECONDS);
071  }
072
073  /** {@inheritDoc} */
074  @Override
075  public String getMonitorInstanceName()
076  {
077    return "JVM Memory Usage";
078  }
079
080
081  /** {@inheritDoc} */
082  public void run()
083  {
084    for (GarbageCollectorMXBean gc :
085         ManagementFactory.getGarbageCollectorMXBeans())
086    {
087      String gcName  = gc.getName();
088      long   gcCount = gc.getCollectionCount();
089      long   gcTime  = gc.getCollectionTime();
090
091      long lastGCCount      = 0L;
092      long lastGCTime       = 0L;
093      long recentGCDuration = 0L;
094      if (lastGCCounts.containsKey(gcName))
095      {
096        lastGCCount      = lastGCCounts.get(gcName);
097        lastGCTime       = lastGCTimes.get(gcName);
098        recentGCDuration = recentGCDurations.get(gcName);
099      }
100
101      if (gcCount > lastGCCount)
102      {
103        long recentGCCount = gcCount - lastGCCount;
104        long recentGCTime  = gcTime  - lastGCTime;
105        recentGCDuration   = recentGCTime / recentGCCount;
106      }
107
108      lastGCCounts.put(gcName, gcCount);
109      lastGCTimes.put(gcName, gcTime);
110      recentGCDurations.put(gcName, recentGCDuration);
111    }
112  }
113
114
115
116  @Override
117  public List<Attribute> getMonitorData()
118  {
119    ArrayList<Attribute> attrs = new ArrayList<>();
120
121    for (GarbageCollectorMXBean gc :
122         ManagementFactory.getGarbageCollectorMXBeans())
123    {
124      String gcName  = gc.getName();
125      long   gcCount = gc.getCollectionCount();
126      long   gcTime  = gc.getCollectionTime();
127
128      long avgGCDuration = 0L;
129      if (gcCount > 0)
130      {
131        avgGCDuration = gcTime / gcCount;
132      }
133
134      long recentGCDuration = 0L;
135      if (recentGCDurations.containsKey(gcName))
136      {
137        recentGCDuration = recentGCDurations.get(gcName);
138      }
139
140      String safeName = gcSafeNames.get(gcName);
141      if (safeName == null)
142      {
143        safeName = generateSafeName(gcName);
144        gcSafeNames.put(gcName, safeName);
145      }
146
147      attrs.add(createAttribute(safeName + "-total-collection-count", gcCount));
148      attrs.add(createAttribute(safeName + "-total-collection-duration", gcTime));
149      attrs.add(createAttribute(safeName + "-average-collection-duration", avgGCDuration));
150      attrs.add(createAttribute(safeName + "-recent-collection-duration", recentGCDuration));
151    }
152
153    for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans())
154    {
155      String      poolName        = mp.getName();
156      MemoryUsage currentUsage    = mp.getUsage();
157      MemoryUsage collectionUsage = mp.getCollectionUsage();
158
159      String safeName = gcSafeNames.get(poolName);
160      if (safeName == null)
161      {
162        safeName = generateSafeName(poolName);
163        gcSafeNames.put(poolName, safeName);
164      }
165
166      long currentBytesUsed = currentUsage != null ? currentUsage.getUsed() : 0;
167      attrs.add(createAttribute(safeName + "-current-bytes-used", currentBytesUsed));
168
169      long collectionBytesUsed = collectionUsage != null ? collectionUsage.getUsed() : 0;
170      attrs.add(createAttribute(safeName + "-bytes-used-after-last-collection", collectionBytesUsed));
171    }
172
173    return attrs;
174  }
175
176  private Attribute createAttribute(String name, Object value)
177  {
178    return Attributes.create(name, String.valueOf(value));
179  }
180
181
182
183  /**
184   * Creates a "safe" version of the provided name, which is acceptable for
185   * use as part of an attribute name.
186   *
187   * @param  name  The name for which to obtain the safe name.
188   *
189   * @return  The calculated safe name.
190   */
191  private String generateSafeName(String name)
192  {
193    StringBuilder buffer = new StringBuilder();
194    boolean lastWasUppercase = false;
195    boolean lastWasDash      = false;
196    for (int i=0; i  < name.length(); i++)
197    {
198      char c = name.charAt(i);
199      if (Character.isLetter(c))
200      {
201        if (Character.isUpperCase(c))
202        {
203          char lowerCaseCharacter = Character.toLowerCase(c);
204          if (buffer.length() > 0 && !lastWasUppercase && !lastWasDash)
205          {
206            buffer.append('-');
207          }
208
209          buffer.append(lowerCaseCharacter);
210          lastWasUppercase = true;
211          lastWasDash = false;
212        }
213        else
214        {
215          buffer.append(c);
216          lastWasUppercase = false;
217          lastWasDash = false;
218        }
219      }
220      else if (Character.isDigit(c))
221      {
222        buffer.append(c);
223        lastWasUppercase = false;
224        lastWasDash = false;
225      }
226      else if (c == ' ' || c == '_' || c == '-')
227      {
228        if (! lastWasDash)
229        {
230          buffer.append('-');
231        }
232
233        lastWasUppercase = false;
234        lastWasDash = true;
235      }
236    }
237
238    return buffer.toString();
239  }
240}
241