Merge branch 'ff/genericjmx'
authorFlorian Forster <octo@huhu.verplant.org>
Thu, 13 Aug 2009 06:14:24 +0000 (08:14 +0200)
committerFlorian Forster <octo@huhu.verplant.org>
Thu, 13 Aug 2009 06:14:24 +0000 (08:14 +0200)
bindings/java/org/collectd/java/GenericJMX.java [new file with mode: 0644]
bindings/java/org/collectd/java/GenericJMXConfConnection.java [new file with mode: 0644]
bindings/java/org/collectd/java/GenericJMXConfMBean.java [new file with mode: 0644]
bindings/java/org/collectd/java/GenericJMXConfValue.java [new file with mode: 0644]
contrib/GenericJMX.conf [new file with mode: 0644]
contrib/README

diff --git a/bindings/java/org/collectd/java/GenericJMX.java b/bindings/java/org/collectd/java/GenericJMX.java
new file mode 100644 (file)
index 0000000..319615c
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMX.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.CollectdConfigInterface;
+import org.collectd.api.CollectdInitInterface;
+import org.collectd.api.CollectdReadInterface;
+import org.collectd.api.CollectdShutdownInterface;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+public class GenericJMX implements CollectdConfigInterface,
+       CollectdReadInterface,
+       CollectdShutdownInterface
+{
+  static private Map<String,GenericJMXConfMBean> _mbeans
+    = new TreeMap<String,GenericJMXConfMBean> ();
+
+  private List<GenericJMXConfConnection> _connections = null;
+
+  public GenericJMX ()
+  {
+    Collectd.registerConfig   ("GenericJMX", this);
+    Collectd.registerRead     ("GenericJMX", this);
+    Collectd.registerShutdown ("GenericJMX", this);
+
+    this._connections = new ArrayList<GenericJMXConfConnection> ();
+  }
+
+  public int config (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigItem> children;
+    int i;
+
+    Collectd.logDebug ("GenericJMX plugin: config: ci = " + ci + ";");
+
+    children = ci.getChildren ();
+    for (i = 0; i < children.size (); i++)
+    {
+      OConfigItem child;
+      String key;
+
+      child = children.get (i);
+      key = child.getKey ();
+      if (key.equalsIgnoreCase ("MBean"))
+      {
+        try
+        {
+          GenericJMXConfMBean mbean = new GenericJMXConfMBean (child);
+          putMBean (mbean);
+        }
+        catch (IllegalArgumentException e)
+        {
+          Collectd.logError ("GenericJMX plugin: "
+              + "Evaluating `MBean' block failed: " + e);
+        }
+      }
+      else if (key.equalsIgnoreCase ("Connection"))
+      {
+        try
+        {
+          GenericJMXConfConnection conn = new GenericJMXConfConnection (child);
+          this._connections.add (conn);
+        }
+        catch (IllegalArgumentException e)
+        {
+          Collectd.logError ("GenericJMX plugin: "
+              + "Evaluating `Connection' block failed: " + e);
+        }
+      }
+      else
+      {
+        Collectd.logError ("GenericJMX plugin: Unknown config option: " + key);
+      }
+    } /* for (i = 0; i < children.size (); i++) */
+
+    return (0);
+  } /* }}} int config */
+
+  public int read () /* {{{ */
+  {
+    for (int i = 0; i < this._connections.size (); i++)
+    {
+      try
+      {
+        this._connections.get (i).query ();
+      }
+      catch (Exception e)
+      {
+        Collectd.logError ("GenericJMX: Caught unexpected exception: " + e);
+        e.printStackTrace ();
+      }
+    }
+
+    return (0);
+  } /* }}} int read */
+
+  public int shutdown () /* {{{ */
+  {
+    System.out.print ("org.collectd.java.GenericJMX.Shutdown ();\n");
+    this._connections = null;
+    return (0);
+  } /* }}} int shutdown */
+
+  /*
+   * static functions
+   */
+  static public GenericJMXConfMBean getMBean (String alias)
+  {
+    return (_mbeans.get (alias));
+  }
+
+  static private void putMBean (GenericJMXConfMBean mbean)
+  {
+    Collectd.logDebug ("GenericJMX.putMBean: Adding " + mbean.getName ());
+    _mbeans.put (mbean.getName (), mbean);
+  }
+} /* class GenericJMX */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/java/GenericJMXConfConnection.java b/bindings/java/org/collectd/java/GenericJMXConfConnection.java
new file mode 100644 (file)
index 0000000..0108b53
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMXConfConnection.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.PluginData;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+class GenericJMXConfConnection
+{
+  private String _host = null;
+  private String _service_url = null;
+  private MBeanServerConnection _jmx_connection = null;
+  private List<GenericJMXConfMBean> _mbeans = null;
+
+  /*
+   * private methods
+   */
+  private String getConfigString (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfConnection: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
+    {
+      Collectd.logError ("GenericJMXConfConnection: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    return (v.getString ());
+  } /* }}} String getConfigString */
+
+private void connect () /* {{{ */
+{
+  JMXServiceURL service_url;
+  JMXConnector connector;
+
+  if (_jmx_connection != null)
+    return;
+
+  try
+  {
+    service_url = new JMXServiceURL (this._service_url);
+    connector = JMXConnectorFactory.connect (service_url);
+    _jmx_connection = connector.getMBeanServerConnection ();
+  }
+  catch (Exception e)
+  {
+    Collectd.logError ("GenericJMXConfConnection: "
+        + "Creating MBean server connection failed: " + e);
+    return;
+  }
+} /* }}} void connect */
+
+/*
+ * public methods
+ *
+ * <Connection>
+ *   Host "tomcat0.mycompany"
+ *   ServiceURL "service:jmx:rmi:///jndi/rmi://localhost:17264/jmxrmi"
+ *   Collect "java.lang:type=GarbageCollector,name=Copy"
+ *   Collect "java.lang:type=Memory"
+ * </Connection>
+ *
+ */
+  public GenericJMXConfConnection (OConfigItem ci) /* {{{ */
+    throws IllegalArgumentException
+  {
+    List<OConfigItem> children;
+    Iterator<OConfigItem> iter;
+
+    this._mbeans = new ArrayList<GenericJMXConfMBean> ();
+
+    children = ci.getChildren ();
+    iter = children.iterator ();
+    while (iter.hasNext ())
+    {
+      OConfigItem child = iter.next ();
+
+      if (child.getKey ().equalsIgnoreCase ("Host"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._host = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("ServiceURL"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._service_url = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Collect"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+        {
+          GenericJMXConfMBean mbean;
+
+          mbean = GenericJMX.getMBean (tmp);
+          if (mbean == null)
+            throw (new IllegalArgumentException ("No such MBean defined: "
+                  + tmp + ". Please make sure all `MBean' blocks appear "
+                  + "before (above) all `Connection' blocks."));
+          Collectd.logDebug ("GenericJMXConfConnection: " + this._host + ": Add " + tmp);
+          this._mbeans.add (mbean);
+        }
+      }
+      else
+        throw (new IllegalArgumentException ("Unknown option: "
+              + child.getKey ()));
+    }
+
+    if (this._service_url == null)
+      throw (new IllegalArgumentException ("No service URL was defined."));
+    if (this._mbeans.size () == 0)
+      throw (new IllegalArgumentException ("No valid collect statement "
+            + "present."));
+  } /* }}} GenericJMXConfConnection (OConfigItem ci) */
+
+  public void query () /* {{{ */
+  {
+    PluginData pd;
+
+    connect ();
+
+    if (this._jmx_connection == null)
+      return;
+
+    Collectd.logDebug ("GenericJMXConfConnection.query: "
+        + "Reading " + this._mbeans.size () + " mbeans from "
+        + ((this._host != null) ? this._host : "(null)"));
+
+    pd = new PluginData ();
+    pd.setHost ((this._host != null) ? this._host : "localhost");
+    pd.setPlugin ("GenericJMX");
+
+    for (int i = 0; i < this._mbeans.size (); i++)
+      this._mbeans.get (i).query (this._jmx_connection, pd);
+  } /* }}} void query */
+
+  public String toString ()
+  {
+    return (new String ("host = " + this._host + "; "
+          + "url = " + this._service_url));
+  }
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/java/GenericJMXConfMBean.java b/bindings/java/org/collectd/java/GenericJMXConfMBean.java
new file mode 100644 (file)
index 0000000..27e9e32
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMXConfMBean.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.PluginData;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+class GenericJMXConfMBean
+{
+  private String _name; /* name by which this mapping is referenced */
+  private ObjectName _obj_name;
+  private String _instance_prefix;
+  private List<String> _instance_from;
+  private List<GenericJMXConfValue> _values;
+
+  private String getConfigString (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfMBean: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
+    {
+      Collectd.logError ("GenericJMXConfMBean: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    return (v.getString ());
+  } /* }}} String getConfigString */
+
+  private String join (String separator, List<String> list) /* {{{ */
+  {
+    StringBuffer sb;
+
+    sb = new StringBuffer ();
+
+    for (int i = 0; i < list.size (); i++)
+    {
+      if (i > 0)
+        sb.append ("-");
+      sb.append (list.get (i));
+    }
+
+    return (sb.toString ());
+  } /* }}} String join */
+
+/*
+ * <MBean "alias name">
+ *   ObjectName "object name"
+ *   InstancePrefix "foobar"
+ *   InstanceFrom "name"
+ *   <Value />
+ *   <Value />
+ *   :
+ * </MBean>
+ */
+  public GenericJMXConfMBean (OConfigItem ci) /* {{{ */
+    throws IllegalArgumentException
+  {
+    List<OConfigItem> children;
+    Iterator<OConfigItem> iter;
+
+    this._name = getConfigString (ci);
+    if (this._name == null)
+      throw (new IllegalArgumentException ("No alias name was defined. "
+            + "MBean blocks need exactly one string argument."));
+
+    this._obj_name = null;
+    this._instance_prefix = null;
+    this._instance_from = new ArrayList<String> ();
+    this._values = new ArrayList<GenericJMXConfValue> ();
+
+    children = ci.getChildren ();
+    iter = children.iterator ();
+    while (iter.hasNext ())
+    {
+      OConfigItem child = iter.next ();
+
+      Collectd.logDebug ("GenericJMXConfMBean: child.getKey () = "
+          + child.getKey ());
+      if (child.getKey ().equalsIgnoreCase ("ObjectName"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp == null)
+          continue;
+
+        try
+        {
+          this._obj_name = new ObjectName (tmp);
+        }
+        catch (MalformedObjectNameException e)
+        {
+          throw (new IllegalArgumentException ("Not a valid object name: "
+                + tmp, e));
+        }
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_prefix = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstanceFrom"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_from.add (tmp);
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Value"))
+      {
+        GenericJMXConfValue cv;
+
+        cv = new GenericJMXConfValue (child);
+        this._values.add (cv);
+      }
+      else
+        throw (new IllegalArgumentException ("Unknown option: "
+              + child.getKey ()));
+    }
+
+    if (this._obj_name == null)
+      throw (new IllegalArgumentException ("No object name was defined."));
+
+    if (this._values.size () == 0)
+      throw (new IllegalArgumentException ("No value block was defined."));
+
+  } /* }}} GenericJMXConfMBean (OConfigItem ci) */
+
+  public String getName () /* {{{ */
+  {
+    return (this._name);
+  } /* }}} */
+
+  public void query (MBeanServerConnection conn, PluginData pd) /* {{{ */
+  {
+    Set<ObjectName> names;
+    Iterator<ObjectName> iter;
+
+    try
+    {
+      names = conn.queryNames (this._obj_name, /* query = */ null);
+    }
+    catch (Exception e)
+    {
+      Collectd.logError ("GenericJMXConfMBean: queryNames failed: " + e);
+      return;
+    }
+
+    if (names.size () == 0)
+    {
+      Collectd.logWarning ("GenericJMXConfMBean: No MBean matched "
+          + "the ObjectName " + this._obj_name);
+    }
+
+    iter = names.iterator ();
+    while (iter.hasNext ())
+    {
+      ObjectName   objName;
+      PluginData   pd_tmp;
+      List<String> instanceList;
+      String       instance;
+
+      objName      = iter.next ();
+      pd_tmp       = new PluginData (pd);
+      instanceList = new ArrayList<String> ();
+
+      Collectd.logDebug ("GenericJMXConfMBean: objName = "
+          + objName.toString ());
+
+      for (int i = 0; i < this._instance_from.size (); i++)
+      {
+        String propertyName;
+        String propertyValue;
+
+        propertyName = this._instance_from.get (i);
+        propertyValue = objName.getKeyProperty (propertyName);
+        if (propertyValue == null)
+        {
+          Collectd.logError ("GenericJMXConfMBean: "
+              + "No such property in object name: " + propertyName);
+        }
+        else
+        {
+          instanceList.add (propertyValue);
+        }
+      }
+
+      if (this._instance_prefix != null)
+        instance = new String (this._instance_prefix
+            + join ("-", instanceList));
+      else
+        instance = join ("-", instanceList);
+      pd_tmp.setPluginInstance (instance);
+
+      Collectd.logDebug ("GenericJMXConfMBean: instance = " + instance);
+
+      for (int i = 0; i < this._values.size (); i++)
+        this._values.get (i).query (conn, objName, pd_tmp);
+    }
+  } /* }}} void query */
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/bindings/java/org/collectd/java/GenericJMXConfValue.java b/bindings/java/org/collectd/java/GenericJMXConfValue.java
new file mode 100644 (file)
index 0000000..cdca02f
--- /dev/null
@@ -0,0 +1,515 @@
+/*
+ * collectd/java - org/collectd/java/GenericJMXConfValue.java
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ */
+
+package org.collectd.java;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.InvalidKeyException;
+
+import org.collectd.api.Collectd;
+import org.collectd.api.DataSet;
+import org.collectd.api.DataSource;
+import org.collectd.api.ValueList;
+import org.collectd.api.PluginData;
+import org.collectd.api.OConfigValue;
+import org.collectd.api.OConfigItem;
+
+/**
+ * Representation of a &lt;value&nbsp;/&gt; block and query functionality.
+ *
+ * This class represents a &lt;value&nbsp;/&gt; block in the configuration. As
+ * such, the constructor takes an {@link org.collectd.api.OConfigValue} to
+ * construct an object of this class.
+ *
+ * The object can then be asked to query data from JMX and dispatch it to
+ * collectd.
+ *
+ * @see GenericJMXConfMBean
+ */
+class GenericJMXConfValue
+{
+  private String _ds_name;
+  private DataSet _ds;
+  private List<String> _attributes;
+  private String _instance_prefix;
+  private boolean _is_table;
+
+  /**
+   * Converts a generic (OpenType) object to a number.
+   *
+   * Returns null if a conversion is not possible or not implemented.
+   */
+  private Number genericObjectToNumber (Object obj, int ds_type) /* {{{ */
+  {
+    if (obj instanceof String)
+    {
+      String str = (String) obj;
+      
+      try
+      {
+        if (ds_type == DataSource.TYPE_GAUGE)
+          return (new Double (str));
+        else
+          return (new Long (str));
+      }
+      catch (NumberFormatException e)
+      {
+        return (null);
+      }
+    }
+    else if (obj instanceof Byte)
+    {
+      return (new Byte ((Byte) obj));
+    }
+    else if (obj instanceof Short)
+    {
+      return (new Short ((Short) obj));
+    }
+    else if (obj instanceof Integer)
+    {
+      return (new Integer ((Integer) obj));
+    }
+    else if (obj instanceof Long)
+    {
+      return (new Long ((Long) obj));
+    }
+    else if (obj instanceof Float)
+    {
+      return (new Float ((Float) obj));
+    }
+    else if (obj instanceof Double)
+    {
+      return (new Double ((Double) obj));
+    }
+    else if (obj instanceof BigDecimal)
+    {
+      return (BigDecimal.ZERO.add ((BigDecimal) obj));
+    }
+    else if (obj instanceof BigInteger)
+    {
+      return (BigInteger.ZERO.add ((BigInteger) obj));
+    }
+
+    return (null);
+  } /* }}} Number genericObjectToNumber */
+
+  private List<Number> genericListToNumber (List<Object> objects) /* {{{ */
+  {
+    List<Number> ret = new ArrayList<Number> ();
+    List<DataSource> dsrc = this._ds.getDataSources ();
+
+    assert (objects.size () == dsrc.size ());
+
+    for (int i = 0; i < objects.size (); i++)
+    {
+      Number n;
+
+      n = genericObjectToNumber (objects.get (i), dsrc.get (i).getType ());
+      if (n == null)
+        return (null);
+      ret.add (n);
+    }
+
+    return (ret);
+  } /* }}} List<Number> genericListToNumber */
+
+  private List<Number> genericCompositeToNumber (List<CompositeData> cdlist, /* {{{ */
+      String key)
+  {
+    List<Object> objects = new ArrayList<Object> ();
+
+    for (int i = 0; i < cdlist.size (); i++)
+    {
+      CompositeData cd;
+      Object value;
+
+      cd = cdlist.get (i);
+      try
+      {
+        value = cd.get (key);
+      }
+      catch (InvalidKeyException e)
+      {
+        return (null);
+      }
+      objects.add (value);
+    }
+
+    return (genericListToNumber (objects));
+  } /* }}} List<Number> genericCompositeToNumber */
+
+  private void submitTable (List<Object> objects, ValueList vl) /* {{{ */
+  {
+    List<CompositeData> cdlist;
+    Set<String> keySet = null;
+    Iterator<String> keyIter;
+
+    cdlist = new ArrayList<CompositeData> ();
+    for (int i = 0; i < objects.size (); i++)
+    {
+      Object obj;
+
+      obj = objects.get (i);
+      if (obj instanceof CompositeData)
+      {
+        CompositeData cd;
+
+        cd = (CompositeData) obj;
+
+        if (i == 0)
+          keySet = cd.getCompositeType ().keySet ();
+
+        cdlist.add (cd);
+      }
+      else
+      {
+        Collectd.logError ("GenericJMXConfValue: At least one of the "
+            + "attributes was not of type `CompositeData', as required "
+            + "when table is set to `true'.");
+        return;
+      }
+    }
+
+    assert (keySet != null);
+
+    keyIter = keySet.iterator ();
+    while (keyIter.hasNext ())
+    {
+      String key;
+      List<Number> values;
+
+      key = keyIter.next ();
+      values = genericCompositeToNumber (cdlist, key);
+      if (values == null)
+      {
+        Collectd.logError ("GenericJMXConfValue: Cannot build a list of "
+            + "numbers for key " + key + ". Most likely not all attributes "
+            + "have this key.");
+        continue;
+      }
+
+      if (this._instance_prefix == null)
+        vl.setTypeInstance (key);
+      else
+        vl.setTypeInstance (this._instance_prefix + key);
+      vl.setValues (values);
+
+      Collectd.dispatchValues (vl);
+    }
+  } /* }}} void submitTable */
+
+  private void submitScalar (List<Object> objects, ValueList vl) /* {{{ */
+  {
+    List<Number> values;
+
+    values = genericListToNumber (objects);
+    if (values == null)
+    {
+      Collectd.logError ("GenericJMXConfValue: Cannot convert list of "
+          + "objects to numbers.");
+      return;
+    }
+
+    if (this._instance_prefix == null)
+      vl.setTypeInstance ("");
+    else
+      vl.setTypeInstance (this._instance_prefix);
+    vl.setValues (values);
+
+    Collectd.dispatchValues (vl);
+  } /* }}} void submitScalar */
+
+  private Object queryAttributeRecursive (CompositeData parent, /* {{{ */
+      List<String> attrName)
+  {
+    String key;
+    Object value;
+
+    key = attrName.remove (0);
+
+    try
+    {
+      value = parent.get (key);
+    }
+    catch (InvalidKeyException e)
+    {
+      return (null);
+    }
+
+    if (attrName.size () == 0)
+    {
+      return (value);
+    }
+    else
+    {
+      if (value instanceof CompositeData)
+        return (queryAttributeRecursive ((CompositeData) value, attrName));
+      else
+        return (null);
+    }
+  } /* }}} queryAttributeRecursive */
+
+  private Object queryAttribute (MBeanServerConnection conn, /* {{{ */
+      ObjectName objName, String attrName)
+  {
+    List<String> attrNameList;
+    String key;
+    Object value;
+    String[] attrNameArray;
+
+    attrNameList = new ArrayList<String> ();
+
+    attrNameArray = attrName.split ("\\.");
+    key = attrNameArray[0];
+    for (int i = 1; i < attrNameArray.length; i++)
+      attrNameList.add (attrNameArray[i]);
+
+    try
+    {
+      value = conn.getAttribute (objName, key);
+    }
+    catch (Exception e)
+    {
+      Collectd.logError ("GenericJMXConfValue.query: getAttribute failed: "
+          + e);
+      return (null);
+    }
+
+    if (attrNameList.size () == 0)
+    {
+      return (value);
+    }
+    else
+    {
+      if (value instanceof CompositeData)
+        return (queryAttributeRecursive((CompositeData) value, attrNameList));
+      else if (value instanceof OpenType)
+      {
+        OpenType ot = (OpenType) value;
+        Collectd.logNotice ("GenericJMXConfValue: Handling of OpenType \""
+            + ot.getTypeName () + "\" is not yet implemented.");
+        return (null);
+      }
+      else
+      {
+        Collectd.logError ("GenericJMXConfValue: Received object of "
+            + "unknown class.");
+        return (null);
+      }
+    }
+  } /* }}} Object queryAttribute */
+
+  private String getConfigString (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_STRING)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one string argument.");
+      return (null);
+    }
+
+    return (v.getString ());
+  } /* }}} String getConfigString */
+
+  private Boolean getConfigBoolean (OConfigItem ci) /* {{{ */
+  {
+    List<OConfigValue> values;
+    OConfigValue v;
+    Boolean b;
+
+    values = ci.getValues ();
+    if (values.size () != 1)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one boolean argument.");
+      return (null);
+    }
+
+    v = values.get (0);
+    if (v.getType () != OConfigValue.OCONFIG_TYPE_BOOLEAN)
+    {
+      Collectd.logError ("GenericJMXConfValue: The " + ci.getKey ()
+          + " configuration option needs exactly one boolean argument.");
+      return (null);
+    }
+
+    return (new Boolean (v.getBoolean ()));
+  } /* }}} String getConfigBoolean */
+
+  /**
+   * Constructs a new value with the configured properties.
+   */
+  public GenericJMXConfValue (OConfigItem ci) /* {{{ */
+    throws IllegalArgumentException
+  {
+    List<OConfigItem> children;
+    Iterator<OConfigItem> iter;
+
+    this._ds_name = null;
+    this._ds = null;
+    this._attributes = new ArrayList<String> ();
+    this._instance_prefix = null;
+    this._is_table = false;
+
+    /*
+     * <Value>
+     *   Type "memory"
+     *   Table true|false
+     *   Attribute "HeapMemoryUsage"
+     *   Attribute "..."
+     *   :
+     *   # Type instance:
+     *   InstancePrefix "heap-"
+     * </Value>
+     */
+    children = ci.getChildren ();
+    iter = children.iterator ();
+    while (iter.hasNext ())
+    {
+      OConfigItem child = iter.next ();
+
+      if (child.getKey ().equalsIgnoreCase ("Type"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._ds_name = tmp;
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Table"))
+      {
+        Boolean tmp = getConfigBoolean (child);
+        if (tmp != null)
+          this._is_table = tmp.booleanValue ();
+      }
+      else if (child.getKey ().equalsIgnoreCase ("Attribute"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._attributes.add (tmp);
+      }
+      else if (child.getKey ().equalsIgnoreCase ("InstancePrefix"))
+      {
+        String tmp = getConfigString (child);
+        if (tmp != null)
+          this._instance_prefix = tmp;
+      }
+      else
+        throw (new IllegalArgumentException ("Unknown option: "
+              + child.getKey ()));
+    }
+
+    if (this._ds_name == null)
+      throw (new IllegalArgumentException ("No data set was defined."));
+    else if (this._attributes.size () == 0)
+      throw (new IllegalArgumentException ("No attribute was defined."));
+  } /* }}} GenericJMXConfValue (OConfigItem ci) */
+
+  /**
+   * Query values via JMX according to the object's configuration and dispatch
+   * them to collectd.
+   *
+   * @param conn Connection to the MBeanServer.
+   * @param objName Object name of the MBean to query.
+   * @param pd      Preset naming components. The members host, plugin and
+   *                plugin instance will be used.
+   */
+  public void query (MBeanServerConnection conn, ObjectName objName, /* {{{ */
+      PluginData pd)
+  {
+    ValueList vl;
+    List<DataSource> dsrc;
+    List<Object> values;
+
+    if (this._ds == null)
+    {
+      this._ds = Collectd.getDS (this._ds_name);
+      if (this._ds == null)
+      {
+        Collectd.logError ("GenericJMXConfValue: Unknown type: "
+            + this._ds_name);
+        return;
+      }
+    }
+
+    dsrc = this._ds.getDataSources ();
+    if (dsrc.size () != this._attributes.size ())
+    {
+      Collectd.logError ("GenericJMXConfValue.query: The data set "
+          + this._ds_name + " has " + this._ds.getDataSources ().size ()
+          + " data sources, but there were " + this._attributes.size ()
+          + " attributes configured. This doesn't match!");
+      this._ds = null;
+      return;
+    }
+
+    vl = new ValueList (pd);
+    vl.setType (this._ds_name);
+    vl.setTypeInstance (this._instance_prefix);
+
+    values = new ArrayList<Object> ();
+
+    assert (dsrc.size () == this._attributes.size ());
+    for (int i = 0; i < this._attributes.size (); i++)
+    {
+      Object v;
+
+      v = queryAttribute (conn, objName, this._attributes.get (i));
+      if (v == null)
+      {
+        Collectd.logError ("GenericJMXConfValue.query: "
+            + "Querying attribute " + this._attributes.get (i) + " failed.");
+        return;
+      }
+
+      values.add (v);
+    }
+
+    if (this._is_table)
+      submitTable (values, vl);
+    else
+      submitScalar (values, vl);
+  } /* }}} void query */
+}
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/contrib/GenericJMX.conf b/contrib/GenericJMX.conf
new file mode 100644 (file)
index 0000000..b88d1a6
--- /dev/null
@@ -0,0 +1,145 @@
+# contrib/GenericJMX.conf
+# -----------------------
+#
+# This is an example config file for the ‘GenericJMX’ plugin, a plugin written
+# in Java to receive values via the “Java Management Extensions” (JMX). The
+# plugin can be found in the
+#   bindings/java/org/collectd/java/
+# directory of the source distribution.
+#
+# This sample config defines a couple of <MBean /> blocks which query MBeans
+# provided by the JVM itself, i. e. which should be available for all Java
+# processes. The following MBean blocks are defined:
+#
+#   +-------------------+------------------------------------------------+
+#   ! Name              ! Description                                    !
+#   +-------------------+------------------------------------------------+
+#   ! classes           ! Number of classes being loaded.                !
+#   ! compilation       ! Time spent by the JVM compiling or optimizing. !
+#   ! garbage_collector ! Number of garbage collections and time spent.  !
+#   ! memory            ! Generic heap/nonheap memory usage.             !
+#   ! memory_pool       ! Memory usage by memory pool.                   !
+#   +-------------------+------------------------------------------------+
+#
+<Plugin "java">
+  LoadPlugin "org.collectd.java.GenericJMX"
+
+  <Plugin "GenericJMX">
+    ################
+    # MBean blocks #
+    ################
+    # Number of classes being loaded.
+    <MBean "classes">
+      ObjectName "java.lang:type=ClassLoading"
+      #InstancePrefix ""
+      #InstanceFrom ""
+
+      <Value>
+        Type "gauge"
+        Table false
+        Attribute "LoadedClassCount"
+        InstancePrefix "loaded_classes"
+      </Value>
+    </MBean>
+
+    # Time spent by the JVM compiling or optimizing.
+    <MBean "compilation">
+      ObjectName "java.lang:type=Compilation"
+      #InstancePrefix ""
+      #InstanceFrom ""
+
+      <Value>
+        Type "total_time_in_ms"
+        Table false
+        Attribute "TotalCompilationTime"
+        InstancePrefix "compilation_time"
+      </Value>
+    </MBean>
+
+    # Garbage collector information
+    <MBean "garbage_collector">
+      # Plugin instance:
+      InstancePrefix "gc-"
+      InstanceFrom "name"
+      ObjectName "java.lang:type=GarbageCollector,name=*"
+
+      <Value>
+        Type "invocations"
+        Table false
+        Attribute "CollectionCount"
+        # Type instance:
+        #InstancePrefix ""
+      </Value>
+
+      <Value>
+        Type "total_time_in_ms"
+        Table false
+        Attribute "CollectionTime"
+        # Type instance:
+        InstancePrefix "collection_time"
+      </Value>
+
+#      # Not that useful, therefore commented out.
+#      <Value>
+#        Type "threads"
+#        Table false
+#        # Demonstration how to access composite types
+#        Attribute "LastGcInfo.GcThreadCount"
+#        # Type instance:
+#        #InstancePrefix ""
+#      </Value>
+    </MBean>
+
+    # Generic heap/nonheap memory usage.
+    <MBean "memory">
+      ObjectName "java.lang:type=Memory"
+      #InstanceFrom ""
+      InstancePrefix "memory"
+
+      # Creates four values: committed, init, max, used
+      <Value>
+        Type "memory"
+        Table true
+        Attribute "HeapMemoryUsage"
+        # Type instance:
+        InstancePrefix "heap-"
+      </Value>
+
+      # Creates four values: committed, init, max, used
+      <Value>
+        Type "memory"
+        Table true
+        Attribute "NonHeapMemoryUsage"
+        # Type instance:
+        InstancePrefix "nonheap-"
+      </Value>
+    </MBean>
+
+    # Memory usage by memory pool.
+    <MBean "memory_pool">
+      ObjectName "java.lang:type=MemoryPool,name=*"
+      InstancePrefix "memory_pool-"
+      InstanceFrom "name"
+
+      <Value>
+        Type "memory"
+        Table true
+        Attribute "Usage"
+        #InstancePrefix ""
+      </Value>
+    </MBean>
+
+    #####################
+    # Connection blocks #
+    #####################
+    <Connection>
+      Host "localhost"
+      ServiceURL "service:jmx:rmi:///jndi/rmi://localhost:17264/jmxrmi"
+      Collect "classes"
+      Collect "compilation"
+      Collect "garbage_collector"
+      Collect "memory"
+      Collect "memory_pool"
+    </Connection>
+  </Plugin>
+</Plugin>
index 119d866..bc1fe9f 100644 (file)
@@ -13,20 +13,18 @@ can seriously fuck up your RRD files if you don't know what you're doing.
 
 collectd-network.py
 -------------------
-
   This Python module by Adrian Perez implements the collectd network protocol
 in pure Python. It currently supports to receive data and notifications from
 collectd.
 
 collectd-unixsock.py
 --------------------
-
   This Python module by Clay Loveless provides an interface to collect's
 unixsock plugin.
 
 collectd2html.pl
 ----------------
-  This script by Vincent Stehlé will search for RRD files in
+  This script by Vincent Stehlé will search for RRD files in
 `/var/lib/collectd/' and generate an HTML file and a directory containing
 several PNG files which are graphs of the RRD files found.
 
@@ -72,6 +70,11 @@ fedora/
   Init-script and Spec-file that can be used when creating RPM-packages for
 Fedora.
 
+GenericJMX.conf
+---------------
+  Example configuration file for the ‘GenericJMX’ Java plugin. Please read the
+documentation at the beginning of the file for more details.
+
 migrate-3-4.px
 --------------
   Migration-script to ease the switch from version 3 to version 4. Many